【问题标题】:"has private access" error with generics泛型的“具有私人访问权限”错误
【发布时间】:2013-12-19 03:13:46
【问题描述】:

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

考虑以下抽象超类 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&lt;?&gt;,而不能使用X&lt;T&gt;

【问题讨论】:

    标签: java generics inheritance


    【解决方案1】:

    我相信我们可以将您的问题简化为:为什么下面的示例无法编译?

    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);
    }
    

    演员阵容这么差吗?并不真地。这肯定与避免使用中间变量进行强制转换一样好或更好。与任何向上转换一样,我认为它会被编译器删除。它仅作为对编译器的提示而存在。我们当然不用担心演员表的安全,因为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"
        }
    }
    

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

    【讨论】:

    • 令我惊讶的是,您的第二个代码示例无法编译 - 但知道这一点,编译器批评我的原始代码是完全正确的。非常感谢您的详细解释!
    • 这里的信息肯定应该是 private 是错误的访问修饰符,用于您希望子类访问的任何字段?为什么不使用 protected 修饰符,或者提供一个 getter 方法?
    • Arkanon - 我没有访问子类中的字段,这就是为什么我想将其保密。如果您再次查看代码,您将看到我正在访问我正在声明的类中的私有字段。否则即使有强制转换也会是编译错误!
    猜你喜欢
    • 2017-11-17
    • 2020-05-07
    • 1970-01-01
    • 2023-03-23
    • 2016-06-20
    • 1970-01-01
    • 2018-11-29
    • 2020-03-01
    • 2013-10-27
    相关资源
    最近更新 更多