Java 继承

父子关系

类的单继承 vs 接口的多实现


一个Java类只能继承一个类,原因主要是菱形继承问题:
假设支持多继承,D同时继承B,C。不巧的是B,C是同源的,各自重写了A中的方法。那么D中的方法有B,C两个来源,产生了混乱。

一个Java类可以实现多个接口,多个相同方法签名会合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface A {
void add();
}

interface B {
void add();
}

class Sample implements A, B {
@Override
public void add() {
System.out.println("AB");
}
}

Java8接口支持default方法,但提供实现同样会遇到类似菱形继承的问题,被实现的多个接口如果包含同名方法,会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface A {
default void add() {
System.out.println("A");
}
}

interface B {
default void add() {
System.out.println("B");
}
}

//java.lang.Error: Unresolved compilation problem:
//Duplicate default methods named add with the parameters () and () are inherited from the types B and A
class Sample implements A, B {
}

默认方法可以被覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Size {
public int getSize();
};

interface SmallSize extends Size {
@Override
public default int getSize() {
return 10;
}
};

interface BigSize extends Size {
@Override
public default int getSize() {
return 100;
}
};

抽象类方法 vs 接口默认方法


Java8可以用default修饰方法,允许接口包含默认实现,接口不仅仅是方法签名,也具备了逻辑实现功能,可以进行基类实现,这符合traits概念。在此之前公共的实现都是通过抽象类来共享,
坏处是如果实现了多个接口,那么这些接口的公共基础实现就都混合在抽象类中,造成类职责复杂,太重量级,破坏了接口的职责划分。

  • 内聚性
    有了默认实现,每个接口负责提供自身的公共基础实现,独立内聚,代码更加清晰。
  • 可扩展性
    接口default方法提升了扩展性,以前如果接口新增方法,那么所有实现类要改动。有了接口默认实现,就不必特殊处理所有实现类。

重写(Overriding) vs 隐藏(Hiding)


子类实例方法可以重写父类同名的方法,应该总是加上@Override标签,让编译器检查。
方法重写要求

  • 名字和参数完全一致
  • 返回值可以是子类(Java5 协变返回类型 covariant return types)
  • 修饰符不能缩小范围

子类可以定义父类同名的静态方法。静态方法需要用类名调用,那个类调用就用哪个类的方法。
子类如果和父类有同名方法,但是一个是静态,一个是实例,将会报编译错误。

重写和隐藏的本质实际上是动态绑定和静态绑定的区别。
重写对应动态绑定,在运行时才能决定调用哪个类的方法,实现了多态性。
隐藏对应静态绑定,在编译时已决定调用方法。
静态域、静态方法都是静态绑定,另外实例域也不具备多态性,即所有域都不能被子类重写。

Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Parent{
public static String staticField = "Parent static field";
public String instanceField = "Parent instance field";

public static String staticMethod(){
return "Parent static method";
}
public String instanceMethod(){
return "Parent instance method";
}

}

class Child extends Parent{
public static String staticField = "Child static field";
public String instanceField = "Child instance field";

public static String staticMethod(){
return "Child static method";
}
@Override
public String instanceMethod(){
return "Child instance method";
}
}

测试

Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
System.out.println(Parent.staticField);//Parent static field
System.out.println(Parent.staticMethod());//Parent static method

System.out.println(Child.staticField);//Child static field
System.out.println(Child.staticMethod());//Child static method

Parent ins = new Child();
//实例变量没有多态性,父类声明就访问父类
System.out.println(ins.instanceField);//Parent instance field
//实例方法有多态性
System.out.println(ins.instanceMethod());//Child instance method
//即便子类实例强转父类还是访问子类方法,相当于子类实例赋值给父类
System.out.println(((Parent) new Child()).instanceMethod()); // Child instance method

父类变量容纳子类


虽然实际是子类对象,但是通过父类变量,只能调用父类方法
Parent p = new Child();

Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Parent {
public void method(Parent p) {
System.out.println("Parent");
}
}

class Child extends Parent {
public void method(Child c) {
System.out.println("Child");
}

@Override
public void method(Parent p) {
System.out.println("Parent-Child"); //call
}
}

public class Test {
public static void main(String[] args) throws Exception {
Parent p = new Child();
Child c = new Child();
p.method(c); //Parent-Child 子类第一个方法不可见,通过父类只可见父类方法,并且此方法被子类重写
}
}