Java наследование и порядок разрешения методов
У меня есть следующий пример кода:
class p {
public void druckauftrag() {
// ...
drucke();
}
public void drucke() {
System.out.println("B/W-Printer");
}
}
class cp extends p {
public void drucke() {
System.out.println("Color-Printer");
}
}
Вызов следующих строк:
cp colorprinter = new cp();
cp.druckauftrag();
Нет проблем с пониманием, почему "cp.druckauftrag();" в результате выводится консоль "Цветной принтер".
Но когда я звоню:
p drucker = (p)colorprinter;
drucker.druckauftrag();
Я получаю тот же вывод - почему? Заменяет ли тип-тип метод "drucker" объекта "drucke" на "drucke" из colorprinter?
Заранее спасибо за каждое объяснение.
3 ответа
colorprinter
это пример cp
, Даже когда вы сбрасываете это p
, его drucke()
метод будет все еще один из cp
,
Разница в том, что после того, как вы upcast colorprinter
вы не сможете вызывать методы, которые cp
определяет самостоятельно.
colorprinter
не перестает быть примером cp
когда вы используете оператор приведения к нему, поэтому его реализация public void drucke()
не меняется
Что вы выражаете своим (p)colorprinter
кастинг - это вид контракта (интерфейса), который вы ожидаете от объекта colorprinter
удовлетворить, который включает в себя публичный метод с подписью public void drucke()
, но не какая-то конкретная реализация.
И, кстати, этот кастинг уже выполняется неявно, когда вы объявляете drucker
типа p
, так (p)
избыточен в p drucker = (p)colorprinter;
, p drucker = colorprinter;
будет достаточно.
Здесь вы можете узнать больше о Typecasting.
Имейте в виду, что рекомендуется выходить за пределы абстрактных классов или интерфейсов и только @Override
(реализовать) абстрактные методы. Лучшим дизайном вашего кода будет:
abstract class BasePrinter {
public void druckauftrag() {
// ...
drucke();
}
public void drucke();
}
class p extends BasePrinter {
public void drucke() {
System.out.println("B/W-Printer");
}
}
class cp extends BasePrinter {
public void drucke() {
System.out.println("Color-Printer");
}
}
Но, конечно, ограничения не всегда допускают такого рода редизайн. Передача базовых требований в качестве параметров конструктору ( внедрение зависимостей) вместо расширения базового класса также может быть хорошей альтернативой:
interface Druckable {
void drucke();
}
class Druckauftrager {
Druckable dk;
Druckauftrager(Drukable dk){
this.dk = dk;
}
public void druckauftrag() {
// ...
dk.drucke();
}
}
class p implements Druckable {
public void drucke() {
System.out.println("B/W-Printer");
}
}
class cp implements Druckable {
public void drucke() {
System.out.println("Color-Printer");
}
}
Теперь, если вы хотите выразить, что принтер требует или может иметь несколько возможностей печати (например, цветной и ч / б), вы просто пишете класс с таким количеством дополнительных свойств Drukable и параметрами конструктора, как вы хотите, например:
class BlackAndWhiteOrColorPrinter {
p blackAndWhitePrintService;
cp colorPrintService;
Druckable selectedPrintService;
BlackAndWhiteOrColorPrinter (p blackAndWhitePrintService, cp colorPrintService){
this.blackAndWhitePrintService = blackAndWhitePrintService;
this.colorPrintService = colorPrintService;
this.selectedPrintService = blackAndWhitePrintService;
}
public void druckauftrag() {
// ...
selectedPrintService.drucke();
}
}
Таким образом, вы даже можете написать class MultiPrinter
с MultiPrinter(List<Druckable> printServices)
конструктор и добавить любое количество режимов печати в свой список услуг печати: p
, cp
и любой другой реализации Druckable
с этими public void drucke()
приходит в будущем Это также очень практично, если вы хотите ввести модульное тестирование, поэтому вы можете предоставить макет объектов, которые вынуждают конкретные условия, которые вы хотите протестировать, например, druke()
бросать PaperJamException
, например.
Для получения дополнительной информации о том, как работают интерфейсы, переопределение и наследование, см. https://docs.oracle.com/javase/tutorial/java/IandI/usinginterface.html
Кстати, согласно последней редакции официального руководства по соглашениям по Java-коду, а также по стандарту де-факто, классы в Java должны использовать соглашение об именовании CamelCase. Вы также можете получить большую пользу от использования именования с разделением во всех ваших определениях, таких как BlackAndWhitePrinter blackAndWhitePrinter
а также ColorPrinter colorPrinter
,
Когда вы создаете объект, используя new
оператор, память выделяется в heap
, Методы и поля действительно существуют в зависимости от конкретного фактического класса объекта. Изменение подкласса переопределяет и изменяет поведение своего суперкласса; вызов переопределенного метода всегда приводит к измененному поведению. Приведение будет означать только то, что объект подкласса теперь представлен супертипом, поскольку объект имеет измененное поведение, поскольку метод всегда приводит к измененному поведению.
Предположим, у вас есть ниже классы
public class Fruit{
public void taste(){
System.out.println("depends upon the actual fruit");
}
}
public class Mango extends Fruit{
@Override
public void taste(){
System.out.println("sweet");
}
public void wayToExposeSuperMethod(){
super.taste();
}
}
Другими словами, это как призыв mango
как fruit
но до сих пор mango
остатки mango
, Для приведенного выше кода
Fruit fruit = new Mango();
fruit.taste(); // <-- this will output : sweet
((Mango)fruit).taste();// <-- this will output : sweet
fruit.wayToExposeSuperMethod(); // <-- this will not compile
((Mango)fruit).wayToExposeSuperMethod(); // <-- this will output : depends upon the actual fruit