2013-03-28 5 views
16

Der folgende Code wird in Java 1.6 kompiliert, kann jedoch nicht in Java 1.7 kompiliert werden. Warum?Warum kompiliert dieser Code in Java 1.6, aber nicht in Java 1.7?

Der relevante Teil des Codes ist der Verweis auf das private Feld 'data'. Die Referenz stammt aus derselben Klasse, in der das Feld definiert ist, und scheint daher legal zu sein. Aber es geschieht über eine generisch typisierte Variable. Dieser Code - ein abgespecktes Beispiel, basierend auf einer Klasse aus einer internen Bibliothek - arbeitete in Java 1.6, jetzt aber nicht in Java 1.7.

Ich frage nicht, wie man das umgeht. Das habe ich schon gemacht. Ich versuche eine Erklärung dafür zu finden, warum das nicht mehr funktioniert. Drei Möglichkeiten in den Sinn kommen:

  • Dieser Code ist NICHT RECHTS zum JLS nach und hätte nie zusammengestellt (es war ein Fehler in der 1,6-Compiler, fest in 1.7)
  • Dieser Code ist LEGAL gemäß der JLS und sollte kompilieren (a Rückwärtskompatibilität Fehler wurde in die 1,7-Compiler eingeführt)
  • Dieser Code fällt in einen GRAY AREA im JLS
Foo.java

:

import java.util.TreeMap; 
import java.util.Map; 

public abstract class Foo<V extends Foo<V>> { 

    private final Map<String,Object> data = new TreeMap<String,Object>(); 

    protected Foo() { ; } 

    // Subclasses should implement this as 'return this;' 
    public abstract V getThis(); 

    // Subclasses should implement this as 'return new SubclassOfFoo();' 
    public abstract V getEmpty(); 

    // ... more methods here ... 

    public V copy() { 
     V x = getEmpty(); 
     x.data.clear();  // Won't compile in Java 1.7 
     x.data.putAll(data); // " 
     return x; 
    } 

} 

Compiler Ausgang:

> c:\tools\jdk1.6.0_11\bin\javac -version 
javac 1.6.0_11 

> c:\tools\jdk1.6.0_11\bin\javac c:\temp\Foo.java 

> c:\tools\jdk1.7.0_10\bin\javac -version 
javac 1.7.0_10 

> c:\tools\jdk1.7.0_10\bin\javac c:\temp\Foo.java 
Foo.java:18: error: data has private access in Foo 
     x.data.clear(); 
     ^
Foo.java:19: error: data has private access in Foo 
     x.data.putAll(data); 
     ^
2 errors 

Addendum. Das gleiche Problem tritt auf, wenn der Verweis auf eine private Methode statt auf eine private Membervariable erfolgt. Dies funktioniert in Java 1.6, aber nicht in 1.7.

Foo2.java:

import java.util.TreeMap; 
import java.util.Map; 

public abstract class Foo2<V extends Foo2<V>> { 

    private final Map<String,Object> data = new TreeMap<String,Object>(); 

    protected Foo2() { ; } 

    // Subclasses should implement this as 'return this;' 
    public abstract V getThis(); 

    // Subclasses should implement this as 'return new SubclassOfFoo();' 
    public abstract V getEmpty(); 

    // ... more methods here ... 

    public V copy() { 
     V x = getEmpty(); 
     x.theData().clear();  // Won't compile in Java 1.7 
     x.theData().putAll(data); // " 
     return x; 
    } 

    private Map<String,Object> theData() { 
     return data; 
    } 

} 

Compiler Ausgabe:

> c:\tools\jdk1.6.0_11\bin\javac c:\temp\Foo2.java 

> c:\tools\jdk1.7.0_10\bin\javac c:\temp\Foo2.java 
Foo2.java:18: error: theData() has private access in Foo2 
     x.theData().clear(); 
     ^
Foo2.java:19: error: theData() has private access in Foo2 
     x.theData().putAll(data); 
     ^
+0

Ich würde vorschlagen, beide generierten Klassendateien zu dekompilieren, dann sollte der Unterschied offensichtlich sein. – Landei

+0

@Landei Es gibt keine generierte Klassendatei im Fall 1.7, da der Compiler es nicht kompiliert. –

Antwort

18

Die aufgezeigte Problem scheint das Verhalten in Oracle bug 6904536 berichtet übereinstimmen. Der Fehler wurde mit der folgenden Erklärung als "Kein Problem" geschlossen:

javac verhält sich gemäß der JLS. Siehe auch 6558551, 6711619 und die zugehörige JLS-Ausgabe 6644562.

Das entsprechende JLS Problem ist ungelöst, mit folgendem Kommentar:

Eine vereinfachte Erklärung für die Mitgliedschaft von Typvariablen ist willkommen. Es gibt eine allgemeine Schwierigkeit mit privaten Mitgliedern eines Typs Variable Grenzen.Formal solche Mitglieder werden nicht Mitglieder des Typ Variable selbst, obwohl javac und Eclipse traditionell sie Mitglieder und Code hat sich auf dem verlassen:

class Test { 
    private int count = 0; 
    <Z extends Test> void m(Z z) { 
    count = z.count; // Legal in javac 1.6, illegal in javac 1.7 due to fix for 6711619 
    } 
} 

Peter einen ähnlichen Test unterzogen:

class A { 
    static class B { private String f; } 

    abstract static class Builder<T extends B> { 
    abstract T getB(); 

    { 
     ((B)getB()).f.hashCode(); 
     getB().f.hashCode(); // error: f has private access in A.B 
    } 

    } 
} 

Da Kreuzungstypen durch Vererbung erstellt werden und private Mitglieder nie vererbt werden, ist es schwierig, die Schnittstellenklassen neu festzulegen, um private Mitglieder zu haben. Nichtsdestotrotz wäre es die kompatible Sache zu tun.

Als Referenz ist der relevante Abschnitt der JLS §4.4.

EDIT:

Ich neige dazu, mit der JLS hier eigentlich zustimmen, weil sie mit sich selbst übereinstimmt, wenn wir Generika aus dem Bild zu entfernen. Betrachten Sie folgendes Beispiel:

static class Parent { 

    private int i; 

    void m(Child child) { 
     i = child.i; //compile error 
    } 
} 

static class Child extends Parent { } 

child.i nicht sichtbar ist, weil der Zugang zu privaten Mitgliedern nicht vererbt wird. Dieser Punkt wird nach Hause durch die Tatsache angetrieben, dass Child seine eigenen i ohne Abschattung haben:

static class Child extends Parent { 
    private int i; //totally fine 
} 

dies so wäre notwendig, ein seltenes Beispiel für Upcasting sein:

void m(Child child) { 
    i = ((Parent)child).i; 
} 

So mit vererbten Zugänglichkeit außerhalb des Bildes scheint die JLS hier korrekt, angesichts der Tatsache, dass die V in Foo<V extends Foo<V>> nicht unbedingt Foo<V> ist aber könnte ein Typ sein, der Foo<V> erweitert.

+0

Danke Paul. Ja, die Antwort scheint zu sein, dass Javac in 6 bestimmte Formen kompiliert hat, die nicht in strikter Übereinstimmung mit der JLS waren, wobei das Obige ein Beispiel ist. javac in 7 hat das behoben. Insbesondere sollte der Zugriff auf private Felder/Methoden über eine generische Referenz * selbst innerhalb derselben Klasse * niemals erlaubt gewesen sein. (Was schade ist. Ich bin kein JLS-Experte, aber ich verstehe nicht sofort, warum es nicht sein sollte.) Kurz gesagt, der JDK-1.6-Compiler hatte einen Fehler. – Paul

+1

@Paul Kein Problem, danke für den interessanten Beitrag. Sehen Sie meine Bearbeitung, wenn Sie interessiert sind. –

+4

Sehr nette Antwort! Danke für die Recherche und Erklärung. – Jesse

Verwandte Themen