1、超类和子类
关键字extends
表明正在构造的新类派生于一个已存在的类。这个已存在的类称为超类
、基类或父类;新类称为子类
、派生类或孩子类。Java 中的类之间只支持单继承
,而接口之间支持多继承
。
在子类的构造器中,我们可以使用super
语句调用超类的构造器(该语句必须是子类构造器的第一条语句),来初始化超类的字段。如果子类的构造器没有显式地调用超类的构造器,将自动地调用超类的无参数构造器。如果超类没有无参数地构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,Java 编译器就会报告一个错误。
2、初始化顺序
继承中的初始化顺序,与普通类的初始化顺序相差不大,代码如下:
package package1;
public class Employee {
static { System.out.println("Employee 静态初始化块"); } // 1
private int e = -100;
{ // 7
System.out.print("Employee 非静态初始化块 ");
foo();
}
public Employee() {
System.out.println("Employee 无参构造器");
}
public Employee(double e) {
this(); // 6
System.out.println("Employee 有参构造器");
}
public void foo() {
System.out.println("e=" + e);
}
}
package package2;
import package1.Employee;
public class Manager extends Employee {
static { System.out.println("Manager 静态初始化块"); } // 2
private int m = 100;
{ // 8
System.out.print("Manager 非静态初始化块 ");
foo();
}
public Manager() {
super(7); // 5
System.out.println("Manager 无参构造器");
}
public Manager(double m) {
this(); // 4
System.out.println("Manager 有参构造器");
}
@Override
public void foo() {
System.out.println("m=" + m);
}
public static void main(String[] args) throws ClassNotFoundException {
Manager manager = new Manager(100.0); // 3
}
}
运行结果:
Employee 静态初始化块
Manager 静态初始化块
Employee 非静态初始化块 m=0
Employee 无参构造器
Employee 有参构造器
Manager 非静态初始化块 m=100
Manager 无参构造器
Manager 有参构造器
不做过多解释,可以参考一下对象与类中描述的初始化顺序。(注意看代码注释标出的顺序,可以打断点调试看看)
3、覆盖方法
覆盖方法
(一般加上@Override
注解,表示当前方法是覆盖方法):
- 子类方法的可见性不能低于超类方法
- 子类的返回值类型可以改为原返回类型的子类型(这时称这两个方法有
可协变
的返回类型) - 子类方法中声明的检查型异常不能比超类方法中声明的异常更通用
- 超类中的 static 方法不能被子类覆盖,但是可以被子类中相同签名的 static 方法隐藏
- 参数数量可变的形参换成数组类型构成重写
额外补充:当子类的返回值类型改为原返回类型的子类型时,编译器会在子类中合成一个桥方法
,该方法会调用覆盖的方法。
package package1;
public class Employee {
public Object foo() {
return new Object();
}
}
package package2;
import package1.Employee;
public class Manager extends Employee {
@Override
public Integer foo() {
return 7;
}
public static void main(String[] args) {
}
}
我们编译上述代码后,使用javap -v Manager.class
命令反编译看看。
public class package2.Manager extends package1.Employee {
public package2.Manager();
public java.lang.Integer foo();
public static void main(java.lang.String[]);
public java.lang.Object foo();
}
可以看到,Manager
类中确实合成了桥方法public java.lang.Object foo()
,在该桥方法中会调用覆盖的方法:
public java.lang.Object foo();
descriptor: ()Ljava/lang/Object;
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #13 // Method foo:()Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lpackage2/Manager;
4、多态(向上转型)
一个对象变量可以指示多种实际类型的现象称为多态
,在运行时能够自动地选择适当的方法,称为动态绑定
。
在将超类强制类型转换成子类之前,应该使用instanceof
进行检查;否则,如果类型不符,将会产生类型转换异常。
Employee e = new Manager(); //多态
if(e instanceof Manager) { // 类型检查
Manager m = (Manager) e; // 强制类型转换
}
5、抽象类
我们可以使用abstract
关键字来声明抽象类
和抽象方法
。(abstract 不能修饰私有方法、静态方法、final 方法/类)
包含抽象方法的类必须被声明为抽象类,但是抽象类可以不包含抽象方法。
6、静态绑定和动态绑定
静态绑定
:调用的方法在编译期间就可以确定(private 方法、static 方法、final 方法和构造器是静态绑定的)动态绑定
:在运行时根据对象的实际类型选择调用的方法
示例代码:
class Employee {
public static String field1 = "Employee Field1";
public static String method1() { return "Employee Method1"; }
public String field2 = "Employee Field2";
public String method2() { return "Employee Method2"; }
}
class Manager extends Employee {
public static String field1 = "Manager Field1";
public static String method1() { return "Manager Method1"; }
public String field2 = "Manager Field2";
public String method2() { return "Manager Method2"; }
}
public class Main {
public static void main(String[] args) {
Employee e = new Manager();
System.out.println(e.field1 + "\n" + e.field2 +
"\n" + e.method1() + "\n" + e.method2());
}
}
运行结果:
Employee Field1
Employee Field2
Employee Method1
Manager Method2
可以发现,字段不论静态与否,都是
由编译器解析的,因此不是多态的;静态方法也是由编译器解析的(静态绑定),而普通方法是在运行时确定的(动态绑定)。
为什么 static 方法不能被覆盖?用反证法证明一下。考虑刚才的示例代码:
public class Main {
public static void main(String[] args) {
Employee e = new Manager();
System.out.println(e.field1 + "\n" + e.field2 +
"\n" + e.method1() + "\n" + e.method2());
}
}
如果 static 方法能被覆盖,会发生什么?结果应该是这样。
Employee Field1
Employee Field2
Manager Method1
Manager Method2
这样就发现,调用哪个静态方法取决于 e 引用的对象,这就导致静态方法变成了动态绑定的,而实际上静态方法是静态绑定的,所以得出结论静态方法不能被覆盖。
个人理解:如果能被覆盖,那么 static 方法就变成了覆盖方法,而覆盖方法是在运行时动态绑定的,static 方法是在编译时静态绑定的。(over!)
如有错误,欢迎指正。.... .- ...- . .- -. .. -.-. . -.. .- -.-- -.-.--