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


88

Я не понимаю, чем отличается переопределение от скрытия в Java. Может ли кто-нибудь предоставить более подробную информацию о том, чем они отличаются? Я прочитал Учебное пособие по Java, но пример кода по-прежнему меня смутил.

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

Глядя на код учебника 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.

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

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

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

Cat.testClassMethod();

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

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

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


Ответы:


103

Переопределение в основном поддерживает позднее связывание. Поэтому во время выполнения решается, какой метод будет вызван. Это для нестатических методов.

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

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

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

Для дальнейших пояснений прочтите Переопределение против сокрытия .


3
Спасибо за быстрый ответ, это проясняет! Я заметил, что в примере JavaRanch они использовали переменную для вызова метода класса вместо прямого использования класса, что упрощает понимание. Я предполагаю, что в руководстве по Java они использовали класс напрямую, потому что использование экземпляра для вызова статического метода, вероятно, не является хорошей практикой, но они должны были использовать myAnimal.testClassMethod () вместо Animal.testClassMethod () .
Lostlinkpr

+1 за умение правильно выразить словами, а не примером! :)
WhyNotHugo

@Kazekage Gaara Есть ли разница между перегрузкой и сокрытием?
gstackoverflow

1
Я конечно согласен с ответом, но как насчет private methods? Они не могут быть overriddenтаковыми, поскольку подкласс не знает об их существовании. Поэтому они могут быть такими hidden.
Paschalis

Отличный ответ! Хотя вы можете добавить пример на coderanch для полноты картины :)
Шубхам Миттал

19

Статические методы скрыты, нестатические методы переопределяются. Разница заметна, когда вызовы не квалифицируются как «something ()» и «this.something ()».

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

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
хорошо, тогда что произойдет, если я назову "dog хаски = new dog ();" и вызовет husky.Animal();ли он печать animal.something или dog.something ? Я думаю, НЕПРАВИЛЬНО говорить **, что ** Это всегда будет вызывать Animal.something ()
амарнатх хариш

@amarnathharish Вы не можете этого сделать .Animal(), помните Animal(), это конструктор.
Dude156,

И как дополнительные разъяснения для всех , кто интересно, причина , по которой something()в Animal() всегда вызове животного something()происходит потому, что вызов метода статического решения во время компиляции , а не во время выполнения. Это означает, что вызов статического метода Animal()всегда вызывается неявно Animal.something(). Если подумать, это довольно интуитивно понятно: вызову статического метода должно предшествовать имя класса (т.е. className.staticMethodName()), если только вызов не относится к тому же классу.
Dude156,

14

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

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

введите описание изображения здесь


3
Вы вырезали и вставили эту таблицу прямо из учебника, который, по словам ОП, не помог ему понять.
wolfcastle

Из таблицы ясно видно, что в примерах были рассмотрены не все случаи.
tutak

3

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

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

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


1
В этом ответе не рассматриваются методы экземпляра и статические методы в отношении переопределения / скрытия.
Пол Беллора

3

Переопределение происходит только с методами экземпляра. Когда тип ссылочной переменной - 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.

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

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

Но если void Nameизменить на void Nameэто Overriding.

А если static Nameпоменять на static NameСкрытие. (Можно вызвать как статический метод подкласса, так и метод суперкласса, в зависимости от типа ссылки, используемой для вызова метода.)


1

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

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

интересно, почему он не получил положительных отзывов .. очень ценный ответ.
амарнатх хариш

0

На основе моих недавних исследований 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
  }
}

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

  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 как статический тип, поэтому, когда мы вызываем метод equal в объекте Point, он ожидает прибытия класса Point в качестве параметра, но прибывает Object. Итак, класс Object запускает метод equals, потому что у нас там есть equals (Object o). В этом случае класс Point равняется, не переопределяет, но скрывает метод equals класса 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

Связанная страница руководства по 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();

Я пытаюсь понять, что означает слово «прятаться». Что что скрывает? Скрыт ли статический метод в родительском классе, потому что (наименее ожидаемый) он вызывается? Или статический метод в классе Child скрыт, потому что он не вызывается?
scorpion

0

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

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

0

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


0

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

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

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

Результат будет таким, как показано ниже:
Метод класса в Animal.


0

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

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


0

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

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

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

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


-1

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


Это не ответ. Это продолжение заданного вопроса. Возможно, это был отдельный вопрос или часть комментариев к вопросу.
Sri9911
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.