【问题标题】:question about virtual methods in java关于java中虚方法的问题
【发布时间】:2010-08-26 10:13:10
【问题描述】:

简单地说:我希望下面的代码打印“sub”:

Element e = new SubElement();
print(e);
... 

private static void print(Element e) {
    System.out.println("e");
}

private static void print(SubElement e) {
    System.out.println("sub");
}

我不想更改打印(元素 e)。所以没有什么像

private static void print(Element e) {
    if (e instanceof SubElement) {
        print((SubElement) e);
    } else {
        System.out.println("e");
    }
}

我想做的是

print(e.getClass().cast(e));

自动将其强制转换为真正的子类并强制系统进入 print(SubElement e)。这有可能吗?

【问题讨论】:

  • 如果你有一个静态方法,它接受一个或多个对象作为参数,你应该考虑使该方法成为实例方法。这简化了您的代码,并且很可能简化了您的逻辑。静态方法最适合用于基元,以及您无法更改的类,例如 String 或 byte[]。

标签: java casting virtual-functions visitor-pattern


【解决方案1】:

在编译时选择运行的重载方法,这就是选择 Element 版本而不是 SubElement 版本的原因。看起来更合乎逻辑的做法是让 Element 或子类包含应该打印的数据。

class Element {

    public String getName() {
        return "e";
    }
}

class SubElement extends Element {
    public String getName() {
        return "sub";
    }
}

然后在打印方法中:

private static void print(Element e) {
    System.out.println(e.getName());
}

这是否有意义将取决于Element 类实际上是什么以及打印的数据代表什么。

【讨论】:

  • +1 用于首先描述问题,然后才描述解决方案。
【解决方案2】:

是的。您可以使用Visitor pattern。但是它适用于已建立的明确定义的层次结构,因为您必须定义的访问者接口需要为每种类型提供一个方法。

interface ElementVisitor {
   visit(Element e);
   visit(SubElement se);
}

class ElementerPrinter implements ElementVisitor {
   visit(Element e) { System.out.println("e"); }
   visit(SubElement e) { System.out.println("sub"); }
}

class Element {
  // here's the trick, Element knows that this is Element
  // and childs have to implement it!
  // if the parent-most class is an interface it force you to implement!
  accept(ElementVisitor visitor) { visitor.visit(this); } 
}

class SubElement {
  // here's the trick, Element knows that this is SubElement
  accept(ElementVisitor visitor) { visitor.visit(this); }
}

【讨论】:

  • 实际上我正在尝试根据我的需要更改访问者模式;)我有一个无法更改的访问者界面,所以如果我必须向系统添加新的子类,我看不到任何处理新的子类的方法在原始访问(元素 e)方法中不使用 instanceof 单独子类。我希望有一种方法可以强制系统自动使用正确的方法,而无需向访问者界面添加代码。
  • 好吧,如果你有一个访问过的元素,你确实有接受方法并且它正在被调用。当您对 Element 进行子类化时,SubElement 类可以重写 accept 方法来调用 visit(SubElement)。但是当然已经存在的Visitor没有这个方法。你可以做 ExtendedVisitor 并且接受应该做if (visitor instanceof ExtendedVisitor) ((ExtendedVisitor)visitor).visit(this);
  • PS:不是很好。但它有效,并且与已经存在的accept调用很好地集成在一起。如果您从头开始这样做,也许 Map 会更好。
  • 好的,谢谢你的信息,我仍然希望找到一种方法来使用反射 API 来自动调用正确的方法,如果它可以避免子类中的此类检查,但我没有得到它现在工作。
【解决方案3】:

print() 需要成为Element 的实例方法,真的。否则,您正试图以一种艰难的方式模仿多态性。如果您希望这样做,您将无法真正避免从Class 映射到函数对象的一系列if 语句。何必呢?

【讨论】:

  • 也许它只是一个简化的例子。但如果是这种情况,我应该做一个多态 Element.getMyString() 方法,并且只让 print 方法知道 System.out。
  • 如果事实上,通过他的第二个例子(演员表)我怀疑他正在尝试做 double dispatch (不仅通过变量类型匹配正确的方法,而且通过具体持有也有实例)。为此存在访问者模式。
【解决方案4】:

您是否能够将行为差异推送到元素类中?

Element e = new SubElement();
print(e);
... 

private static void print(Element e) {
    System.out.println(e.getMessageToPrint());
}

// no longer needed
//
//private static void print(SubElement e) {
//    System.out.println("sub");
//}

这样,SubElement 可以覆盖 getMessageToPrint() 方法。

或者更好:

Element e = new SubElement();
e.print();

【讨论】:

    【解决方案5】:

    我会选择不同的方法。要么

    1. 按照其他人的建议使用多态,扩展 Element 以添加 print() 方法(可以被子类覆盖)或
    2. 定义一个帮助接口并结合使用策略和工厂模式:

    基类

     public class Element{}
    

    派生类

     public class SubElement extends Element{}
    

    打印元素的帮助界面

    public interface PrintHelper{
        void print(Element element);
    }
    

    为给定元素获取最佳 PrintHelper 的工厂

    public class PrintHelperFactory{
    
        private final Map<Class<? extends Element>, PrintHelper> registeredHelpers =
            new HashMap<Class<? extends Element>, PrintHelper>();
    
        // Register a PrintHelper for a given Element class.
        public void registerHelper(final Class<? extends Element> clazz,
          final PrintHelper helper){
            this.registeredHelpers.put(clazz, helper);
        }
    
        // Get the most specific PrintHelper for a given Element.
        public PrintHelper getHelperForElement(final Element element){
            Class<? extends Element> clazz = element.getClass();
            while(!Object.class.equals(clazz)){
                if(this.registeredHelpers.containsKey(clazz)){
                    return this.registeredHelpers.get(clazz);
                }
                clazz = (Class<? extends Element>) clazz.getSuperclass();
            }
            return null;
        }
    
    }
    

    客户端测试类,作为 Java 应用程序运行

    public class Main{
    
        public static void main(final String[] args){
    
            final PrintHelperFactory factory = new PrintHelperFactory();
            factory.registerHelper(Element.class, new PrintHelper(){
                @Override
                public void print(final Element element){
                    System.out.println("Element");
                }
            });
            factory.registerHelper(SubElement.class, new PrintHelper(){
                @Override
                public void print(final Element element){
                    System.out.println("Sub Element");
                }
            });
    
            // test it with an Element  
            final Element elem = new Element();
            factory.getHelperForElement(elem).print(elem);
    
            // test it with a sub class
            final Element sub = new SubElement();
            factory.getHelperForElement(sub).print(sub);
    
        }
    
    }
    

    输出

    Element
    Sub Element
    

    【讨论】:

      猜你喜欢
      • 2011-02-16
      • 2013-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 2010-10-11
      • 1970-01-01
      相关资源
      最近更新 更多