能让你面试到崩溃的 interface 泛型及继承关系。很多人不相信,于是我就发到群里,错的人一大堆。很多高级的 Java 工程师在被我面试时,也会崩溃的。一不小心就会掉到陷阱里,越是自信慢慢的人,我打击的越狠!
一个接口可以继承多个接口。interface C extends A, B {}是可以的。一个类可以实现多个接口: class D implements A,B,C{}是可以的。但是一个类只能继承一个类,不能继承多个类。class B extends A{}是可以的。在继承类的同时,也可以继承接口: class E extends D implements A,B,C{} 这也正是选择用接口而不是抽象类的原因。
这只能是初级工程师的面试题,高级的工程师,我就加点料。来点泛型及继承关系,错的人也是非常的多。
下面我们先来看看第一个,先来个简单一点的。
interface X { int m(Iterable<String> arg); } interface Y { int m(Iterable<String> arg); } interface Z extends X, Y {}
接口 Z 继承了 X,Y 接口的 m 方法,由于这两个方法的签名相同,返回值也一样,所以 Z 有唯一的一个抽象方法 int m(Iterable<String> arg); 并且接口 Z 可以作为函数式接口。
我们简单的变一下形,看第二个例子。
interface X { Iterable m(Iterable<String> arg); } interface Y { Iterable<String> m(Iterable arg); } interface Z extends X, Y {}
方法签名 Y.m 既满足签名是 X.m,并且返回值也满足 return-type-substitutable。所以 Z 是函数式接口,函数类型为 Iterable<String> m(Iterable arg)。
继续,我们再看第三个例子。
interface X { int m(Iterable<String> arg); } interface Y { int m(Iterable<Integer> arg); } interface Z extends X, Y {}
编译出错,错误原因是没有一个方法的签名是所有方法的子签名。
下面我们再看第四个例子。
interface X { int m(Iterable<String> arg, Class c); } interface Y { int m(Iterable arg, Class<?> c); } interface Z extends X, Y {}
Compiler error: No method has a subsignature of all abstract methods
编译出错, 没有一个方法的签名是所有方法的子签名。
继续第五个例子,这个比较简单。
interface X { long m(); } interface Y { int m(); } interface Z extends X, Y {}
Compiler error: no method is return type substitutable
编译出错, 返回值类型不同。
继续第六个案例。
interface Foo<T> { void m(T arg); } interface Bar<T> { void m(T arg); } interface FooBar<X, Y> extends Foo<X>, Bar<Y> {}
Compiler error: different signatures, same erasure
编译出错。
第七个案例。
interface Foo { void m(String arg); } interface Bar<T> { void m(T arg); } interface FooBar<T> extends Foo, Bar<T> {}
虽然可以正常使用,但不能作为一个函数式接口,因为两个方法的类型参数不一样。
第八个案例,稍微复杂一点。
interface X { void m() throws IOException; } interface Y { void m() throws EOFException; } interface Z { void m() throws ClassNotFoundException; } interface XY extends X, Y {} interface XYZ extends X, Y, Z {}
X.m,Y.m,Z.m 方法签名相同,返回值类型都是 void,只是异常列表不同。 EOFException 是 IOException 的子类。
在这种情况下 XY 和 XYZ 都是函数式接口,但是函数类型不同。
// XY has function type ()->void throws EOFException
// XYZ has function type ()->void (throws nothing)
案例九。
interface A { List<String> foo(List<String> arg) throws IOException, SQLTransientException; } interface B { List foo(List<String> arg) throws EOFException, SQLException, TimeoutException; } interface C { List foo(List arg) throws Exception; } interface D extends A, B {} interface E extends A, B, C {}
// D has function type (List)->List throws EOFException, SQLTransientException
// E has function type (List)->List throws EOFException, SQLTransientException
案例十。
interface G1 { <E extends Exception> Object m() throws E; } interface G2 { <F extends Exception> String m() throws Exception; } interface G extends G1, G2 {}
G has function type ()->String throws F。
案例十一。
public class Z { public static void main(String[] args) { Object o = (I & J) () -> {}; } } interface I { void foo(); } interface J { void foo(); }
I 和 J 方法的交集依然符合函数式接口的定义。 上述代码可以用 JDK 中的 javac 编译通过但是 Eclipse 报错,这是 Eclipse 的一个 bug。
案例十二。
public class Z { public static void main(String[] args) { Object o = (I & J) () -> {}; } } interface I { void foo(); } interface J { void foo(); void bar(); }
上述代码 Eclipse 不会报错但是 javac 无法编译,javac 认为 (I & J)不是一个函数式接口。 看起来 javac 工作正常,Eclipse 处理这样的 case 还有问题。
说了这么多,有规律吗?有,当然有。
接口可以继承接口。 如果父接口是一个函数接口, 那么子接口也可能是一个函数式接口。我们的判断依据如下:
对于接口 I, 假定M是接口成员里的所有抽象方法的继承(包括继承于父接口的方法), 除去具有和 Object 的 public 的实例方法签名的方法, 那么我们可以依据下面的条件判断一个接口是否是函数式接口, 这样可以更精确的定义函数式接口。
如果存在一个一个方法 m, 满足:
- m 的签名(subsignature)是 M 中每一个方法签名的子签名(signature)
- m 的返回值类型是 M 中的每一个方法的返回值类型的替代类型(return-type-substitutable)
那么 I 就是一个函数式接口。
说了这么多,感觉复杂吗?一点也不复杂,记住上面这个定义即可。
: » 能让你面试到崩溃的 interface 泛型及继承关系
原创文章,作者:Carrie001128,如若转载,请注明出处:https://blog.ytso.com/251944.html