Comprensione del polimorfismo e dell'interfaccia in Java

5

Sto leggendo alcune note. E non sto comprendendo le seguenti due dichiarazioni.

  1. Il polimorfismo indica che è sempre la classe dell'oggetto reale in fase di esecuzione a determinare quale metodo verrà chiamato in fase di esecuzione.

    class A{
        public void m1(){
        System.out.println(" A");
        }
    } 
    
    class B extends A{
        public void m1(){
        System.out.println(" B");
        }
    
        public void m2(){
        System.out.println(" m2");
        }
    } 
    
    B b = new B();
    A a = new B();
    // will print B
    b.m1();
    // will also print B because a refers to an object of class B.
    a.m1();
    
  2. Durante l'accesso a un metodo o variabile, il compilatore ti permetterà solo di accedere a un metodo o variabile visibile attraverso la classe del riferimento.

    interface Flyer{
        String getName();
    } 
    class Bird implements Flyer{
    ...
        public String getName(){
            return name;
        }
    
        public String getNativeOf(){
        return nativeOf;
        }
    }
    
    Flyer f = new Bird();
    f.getName(); //valid
    f.getNativeOf(); //will not compile because the class of reference f is Flyer and not Bird.
    ((Bird) f).getNativeOf(); //valid because class of the object referred to by f is Bird.
    

Quindi è per oggetto reale o riferimento?

    
posta fizzix 13.04.2016 - 18:21
fonte

2 risposte

2

Entrambi. Stai un po 'confondendo le nozioni di disponibilità metodo / campo per tipo e late binding. Sono separati, anche se correlati.

... actual object at runtime determines which method ...

Potrei riaffermarlo come "il cui" metodo invece di "quale" metodo per essere più specifico. Il nome del metodo + firma che verrà chiamato viene determinato al momento della compilazione e risolto, ma la particolare implementazione (ad es. Base o qualche override) di quel metodo viene determinata in fase di esecuzione. Questo è talvolta chiamato anche legame tardivo.

(Oltre al nome + firma, il metodo che verrà chiamato sarà anche direttamente correlato al metodo della classe che lo ha originariamente introdotto, ad esempio sarà il metodo stesso o un override di quello in una sottoclasse. )

Prendi la situazione in cui abbiamo:

class A {
     void foo ();
}
class B extends A { ... }
class C extends B { ... }
class D extends A { ... }
...
class U {
    void method ( A a ) {
        a.foo ();
    }
}
...
U u = new U();
u.method ( new A () );
u.method ( new B () );
u.method ( new C () );
u.method ( new D () );

Sappiamo che method chiamerà un metodo esistente denominato foo senza parametri, introdotto da class A . Tuttavia, può chiamare A 's foo , B ' s foo , C 's foo , o D ' s foo . Qui il runtime della lingua (Macchina virtuale) che esegue method determina il cui foo da chiamare utilizzando l'associazione tardiva.

Il problema di accesso è una determinazione del tempo di compilazione effettuata utilizzando l'analisi statica e le regole del sistema di tipi (della particolare lingua coinvolta). Usando i tipi in fase di compilazione, le lingue possono prevenire determinati stati di programmi illegali (con un errore di compilazione nel tempo invece di un errore di runtime), come non c'è foo da chiamare.

Dovresti anche notare che ci sono fondamentalmente diversi tipi di cast. Gli aggiornamenti (casting da B a A , o C a B o C a A ) sono sicuri e possono essere convalidati in fase di compilazione. I cast down (ad es. Da A a B , o A a D ) non sono sempre necessariamente analizzabili nel tempo di compilazione, e quindi sono progettati nella lingua per eseguire un controllo di runtime al punto di cast (prima delle cose andare oltre e utilizzare il valore casted).

Di conseguenza, è possibile ottenere un'eccezione di cast illegale, in fase di runtime. Avendo definito la lingua in modo che il compilatore e il runtime cospirino per controllare i cast illegali, non devono controllare il metodo non trovato in runtime, indipendentemente dalla classe reale dell'oggetto: questo perché sa che il cast ha funzionato, quindi il metodo esiste (nonostante la riflessione, anche il versioning delle DLL complica le cose, ma c'è un controllo del tempo di caricamento aggiuntivo che integra il controllo del tempo di compilazione e entrambi insieme sostituiscono la necessità di un controllo runtime per i metodi esistenti o meno.)

Anche se nel tuo particolare esempio con Flyer e Bird , un'analisi statica avrebbe potuto eliminare il controllo del cast runtime in ((Bird) f) , in generale questo non è sempre possibile. Il tuo esempio continua a chiamare un metodo (solo) disponibile in Bird .

Si noti che, per definizione delle istruzioni del codice byte, questo richiamo del metodo ( getNativeOf() ) può essere eseguito solo (cioè può essere raggiunto solo dal flusso del controllo) se il cast del runtime ha esito positivo, in quanto un fallimento del cast lanciare un'eccezione modificando il normale flusso di controllo e impedendo il raggiungimento dell'invocazione del metodo.

(Inoltre, il compilatore non genererà quanto segue, un programma di codice di byte appositamente progettato che tenta di accedere a getNativeOf() da un oggetto Flyer senza un cast di runtime protettivo necessario su Bird fallirà un tempo di caricamento controllo di verifica! Sebbene ciò avvenga dopo la compilazione, è anche una forma di analisi statica, in altre parole, viene eseguito una sola volta in fase di esecuzione (noto come tempo di caricamento) anziché ogni volta che viene utilizzato il codice.)

Dopo il cast, il compilatore genererà l'invocazione del metodo assumendo che il cast abbia successo (sapendo che se siamo a questo punto, il cast deve aver avuto successo) e come tale conosce al momento della compilazione il nome + signature (e introducendo la classe ) di quale metodo chiamare. Il metodo effettivo risultante chiamato utilizza ancora l'associazione tardiva in fase di esecuzione per trovare la cui implementazione o sovrascrittura di tale metodo è appropriata per il tipo reale dell'oggetto (nonostante l'ottimizzazione, ma qualsiasi ottimizzazione deve fornire gli stessi risultati se si utilizza l'associazione tardiva).

    
risposta data 13.04.2016 - 19:29
fonte
0

Il compilatore può parlare solo di oggetti in termini di interfaccia / contratto. Tuttavia, il runtime Java utilizza sempre riferimenti per fare riferimento agli oggetti in fase di runtime. Notare la differenza tra l'ambiente statico per il compilatore e l'ambiente dinamico per il runtime.

per es.

Square square = new Square();
square.setSideLength(2);

Shape shape = square; // assuming Square is a Shape
shape.size() == 4;

quindi il compilatore non mi lascerà setSideLength() su una 'forma', poiché setSideLength() è un metodo sul% co_de dichiarato.

Ma quando chiamo Square sul riferimento forma, quale metodo chiamare è determinato in runtime . per esempio. immagina questo:

List<Shape> shapes = mixtureOfSquaresAndTrianglesAndCircles;
shapes.get(2).area(); // I can call this regardless of the underlying being a triangle, square, circle etc.
    
risposta data 13.04.2016 - 18:28
fonte

Leggi altre domande sui tag