【问题标题】:Java inheritance and method resolution orderJava继承和方法解析顺序
【发布时间】:2017-02-05 09:36:16
【问题描述】:

我有以下代码示例:

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

我得到相同的输出 - 为什么? 类型转换是否会用 colorprinter 中的“drucker”覆盖对象“drucker”的方法“drucker”?

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

【问题讨论】:

  • 一点点挑剔:为清楚起见,用大写字母命名你的类。
  • 在命名课程、方法等时,永远不要使用非英语母语。使用英语 camelCase。
  • 类类型定义了“外部用户”(其他类)如何看待对象。方法实现绑定到一个实例,类型只定义它的外部契约。

标签: java inheritance casting method-resolution-order


【解决方案1】:

colorprinter 在您对其使用强制转换运算符时不会停止成为cp 的实例,因此它对public void drucke() 的实现不会改变

你用(p)colorprinter 转换表达的是你期望对象colorprinter 满足的那种契约(接口),它包括一个带有签名public void drucke() 的公共方法,但没有任何具体的实现。

顺便说一下,当你声明p 类型的drucker 时,这个转换已经隐式执行了,所以(p)p drucker = (p)colorprinter; 中是多余的。 p drucker = colorprinter; 就足够了。

Here you can learn more about 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");
    }
}

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

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

}

这样,您甚至可以编写带有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 的最新版本以及事实上的标准,Java 中的类应该使用CamelCase 命名约定。您还可以从对所有定义使用语义命名中受益匪浅,例如 BlackAndWhitePrinter blackAndWhitePrinterColorPrinter colorPrinter

【讨论】:

    【解决方案2】:

    colorprintercp 的一个实例。即使您将其向上转换为p,它的drucke() 方法仍将是来自cp 的方法。

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

    【讨论】:

      【解决方案3】:

      当您使用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
      

      【讨论】:

        猜你喜欢
        • 2020-03-23
        • 2015-12-27
        • 2012-10-18
        • 2014-03-06
        • 1970-01-01
        • 2016-09-10
        • 2010-12-13
        • 1970-01-01
        相关资源
        最近更新 更多