"has private access" error with generics

10,744

I believe we can reduce your question down to: Why does the following example fail to compile?

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

It's a great question, and it sure took me by surprise. But we can actually remove Generics from the equation by noting that this doesn't work either:

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 {      
}

In other words, the issue is that you're dealing with a subclass of Foo, not Foo itself. In the case of T, we don't know which subclass, but we know it is a subclass, or Foo.

As you've already figured out, the solution (surprisingly, at least to me) is to upcast:

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

Or for the Generic case:

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

Is the cast so bad? Not really. It's certainly as good or better than avoiding the cast with an intermediate variable. As with any upcast, I would assume it would be removed by the compiler. It exists only as a hint to the compiler. We certainly don't have to worry about the safety of the cast, because the erasure of T is already Foo.

I can only assume this restriction is required so as to be clear about the access...since SubFoo could redeclare bar itself, it could become ambiguous which bar is being referred to, and so the cast is necessary. This is demonstrated in this complicated example:

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"
    }
}

In this regard, it serves more as a qualifier than as a cast.

Share:
10,744

Related videos on Youtube

Bernhard
Author by

Bernhard

Updated on September 15, 2022

Comments

  • Bernhard
    Bernhard over 1 year

    I had a problem I could actually solve myself, but I still don't understand why my original code doesn't work, or if there is a more elegant solution than the one I found. I'm presenting a simplified version of my code here.

    Consider the following abstract superclass X:

    public abstract class X{
    
      private int i;
    
      public void m1(X x){
        x.i = 1; 
        m2(x);
      }
    
      public abstract void m2(X x);
    
    }
    

    When m1 is called, we manipulate a private field of X of the instance passed, and then we call m2 with that instance.

    I have several subclasses of X, they are all alike in the sense that they also declare private members which they manipulate. In order to achieve that, they always need to make a cast at the beginning of m2. Here is one of them:

    public class Y extends X{
    
      private int j;
    
      public void m2(X x){
         Y y = (Y) x;
         y.j = 0;
      }
    
    }
    

    But - I can guarantee that every call of m1 of an instance of a subclass of X will always have a parameter that is of the same type, e.g. when I have an instance of Y, the parameter of the method m1 will always be another instance of Y.

    Because of that guarantee, I wanted to make the cast unnecessary, by introducing generics. This is how I want my subclasses to look like:

    public class Y extends X<Y>{
    
      private int j;
    
      public void m2(Y y){
         y.j = 0;
      }
    
    }
    

    How does the superclass X have to look like now? My first try was that:

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

    But - that doesn't work, when I compile this, I get the following error:

    X.java:6: error: i has private access in X
    

    That's usually what you get you try to access the private members of another class. Obviously, Java doesn't recognise that T is always an instance of X as well, although I used "T extends X" in the declaration.

    I fixed X like this:

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

    At least I'm not using casts any more - but why is this extra assignment necessary? And why didn't the original code work? Also, I found it strange I had to use X<?> and could not use X<T>.

  • Bernhard
    Bernhard over 10 years
    And it took me by surprise that your second code sample wouldn't compile - but knowing that, the compiler was perfectly right to criticize my original code. Thank you very much for this detailed explanation!
  • Bobulous
    Bobulous over 10 years
    Surely the message here should be that private is the wrong access modifier to use for any field you want accessible to subclasses? Why not use the protected modifier instead, or provide a getter method?
  • Bernhard
    Bernhard over 10 years
    Arkanon - I'm not accessing the field in subclasses, that's why I want to keep it private. If you look at the code again, you will see that I am accessing the private field inside the class I'm declaring it. Otherwise it would be a compile error, even with a cast!