Por que o compilador Java decide se você pode chamar um método baseado no tipo “referência” e não no tipo “objeto” real?

4

Eu estava me perguntando por que o compilador Java decide se você pode chamar um método baseado no tipo "referência" e não no tipo "objeto" real? Para explicar, gostaria de citar um exemplo:

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
    }
}

Isto irá produzir um Erro de Tempo de Compilação que o método methB () não encontrou na classe A, embora a Referência de Objeto "ob" contenha um objeto da classe B que consiste no método methB (). O motivo disso é que o Java Compiler verifica o método na Classe A (o tipo de referência) que não está na Classe B (o tipo de objeto real). Então, eu quero saber qual é a razão por trás disso. Por que o Java Compiler procura o método na Classe A, por que não na Classe B (o tipo de objeto real)?

    
por Sahil Chhabra 31.07.2013 / 09:32
fonte

6 respostas

7

Seu exemplo é, de alguma forma, um caso especial. Em um programa não trivial, você normalmente não pode determinar a classe do objeto que determinada referência aponta para melhor que "é do tipo que a referência foi declarada ou um subtipo dela".

Todo o conceito de polimorfismo é baseado no fato de que a classe concreta é conhecida apenas em tempo de execução, mas não em tempo de compilação. Isso, é claro, significa que o compilador deve garantir que os métodos que são chamados em uma referência estejam disponíveis no objeto referenciado no tempo de execução. Os únicos métodos para os quais isso vale são os métodos da classe para a qual a variável foi declarada (incluindo todos os métodos herdados das superclasses).

EDITAR

Como @David disse em seu comentário, a razão por trás disso é que a digitação é feita estaticamente pelo programador em java. Se java tivesse inferência de tipos, a digitação seria feita pelo compilador (mas ainda estaticamente) e seu exemplo seria absolutamente válido. Mas poderia ser invalidado por outra linha fazendo isso ob = new A() .

Se o java fosse uma linguagem tipada dinamicamente, o compilador não se importaria (em tempo de compilação) com os métodos que você chama em um objeto.

    
por 31.07.2013 / 09:47
fonte
12

Ao declarar ob como A , você está dizendo ao compilador que está usando um objeto que se comporta como A .

Java executa uma verificação de tipo estrita. Se você disser que ob é uma instância de A , então, o que você atribuir a ela, o Java garantirá que você mantenha o contrato de A .

Isso pode ajudar você a criar código pouco acoplado que resiste a alterações futuras.

Digamos que, em algum momento, você precise de outra implementação de A:

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

E que você aderiu ao A contract:

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

Então, você pode facilmente substituir C por B , porque seu código não depende de B:

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

O Java permite que você escolha o nível de abstração necessário e, em seguida, ajude-o a aplicá-lo .

Se você escrever um código que precise chamar methB (e, portanto, depende da B class), então informe ao Java:

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

Agora apenas instâncias de B ou subclasses de B podem ser atribuídas a ob .

    
por 31.07.2013 / 10:12
fonte
4

Deixe-me propor uma pergunta. Vamos ter esse código:

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.");
    }
}

Agora, o que acontecerá se a instância da classe C for salva em ob? Qual método será chamado?

O ponto é que as chamadas de método são resolvidas em tempo de compilação e o compilador não pode saber qual instância de classe concreta estará dentro da variável durante a compilação. É por isso que você só pode chamar o método que existe na classe que é definida como tipo de variável dada. E se o método for virtual, o tempo de execução escolherá a sobrecarga correta quando o programa for executado. Esta é a base para ligação tardia em linguagens OOP.

    
por 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?

Em vez de debater em Por que o compilador Java decide se você pode chamar um método baseado no tipo "referência" e não no tipo "objeto" real? vamos dizer que o compilador java permite isso.Considere o seguinte exemplo

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
    }
}

Veja o método ob.methC (); ? Este método não está na Classe A ou na Classe B. Se o compilador Java permitisse isso, causaria um Erro. Como diz o ditado, é melhor cuidar de si mesmo do que ficar doente e comer remédios. Java foi projetado para ser simples e não permitir erros que sabemos que têm alta probabilidade de ocorrer (uma das razões pelas quais as exceções foram introduzidas desde o início).

Também todo conceito de polimorfismo é baseado no fato de que a classe concreta é conhecida apenas em tempo de execução, mas não em tempo de compilação. Então, para compiladores Java de segurança são projetados dessa maneira. Embora você possa decidir apenas declarar seu método no superClass (como se fosse abstrato) e implementá-lo na subclasse.

    
por 31.07.2013 / 13:48
fonte
1

Minha analogia favorita para o polimorfismo é usar os professores. Isso fornecerá mais clareza do que o resumo MethodA() e MethodB() . Digamos que você tenha um professor e um professor de matemática. Esta última é uma versão mais específica e representa um relacionamento IS-A. Um professor de matemática é um professor, então pode fazer qualquer coisa que um professor possa fazer, mas o oposto não é verdadeiro. Nem todos os professores podem ensinar matemática. Se eu tiver uma referência de professor,

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

Não seria seguro assumir que o professor conhece matemática, poderia ser um letramento ou professor de ciências. Sabemos que todos os professores têm o método Teach() , portanto, é seguro ligar. A referência define a interface ou contrato. Esta separação de interface da implementação é um princípio OOP muito poderoso. Por esse motivo, muitas vezes é recomendável separar totalmente sua implementação. Eu não preciso saber que tipo de professor a referência contém, porque sei que ela pode ensinar de qualquer maneira.

Isso pode continuar ainda mais quando você for mais específico, você pode ter um professor de Cálculo que é um professor de matemática. Tanto o professor de matemática quanto o professor de cálculo possuem o método DoEquation() e Teach() , mas somente o professor de cálculo pode chamar DoAdvancedEqation() .

Pensar em herança como um relacionamento IS-A deve ajudá-lo a entender por que não é seguro fazer suposições como as propostas na pergunta.

    
por 26.08.2013 / 10:01
fonte
0

Seu objeto é do tipo 'A' e 'A' não está tendo uma definição de 'methB ()'. É por isso que o compilador Java não está permitindo.

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 você substituir qualquer método na classe derivada, em tempo de execução, ele chamará o método real, dependendo do objeto apontado pela referência.

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
    }
}
    
por 31.07.2013 / 10:43
fonte

Tags