泛型有"私有访问"错误

Ber*_*ard 11 java generics inheritance

我有一个问题,我实际上可以解决自己,但我仍然不明白为什么我的原始代码不起作用,或者是否有一个比我找到的更优雅的解决方案.我在这里展示了我的代码的简化版本.

考虑以下抽象超类X:

public abstract class X{

  private int i;

  public void m1(X x){
    x.i = 1; 
    m2(x);
  }

  public abstract void m2(X x);

}
Run Code Online (Sandbox Code Playgroud)

当调用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;
  }

}
Run Code Online (Sandbox Code Playgroud)

但是 - 我可以保证每个X子类实例的m1调用总是有一个相同类型的参数,例如当我有一个Y的实例时,方法m1的参数将始终是另一个实例Y.

由于这种保证,我想通过引入泛型来使演员不必要.这就是我希望我的子类看起来像这样的样子:

public class Y extends X<Y>{

  private int j;

  public void m2(Y y){
     y.j = 0;
  }

}
Run Code Online (Sandbox Code Playgroud)

超类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);

}
Run Code Online (Sandbox Code Playgroud)

但是 - 这不起作用,当我编译它时,我收到以下错误:

X.java:6: error: i has private access in X
Run Code Online (Sandbox Code Playgroud)

这通常是你试图访问另一个类的私人成员.显然,虽然我在声明中使用了"T extends X",但Java并不认识T总是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);

}
Run Code Online (Sandbox Code Playgroud)

至少我不再使用演员了 - 但为什么这个额外的作业是必要的呢?为什么原始代码不起作用?此外,我发现它很奇怪我必须使用X<?>,无法使用X<T>.

Mar*_*ers 8

我相信我们可以将您的问题减少到:为什么以下示例无法编译?

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
   }
}
Run Code Online (Sandbox Code Playgroud)

这是一个很好的问题,它确实让我感到意外.但是我们实际上可以通过注意到这也不起作用从等式中删除泛型:

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 {      
}
Run Code Online (Sandbox Code Playgroud)

换句话说,问题是你正在处理的是子类Foo,而不是Foo它自己.在这种情况下T,我们不知道哪个子类,但我们知道它是一个子类,或者Foo.

正如你已经想到的那样,解决方案(令人惊讶的是,至少对我来说)是向上的:

System.out.println(((Foo)baz).bar);
Run Code Online (Sandbox Code Playgroud)

或者对于通用案例:

public <T extends Foo> void printFoo(T baz) {
    System.out.println(((Foo)baz).bar);
}
Run Code Online (Sandbox Code Playgroud)

演员是如此糟糕?并不是的.它肯定与避免使用中间变量的演员表一样好或更好.与任何upcast一样,我认为它会被编译器删除.它仅作为编译器的提示存在.我们当然不必担心演员的安全,因为T已经擦除了Foo.

我只能假设这个限制是必需的,以便明确访问...因为SubFoo可以重新声明bar自己,它可能变得模糊不清bar,所以演员是必要的.这个复杂的例子证明了这一点:

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"
    }
}
Run Code Online (Sandbox Code Playgroud)

在这方面,它更像是一个限定词,而不是一个演员.