Vraag IllegalAccessError bij het werken aan erfenis - Waarom?


ik vond deze JDK-bug en wil je begrijpen waarom het gebeurt.

Het scenario (overgenomen van het bugrapport) is heel eenvoudig: a class verklaren a private methode, en een interface verklaren a public methode met dezelfde handtekening. Het compileert zonder fouten.

Nochtans, wanneer ik deze code in werking stel die ik heb gekregen IllegalAccessError

interface I {
    public void m();
}

class A {
    private void m() {
        System.out.println("Inside Class A");
    }

}

abstract class B extends A implements I {
}

class C extends B {
    public void m() {
        System.out.println("Inside Class C");
    }
}

public class Test {
    public static void main(String... args) {
        B b = new C();
        b.m();
    }
}

Help me alsjeblieft te begrijpen waarom deze fout er is mijn code compileert goed.

Exception in thread "main" java.lang.IllegalAccessError:  
tried to access method A.m()V from class Test
    at Test.main(Test.java:25)

15
2017-09-27 09:46


oorsprong


antwoorden:


Het compileert als alles goed lijkt.

Echter b.m() wordt vertaald als zoeken in de handtekening m(), in B, kennelijk eerst binnen A en (bedoeld) later in de interfaces. In A een prive m() is gevonden en knal.

Inconsistent taalgedrag en theoretisch vermijdbaar door de compiler.


geherformuleerd

Tijdens de compilatie wordt de methode van de openbare interface gevonden - goed. Tijdens runtime wordt de (modifierloze) handtekening gevonden in A, waarbij de methode privé is en nooit de handtekening in de interface bereikt waar de methode openbaar is.


[FYI] Demontage met javap

invokevirtual method .../.../B.m:()V

Natuurlijk op een C voorwerp.


7
2017-09-27 12:30



Het compileert omdat klasse B een abstracte klasse is die verklaart dat het interface I implementeert - het veronderstelt dat de implementatie de vereiste methode zal hebben.

Het type object b wordt tijdens het compileren gedeclareerd als B. Je kunt zien dat het B is en niet C als je een beetje speelt zoals in de onderstaande voorbeelden:

Om het eenvoudig te maken met een voorbeeld, als je een nieuwe methode hebt in klas c

class C extends B {
    public void m() {
        System.out.println("C.m");
    }

    public void testFromC() {}
}

dan proberen om dit in je hoofd te noemen, zal niet compileren.

public static void main(String[] args) {
    B b = new C();
    b.testFromC(); // doesnt compile
}

Als je een methode in B toevoegt, komt het goed.

abstract class B extends A implements I {
    public void testFromB() { }
}

public static void main(String[] args) {
    B b = new C();
    b.testFromB(); // compiles
}

Wanneer het programma wordt uitgevoerd en als object van klasse B wordt behandeld, wordt de implementatie van A gevonden die privé is. Als u b forceert om vanaf type C te worden bekeken door te casten, zal het werken.

public static void main(String[] args) {
    B b = new C();
    ((C)b).testFromC();
}

Als u de privé-implementatie van A voor m verwijdert, werkt deze ook zonder de casting.

class A {
    //private void m() {
    //System.out.println("A.m");
    //}
}

Dit werkt nu:

public static void main(String[] args) {
    B b = new C();
    b.m();
}

Dus zoals ik het nu begrijp, lijkt het erop dat tijdens runtime eerst wordt gecontroleerd of methode m op B of zijn ouder, en als het niets vindt, gaat het naar de implementatie van B die van klasse C is.


1
2017-09-27 12:16



Dit is een bekend probleem dat momenteel wordt bijgehouden:

JDK-8021581 Private class-methoden interfereren met aanroepingen van interfacemethoden

Dit ticket bevat een gedetailleerde analyse van het probleem, discussie over compatibiliteitsproblemen en risico's van voorgestelde oplossingen en is nog steeds open.

Oudere discussies over het onderwerp zijn hier te vinden:

JDK-6684387 IllegalAccessError voor code die door de compiler is doorgegeven
(die was gekoppeld door shmosel in zijn commentaar - dank daarvoor)

JDK-6691741 JLS-lidmaatschapsalgoritme is te sterk voor de resolutie van de JVMS-methode



1