Какое значение имеет термин "скрытие" в концепции метода скрытия в Java?
Вопрос был поставлен для обсуждения термина скрытия, который связан со статическими методами в Java. Всякий раз, когда статический метод с одинаковой сигнатурой определяется в родительском и дочернем классах, говорят, что метод дочернего класса скрыл метод в родительском классе. Мой вопрос касается использования сокрытия, поскольку мы знаем, что статические методы будут доступны по имени класса или, если мы попытаемся создать ссылку (что является плохой практикой), метод будет вызван на основе ссылочного типа. Итак, как выглядит скрытие, возьмите пример кода ниже:
public class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
}
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The static method in Cat");
}
public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod(); // prints The static method in Animal
}
}
Может кто-нибудь объяснить, как дочерний метод скрыл здесь родительский метод? (родительский метод вызывается с использованием родительской ссылки, так как скрывается, как это происходит)
4 ответа
Это наглядно демонстрирует, почему не стоит скрывать статический метод. Метод выбирается статически, во время компиляции, без учета фактического типа экземпляра.
Я предполагаю, что фраза take home должна быть "скрытие - это не переопределение... и не делайте этого ", чтобы означать, что Java даже не смотрит на объект / экземпляр (только смотрит на объявленный тип):
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testClassMethod(); //bad to do, but you can see it uses the declared "Animal" class
Cat.testClassMethod(); //Uses method in Cat
myCat.testClassMethod(); //Uses method in Cat
((Animal) null).testClassMethod(); //Uses Animal method, doesn't look at instance
((Cat) null).testClassMethod(); //Uses Cat method, doesn't look at instance
Теперь интересная часть: что если вы удалите метод из Cat
? Все эти вызовы будут по-прежнему работать, используя Animal
метод, который означает, что:
- Скрывать статические методы - очень плохая практика.
- В равной степени плохо использовать экземпляры классов для вызова статических методов, потому что в случае со скрытыми методами их легко ввести в заблуждение...
Чтобы ответить на вопрос: сокрытие иллюстрируется Cat.testClassMethod()
или же myCat.testClassMethod()
, который вызывает статический метод на основе объявленного типа. Когда нет testClassMethod()
в Cat
Ява вызывает родителя.
Как дочерний метод скрыл здесь родительский метод?
Как вы сказали, путем определения статического метода с идентичной подписью.
Как скрытие попадает в картину?
Cat.testClassMethod();
Некоторые могут ожидать здесь вызова родительского метода (по аналогии с полиморфизмом). Но идея сокрытия методами класса другая:
Если класс C объявляет или наследует статический метод m, то говорят, что m скрывает любой метод m', где сигнатура m является подписями (§8.4.2) сигнатуры m', в суперклассах и суперинтерфейсах C, который в противном случае был бы доступен (§6.6) для кодирования в C.
...
Доступ к скрытому методу можно получить с помощью квалифицированного имени или с помощью выражения вызова метода (§15.12), которое содержит ключевое слово super или приведение к типу суперкласса.
...class Super { static String greeting() { return "Goodnight"; } String name() { return "Richard"; } } class Sub extends Super { static String greeting() { return "Hello"; } String name() { return "Dick"; } } class Test { public static void main(String[] args) { Super s = new Sub(); System.out.println(s.greeting() + ", " + s.name()); // Goodnight, Dick } }
Этот пример идеально подходит, чтобы показать разницу между переопределением и сокрытием. В то же время это плохая практика - вызов статических методов для экземпляра.
Я постараюсь прояснить это, приведя еще один пример.
Так как открытые статические методы наследуются, следующий фрагмент
class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
}
class Cat extends Animal {
public static void main(String[] args) {
// Cat: I don't have own method, probably I inherited it from the parent.
// O, yes. I can call it.
Cat.testClassMethod();
}
}
печать
The static method in Animal
Теперь мы собираемся добавить testClassMethod
в Cat
,
class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
}
class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The static method in Cat");
}
public static void main(String[] args) {
// Cat: I have two version of the method: mine and the inherited one.
// The caller specified my class name, so he wanted my version.
// I will call my method hiding the parent method.
Cat.testClassMethod();
// If he wanted Animal's version, he would write
Animal.testClassMethod();
// or (DON'T DO THIS)
((Animal)new Cat()).testClassMethod();
}
}
Статические методы не участвуют в динамическом связывании (полиморфизм).
статические методы не перезаписываются, статические методы скрыты подклассом.
тот, кто звонит Cat.testClassMethod()
может ожидать поведение Animal.testClassMethod()
Так Cat.testClassMethod()
прячется Animal.testClassMethod()
Все конечные, статические и приватные методы и переменные используют статическое связывание и связаны компилятором. Статическая привязка использует информацию Типа для привязки (в данном случае Animal).
Итак, в вашем случае myAnimal.testClassMethod();
напечатает статический метод в Animal
потому что объявленный тип Animal
,