Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type-parameterized field of a generic class becomes invisible after upgrading to Java 7

Now Eclipse Indigo SR1 with builtin Java 7 support is finally out since a week or two, I'm migrating my playground projects from Helios SR2 + JDK 1.6_23 to Indigo SR1 + JDK 1.7.0. After a full rebuild of all projects, only one class has failed to compile. It's the following class which compiles and runs perfectly fine on Java 1.6 (and 1.5):

public abstract class Area<A extends Area<?>> implements Comparable<Area<?>> {      private String name;     private Area<?> parent;     private Set<A> areas;      protected Area(String name, A... areas) {         this.name = name;         this.areas = new TreeSet<A>();         for (A area : areas) {             area.parent = this;             this.areas.add(area);         }     }      public Set<A> getAreas() {         return areas;     }      // ... } 

The line area.parent = this; fails with the following error on parent:

The field Area<capture#1-of ?>.parent is not visible

After first suspecting the Eclipse compiler, I tried with plain javac from JDK 1.7.0, but it gives basically the same error whereas the javac from JDK 1.6.0_23 succeeds without errors.

Changing the visibility to protected or default solves the problem. But the why is completely beyond me. I peeked around on http://bugs.sun.com, but I couldn't find any similar report.

Another way to fix the compilation error is to replace all used A declarations inside the class by Area<?> (or to remove it altogether):

public abstract class Area<A extends Area<?>> implements Comparable<Area<?>> {      private String name;     private Area<?> parent;     private Set<Area<?>> areas;      protected Area(String name, Area<?>... areas) {         this.name = name;         this.areas = new TreeSet<Area<?>>();         for (Area<?> area : areas) {             area.parent = this;             this.areas.add(area);         }     }      public Set<Area<?>> getAreas() {         return areas;     }      // ... } 

But this breaks the purpose of the getter. In case of for example the following class:

public class Country extends Area<City> {      public Country(String name, City... cities) {         super(name, cities);     }  } 

I'd expect it to return Set<City>, not Set<Area<?>>.

Which change in Java 7 has caused those type-parameterized fields to become invisible?

like image 450
BalusC Avatar asked Oct 10 '11 23:10

BalusC


People also ask

What can a generic class be parameterized for?

Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.

How do you provide a generic parameterized type?

In order to use a generic type we must provide one type argument per type parameter that was declared for the generic type. The type argument list is a comma separated list that is delimited by angle brackets and follows the type name. The result is a so-called parameterized type.

How many type parameters can be used in a generic class?

You can also use more than one type parameter in generics in Java, you just need to pass specify another type parameter in the angle brackets separated by comma.

What is parameterized class in Java?

The type parameter section of a generic class can have one or more type parameters separated by commas. These classes are known as parameterized classes or parameterized types because they accept one or more parameters.


1 Answers

This appears to be a javac6 bug, which is fixed in javac7.

The workaround is by up-cast:

((Area<?>)area).parent = this; 

Which seems really odd - why would we need an up-cast to access a member in super class?

The root problem is, private members are specifically excluded from inheritance, therefore A does not have a parent member. The same problem can be demonstrated by a non-generic example.

The error message "parent has private access in Area" is not quite accurate, though it's probably fine for most cases. However in this case, it's misleading, a better message would be "A does not inherit the private member 'parent' from Area"


For the interest of investigation, let's do a full analysis on your example based on the JLS:

  • §4.4: The members of a type variable X with bound T & I1 ... In are the members of the intersection type (§4.9) T & I1 ... In appearing at the point where the type variable is declared.

  • §4.9: The intersection type has the same members as a class type (§8) with an empty body, direct superclass Ck and direct superinterfaces IT1 , ..., ITn, declared in the same package in which the intersection type appears.

  • §6.6.1: If the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

  • §8.2: Members of a class that are declared private are not inherited by subclasses of that class.

  • §8.5: A class inherits from its direct superclass and direct superinterfaces all the non-private member types of the superclass and superinterfaces that are both accessible to code in the class and not hidden by a declaration in the class.

like image 179
irreputable Avatar answered Oct 01 '22 10:10

irreputable