2013-03-30 7 views
20

Ich fand eine seltsame Kompilierungseinschränkung, die ich nicht erklären kann, und ich verstehe den Grund dieser Einschränkung nicht."Methode ___() in ___ ist in unzugänglicher Klasse oder Schnittstelle definiert" Kompilierungsfehler

Beispiel 1:

Betrachten Sie diese Klassen:

In package e1;:

public class C1 { 
    enum E1 { A, B, C } 
    public E1 x; 
} 

In package e2;:

import e1.C1; 
public class C2 { 
    public String test(C1 c1) { 
     return c1.x.toString(); // here compilation error 
    } 
} 

Dies bewirkt, dass die folgenden Kompilierung-Fehler:

Error:(5,20) java: toString() in java.lang.Enum is defined in an inaccessible class or interface

Beispiel 2:

Betrachten Sie diese Klassen:

In package i1;:

public interface I1 { 
    int someMethod(); 
} 

public class C1 { 
    static class I2 implements I1 { 
     public int someMethod() { 
      return 1; 
     } 
    } 
    public I2 x = new I2(); 
} 

In package i2;:

import i1.C1; 
import i1.I1; 
public class C2 { 
    public static void main(String[] args) { 
     C1 c1 = new C1(); 
     System.out.println(c1.x.someMethod()); // compilation error 
    } 
} 

Dies bewirkt, dass auch den gleichen Übersetzungsfehler, aber wenn wir die Codezeile zu ändern:

System.out.println(((I1)c1.x).someMethod()); 

Dann ist diese kompiliert werden kann und funktioniert einwandfrei.


Also, die Frage ist:

Warum ist diese Einschränkung der Zugänglichkeit erforderlich?

Ja, ich verstehe, dass Klassen C1.E in Beispiel-1) und C1.I2 in Beispiel-2) sind Paket privat. Aber gleichzeitig ist klar, dass niemand schwächeren Zugriffsberechtigungen für Methoden einer Basisschnittstelle zuweisen kann (I1 von Object), so dass es immer sicher ist, ein Objekt direkt auf seine Basisschnittstelle zu werfen und Zugriff auf die eingeschränkte Methode zu erhalten.

Kann jemand den Zweck und den Grund dieser Einschränkung erklären?

UPDATE: assylias wies darauf hin, JLS §6.6.1:

A member (class, interface, field, or method) of a reference (class, interface, or array) type or a constructor of a class type is accessible only if the type is accessible...

aussieht wie dies die Einschränkung ist, aber es nicht nicht erklären, warum diese Beschränkung (in den Fällen der oben genannten Beispielen) ist benötigt ...

+0

schreibe'Enum E1 {A, B, C} 'als' public enum E1 {A, B, C} ' –

+1

@Andremoniy Die Antwort ist in [# 6.6.1] (http://docs.oracle. com/javase/specs/jls/se7/html/jls-6.html # jls-6.6.1) Ich glaube: * Ein Mitglied (Klasse, Schnittstelle, Feld oder Methode) einer Referenz (Klasse, Schnittstelle oder Array) type oder ein Konstruktor eines Klassentyps ist nur zugänglich ** wenn der Typ zugänglich ist ** und das Mitglied oder der Konstruktor deklariert ist, um den Zugriff zu erlauben *. – assylias

+0

@BheshGurung Ich weiß, ich frage: Warum brauchen wir diese Einschränkung der Zugänglichkeit? – Andremoniy

Antwort

10

Für den Aufruf von Instanzmethoden wird invokevirtual Anweisung verwendet.Um diese Methode Klasse aufrufe muss diese Methode

Von invokevirtual Spezifikation einen aufgelösten Bezug hat:

Linking Exceptions

During resolution of the symbolic reference to the method, any of the exceptions pertaining to method resolution (§5.4.3.3) can be thrown.

5.4.3.3. Verfahren Auflösung:

To resolve an unresolved symbolic reference from D to a method in a class C, the symbolic reference to C given by the method reference is first resolved (§5.4.3.1).

5.4.3.1. Klasse und Schnittstelle Auflösung:

If C is not accessible (§5.4.4) to D, class or interface resolution throws an IllegalAccessError.

5.4.4. Zugangskontrolle:

A class or interface C is accessible to a class or interface D if and only if either of the following conditions is true:

  • C is public.

  • C and D are members of the same run-time package (§5.3).

C und D sind nicht aus dem gleichen Paket. Selbst wenn Java diesen Code für Sie kompiliert, wird während des Aufrufs ein IllegalAccessError ausgelöst. Der Compiler ist clever genug, um solche offensichtlichen Fehler zu verhindern. Diese Einschränkungen ergeben sich aus den Anforderungen des Klassenauflösungsprozesses von Java.

Zum Aufrufen der Instanzmethode benötigt JVM zwei Dinge: Verweis auf das Objekt und Beschreibung des Objekts (Klasse oder Schnittstelle). Die Beschreibung wird über resolution process aufgerufen. Wenn dies fehlschlägt, schlägt der Aufruf fehl.

If an error occurs during resolution of a symbolic reference, then an instance of IncompatibleClassChangeError (or a subclass) must be thrown at a point in the program that (directly or indirectly) uses the symbolic reference.

In Ihrem Fall hat C2 Zugriff auf I1. Der Schnittstellenaufruf funktioniert also gut. Aber C2 hat keinen Zugriff auf Klasse I2. Und deshalb könnte IllegalAccessError zur Laufzeit geworfen werden, wenn dieser Code kompiliert wird.

Wie IllegalAccessError reproduzieren:

  1. Machen innere Klasse Öffentlichkeit und all beispielsweise
  2. Machen innere Klasse Paket privat in IDE kompilieren und kompilieren es javac mit nur von der Kommandozeile.
  3. Ersetzen IDE generierten Klassen mit cmd
  4. erzeugt Und Sie werden so etwas wie sehen:

Exception in thread "main" java.lang.IllegalAccessError: tried to access class qq.Test1$I2 from class Test at Test.main(Test.java:30)

+0

Nun, danke für deine Antwort. Es ist ziemlich überzeugend. Ich würde gerne auf eine andere Variante der Antwort und auf eine andere Antwort warten. – Andremoniy

2

Die Zugriffskontrollen (privat, geschützt, öffentlich, Paket) sind alle gut definiert und geben dem Programmierer eine Menge Flexibilität, um den Zugriff auf Klassen, Variablen, Methoden usw. zu kontrollieren.Ihre Frage veranschaulicht nur ein bestimmtes Beispiel für die Anwendung dieser Zugriffskontrollen. Die Art, wie Sie Ihr Paket i1 programmiert haben, haben Sie gesagt, dass es für jeden okay ist, Schnittstelle I1 zu sehen, aber dass es nicht für jeden OK ist, Klasse I2 zu sehen. Auf diese Weise können Sie der Klasse I2, die nicht öffentlich ist, Funktionalität hinzufügen und gleichzeitig den Zugriff auf das Bit I2 zulassen, das I1 implementiert. Zum Beispiel, wenn ich ein Paket lokale Integer-Klasse hinzufügen I2

public class C1 { 
    static class I2 implements I1 { 
     int i; 
     public int xxx() { 
      return 1; 
     } 
    } 
    public I2 x = new I2(); 
} 

dann haben Sie Zugriff auf die int in Ihrem Paket erhalten, aber nicht außerhalb, und außerhalb Ihres Pakets können Sie nicht durch Gießen dieses Problem umgehen, :-).

+0

Sehr geehrte @Stochastically, die Frage war nicht über ** wie eine Problemumgehung zu finden und den Code ** zu beheben. Die Frage ist: ** Warum sollte ich mein Referenzobjekt in die Klasse 'Object' (zum Beispiel) umwandeln, um Zugriff auf 'Object' Klassenmethoden zu bekommen, während alle Klassen implizit die Vererbung' Object' Klasse **. – Andremoniy

+1

@Andremoniy, ich habe Ihren Code nicht repariert, ich habe ein Beispiel dafür gefunden, warum die Regeln sinnvoll sind. Die Object-Klasse ist public, die Instanz x der Klasse I2 in der Klasse C1 ist public, also dürfen Sie gemäß den Regeln von Java außerhalb des Pakets auf x entweder (a) ein Objekt oder (b) eine Klasse zugreifen implementiert I1. Da die Klassendefinition von I2 jedoch Paket lokal ist, ist es nicht erlaubt, auf andere Weise darauf zuzugreifen. Außerhalb des Pakets können Sie also nicht auf das von mir hinzugefügte int zugreifen, und wenn es z. eine lokale Paketschnittstelle I3, dann konnte man auch nicht als I3 darauf zugreifen. – Stochastically

1

E1 aus Beispiel 1 und I2 aus Beispiel 2 sind beide für die Anrufer in diesen Beispielen nicht sichtbar. So ist es für mich klar, dass der Compiler sagt, dass er nicht damit umgehen kann. Deine Absicht ist mehrdeutig. Oder anders herum: Wie sollte die JVM solch einen Aufruf zu unsichtbaren Klassen handhaben? An eine Schnittstelle werfen? Welche als Klasse kann mehrere implementieren.

Vielleicht wäre es möglich, den Compiler in speziellen Fällen einen Cast zu seiner Schnittstelle hinzuzufügen, aber es ist nur das: Sonderfälle und keine allgemeine Regel. Außerdem wäre es für Entwickler sehr verwirrend, eine Instanz einer Klasse zu erhalten, auf die nicht zugegriffen werden kann (denken Sie an Frameworks).

Wenn Sie Interna verbergen möchten, verwenden Sie lieber (öffentliche) Schnittstellen anstelle von paket-privaten Implementierungen.

+0

sie beide sind nicht sichtbar, aber beide von ihnen tun Vererbung von 'Object' Klasse. Also, warum sollte ich sie offensichtlich zu "Object" umwandeln, wenn ich nur 'toString()' Methode aufrufen möchte? – Andremoniy

+0

Warum erwarten Sie, dass es funktioniert? Sie geben explizit eine Instanz eines INVISIBLE-Typs zurück. In Java kommt es vor, dass alle Klassen von "Object" erben, und wenn Sie den Typ als "Object" deklarieren, ist alles in Ordnung, da sich Caller und Callee auf einen Vertrag einigen, den beide sehen/validieren können. In Ihrem Beispiel verbergen Sie diese Verträge. Obwohl es für Sie offensichtlich ist, dass es sich um den gleichen Vertrag wie für "Objekt" handelt, ist es nicht für den Compiler - es kann einfach nicht die gleiche Ansicht haben wie Sie. – Andy

-1

Ich werde versuchen, es zu erklären, indem Sie Ihr Beispiel 2 verwenden und es ein wenig ändern.

Angenommen Klasse C1 waren wie diese

public class C1 { 
    static class I2 implements I1 { 
     public int xxx() { 
      return 1; 
     } 

     public int hello(){ 
      return 1; 
     } 
    } 
    public I2 x = new I2(); 
} 

Ich habe gerade eine andere öffentliche Methode hallo() der Klasse I2 hinzugefügt.

In

package i2;: 

import i1.C1; 
import i1.I1; 
public class C2 { 
    public static void main(String[] args) { 
     C1 c1 = new C1(); 

     c1.x.hello(); // Will not compile. 
     c1.x.toString(); //Will not compile.    

     Object mObject=(Object)c1.x; 
     mObject.toString(); //Will compile. 
    } 
} 

nun Ihre Frage, warum die Beschränkung zu beantworten. Die Einschränkung ermöglicht dem Programmierer den Zugriff auf die öffentlichen Methoden der Klasse I2 zu verhindern. Da der Zugriffsspezifizierer I2 der Standardzugriffsspezifizierer ist, sollte keine seiner Methoden außerhalb des Pakets verfügbar sein, daher die Einschränkung. Wenn die Einschränkung nicht vorhanden wäre, hätten wir die Methode hello() von C2 (die in einem anderen Paket ist) zugreifen können, obwohl I2 den Standardzugriffsspezifizierer hat.

Casting c1.x zu Object funktioniert, weil jetzt der Compiler es als ein Objekt der Klasse Object behandelt. Daher können wir auf die Methode toString() zugreifen. Daher funktioniert mObject.toString(), während c1.x.toString() nicht Klasse I2 ist nicht von Klasse C2 zugänglich.

+1

Sie sprechen über offensichtliche Dinge. Ich suche nach einer tieferen Analyse und Erklärung dieser Einschränkung. Es tut uns leid. – Andremoniy

-1

In beiden Beispielen Sie innere Klassen/Schnittstellen verwenden. Auf innere Klassen kann von keinem Objekt außerhalb der Klasse zugegriffen werden, in der sie definiert sind.

Daher müssen Sie sie auf Objekte (oder einen anderen Supertyp Ihrer inneren Klasse) umwandeln. Sie müssen diese Besetzung manuell durchführen, da das JDK keine impliziten Umwandlungen kennt.

Der Weg, damit umzugehen, besteht darin, Ihre inneren Klassen in reguläre Klassen zu verwandeln. Innere Klassen sind sehr nützlich, aber nur für sehr spezifische Aufgaben.

+1

Das ist nicht so. Wenn eine innere Klasse öffentlich ist, können Sie von einer anderen Klasse oder einem anderen Paket darauf zugreifen. Wenn die innere Klasse "public static" ist, dann ist sie genau wie jede andere Klasse. Wenn die innere Klasse "public" ist, aber nicht statisch, dann können Sie keine neue Instanz aus einer anderen Klasse erstellen, weil der Bereich fehlt. –

Verwandte Themen