Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

java基础(五)之复用类

2017-06-10
nivelle

复用类

复用代码

  • 只需要在新的类中产生现有类的对象,由于新的类是由现有类的对象组成,所以这种方法称为组合。
  • 它按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新代码,也即继承。

组合语法

每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用。

编译器并不是简单地为每一个引用都创建默认对象,这一点很有意义,初始化这些引用,可以在以下位置进行:

  • 在定义对象的地方。这意味着它们总是能够在构造器被调用之前被初始化。
  • 在类的构造器中
  • 就在正要使用这些对象之前,惰性初始化。
  • 使用实例初始化。

继承语法

当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从java的标准根类Object进行继承。在继承过程中,需要首先声明“新类与旧类相似”这种申明是通过在类的主体的左边花括号之前,书写后面紧随基类名称的关键字extends而实现的。

当这么做时,会自动得到基类中所有的域和方法。

方法必须是Public,默认是包访问权限。

在java中super表示超类的意思,当前类就是从这个超类继承来的。为此,表达式super.方法()将调用基类版本的方法。

并不一定要使用基类的方法,也可以在导出类中添加新方法,其添加方式与在新类中添加任意方法一样,即对其加以定义即可。

初始化基类

当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。二者的区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部。

java会自动在导出类的构造器中插入对基类构造器的调用。

public class Cartoon extends Drawing {
  public Cartoon() {
    System.out.println("cartoon constructor");
  }

  public static void main(String[] args) {
    Cartoon x = new Cartoon();
  }
}
class Art {
  Art() {
    System.out.println("art constructor");
  }
}

class Drawing extends Art {
  public Drawing() {
    System.out.println("Drawing constructor");
  }
}

结果:art constructor,Drawing constructor,cartoon constructor

构建过程是从基类“向外”扩散的,所以基类在导出类构造前可以访问它之前,就已经完成了初始化。

带参数的构造器

如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须使用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列表。

class Game{
  public Game(int i) {
    System.out.println("Game constructor");
  }
}

class BoardGame extends Game{
  public BoardGame(int i) {
    super(i);
    System.out.println("board game constructor");
  }
}
public class Chess extends BoardGame{
   public Chess() {
     super(11);
     System.out.println("chess constructor");
  }
   public static void main(String args[]){
     Chess x = new Chess();
   }
}

代理

java并没有提供对它的直接支持,这是继承与组合之间的中庸之道,因为我们将一个成员对象置于所要构造的类中,但与此同时我们在新类中暴露了该成员对象的所有方法。使用代理时可以拥有更多的控制能力,我们可以选择只提供在成员对象中的方法的某个子集。

确保正确清理

因为并不知道垃圾回收器何时将会被调用,或者它是否将被调用。因此,如果想要某个类清理一些东西,就必须显示地编写方法处理,并确保客户端程序员知晓他们必须要调用这一方法。

将清理动作置于finally子句中,防止异常出现。垃圾回收器是不可靠的,如果需要清理,最好是编写自己的清理方法,但不要使用finalize();

名称屏蔽

如果java的基类拥有某个已经被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本。

class Homer {
  char dod(char c) {
    System.out.println("doh(char)");
    return 'd';
  }

  float dod(float c) {
    System.out.println("doh(folat)");
    return 1.0f;
  }
}
class Milhouse{};

class Bart extends Homer{
  void doh(Milhouse m){
    System.out.println("doh(Milhouse)");
  }
}

public class Hide {
  public static void main(String[] args) {
    Bart b =new Bart();
    b.dod(1);
    b.dod('x');
    b.dod(1.0f);
    b.doh(new Milhouse());
  }
}

虽然在Bart引入了一个新的重载方法,但是在Bart中Homer的所有重载方法都是可用的。

@Override 注解要求必须要覆写基类的一个具有相同名称和参数的方法,防止不小心重载了该方法,而不知道。

protected 关键字

理想世界中,仅靠关键字private就足够了。但在实际项目中,经常会想要将某些事物尽可能对这个世界隐藏起来,但任然允许导出类成员访问它们。

就类用户而言,这是private的,但对于任何继承与此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的。

final 数据

向编译器告知一块数据是恒定不变的。有时数据的恒定不变是很有用的:

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

  • 一个永不改变的运行期的编译期常量(final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。) ```

public class Test { public static void main(String[] args) { String a = “hello2”; final String b = “hello”; String d = “hello”; String c = b + 2; String e = d + 2; System.out.println((a == c)); System.out.println((a == e)); } }

true false


- 一个在运行时才被初始化的值,而你不希望它被改变。


对于编译器常量这种情况,编译器可以将该常量值代入任何可能用到它的计算式中,也就是说,可以在编译时执行计算式,者减少了一些运行时的负担。**java中这类常量必须是基本数据类型的,并且以关键字fianl表示。这个常量在定义时,必须对其初始化。**


---

一个既是static又是final的域只占据一段不能改变的存储空间。

public static final int VALUE_THREE = 39; ``` 定义为public,则可以被用于包之外,定义为static,则强调只有一份;定义为final,则说明他是一个常量。

空白引用

所谓空白final是指被声明为final但又未给定初值的域。无论什么情况下,编译器都确保空白final在使用前必须被初始化。但是,空白final在关键字final的使用上提供了更大的灵活性,为此一个类中的final域就可以做到根据对象而有所不同,却又保证其恒定不变的特性。

必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因。也就是当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。

final参数

java允许在参数列表中以申明的方式将参数指明为final。这意味着无法在方法中更改参数引用所指向的对象。

final方法

使用final方法的原因有两个:

  • 把方法锁定,以防止任何继承类修改它的含义,确保在继承继承中使用方法行为保持不变,并且不会被覆盖。

  • 效率。当编译器发现一个final方法调用命令是,它会根据自己的谨慎判断,跳过插入程序代码这种正常方式而执行方法调用机制(将参数压入栈,跳至方法代码处并执行,然后跳回并清理栈中的参数,处理返回值),并且以方法体中的实际代码的副本来替代方法调用。这将消除方法调用的开销。

注:类的private方法会隐式地被指定为final方法。

final和private关键字

类中所有的private方法都隐式地指定为final的,由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但这并不能给该方法添加任何额外的意义。

“覆盖”只有在某方法是基类的接口的一部分时才出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的程序代码,只不过具有相同的名称而已,但如果在导出类中以相同的名称生成一个public、protected或包访问权限的方法,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时并没有覆盖方法,仅是生成一个新的方法。

final类

当某个类的整体定义为fianl时,就表明你不打算继承该类,而且也不允许别人这样做。换句话说,出于某种考虑,你对该类的设计永远不需要做任何变动,或者出于某种考虑,你对该类的设计永不需要做任何变动,或者出于安全的考虑,不希望它有子类。。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

初始化及类的加载

每个类的编译代码都存在于自己的独立文件中,该文件只在需要使用程序代码时才会被加载,一般来说,可以说:“类的代码在初次使用时才加载”这通常是指加载发生于创建类的第一个对象之时,但是当访问static于或者static方法时,也会放生加载。

初次使用之处,也是static初始化之处,所有的static对象和static代码段都会在加载时依程序中的顺序(即:定义类时的书写顺序)而依次初始化。。当然,定义为static的东西只会被初始化一次。

加载以及初始化过程:

  • 加载器开始启动并找出类的编译代码

  • 编译器注意到它有一个基类,于是它继续进行加载。

  • 如果基类还有自身的基类,那么第二个基类就会被加载。

  • 接下来就是根基类中的static初始化即会被执行,然后是下一个导出类,依次类推 (保证导出类的static初始化可能会依赖于基类成员能否被正确初始化)

  • 类加载完毕,开始创建对象,首先对象中所有基本类型都会被设置为默认值,对象引用设置为null

  • 基类构造器和导出类构造器一样,以同样的顺序来经历相同的过程。

  • 基类构造器就完成之后,实例变量按照其次序被初始化。最后,构造器的其余部分被执行。


评论