Perché il compilatore Java decide se è possibile chiamare un metodo basato sul tipo "di riferimento" e non sul tipo "oggetto" effettivo?

4

Mi chiedevo solo perché il compilatore Java decide se è possibile chiamare un metodo basato sul tipo "di riferimento" e non sul tipo "oggetto" effettivo? Per spiegare vorrei citare un esempio:

class A {
    void methA() {
        System.out.println("Method of Class A.");
    } 
}

class B extends A {
    void methB() {
        System.out.println("Method of Class B.");
    }
    public static void main(String arg[]) {
        A ob = new B();
        ob.methB();       // Compile Time Error
    }
}

Questo produrrà un errore di compilazione del metodo che methB () non trova nella classe A, sebbene il riferimento all'oggetto "ob" contenga un oggetto di classe B che consiste nel metodo methB (). Il motivo è che Java Compiler controlla il metodo in Classe A (il tipo di riferimento) non in Classe B (il tipo di oggetto effettivo). Quindi, voglio sapere qual è la ragione di questo. Perché Java Compiler cerca il metodo in Classe A perché non in Classe B (il tipo di oggetto reale)?

    
posta Sahil Chhabra 31.07.2013 - 09:32
fonte

6 risposte

7

Il tuo esempio è in qualche modo un caso speciale. In un programma non banale, in genere, non è possibile determinare la classe dell'oggetto con un determinato punto di riferimento migliore di "è del tipo per il quale è stato dichiarato il riferimento o un sottotipo di esso".

L'intero concetto di polimorfismo si basa sul fatto che la classe concreta è conosciuta solo in fase di esecuzione ma non in fase di compilazione. Ciò ovviamente significa che il compilatore deve garantire che i metodi richiamati su un riferimento siano disponibili sull'oggetto di riferimento in fase di runtime. Gli unici metodi per cui ciò è vero sono i metodi della classe per cui è stata dichiarata la variabile (compresi tutti i metodi ereditati dalle super classi).

Modifica

Come ha detto @David nel suo commento, la ragione di ciò è che la scrittura viene eseguita staticamente dal programmatore in java. Se java avesse un'inferenza di tipo, la digitazione sarebbe stata fatta dal compilatore (ma ancora staticamente) e il tuo esempio sarebbe assolutamente valido. Ma potrebbe essere invalidato da un'altra riga che esegue questo ob = new A() .

Se java fosse un linguaggio tipizzato dinamico, al compilatore non interesserebbe affatto (al momento della compilazione) quali metodi si chiamano su un oggetto.

    
risposta data 31.07.2013 - 09:47
fonte
12

Dichiarando ob come A , stai dicendo al compilatore che stai usando un oggetto che si comporta come A .

Java esegue rigorosi controlli di tipo. Se dici che ob è un'istanza di A , quindi, qualunque sia il tuo assegnamento, Java si assicurerà di mantenere il contratto di A .

Questo può aiutarti a creare codice ad accoppiamento lento che resiste ai cambiamenti futuri.

Diciamo che ad un certo punto hai bisogno di un'altra implementazione di A:

class C extends A {
    void methA() {
        System.out.println("Overriden methA.");
    }
}

E che ti sei attenuto a A contratto:

   public static void main(String arg[])
   {
      A ob = new B();
      ob.methA();  // output: "Method of Class A."
   }

Quindi, puoi facilmente sostituire C in B , perché il tuo codice non dipende da B:

   public static void main(String arg[])
   {
      A ob = new C();
      ob.methA(); // output: "Overriden methA."
   }

Java ti consente di scegliere il livello di astrazione di cui hai bisogno e quindi aiutarti applicarlo .

Se scrivi un codice che deve chiamare methB (e quindi dipende dalla classe B ), allora dillo a Java:

   public static void main(String arg[])
   {
      B ob = new B();
      ob.methB();  // output: "Method of Class B."
   }

Ora solo le istanze di B o sottoclassi di B possono essere assegnate a ob .

    
risposta data 31.07.2013 - 10:12
fonte
4

Permettimi di proporre una domanda. Consente questo codice:

class A {
    void methA() {
        System.out.println("Method of Class A.");
    }

    public static void main(String arg[]) {
        A ob;
        if ((new Random().nextInt(2)) == 0){
            ob = new B();
        } else {
            ob = new C();
        }
        ob.methB(); // what now?
    } 
}

class B extends A {
    void methB() {
        System.out.println("Method of Class B.");
    }
}

class C extends A {
    void methC() {
        System.out.println("Method of Class C.");
    }
}

Ora, cosa succederà se l'istanza della classe C viene salvata in ob? Quale metodo verrà chiamato?

Il punto è che le chiamate di metodo vengono risolte in fase di compilazione e il compilatore non può sapere quale sarà l'istanza di classe concreta all'interno della variabile durante la compilazione. Questo è il motivo per cui puoi chiamare solo il metodo che esiste in classe che è definito come tipo di variabile data. E se tale metodo è virtuale, il runtime sceglierà il sovraccarico corretto quando il programma viene eseguito. Questa è la base per l'associazione tardiva nei linguaggi OOP.

    
risposta data 31.07.2013 - 14:53
fonte
3

I was just wondering why does Java compiler decide whether you can call a method based on the "reference" type and not on actual "object" type?

Invece di discutere su Perché il compilatore Java decide se è possibile chiamare un metodo basato sul tipo "di riferimento" e non sul tipo "oggetto" effettivo? diciamo che il compilatore java lo consente.Consultare il seguente esempio

class A {
    void methA() {
        System.out.println("Method of Class A.");
    } 
}

class B extends A {
    void methB() {
        System.out.println("Method of Class B.");
    }
    public static void main(String arg[]) {
        A ob = new B();
        ob.methC();       // Compile Time Error
    }
}

Vedi il metodo ob.methC (); ? Questo metodo non è né in Classe A né in Classe B. Se il compilatore Java lo avrebbe consentito, avrebbe causato un errore. Come dice il proverbio è meglio prendersi cura di se stessi piuttosto che ammalarsi e mangiare medicine. Java è stato progettato per essere semplice e non consentire errori che sappiamo avere un'alta probabilità di verificarsi (uno dei motivi per cui le eccezioni sono state introdotte sin dall'inizio).

Anche l'intero concetto di polimorfismo si basa sul fatto che la classe concreta è conosciuta solo in fase di esecuzione ma non in fase di compilazione. Quindi per la sicurezza i compilatori Java sono progettati in questo modo. Sebbene tu possa decidere solo di dichiarare il tuo metodo in superClass (come renderlo astratto) e poi implementarlo nella sottoclasse.

    
risposta data 31.07.2013 - 13:48
fonte
1

La mia analogia preferita per il polimorfismo è usare gli insegnanti. Ciò fornirà una maggiore chiarezza piuttosto che l'astratto MethodA() e MethodB() . Di 'che hai un insegnante e un insegnante di matematica. Quest'ultima è una versione più specifica e rappresenta una relazione IS-A. Un insegnante di matematica È un insegnante, quindi può fare tutto ciò che un insegnante può fare, ma il contrario non è vero. Non tutti gli insegnanti possono insegnare matematica. Se ho un riferimento per l'insegnante,

Teacher teacher = GetTeacher();
teacher.Teach();

Non sarebbe sicuro assumere che l'insegnante sappia la matematica, potrebbe essere un insegnante di lingue o di scienze. Sappiamo che tutti gli insegnanti hanno il metodo Teach() , quindi è sicuro chiamare. Il riferimento definisce l'interfaccia o il contratto. Questa separazione dell'interfaccia dall'implementazione è un principio OOP molto potente. Per questo motivo è spesso incoraggiato a disaccoppiare completamente la tua implementazione. Non ho bisogno di sapere che tipo di insegnante ha il riferimento, perché so che può insegnare a prescindere.

Questo può continuare ulteriormente man mano che diventi più specifico, puoi avere un insegnante di Calcolo che è un insegnante di matematica. Sia l'insegnante di matematica che l'insegnante di calcolo hanno il metodo DoEquation() e Teach() , ma solo l'insegnante di calcolo può chiamare DoAdvancedEqation() .

Pensare all'eredità come una relazione IS-A dovrebbe aiutarti a capire perché non è sicuro fare assunzioni come quelle proposte nella domanda.

    
risposta data 26.08.2013 - 10:01
fonte
0

Il tuo oggetto è di tipo 'A' e 'A' non ha una definizione di 'methB ()'. Ecco perché il compilatore Java non consente.

class ClassA
{
    void MethodA() { }
}

class ClassB extends ClassA
{
    void MethodB() { }
}

class Program
{
    public static void main(string[] args)
    {
        ClassA a = new ClassB();
        a.MethodB(); // Compile time error.
    }
}

Se si esegue l'override di qualsiasi metodo nella classe derivata, in fase di esecuzione chiamerà il metodo effettivo in base all'oggetto puntato dal riferimento.

class ClassA
{
    void MethodA() 
    { 
        Console.WriteLine("A"); 
    }
}

class ClassB extends ClassA
{
    void MethodA() 
    { 
        Console.WriteLine("B"); 
    }
}

class Program
{
    public static void main(string[] args)
    {
        ClassA a = new ClassB();
        a.MethodA(); // Output: B
    }
}
    
risposta data 31.07.2013 - 10:43
fonte

Leggi altre domande sui tag