Substituindo vs Escondendo Java - Confuso


88

Estou confuso sobre como substituir difere de ocultar em Java. Alguém pode fornecer mais detalhes sobre como eles diferem? Eu li o tutorial Java, mas o código de amostra ainda me deixou confuso.

Para ser mais claro, entendo bem a substituição. Meu problema é que não vejo como ocultar é diferente, exceto pelo fato de que um está no nível da instância enquanto o outro está no nível da classe.

Olhando para o código do tutorial Java:

public class Animal {
    public static void testClassMethod() {
        System.out.println("Class" + " method in Animal.");
    }
    public void testInstanceMethod() {
        System.out.println("Instance " + " method in Animal.");
    }
}

Então temos uma subclasse Cat:

public class Cat extends Animal {
    public static void testClassMethod() {
        System.out.println("The class method" + " in Cat.");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method" + " in Cat.");
    }

    public static void main(String[] args) {
        Cat myCat = new Cat();
        Animal myAnimal = myCat;
        Animal.testClassMethod();
        myAnimal.testInstanceMethod();
    }
}

Então eles dizem:

A saída deste programa é a seguinte:

Método de aula em Animal.

O método de instância em Cat.

Para mim, o fato de chamar um método de classe testClassMethod()diretamente da Animalclasse executa o método em Animalclasse é bastante óbvio, nada de especial nisso . Em seguida, eles chamam testInstanceMethod()de uma referência para myCat, então, novamente, bastante óbvio que o método executado então é aquele na instância deCat .

Pelo que vejo, a ocultação de chamadas se comporta como uma substituição, então por que fazer essa distinção? Se eu executar este código usando as classes acima:

Cat.testClassMethod();

Vou pegar: O método de classe em Cat. Mas se eu remover testClassMethod()de Cat, então terei: O método de classe em Animal.

O que me mostra que escrever um método estático, com a mesma assinatura do pai, em uma subclasse praticamente faz uma substituição.

Espero estar deixando claro onde estou confuso e alguém pode lançar alguma luz. Muito obrigado antecipadamente!


Respostas:


103

A substituição basicamente oferece suporte à vinculação tardia. Portanto, é decidido em tempo de execução qual método será chamado. É para métodos não estáticos.

Ocultar é para todos os outros membros (métodos estáticos, membros de instância, membros estáticos). Baseia-se na ligação inicial. Mais claramente, o método ou membro a ser chamado ou usado é decidido durante o tempo de compilação.

Em seu exemplo, a primeira chamada Animal.testClassMethod()é uma chamada para um staticmétodo, portanto, é quase certo que método será chamado.

Na segunda chamada, myAnimal.testInstanceMethod()você chama um método não estático. Isso é o que você chama de polimorfismo em tempo de execução. Não é decidido até o tempo de execução qual método deve ser chamado.

Para maiores esclarecimentos, leia Overriding vs. Hiding .


3
Obrigado pela resposta rápida, isso esclarece tudo! Notei que no exemplo JavaRanch, eles usaram a variável para chamar o método de classe em vez de usar a classe diretamente, o que torna mais fácil de entender. Acho que no tutorial Java eles usaram a classe diretamente porque usar uma instância para chamar um método estático provavelmente não é uma boa prática, mas eles deveriam ter usado myAnimal.testClassMethod () em vez de Animal.testClassMethod () .
Lostlinkpr

+1 por ser capaz de colocá-lo em palavras corretamente, ao invés de por exemplo! :)
WhyNotHugo

@Kazekage Gaara Existe diferença entre sobrecarregar e se esconder?
gstackoverflow

1
Eu concordo com a resposta, mas e quanto private methods? Eles não podem ser, overriddenpois a subclasse não sabe sobre sua existência. Portanto, eles podem ser hidden.
Paschalis

Excelente resposta! Embora você possa adicionar o exemplo no coderanch por uma questão de integridade :)
Shubham Mittal

19

Os métodos estáticos são ocultados, os métodos não estáticos são substituídos. A diferença é notável quando as chamadas não são qualificadas "something ()" vs "this.something ()".

Eu realmente não consigo colocar isso em palavras, então aqui vai um exemplo:

public class Animal {

    public static void something() {
        System.out.println("animal.something");
    }

    public void eat() {
        System.out.println("animal.eat");
    }

    public Animal() {
        // This will always call Animal.something(), since it can't be overriden, because it is static.
        something();
        // This will call the eat() defined in overriding classes.
        eat();
    }

}


public class Dog extends Animal {

    public static void something() {
        // This method merely hides Animal.something(), making it uncallable, but does not override it, or alter calls to it in any way.
        System.out.println("dog.something");
    }

    public void eat() {
        // This method overrides eat(), and will affect calls to eat()
        System.out.println("dog.eat");
    }

    public Dog() {
        super();
    }

    public static void main(String[] args) {
        new Dog();
    }

}

RESULTADO:

animal.something
dog.eat

1
ok, então o que aconteceria se eu chamasse `dog husky = new dog (); ' e chamar husky.Animal();vai imprimir animal.something or dog.something ? Acho que é ERRADO dizer ** que ** Isso sempre será chamado de Animal.something ()
amarnath harish

@amarnathharish Você não pode fazer .Animal(), lembre-seAnimal() é um construtor.
Dude156

E quanto mais esclarecimentos para qualquer um se perguntando, o motivo do something()no Animal() sempre de chamada animal de something()é porque uma chamada para um método estático é resolvido em tempo de compilação ao invés de tempo de execução. Isso significa que a chamada do método estático Animal()está sempre chamando implicitamente Animal.something(). Isso é bastante intuitivo se você pensar sobre isso: uma chamada para um método estático deve ser precedida por um nome de classe (ou seja className.staticMethodName()), a menos que a chamada seja da mesma classe.
Dude156

13

Esta é a diferença entre substituir e ocultar,

  1. Se ambos os métodos da classe pai e da classe filha forem um método de instância, ele será chamado de substituições.
  2. Se ambos os métodos na classe pai e na classe filha forem métodos estáticos, ele será chamado de ocultar.
  3. Um método não pode ser estático no pai e como uma instância no filho. e vice-versa.

insira a descrição da imagem aqui


3
Você cortou e colou aquela tabela diretamente do tutorial que o OP disse que não o ajudou a entender.
wolfcastle de

A tabela deixa isso muito claro, nos exemplos nem todos os casos foram considerados.
tutak

3

Se entendi sua pergunta corretamente, a resposta é "você já está substituindo".

"O que me mostra que escrever um método estático, com o mesmo nome do pai, em uma subclasse praticamente faz uma substituição."

Se você escrever um método em uma subclasse com exatamente o mesmo nome de um método em uma superclasse, ele substituirá o método da superclasse. A anotação @Override não é necessária para substituir um método. No entanto, torna seu código mais legível e força o compilador a verificar se você está realmente sobrescrevendo um método (e não digitou incorretamente o método da subclasse, por exemplo).


1
Esta resposta falha em abordar métodos de instância vs métodos estáticos em relação à substituição / ocultação.
Paul Bellora

3

A substituição ocorre apenas com métodos de instância. Quando o tipo da variável de referência é Animal e o objeto é Cat, o método de instância é chamado de Cat (isso é sobreposto). Para o mesmo objeto acat, o método de classe Animal é usado.

public static void main(String[] args) {
    Animal acat = new Cat();
    acat.testInstanceMethod();
    acat.testClassMethod();

}

O resultado é:

The instance method in Cat.
Class method in Animal.

2
public class First {

    public void Overriding(int i) {  /* will be overridden in class Second */ }

    public static void Hiding(int i) {  /* will be hidden in class Second
                                           because it's static */ }
}


public class Second extends First {

    public void Overriding(int i) {  /* overridden here */  }

    public static void Hiding(int i) {  /* hides method in class First
                                           because it's static */ } 
}

A regra para memorizar é simples: um método em uma classe extensível não pode mudar de estático para vazio e não pode mudar de vazio para estático. Isso causará um erro de compilação.

Mas se void Namefor alterado paravoid Name Substituir.

E se static Namefor alterado para static NameEscondendo. (Tanto o método estático da subclasse quanto o da superclasse podem ser chamados, dependendo do tipo de referência usada para chamar o método.)


1

Neste trecho de código, eu uso o modificador de acesso 'privado' em vez de 'estático' para mostrar a diferença entre ocultar métodos e substituir métodos.

class Animal {
// Use 'static' or 'private' access modifiers to see how method hiding work.
private void testInstancePrivateMethod(String source) {
    System.out.println("\tAnimal: instance Private method calling from "+source);
}
public void testInstanceMethodUsingPrivateMethodInside() {
    System.out.println("\tAnimal: instance Public method with using of Private method.");
    testInstancePrivateMethod( Animal.class.getSimpleName() );
}

// Use default, 'protected' or 'public' access modifiers to see  how method overriding work.
protected void testInstanceProtectedMethod(String source) {
    System.out.println("\tAnimal: instance Protected method calling from "+source);
}
public void testInstanceMethodUsingProtectedMethodInside() {
    System.out.println("\tAnimal: instance Public method with using of Protected method.");
    testInstanceProtectedMethod( Animal.class.getSimpleName() );
  } 
}  


public class Cat extends Animal {
private void testInstancePrivateMethod(String source) {
    System.out.println("Cat: instance Private method calling from " + source );
}
public void testInstanceMethodUsingPrivateMethodInside() {
    System.out.println("Cat: instance Public method with using of Private method.");
    testInstancePrivateMethod( Cat.class.getSimpleName());
    System.out.println("Cat: and calling parent after:");
    super.testInstanceMethodUsingPrivateMethodInside();
}

protected void testInstanceProtectedMethod(String source) {
    System.out.println("Cat: instance Protected method calling from "+ source );
}
public void testInstanceMethodUsingProtectedMethodInside() {
    System.out.println("Cat: instance Public method with using of Protected method.");
    testInstanceProtectedMethod(Cat.class.getSimpleName());
    System.out.println("Cat: and calling parent after:");
    super.testInstanceMethodUsingProtectedMethodInside();
}

public static void main(String[] args) {
    Cat myCat = new Cat();
    System.out.println("----- Method hiding -------");
    myCat.testInstanceMethodUsingPrivateMethodInside();
    System.out.println("\n----- Method overriding -------");
    myCat.testInstanceMethodUsingProtectedMethodInside();
}
}

Resultado:

----- Method hiding -------
Cat: instance Public method with using of Private method.
Cat: instance Private method calling from Cat
Cat: and calling parent after:
   Animal: instance Public method with using of Private method.
   Animal: instance Private method calling from Animal

----- Method overriding -------
Cat: instance Public method with using of Protected method.
Cat: instance Protected method calling from Cat
Cat: and calling parent after:
   Animal: instance Public method with using of Protected method.
Cat: instance Protected method calling from Animal

pergunto por que não obteve os votos positivos .. resposta muito apreciada.
Amarnath Harish

0

Com base em meus estudos recentes de Java

  • substituição de método , quando a subclasse tem o mesmo método com a mesma assinatura na subclasse.
  • Ocultar método , quando a subclasse tem o mesmo nome de método, mas parâmetro diferente. Nesse caso, você não está substituindo o método pai, mas o ocultando.

Exemplo do livro OCP Java 7, páginas 70-71:

public class Point {
  private int xPos, yPos;
  public Point(int x, int y) {
        xPos = x;
        yPos = y;
  }

  public boolean equals(Point other){
  .... sexy code here ...... 
  }

  public static void main(String []args) {
   Point p1 = new Point(10, 20);
   Point p2 = new Point(50, 100);
   Point p3 = new Point(10, 20);
   System.out.println("p1 equals p2 is " + p1.equals(p2));
   System.out.println("p1 equals p3 is " + p1.equals(p3));
   //point's class equals method get invoked
  }
}

mas se escrevermos o seguinte principal:

  public static void main(String []args) {
   Object p1 = new Point(10, 20);
   Object p2 = new Point(50, 100);
   Object p3 = new Point(10, 20);
   System.out.println("p1 equals p2 is " + p1.equals(p2));
   System.out.println("p1 equals p3 is " + p1.equals(p3));
   //Object's class equals method get invoked
  }

No segundo principal, usamos a classe Object como tipo estático, então, quando chamamos o método equal no objeto Point, ele está esperando uma classe Point chegar como parâmetro, mas Object vindo. Portanto, a classe Object é igual ao método sendo executado, porque temos um equals (Object o) ali. Neste caso, a classe equals do Point não se sobrepõe, mas oculta o método equals da classe Object .


0
public class Parent {

  public static void show(){
    System.out.println("Parent");
  }
}

public class Child extends Parent{

  public static void show(){
    System.out.println("Child");
  }
}

public class Main {

public static void main(String[] args) {
    Parent parent=new Child();
    parent.show(); // it will call parent show method
  }
}

// We can call static method by reference ( as shown above) or by using class name (Parent.show())

0

A página vinculada do tutorial Java explica o conceito de substituir e ocultar

Um método de instância em uma subclasse com a mesma assinatura (nome, mais o número e o tipo de seus parâmetros) e tipo de retorno como um método de instância na superclasse substitui o método da superclasse.

Se uma subclasse define um método estático com a mesma assinatura de um método estático na superclasse, então o método na subclasse oculta o da superclasse.

A distinção entre ocultar um método estático e substituir um método de instância tem implicações importantes:

  1. A versão do método de instância substituído que é invocado é a que está na subclasse.
  2. A versão do método estático oculto que é invocado depende se ele é invocado a partir da superclasse ou da subclasse.

Voltando ao seu exemplo:

Animal myAnimal = myCat;

 /* invokes static method on Animal, expected. */
 Animal.testClassMethod(); 

 /* invokes child class instance method (non-static - it's overriding) */
 myAnimal.testInstanceMethod();

A declaração acima não mostra esconder ainda.

Agora mude o código conforme abaixo para obter uma saída diferente:

  Animal myAnimal = myCat;
  
  /* Even though myAnimal is Cat, Animal class method is invoked instead of Cat method*/
  myAnimal.testClassMethod();
  
  /* invokes child class instance method (non-static - it's overriding) */
  myAnimal.testInstanceMethod();

Estou tentando entender o que a própria palavra esconder significa. O que está escondendo o quê? O método estático na classe Pai está oculto porque (menos esperado) é invocado? Ou o método estático da classe Child está oculto porque não é invocado?
escorpião

0

Além dos exemplos listados acima, aqui está um pequeno código de amostra para esclarecer a distinção entre ocultar e substituir:

public class Parent {

    // to be hidden (static)
    public static String toBeHidden() {
        return "Parent";
    }

    // to be overridden (non-static)
    public String toBeOverridden() {
        return "Parent";
    }

    public void printParent() {
        System.out.println("to be hidden: " + toBeHidden());
        System.out.println("to be overridden: " + toBeOverridden());
    }
}

public class Child extends Parent {

    public static String toBeHidden() {
        return "Child";
    }

    public String toBeOverridden() {
        return "Child";
    }

    public void printChild() {
        System.out.println("to be hidden: " + toBeHidden());
        System.out.println("to be overridden: " + toBeOverridden());
    }
}

public class Main {

    public static void main(String[] args) {
        Child child = new Child();
        child.printParent();
        child.printChild();
    }
}

A chamada das child.printParent()saídas:
a ser oculto: Pai
a ser substituído: filho

A chamada de child.printChild()outputs:
to be hidden: Child
to be override: Child

Como podemos ver nas saídas acima (especialmente as saídas marcadas em negrito), a ocultação do método se comporta de maneira diferente da substituição.

Java permite ocultar e substituir apenas para métodos. A mesma regra não se aplica a variáveis. A substituição de variáveis ​​não é permitida, portanto, as variáveis ​​só podem ser ocultadas (nenhuma diferença entre variável estática ou não estática). O exemplo abaixo mostra como o método getName()é sobrescrito e a variável nameé oculta:

public class Main {

    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.name); // prints Parent (since hiding)
        System.out.println(p.getName()); // prints Child (since overriding)
    }
}

class Parent {
    String name = "Parent";

    String getName() {
        return name;
    }
}

class Child extends Parent {
    String name = "Child";

    String getName() {
        return name;
    }
}

0

Em tempo de execução, a versão filho de um método sobrescrito é sempre executada para uma instância, independentemente de a chamada do método ser definida em um método de classe pai ou filho. Dessa forma, o método pai nunca é usado, a menos que uma chamada explícita ao método pai seja referenciada, usando a sintaxe ParentClassName.method (). Como alternativa, em tempo de execução, a versão pai de um método oculto é sempre executada se a chamada para o método for definida na classe pai.


0

Na substituição de método , a resolução do método é feita pela JVM com base no objeto de tempo de execução. Enquanto na ocultação de método, a resolução do método é feita pelo compilador com base na referência. Portanto,

Se o código tivesse sido escrito como,

public static void main(String[] args) {
        Animal myCat = new Cat();        
        myCat.testClassMethod();        
    }

A saída seria a seguinte:
Método de classe em Animal.


0

É chamado de ocultar porque o compilador oculta a implementação do método da superclasse, quando a subclasse possui o mesmo método estático.

O compilador não tem visibilidade restrita para métodos sobrescritos e é apenas durante o tempo de execução que é decidido qual deles será usado.


0

Esta é a diferença entre substituir e ocultar:

Animal a = novo gato ();

a.testClassMethod () chamará o método na classe pai, pois é um exemplo de ocultação de método. O método a ser chamado é determinado pelo tipo da variável de referência e decidido em tempo de compilação.

a.testInstanceMethod () chamará o método na classe filha, pois é um exemplo de substituição de método. O método a ser chamado é determinado pelo objeto que é usado para chamar o método em tempo de execução.


-1

Como o método estático oculto está acontecendo em java? A classe Cat está estendendo a classe Animal. Portanto, a classe Cat terá ambos os métodos estáticos (ou seja, o método estático da classe Child e o método estático da classe Parent). Mas como a JVM esconde o método estático Parent? Como está lidando com Heap e Stack?


Esta não é uma resposta. É a extensão da pergunta feita. Pode ter sido uma pergunta separada ou parte de comentários sobre a pergunta.
Sri9911
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.