1.LSP原则:
内容:
任何基类可以出现的地方,子类一定可以出现。
四条原则:
- 子类完全拥有父类的方法,当子类不是抽象类时,必须将父类的抽象方法全部实现。
- 子类可以拥有父类没有的方法
- 在子类中override或实现父类的方法时,前置条件需要更加宽松(形参比父类方法更加宽松)。
- 在子类中override或实现父类的方法是,后置条件需要更加严格(返回值)。
个人理解:
- LSP的四条原则其实是为了保证同一件事:父类是可以被任意一个子类在任何时候替换的。这也是继承和复用的前提。
- 总的来说,对于后两条的理解,其实就是一定要保证子类的各种输入输出在其父类中一定是合法的。实际上我们在写代码的时候,对于同一个函数,如果前置条件宽松,后置条件严格,spec实际上是增强了的。单从后两条来看,对于子类与父类来说,我们也可以将子类看成是拥有“改进过spec”的更强的方法的父类。
2. 协变和逆变:
内容:
- 协变:
- 对于某一个方法Fun(),其传入参数为A、B,当A为B的子类时,Fun(A)也是Fun(B)的子类。
- 逆变:
- 对于一个方法Fun(),其传入参数为A、B。当A为B的子类时,Fun(A)也是Fun(B)的子类。
一些奇怪的应用:
1. 类型擦除
例如这段代码:
List<Animal> ani = new ArrayList<>();
List<Goat> goats = new ArrayList<>();
虽然在这里,Goat类是Animal的子类,但是Animal的List和Goat的List并不存在继承关系,因为在编译阶段,Animal和Goat的位置是泛型结构,编译之后会被编译器抹去,运行时,ani和goats两个变量只会保留List这个类型,又因为后边实例化了ArrayList,实际打印类型我们可以得到:
System.out.println(ani.getClass().getName());
// OUTPUT:
// java.util.ArrayList
System.out.println(goats.getClass().getName());
// OUTPUT:
// java.util.ArrayList
实际上,类型擦除就是将泛型的结构出填写的类型替换为其上限类型(个人理解,就是规定好的能够兼容这个类的“最高父类”),这里没有指定上限类型,Animal和Goat将全部被替换为Object。因此,List和List,在运行时都是List,继承关系消失。
2. 泛型的协变和逆变:
例如这段代码:
public class Animal(){
String name;
public void eat(Object a){
}
}
public class Goat extends Animal(){
@Override
public void eat(Plant a){
}
}
对于:
// 下边的一行是不能通过的。
List<Animal> animals = new ArrayList<Goat>();
这两段代码描述了一个情形:我们设置了泛型结构,但是我们还需要避免类型擦除,实现泛型的协变和逆变。这时,通配符<?>登场了。
这里,我们定义了一个Animal的集合,但是想把它作为一个Goat集合使用,按理说应该是被允许的,但是java(junk)的编译器会报错,因为在java编译器的眼里,这俩玩意不是已经“父子兵”,而是“亲兄弟”了。这时候,我们就需要借助上界通配符<? extends Animal>的帮助 ,用来规定上限类型,如下:
List<? extends Animal> animals2 = new ArrayList<Goat>();
Animal ani = animals2.get(0);
对于:
// 下面一行编译是不能通过的
List<Goat> goats = new ArrayList<Animal>();
这里,我们所需要的和上一个问正好相反,此时下界通配符<? super Goat>登场:
List<? super Goat> animals2 = new ArrayList<Animal>();