复用代码是Java众多引人注目的功能之一。但想要成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情。
组合语法
将现有类的对象的引用置于新类中。被视为“has-a”(拥有)关系。如果是动态发生称为“聚合”。
在使用对像的引用前必须要赋值
- 在定义对象的地方。
- 在类的构造器中。
- 就在正要使用对象这前,称为“惰性初始化”。
- 使用实例初始化(块语句)
继承语句
- 关键字extends。子类继承父类,子类继承了父类的所有域和方法(尽管是prviate,但不能访问)
- Java中规定,创建一个类除非已经明确指出要从其他类中继承,否则就会隐式地从Java的标准根类Object进行继承。
- Java中规定要求继承只能是单根继承,即一个子类只能有一个父类(基类,超类),一个类可以有多个子类。
- 如果子类(导出类)只覆盖基类的方法,即导出类和基类是完全相同的类型,因为他们具有完全相同的接口(方法),能接受的所有的消息都是一样的。结果可以用导出类对象来完全代替基类对像,这种被视为纯粹替代,通常称为替代原则。在某种意义上这是一种处理继承的理想方法。这种情况下的基类和导出类之间的关系称为“is-a”(是一个)。如果在导出类中添加了新的方法,即可以发给导出类的对象的消息就不一定能够发给基类的对象了,这种情况下我们可以描述为“is-like-a”(像一个)关系。
- 基类初始化
当创建一个导出类的对象时,该对象包含一个基类的子对象。这个子对像与你用基类直接创建的对象是一样的。二者区别在于,后者来自外部,而基类来自子对象的被包装在导出类对象的内部。
对基类的子对象正确初始化也是至关重要的,而且也仅有一种方法来保证这一点:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。Java会自动在导出类的构造器中插入对基类构造器的调用。
class Art{
Art(){
print("Art constructor");
}
}
class Drawing entends Art{
Drawing(){
print("Drawing constructor");
}
}
public class Cartoon entends Drawing{
Cartoon(){
print("Cartoon constructor");
}
pubilc static void main(String[] args){
Cartoon x = new Cartoon();
}
}
//log
Art constructor
Drawing constructor
Cartoon constructor
可以发现,构建过程是从基类“向外”扩散的,所以基类是在导出类构造器可以访问它之前,就已经完成了初始化。
当基类没有默认构造器时,需要在导出类中显性的调用基类的构造方法,并传入相应的参数列表。使用super关键字,表示超类的意思。
class Art{
Art(int i){
print("Art constructor");
}
}
class Drawing entends Art{
Drawing(){
super(1); // 显性的调用基类的构造方法
print("Drawing constructor");
}
}
通过super关键字调用基类的构造方法的代码语句必须在导出类的构造方法中,并放在第一句。
- 方法覆盖
- 方法名、方法参数列表的类型和顺序,返回值类型相同或是基类方法返回值类型的导出类(协变返回类型)
- 要覆写基类的方法,可以用注解@Override来明确标示是重写基类的方法,利用编译器来帮忙做检查。
- 方法覆盖,重写后的方法的访问权限不能小于基类的。(pubilc-protected-包访问权限-private)否则编译错误不通过。private修饰的方法是不能被重写的。
- 在导出类中定义了与父类方法同名的方法,如果不满足重写,则这是导出类的一个新的方法,不会屏蔽基类的方法。
-
向上转型
将导出类的对象的引用赋值给基类类型的变量引用。这种转型是安全的,因为基类的接口不可能比导出类的接口更窄,即能发送给导出类的对象的消息都可以发送给基类的对象的消息。 -
初始化顺序
当基类和导出类中有静态子句和非静态子句实例来初始化变量时,他们的初始化顺序为:
- 基类的静态子句(多个时按定义顺序)- 导出类的静态子句(多个时按定义顺序)。这执行一下,虚拟机第一次加载类的时候。
- 当基类和导出类的所有静态子句都执行完后在到基类的非静态子句(多个时按定义顺序)- 导出类的非静态子句(多个时按定义顺序)。
- 清理
清理顺序和变量定义的相反,先导出类再到基类。
组合与继承的选择
在复用类的时候选择组合还是继承?可以根据自己的需要来问自己,我需要将一个对象向上转型吗?如果需要,则使用继承,如果不需要则使用组合。在想要向上转型时反复问自己,真的需要向上转型吗?
关键字final
- final数据
- 编译时常量。只能是基本数据类型,并在定义时已经明确赋值并知道具体的值。
- 一个在运行时被初始化的值,而你不希望它被改变。
- final用作基本数据类型时表示值不能改变。
- final用作对象引用时表示不能把另外一个对象的引用赋值给它,即它不能“放弃”原来关联的对象而去关联别的对象。但对像的数据时可以改变的。
- final说明的是它是一个常量,static强调的时只有一份。所以一个即为static又为fianl的的域只占据一段不能改变的存储空间。
- 空白final:是指被声明为final但又为给定初始值的域。但无论什么情况,编译器都确保空白final在使用前必须被初始化。
例 private final int i ; - final参数,方法的参数被定义为final,说明方法只能访问该参数的值不能修改改参数变量的值。
- final方法
- 方法使用fina的是想把方法锁定,以防任何继承类修改它的含义(通过覆盖,即final不能被覆盖重写)
- 类中所有private的方法都隐式的指定为final的。导出类由于无法取用private的方法,所以也就无法覆盖它。
- final类
- 将类声明为final的,表明了该类是最终类,即没有子类,所以不能有任何类继承他。
- final类的域可以根据个人的意愿来选择是不是为final,不论类是否被定义为final相同的规则都适用于定义为final的域。
- final类是被禁止继承的,所以类的所有方法都隐式指定为final的,因为无法覆盖他们。