泛型中的“私有访问”错误

时间:2013-12-19 03:13:47

标签: 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);

}

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

1 个答案:

答案 0 :(得分:7)

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

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

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

System.out.println(((Foo)baz).bar);

或者通用案例:

public <T extends Foo> void printFoo(T baz) {
    System.out.println(((Foo)baz).bar);
}

演员是如此糟糕?并不是的。它肯定与避免使用中间变量的演员表一样好或更好。与任何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"
    }
}

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