能让你面试到崩溃的 interface 泛型及继承关系

能让你面试到崩溃的 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 泛型及继承关系

: » 能让你面试到崩溃的 interface 泛型及继承关系

原创文章,作者:Carrie001128,如若转载,请注明出处:https://blog.ytso.com/251944.html

(0)
上一篇 2022年5月3日
下一篇 2022年5月3日

相关推荐

发表回复

登录后才能评论