Eredità alla visibilità del pacchetto in Java




inheritance overriding (2)

Sto cercando una spiegazione per il seguente comportamento:

  • Ho 6 classi, {aA, bB, cC, aD, bE, cF}, ognuna con un pacchetto visibile metodo m () che scrive il nome della classe.
  • Ho una classe a.Main con un metodo principale che esegue alcuni test di queste classi.
  • L'output sembra non seguire le regole di ereditarietà appropriate.

Ecco le lezioni:

package a;

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

// ------ 

package b;

import a.A;

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

// ------ 

package c;

import b.B;

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

// ------ 

package a;

import c.C;

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

// ------ 

package b;

import a.D;

public class E extends D {
    void m() { System.out.println("E"); }
}

// ------ 

package c;

import b.E;

public class F extends E {
    void m() { System.out.println("F"); }
}

La classe principale è nel package a :

package a;

import b.B;
import b.E;
import c.C;
import c.F;

public class Main {

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        C c = new C();
        D d = new D();
        E e = new E();
        F f = new F();

        System.out.println("((A)a).m();"); ((A)a).m();
        System.out.println("((A)b).m();"); ((A)b).m();
        System.out.println("((A)c).m();"); ((A)c).m();
        System.out.println("((A)d).m();"); ((A)d).m();
        System.out.println("((A)e).m();"); ((A)e).m();
        System.out.println("((A)f).m();"); ((A)f).m();

        System.out.println("((D)d).m();"); ((D)d).m();
        System.out.println("((D)e).m();"); ((D)e).m();
        System.out.println("((D)f).m();"); ((D)f).m();
    }
}

Ed ecco l'output:

((A)a).m();
A
((A)b).m();
A
((A)c).m();
A
((A)d).m();
D
((A)e).m();
E
((A)f).m();
F
((D)d).m();
D
((D)e).m();
D
((D)f).m();
D

Ed ecco le mie domande:

1) Capisco che Dm() nasconde Am() , ma un cast su A dovrebbe esporre il metodo nascosto m() , è vero? Oppure Dm() ignora Am() nonostante il fatto che Bm() e Cm() rompano la catena ereditaria?

((A)d).m();
D

2) Ancora peggio, il codice seguente mostra la priorità effettiva, perché?

((A)e).m();
E
((A)f).m();
F

E perché non in questa parte:

((A)a).m();
A
((A)b).m();
A
((A)c).m();
A

e questo?

((D)d).m();
D
((D)e).m();
D
((D)f).m();
D

Sto usando OpenJDK javac 11.0.2.

MODIFICA: alla prima domanda viene data risposta Come sovrascrivere un metodo con ambito di visibilità predefinito (pacchetto)?

Un metodo di istanza mD dichiarato o ereditato dalla classe D, sostituisce da D un altro metodo mA dichiarato nella classe A, se sono vere tutte le seguenti condizioni:

  • A è una superclasse di D.
  • D non eredita mA (perché attraversa i confini del pacchetto)
  • La firma di mD è una sottoscrizione (§8.4.2) della firma di mA.
  • È vero quanto segue: [...]
    • mA è dichiarato con accesso al pacchetto nello stesso pacchetto di D (in questo caso) e D dichiara mD o mA è un membro della superclasse diretta di D. [...]

MA: la seconda domanda è ancora irrisolta.


Capisco che Dm() nasconde Am() , ma un cast su A dovrebbe esporre il metodo m() nascosto, è vero?

Ad esempio, non è possibile nascondere metodi (non statici). Ecco un esempio di shadowing . Un cast su A nella maggior parte dei posti aiuta solo a risolvere l'ambiguità (ad esempio cm() come può fare riferimento sia ad A#m C#m [che non sono accessibili da a ]) che altrimenti porterebbe a un errore di compilazione.

Oppure Dm() ignora Am() nonostante il fatto che Bm() e Cm() rompano la catena ereditaria?

bm() è una chiamata ambigua perché sia A#m che B#m sono applicabili se si mette da parte il fattore di visibilità. Lo stesso vale per cm() . ((A)b).m() e ((A)c).m() si riferiscono chiaramente ad A#m che è accessibile per il chiamante.

((A)d).m() è più interessante: sia A che D risiedono nello stesso pacchetto (quindi accessibile [che è diverso dai due casi precedenti]) e D eredita indirettamente A Durante l'invio dinamico, Java sarà in grado di chiamare D#m perché D#m sovrascrive effettivamente A#m e non c'è motivo di non chiamarlo (nonostante il disordine continui sul percorso dell'eredità [ricorda che né B#mC#m ignora A#m causa del problema di visibilità]).

Ancora peggio, il codice seguente mostra la priorità effettiva, perché?

Non posso spiegarlo perché non è il comportamento che mi aspettavo.

Oserei dire che il risultato di

((A)e).m();
((A)f).m();

dovrebbe essere identico al risultato di

((D)e).m();
((D)f).m();

che è

D
D

poiché non è possibile accedere ai metodi pacchetto privato in c da a .


Domanda interessante. Ho verificato che in Oracle JDK 13 e Open JDK 13. Entrambi danno lo stesso risultato, esattamente come hai scritto. Ma questo risultato è in contraddizione con le specifiche del linguaggio Java .

A differenza della classe D, che si trova nello stesso pacchetto di A, le classi B, C, E, F si trovano in un pacchetto diverso e a causa della dichiarazione privata del pacchetto di Am() non possono vederlo e non possono sovrascriverlo. Per le classi B e C funziona come specificato in JLS. Ma per le classi E e F no. I casi con ((A)e).m() e ((A)f).m() sono bug nell'implementazione del compilatore Java.

Come dovrebbe funzionare ((A)e).m() e ((A)f).m() ? Poiché Dm() sostituisce Am() , questo dovrebbe valere anche per tutte le loro sottoclassi. Pertanto, entrambi ((A)e).m() e ((A)f).m() dovrebbero essere gli stessi di ((D)e).m() e ((D)f).m() , significa che tutti dovrebbero chiamare Dm() .





package-private