java中的虚拟方法问题

java中的虚拟方法问题,java,casting,virtual-functions,visitor,Java,Casting,Virtual Functions,Visitor,简单地说:我希望以下代码打印“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)); 自动将其转换为真正的子类

简单地说:我希望以下代码打印“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(子元素e)。这可能吗?

是的。你可以用。但是,它适用于已建立的定义良好的层次结构,因为您必须定义的访问者界面需要针对每种类型的方法

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

print()
确实需要成为
元素的实例方法。否则,您正试图以一种艰难的方式模拟多态性。如果您希望这样做,就不能真正避免从
Class
映射到函数对象的一系列
If
语句。何必麻烦?

在编译时选择运行的重载方法,因此选择元素版本而不是子元素版本。更合乎逻辑的是让元素或子类包含应该打印的数据

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 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
就可以重写
getMessageTopPrint()
方法

或者更好:

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

我会选择不同的方法。或者

  • 按照其他人的建议使用多态性,扩展元素以添加print()方法(可以被子类覆盖)或
  • 定义助手界面并结合使用策略和工厂模式:
  • 基类

     public class Element{}
    
     public class SubElement extends Element{}
    
    public interface PrintHelper{
        void print(Element element);
    }
    
    派生类

     public class Element{}
    
     public class SubElement extends Element{}
    
    public interface PrintHelper{
        void print(Element element);
    }
    
    打印元素的辅助界面

     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;
        }
    
    }
    
    Element
    Sub Element
    
    输出

    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;
        }
    
    }
    
    Element
    Sub Element
    

    也许这只是一个简单的例子。但是如果是这种情况,我应该执行一个多态元素.getMyString()方法,并且只让print方法知道System.out.if事实上,通过他的第二个示例(cast),我怀疑他试图执行双重分派(不仅通过变量类型匹配正确的方法,而且还通过具体的保持实例匹配正确的方法)。访问者模式就是这样存在的。实际上,我正在尝试根据我的需要更改访问者模式;)我有一个不能更改的访问者界面,因此如果我必须向系统添加新的子类,我看不到任何方法可以单独处理新的子类,而不使用原始visit(元素e)方法中的instanceof。我希望有一种方法可以强制系统自动使用正确的方法,而无需向访问者界面添加代码。当您对元素进行子类化时,子元素类可以重写accept方法来调用visit(子元素)。当然,已经存在的访问者没有这个方法。您可以执行ExtendedVisitor,如果(ExtendedVisitor的访问者实例)((ExtendedVisitor)访问者),则接受应该执行
    if附言:没那么好。但它可以工作,并且与已经存在的accept调用很好地集成。如果你从头开始做这件事,也许地图会更好。好的,谢谢你提供的信息,我仍然希望找到一种方法,使用反射API自动调用正确的方法,如果它可以避免子类中的这种检查,但是我现在还没有让它工作。+1首先描述问题,然后描述一个解决方案。如果你有一个静态方法,它将一个或多个对象作为一个参数,那么你应该考虑使该方法成为一个实例方法。这简化了您的代码,并且很可能简化了您的逻辑。静态方法最适合用于原语和不能更改的类,如字符串或字节[]。