java核心技术卷I-继承

继承

人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域, 以满足新的需求。

术语

关键字 extends 表明正在构造的新类派生于一个已存在的类。 已存在的类称为超类
( superclass)、 基类( base class) 或父类(parent class); 新类称为子类(subclass、) 派生类( derived class) 或孩子类(child class)。 超类和子类是 Java 程序员最常用的两个术语,而了解其他语言的程序员可能更加偏爱使用父类和孩子类。
子类比超类拥有的功能更加丰富。
在通过扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。因此在设计类的时候,应该将通用的方法放在超类中, 而将具有特殊用途的方法放在子类中,这种将通用的功能放到超类的做法,在面向对象程序设计中十分普遍。

覆盖方法(override)

调用超类中的方法, 而不是当前类的这个方法。为此, 可以使用特定的关键字 super 解决这个问题

super.getSalary()

有些人认为 super 与 this 引用是类似的概念, 实际上,这样比较并不太恰当。这是因为 super 不是一个对象的引用, 不能将 super 赋给另一个对象变量, 它只是一个指示编译器调用超类方法的特殊关键字。
在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。

子类构造器

由于子类的构造器不能访问超类的私有域, 所以必须利用超类的构造器对这部分私有域进行初始化,我们可以通过 super 实现对超类构造器的调用。使用super 调用构造器的语句必须是子类构造器的第一条语句。
如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数 )的构造器。 如果超类没有不带参数的构造器, 并且在子类的构造器中又没有显式地调用超类的其他构造器,则 Java 编译器将报告错误。

public Manager(String name, double salary, int year, int month, int day) {
	super(name, salary, year, month, day);
	bonus = 0; 
}

多态

一个对象变量(例如, 变量 e ) 可以指示多种实际类型的现象被称为多态( polymorphism)。

Employee e; 
e = new Employee(. . .); // Employee object expected
e = new Manager(. . .); // OK, Manager can be used as well

在 Java 程序设计语言中,对象变量是多态的。 一个 Employee 变量既可以引用一个Employee 类对象, 也可以引用一个 Employee 类的任何一个子类的对象(例如, Manager、Executive、Secretary 等)
然而,不能将一个超类的引用赋给子类变量。

动态绑定

在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)。动态绑定有一个非常重要的特性: 无需对现存的代码进行修改,就可以对程序进行扩展。假设增加一个新类 Executive, 并且变量 e 有可能引用这个类的对象, 我们不需要对包含调用e.getSalary() 的代码进行重新编译。如果 e 恰好引用一个 Executive 类的对象,就会自动地调用 Executive.getSalaryO 方法。

理解方法调用

1 ) 编译器査看对象的声明类型和方法名。假设调用 x.f(param) 且隐式参数 x 声明为 C类的对象。需要注意的是:有可能存在多个名字为 f, 但参数类型不一样的方法。例如,可能存在方法 f(int) 和方法 f(String) 编译器将会一一列举所有 C 类中名为 f 的方法和其超类中访问属性为 public 且名为 f 的方法(超类的私有方法不可访问)。
至此, 编译器已获得所有可能被调用的候选方法。
2 ) 接下来,编译器将査看调用方法时提供的参数类型。如果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重栽解析( overloading resolution)。例如,对于调用 x.f(“ Hello” )来说, 编译器将会挑选 f(String) 而不是 f(int)。由于允许类型转换( int 可以转换成 double, Manager 可以转换成 Employee, 等等,) 所以这个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法, 或者发现经过类型转换后有多个方法与之匹配, 就会报告一个错误。
至此, 编译器已获得需要调用的方法名字和参数类型。
3 ) 如果是 private 方法、 static 方法、 final 方法或者构造器, 那么编译器将可以准确地知道应该调用哪个方法, 我们将这种调用方式称为静态绑定( static binding )。 与此对应的是,调用的方法依赖于隐式参数的实际类型, 并且在运行时实现动态绑定。在我们列举的示例中, 编译器采用动态绑定的方式生成一条调用 f (String) 的指令。
4 ) 当程序运行,并且采用动态绑定调用方法时, 虚拟机一定调用与 x 所引用对象的实际类型最合适的那个类的方法。假设 x 的实际类型是 D,它是 C 类的子类。如果 D 类定义了方法 f(String) 就直接调用它;否则, 将在 D 类的超类中寻找 f(String) 以此类推。
每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表( method table), 其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候, 虚拟机仅查找这个表就行了。在前面的例子中, 虚拟机搜索 D 类的方法表, 以便寻找与调用 f(Sting) 相K配的方法。这个方法既有可能是 D.f(String), 也有可能是X.f(String), 这里的 X 是 D 的超类。这里需要提醒一点,如果调用super.f(param), 编译器将对隐式参数超类的方法表进行搜索。方法表如下:

Employee:
getName()> Employee.getName()
getSalary() -> Employee.getSalary()
getHireDay()-> Employee.getHireDay()
raiseSalary(double) -> Employee.raiseSalary(doubl e)

在运行时, 调用 e.getSalaryO 的解析过程为:
1 ) 首先,虚拟机提取 e 的实际类型的方法表。既可能是 Employee、 Manager 的方法表,也可能是 Employee 类的其他子类的方法表。
2 ) 接下来, 虚拟机搜索定义 getSalary 签名的类。此时,虚拟机已经知道应该调用哪个方法。
3 ) 最后,虚拟机调用方法

阻止继承:final 类和方法

有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为 final 类。如果在定义类的时候使用了 final 修饰符就表明这个类是 final 类。例如, 假设希望阻止人们定义Executive类的子类,就可以在定义这个类的时候使用 final 修饰符声明。
类中的特定方法也可以被声明为 final。如果这样做,子类就不能覆盖这个方法( final 类)中的所有方法自动地成为 final 方法 。

强制类型转换

将一个类型强制转换成另外一个类型的过程被称为类型转换。Java 程序设计语言提供了一种专门用于进行类型转换的表示法。

double x = 3.405;
int nx = (int) x ;

将一个值存人变量时, 编译器将检查是否允许该操作。将一个了-类的引用赋给一个超类变量, 编译器是允许的。但将一个超类的引用赋给一个子类变量, 必须进行类型转换, 这样才能够通过运行时的检査。
应该养成这样一个良好的程序设计习惯:在进行类型转换之前,先查看一下是否能够成功地转换。这个过程简单地使用instanceof 操作符就可以实现。

if (staff[1] instanceof Manager) {
boss = (Manager) staff[1]:
...
}

综上所述:
只能在继承层次内进行类型转换。
在将超类转换成子类之前,应该使用 instanceof进行检查。

抽象类

如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看, 祖先类更加通用, 人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。
使用 abstract 关键字,这样就完全不需要实现这个方法

public abstract String getDescription();

包含一个或多个抽象方法的类本身必须被声明为抽象的。除了抽象方法之外,抽象类还可以包含具体数据和具体方法。
抽象方法充当着占位的角色, 它们的具体实现在子类中。扩展抽象类可以有两种选择。
一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
类即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能被实例化。

可见性

1 ) 仅对本类可见 private。
2 ) 对所有类可见 public。
3 ) 对本包和所有子类可见 protected。
4 ) 对本包可见—默认(很遗憾) 不需要修饰符。

继承的设计技巧

  1. 将公共操作和域放在超类
  2. 不要使用受保护的域
  3. 使用继承实现“ is-a” 关系
  4. 除非所有继承的方法都有意义,否则不要使用继承
  5. 在覆盖方法时,不要改变预期的行为
  6. 使用多态, 而非类型信息
  7. 不要过多地使用反射
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

局外人一枚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值