即入参只能是父类入参或者父类入参的父类。即如果父类方法的入参是 P 类,那么子类方法的入参只能是 P 类,或者 P 类的父类。
我们举例说明一下。 下面的例子中, 我们定义了三个类, A,B, C。 其中 B 和 C 是 A 的子类。B 类的入参是P,比父类的宽松,满足法则。而 C 类的入参是 P2,比父类的严格,不满足法则。
之所以这样做,是为了避免子类使用了更加宽松的入参类型中,特有的一些方法,而导致程序出错。
比如下面的例子中,调用了 A 类的 test 方法,并且入参是 P 类。接着用 C 类替换 A 类的位置,同样执行 test 方法,入参还是 P,但是执行会报错(先不考虑编译问题,假设传参能够成功),因为 C 类的 test 方法中,调用了 p2Method 方法,
而这个方案是 P 的子类 P2 的独有方法。
class P {
private void method(){};
}
class P1 extend P {
}
class P2 extend P1{
private void p2Method(){};
}
class A {
private void test(P1 p){};
}
// 符合
class B extend A {
private void test(P p){};
}
// 不符合
class C extend A {
private void test(P2 p){
p.p2Method();
};
}
P p = new P();
A a = new A();
a.test(p);
C c = new C();
// 执行失败,因为 P 没有 p2Method 方法。如果用c替换掉a,则会失败
c.test(p);
复制代码
1.2 返回值不能比父类宽松
即返回值只能是父类返回值,或者父类返回值的子类。
下面的例子中,父类A的返回值是 R1, B的返回值类型是 R2, 满足规则。而 C 的返回值是 R,不满足规则。 之所以要这样做,是因为如果子类返回的类型更加宽松,会导致调用方调用出错。
比如下面的例子中 A 执行了 test方法,返回值是 R1,此时调用 r1Method 是合法的。而如果用 C 类替换掉 A类的位置,因为 C 的test 方法返回是 R,没有r1Method方法,所以调用会出错。(先忽略编译失败的问题)
这里的逻辑处理指的是条件判断,类型转换等等。
下面用两个例子来说明。第一个例子是关于类型转换的逻辑。这里有三个参数类,P,P1,P2,其中 P1 和 P2 是 P 的子类。
A 类提供了 test 方法,并且接收的 入参是 P 类。B 类继承自 A 类,所不同的是, B 类重新实现了 test 方法。
我们注意到, B 的 test 方法中,对入参 P 类进行了强转,虽然是符合语法约束的,但在某些场景下会出现问题。比如在调用 B 类的 test 方法时,传了 P2,那么强转就会失败了。
class P {
private void method(){...};
}
class P1 extend P {
private void p1Method(){...};
}
class P2 extend P {
private void p2Method(){...};
}
class A {
private void test(P p){...};
}
class B extend A {
private void test(P p){ // 这里做了强制转换 P1 p = (P1)p;
p.p1Method();
};
}
// 这样处理没问题
P p = new P1();
A a = new B();
a.test(P1);
// 但如果入参是 P2,就会报错了。因为B类中,会将入参强制转换成 P1,类型转换失败
P p = new P2();
A a = new B();
a.test(P1);
复制代码
这个例子并非属于极端例子,如果翻看一下实际的应用代码,我相信比比皆是。 那对于这种场景,我们应该怎样去做调整呢?
本质的问题在于,为什么要在代码逻辑中,转成具体的子类 P1 去操作? 因为要使用 P1 的独有方法。 对此我们可以倒推,原本父类定义中的接口入参类型已经满足不了我们的需求。
解决的方法是,考虑重新对父类的入参进行抽象,将子类的 P1 独有方法沉淀进 P 类。第二个例子是关于条件判断的,子类不能比父类严格。B 类重新实现了 test 方法,并且对于入参的前置条件判断,