Я не понимаю, чем отличается переопределение от скрытия в 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.
Это показывает мне, что написание статического метода с той же сигнатурой, что и в родительском, в подклассе в значительной степени отменяет.
Надеюсь, я проясняю, где я запутался, и кто-то может пролить свет. Большое спасибо заранее!
источник
Ответы:
Переопределение в основном поддерживает позднее связывание. Поэтому во время выполнения решается, какой метод будет вызван. Это для нестатических методов.
Скрытие предназначено для всех остальных членов (статических методов, членов экземпляра, статических членов). В его основе лежит раннее связывание. Более ясно, метод или член, который будет вызываться или использоваться, определяется во время компиляции.
В вашем примере первый вызов
Animal.testClassMethod()
- это вызовstatic
метода, поэтому совершенно точно известно, какой метод будет вызван.Во втором вызове
myAnimal.testInstanceMethod()
вы вызываете нестатический метод. Это то, что вы называете полиморфизмом времени выполнения. До времени выполнения не решается, какой метод следует вызвать.Для дальнейших пояснений прочтите Переопределение против сокрытия .
источник
private methods
? Они не могут бытьoverridden
таковыми, поскольку подкласс не знает об их существовании. Поэтому они могут быть такимиhidden
.Статические методы скрыты, нестатические методы переопределяются. Разница заметна, когда вызовы не квалифицируются как «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(); } }
ВЫХОД:
источник
husky.Animal();
ли он печать animal.something или dog.something ? Я думаю, НЕПРАВИЛЬНО говорить **, что ** Это всегда будет вызывать Animal.something ().Animal()
, помнитеAnimal()
, это конструктор.something()
вAnimal()
всегда вызове животногоsomething()
происходит потому, что вызов метода статического решения во время компиляции , а не во время выполнения. Это означает, что вызов статического методаAnimal()
всегда вызывается неявноAnimal.something()
. Если подумать, это довольно интуитивно понятно: вызову статического метода должно предшествовать имя класса (т.е.className.staticMethodName()
), если только вызов не относится к тому же классу.В этом разница между переопределением и скрытием,
источник
Если я правильно понимаю ваш вопрос, то ответ - «вы уже переопределяете».
«Это показывает мне, что написание статического метода с тем же именем, что и в родительском, в подклассе в значительной степени отменяет».
Если вы напишете метод в подклассе с точно таким же именем, что и метод в суперклассе, он переопределит метод суперкласса. Аннотации @Override не требуется для переопределения метода. Однако это делает ваш код более читабельным и заставляет компилятор проверять, действительно ли вы переопределяете метод (и, например, не ошиблись в написании метода подкласса).
источник
Переопределение происходит только с методами экземпляра. Когда тип ссылочной переменной - Animal, а объект - Cat, тогда метод экземпляра вызывается из Cat (это переопределяет). Для того же объекта acat используется метод класса Animal.
public static void main(String[] args) { Animal acat = new Cat(); acat.testInstanceMethod(); acat.testClassMethod(); }
Выход:
источник
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
Скрытие. (Можно вызвать как статический метод подкласса, так и метод суперкласса, в зависимости от типа ссылки, используемой для вызова метода.)источник
В этом фрагменте кода я использую модификатор доступа 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(); } }
Выход:
источник
На основе моих недавних исследований 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 .
источник
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())
источник
Связанная страница руководства по java объясняет концепцию переопределения и скрытия
Различие между скрытием статического метода и переопределением метода экземпляра имеет важные последствия:
Возвращаясь к вашему примеру:
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.
источник
Это называется скрытием, потому что компилятор скрывает реализацию метода суперкласса, когда подкласс имеет тот же статический метод.
Компилятор не имеет ограниченной видимости для переопределенных методов, и только во время выполнения он решает, какой из них использовать.
источник
В этом разница между переопределением и скрытием:
Животное a = новый Кот ();
a.testClassMethod () вызовет метод в родительском классе, поскольку это пример скрытия метода. Вызываемый метод определяется типом ссылочной переменной и определяется во время компиляции.
a.testInstanceMethod () вызовет метод в дочернем классе, поскольку это пример переопределения метода. Вызываемый метод определяется объектом, который используется для вызова метода во время выполнения.
источник
Как происходит скрытие статического метода в Java? Класс Cat расширяет класс Animal. Итак, в классе Cat будут оба статических метода (я имею в виду статический метод дочернего класса и статический метод родительского класса). Но как JVM скрывает родительский статический метод? Как это работает в кучах и стеках?
источник