Java继承和方法解析顺序

Java inheritance and method resolution order

我有以下代码示例:

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();" 导致控制台输出 "Color-Printer" 没有问题。

但是当我打电话时:

    p drucker = (p)colorprinter;
    drucker.druckauftrag();

我得到相同的输出 - 为什么? 类型转换是否用来自 colorprinter 的 "drucke" 覆盖对象 "drucker" 的方法 "drucke"?

提前感谢您的每一个解释。

colorprintercp 的实例。即使你将它向上转换为 p,它的 drucke() 方法仍然是来自 cp.

的方法

不同的是,在你upcast colorprinter之后,你将无法调用cp自己定义的方法。

colorprinter 不会停止成为 cp 的一个实例,当你在它上面使用 cast 运算符时 ,所以 它的实现public void drucke()不变

你用 (p)colorprinter 转换表达的是你希望对象 colorprinter 满足的契约(接口)类型,其中包括一个 public 方法,签名为 public void drucke(),但没有具体实现。

而且,顺便说一句,当您声明 p 类型的 drucker 时,此转换已经隐式执行,因此 (p)p drucker = (p)colorprinter; 中是多余的。 p drucker = colorprinter; 就足够了。

Here you can learn more about typecasting

请记住,最好的做法是从抽象 class es 或接口和仅 @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");
    }
}

当然,约束并不总是允许进行这种重新设计。将基础要求作为参数传递给构造函数 (dependency injection) 而不是扩展基础 class 也是一个不错的选择:

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

现在,如果您想表达打印机需要或可以具有多种打印功能(如颜色和 b/w),您只需编写具有尽可能多的额外 Drukable 属性的 class 和构造函数参数随心所欲,例如:

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

}

这样,您甚至可以使用 MultiPrinter(List<Druckable> printServices) 构造函数编写 class MultiPrinter,并向其打印服务列表添加任意数量的打印模式:pcp,以及未来 Druckable 及其 public void drucke() 的任何其他实现。如果您想引入单元测试,它也非常实用,因此您可以提供模型对象来强制执行您想要测试的特定条件,例如 druke() 抛出 PaperJamException

有关接口、覆盖和继承如何工作的更多信息,请参阅 https://docs.oracle.com/javase/tutorial/java/IandI/usinginterface.html

顺便说一句,根据官方 java code conventions guide and also by de facto standard, classes in Java should use CamelCase 命名约定的最新修订。您还可以从对所有定义使用语义命名中受益匪浅,例如 BlackAndWhitePrinter blackAndWhitePrinterColorPrinter colorPrinter.

当您使用 new 运算符创建对象时,内存分配在 heap 中。方法和字段实际上取决于对象的具体实际 class。 改变子 class 覆盖和修改其超级 class 的行为,调用被覆盖的方法将始终导致修改后的行为。转换仅意味着 sub class 的对象现在由超类型表示,因为该对象具有修改后的行为,方法将始终导致修改后的行为。

假设您有以下 classes

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 作为 fruitmango 仍然是 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