Переопределение против сокрытия Java - Confused

Я запутался в том, как Overriding отличается от Hiding в Java. Кто-нибудь может предоставить более подробную информацию о том, как они отличаются? Я прочитал Учебник по Java, но пример кода все еще оставил меня в замешательстве.

Чтобы быть более ясным, я хорошо понимаю Overriding. Моя проблема в том, что я не вижу, чтобы скрытие отличалось, за исключением того факта, что один находится на уровне экземпляра, а другой - на уровне класса.

Глядя на учебный код 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.");
    }
}

Тогда у нас есть подкласс 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();
    }
}

Тогда они говорят:

Вывод этой программы следующий:

Метод класса в Animal.

Метод экземпляра в кат.

Для меня тот факт, что вызов метода класса testClassMethod() непосредственно из класса Animal, выполняет метод в классе Animal, довольно очевиден, ничего особенного там нет. Затем они вызывают testInstanceMethod() из ссылки на myCat, так что, опять же, довольно очевидно, что выполняемый метод является тем же, что и в случае с Cat.

Из того, что я вижу, то, что скрытие вызова ведет себя как переопределение, так зачем делать это различие. Если я запускаю этот код, используя классы выше:

Cat.testClassMethod();

Я получу: метод класса в Cat. Но если я удалю testClassMethod() из Cat, то получу: Метод класса в Animal.

Что показывает мне, что написание статического метода с той же сигнатурой, что и в родительском, в подклассе в значительной степени переопределяет.

Надеюсь, я проясняю, где я в замешательстве, и кто-то может пролить свет. Большое спасибо заранее!

13 ответов

Решение

Перекрытие в основном поддерживает позднюю привязку. Поэтому, какой метод будет вызван, определяется во время выполнения. Он предназначен для нестатических методов. Скрытие предназначено для всех других членов (статические методы, члены экземпляра, статические члены). Он основан на раннем связывании. Более ясно, метод или член, который будет вызван или использован, определяется во время компиляции.

В вашем примере первый звонок, Animal.testClassMethod() это вызов static метод, следовательно, вполне уверен, какой метод будет вызван.

Во втором звонкеmyAnimal.testInstanceMethod()он вызывает нестатический метод. Это то, что вы называете полиморфизмом во время выполнения. До времени выполнения не определено, какой метод должен быть вызван.

Для дальнейшего разъяснения прочитайте это.

Статические методы скрыты, нестатические методы переопределяются. Разница заметна, когда вызовы не квалифицированы как "что-то ()" против "это. Что-то ()".

Я не могу выразить это словами, так что вот пример:

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();
    }

}

ВЫХОД:

animal.something
dog.eat

В этом разница между переопределением и сокрытием,

  1. Если оба метода в родительском классе и дочернем классе являются методом экземпляра, он называется переопределением.
  2. Если оба метода в родительском и дочернем классах являются статическими, это называется скрытием.
  3. Один метод не может быть статическим в родительском и как экземпляр в дочернем. и наоборот.

Если я правильно понимаю ваш вопрос, то ответ "вы уже переопределяете".

"Что показывает мне, что написание статического метода с тем же именем, что и у родителя, в подклассе в значительной степени переопределяет".

Если вы напишите метод в подклассе с тем же именем, что и метод в суперклассе, он переопределит метод суперкласса. Аннотация @Override не требуется для переопределения метода. Однако он делает ваш код более читабельным и заставляет компилятор проверять, что вы на самом деле переопределяете метод (и, например, не ошиблись в написании метода подкласса).

Переопределение происходит только с методами экземпляра. Если тип ссылочной переменной - Animal, а объект - Cat, тогда метод экземпляра вызывается из Cat (это переопределение). Для того же объекта acat используется метод класса Animal.

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

}

Выход:

The instance method in Cat.
Class method in Animal.
public class First {

public void Overriding(int i) {  // will be overrided 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) {  // overrided here  }

public static void Hiding(int i) {  // hidden
                                    // because it's static } 
}

Правило запоминания простое: метод в расширяющем классе не может изменить статический тип на void и не может изменить void на статический. Это приведет к ошибке компиляции.

Но если void Name изменилось на void Name, оно переопределяется.

И если статическое имя изменилось на статическое имя, оно скрывается. (Когда компилятор видит статический метод в объекте суперкласса, он не проверяет метод в подклассе.)

Думаю, это еще не до конца объяснено. См. Следующий пример.

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


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

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

Результат будет следующим.

The static method in Animal
The static method in Cat
The instance method in Cat
The instance method in Cat

В этом фрагменте кода я использую "приватный" модификатор доступа вместо "статический", чтобы показать вам разницу между методами скрытия и переопределением.

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();
}
}

Выход:

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

Связанная страница учебника Java объясняет понятие переопределения и сокрытия

Метод экземпляра в подклассе с той же сигнатурой (имя, плюс номер и тип его параметров) и тип возвращаемого значения, как метод экземпляра в суперклассе, переопределяет метод суперкласса.

Если подкласс определяет статический метод с той же сигнатурой, что и статический метод в суперклассе, то метод в подклассе скрывает метод в суперклассе.

Различие между сокрытием статического метода и переопределением метода экземпляра имеет важные последствия:

  1. Версия переопределенного метода экземпляра, который вызывается, является той из подкласса.
  2. Версия скрытого статического метода, который вызывается, зависит от того, вызывается ли он из суперкласса или подкласса.

Возвращаясь к вашему примеру:

Animal myAnimal = myCat;

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

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

Выше заявление еще не показывает скрытие.

Теперь измените код, как показано ниже, чтобы получить другой вывод:

  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();

В дополнение к примерам, перечисленным выше, вот небольшой пример кода, чтобы прояснить различие между сокрытием и переопределением:

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();
    }
}

Зов child.printParent() выходы:
быть скрытым: родитель
быть отмененным: ребенок

Зов child.printChild() выходы:
быть скрытым
быть отмененным: ребенок

Как видно из приведенных выше выходных данных (особенно выделенных жирным шрифтом выходных данных), скрытие методов ведет себя иначе, чем переопределение.

Java позволяет скрывать и переопределять только методы. Это же правило не распространяется на переменные. Переопределение переменных не допускается, поэтому переменные могут быть только скрыты (без разницы между статической или нестатической переменной). Пример ниже показывает, как метод getName() переопределяется и переменная name скрыт:

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

Во время выполнения дочерняя версия переопределенного метода всегда выполняется для экземпляра независимо от того, определен ли вызов метода в методе родительского или дочернего класса. Таким образом, родительский метод никогда не используется, если на него не ссылается явный вызов родительского метода с использованием синтаксиса ParentClassName.method(). Кроме того, во время выполнения родительская версия скрытого метода всегда выполняется, если вызов метода определен в родительском классе.

В переопределении метода разрешение метода выполняется JVM на основе объекта времени выполнения. Принимая во внимание, что при скрытии метода разрешение метода выполняется компилятором на основе ссылки. Таким образом,

Если код был бы написан как,

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

Результат будет следующим:
Метод класса в Animal.

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())

Это называется скрытием, потому что компилятор скрывает реализацию метода суперкласса, когда подкласс имеет тот же статический метод.

Компилятор не имеет ограниченной видимости для переопределенных методов, и только во время выполнения он решает, какой из них использовать.

В этом разница между переопределением и скрытием:

Животное a = новый Кот ();

a.testClassMethod() вызовет метод в родительском классе, поскольку это пример скрытия метода. Вызываемый метод определяется типом ссылочной переменной и определяется во время компиляции.

a.testInstanceMethod() вызовет метод в дочернем классе, поскольку это пример переопределения метода. Вызываемый метод определяется объектом, который используется для вызова метода во время выполнения.

Основанный на моих недавних исследованиях Java

  • переопределение метода, когда у подкласса есть тот же самый метод с той же самой подписью в подклассе.
  • Метод скрывается, когда подкласс имеет одинаковое имя метода, но другой параметр. В этом случае вы не переопределяете родительский метод, а скрываете его.

Пример из книги OCP Java 7, стр. 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
  }
}

но если мы напишем следующее основное:

  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
  }

Во втором основном мы используем класс Object в качестве статического типа, поэтому, когда мы вызываем равный метод в объекте Point, он ожидает, что класс Point прибудет в качестве параметра, но прибывает объект. Итак, класс Object запускается методом equals, потому что у нас есть equals(Object o). В этом случае класс Equals Point не переопределяет, а скрывает метод equals класса Object.

Как происходит скрытие статического метода в Java? Класс Cat расширяет класс Animal. Таким образом, в классе Cat будут оба статических метода (я имею в виду статический метод дочернего класса и статический метод родительского класса). Но как JVM скрывает статический метод родительского класса? Как это работает в куче и стеке?

Другие вопросы по тегам