考虑以下抽象超类X:
public abstract class X{ private int i; public void m1(X x){ x.i = 1; m2(x); } public abstract void m2(X x); }
当调用m1时,我们操纵传递的实例的X的私有字段,然后我们使用该实例调用m2.
我有几个X的子类,它们都是一样的,他们也声明他们操纵的私有成员.为了达到这个目标,他们总是需要在m2的开始时做一个演员.这是其中之一:
public class Y extends X{ private int j; public void m2(X x){ Y y = (Y) x; y.j = 0; } }
但是 – 我可以保证X的子类的实例的每个m1的调用将始终具有相同类型的参数,例如当我有一个Y的实例时,方法m1的参数将永远是另一个Y的实例.
由于这样的保证,我想通过引入泛型来使得演员不必要.这就是我想让我的子类看起来像:
public class Y extends X<Y>{ private int j; public void m2(Y y){ y.j = 0; } }
超类X如何看起来像现在?我的第一个尝试是:
public abstract class X<T extends X<T>>{ private int i; public void m1(T x){ x.i = 1; m2(x); } public abstract void m2(T x); }
但是 – 这不工作,当我编译这个,我得到以下错误:
X.java:6: error: i has private access in X
这通常是你让你尝试访问另一个类的私人成员.显然,Java不知道T总是X的一个实例,尽管我在声明中使用了“T extends X”.
我固定X这样:
public abstract class X<T extends X<T>>{ private int i; public void m1(T x){ X<?> y = x; y.i = 1; m2(x); } public abstract void m2(T x); }
至少我不再使用演员了 – 但为什么这个额外的任务是必要的?为什么原代码没有工作?此外,我发现这很奇怪,我不得不使用X<?并且不能使用X T.
解决方法
public class Foo { private final String bar = "bar"; public <T extends Foo> void printFoo(T baz) { System.out.println(baz.bar); //bar is not visible } }
这是一个很好的问题,它确实令我惊喜.但是我们实际上可以从方程式中删除泛型,注意到这也不行:
public class Foo { private final String bar = "bar"; public void printFoo(SubFoo baz) { System.out.println(baz.bar); //bar is not visible } } class SubFoo extends Foo { }
换句话说,问题是你正在处理Foo的子类,而不是Foo本身.在T的情况下,我们不知道哪个子类,但是我们知道它是一个子类,或者是Foo.
如你已经想到的那样,解决方案(令人惊讶的是,至少对我来说)是upcast:
System.out.println(((Foo)baz).bar);
或对于通用情况:
public <T extends Foo> void printFoo(T baz) { System.out.println(((Foo)baz).bar); }
演员是不是很糟糕?不是真的.它绝对与避免使用中间变量的转换一样好或更好.和任何upcast一样,我会假设它将被编译器删除.它仅作为编译器的提示存在.我们当然不必担心演员的安全,因为T的擦除已经是Foo.
我只能假设这个限制是必需的,以便清楚访问…因为SubFoo可以重新声明条的本身,它可能会变得模糊,哪个栏被引用,所以演员是必要的.这在这个复杂的例子中得到证明:
public class Foo { private final String bar = "hello"; static class SubFoo extends Foo { private final String bar = "world"; } public <T extends SubFoo> void printFoo(T baz) { // System.out.println(baz.bar); // doesn't compile System.out.println(((Foo)baz).bar); //hello System.out.println(((SubFoo)baz).bar); //world } public static void main(String[] args) { new Foo().printFoo(new SubFoo()); //prints "hello\nworld" } }
在这方面,它作为一个限定词,而不是作为一个演员.