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
Другие вопросы по тегам