【问题标题】:Java Polymorphism : How can I avoid type casting input parameters?Java 多态性:如何避免类型转换输入参数?
【发布时间】:2016-07-24 11:57:51
【问题描述】:

假设我们有一个带有 compare() 函数的 Parent 接口。

public interface Parent {
    public int compare(Parent otherParent);
}

假设孩子Child1、Child2、Child3实现了这个接口Parent

public class Child1 implements Parent {
    @Override
    public int compare(Parent other) {
        Child1 otherChild = (Child1)other;
    }
}

另外,我在代码中的其他任何地方都使用泛型<T extends Parent>。所以我需要从代码的其他部分比较两个类型为 T 的对象。

我知道这是一个糟糕的设计,因为我在 compare() 函数中对 Parent 对象进行类型转换,我什至不知道输入是否属于 Child1 类型。

在使用泛型时如何避免这种类型转换?

【问题讨论】:

    标签: java generics casting polymorphism


    【解决方案1】:

    为什么不这样?

    interface Parent<T extends Parent<?>> {
        int compare(T type);
    }
    
    class Child1 implements Parent<Child1>{
    
        @Override
        public int compare(Child1 type) {
            return 0;
        }
    }
    

    编辑:为确保正确使用,您可以使用

    interface Parent<T extends Parent<T>>{ /* ... */ }   //instead of wildcard
    

    但老实说,“循环”看起来并不漂亮,并且由于 Java 中的泛型在运行时不起作用 (more information),因此对于您称为“糟糕设计”的同一个演员,它们本质上是句法糖" 所以我不认为你目前的方法不好。

    【讨论】:

    • extends Parent&lt;?&gt; 部分确实看起来不漂亮。但事实上,它可以被忽略——假设实现者负责使用正确的TComparable 也是如此。
    【解决方案2】:

    答案取决于您如何处理继承层次结构:

    • 如果派生类必须仅与它们自己的类的实例进行比较,并且允许在呈现不同类的对象时抛出错误,请使用强制转换,但通过检查 instanceof 来保护它
    • 如果必须跨类型边界比较派生类,则需要更复杂的策略来避免强制转换:您可以使用类似访问者的模式来实现执行比较所需的双重分派。

    这是一个使用抽象类的示例实现:

    // Use this class as the base class of your Child classes
    class AbstractChild implements Parent {
        @Override
        public int compare(Parent otherObj) {
            if (!()) {
                throw new IllegalStateException("Unexpected implementation of Child");
            }
            AbstractChild other = (AbstractChild)otherObj;
            return doCompare(other);
        }
        protected abstract int doCompare(AbstractChild other);
        protected abstract int accept(Child1 c1);
        protected abstract int accept(Child2 c2);
        protected abstract int accept(Child3 c3);
    }
    class Child1 extends AbstractChild {
        @Override
        protected int doCompare(AbstractChild other) {
             return other.accept(this);
        }
        @Override
        protected int accept(Child1 c1) {
            // Compare c1 instance to this object
        }
        @Override
        protected int accept(Child2 c2) {
            // Compare c2 instance to this object
        }
        @Override
        protected int accept(Child3 c3) {
            // Compare c3 instance to this object
        }
    }
    class Child2 extends AbstractChild {
        @Override
        protected int doCompare(AbstractChild other) {
             return other.accept(this);
        }
        @Override
        protected int accept(Child1 c1) {
            // Compare c1 instance to this object
        }
        @Override
        protected int accept(Child2 c2) {
            // Compare c2 instance to this object
        }
        @Override
        protected int accept(Child3 c3) {
            // Compare c3 instance to this object
        }
    }
    class Child3 extends AbstractChild {
        @Override
        protected int doCompare(AbstractChild other) {
             return other.accept(this);
        }
        @Override
        protected int accept(Child1 c1) {
            // Compare c1 instance to this object
        }
        @Override
        protected int accept(Child2 c2) {
            // Compare c2 instance to this object
        }
        @Override
        protected int accept(Child3 c3) {
            // Compare c3 instance to this object
        }
    }
    

    这种方法的唯一转换是在抽象类级别。实际比较逻辑的实现包含在accept 方法中,其中在两个已知类型的对象之间进行比较。

    【讨论】:

    • 我必须承认,与使用方法内类型转换/检查相比,拥有此代码的优势让我有些茫然。在过去的十年里,我并没有成为一名 Java 专家(Java 1.4 是什么时候出现的?从那以后),所以:让 JVM 进行必要的类型比较以“委托”到正确的方法是否真的有任何性能优点还是有其他优势(除了可能是“正确的做法”,让强类型语言处理类型消歧)?
    • @MarcusMüller 这是对“如何避免演员表”问题的回答,假设您出于某种原因想要避免演员表。我过去曾在非常特定的情况下使用过类似的方法,那时我可以利用访问者进行比比较更多的事情。在 Java-5 出现的时候,我已经从 Java 过渡到了 C#,并且我使用了不同的语言机制来在那里实现我的多重调度(即,dynamic,它从 C# 4 开始可用)。
    • 同意我的评论有点跑题了 :) 显然,访问者模式是 恢复类型信息的经典方法,它通过在多态语言中传递东西而丢失在干净地处理不同的类型信息对象方面有点有限(将 Java 与 C# 进行比较,甚至像 Python 这样的动态类型脚本语言,如果我愿意的话,我实际上可以使用类型作为可调用对象的键)。我的希望只是你能在这方面积累一些经验,你做到了:) 谢谢!
    • if (!()) { ... } 在语法上是正确的,还是一种伪代码占位符?我的 IDE 抱怨它。我并不是要指出任何可能的错误,我只是想知道这是否是我以前从未见过的 Java 功能。
    【解决方案3】:

    你不能。 Java 接口正是顾名思义——一个指示接受类型的接口;因此在编译时需要完全与接口中定义的方法签名相同。

    是的,将其转换回 Child 类型感觉像是糟糕的设计 - 但它确实是 Java 规定的糟糕设计,所以这不是你的错。

    【讨论】:

    • 猜猜我将不得不使用类型转换。谢谢!
    【解决方案4】:

    在一般情况下你无法避免它。但是,如果子类的数量是固定的,您可以应用类似于访问者模式的方法来避免强制转换,但会以编写更多代码为代价:

    interface Parent {
       int compare(Parent p);
       protected int compareWith(Child1 c);
       protected int compareWith(Child2 c);
       protected int compareWith(Child3 c);
    }
    
    class Child1 implements Parent {
        @Override int compare(Parent p) {
            return p.compareWith(this);
        }
        @Override int compareWith(Child1 c) {
           //specific code to child1
        }
        @Override int compareWith(Child2 c) {
           //specific code to child2
        }
        @Override int compareWith(Child3 c) {
           //specific code to child3
        }
    }
    // and so on for the other children
    

    这确实避免了强制转换,但我不确定在您在这里介绍的情况下额外的努力是否值得。

    【讨论】:

      【解决方案5】:

      您在那里描述的模式正是Comparable 的模式。实际上,您应该考虑省略您的Parent 接口,而将其替换为Comparable

      Comparable 接口及其使用也展示了如何解决这个问题:类型可以作为参数给出,以确保只有匹配的类型可以传递给compare 方法:

      interface Parent<T> {
          int compare(T that);
      }
      
      class Child1 implements Parent<Child1> {
          @Override
          public int compare(Child1 that) {
              ...
          }
      }
      

      对于所有其他情况,您至少必须考虑将不同的Child-classes 相互比较时会发生什么:

      Child1 c1 = new Child1();
      Child2 c2 = new Child2();
      
      // Should this be possible? What should happen here? 
      // How should different child classes be compared?
      c1.compare(c2); 
      

      【讨论】:

        猜你喜欢
        • 2014-11-26
        • 1970-01-01
        • 1970-01-01
        • 2020-01-18
        • 1970-01-01
        • 2019-10-04
        • 1970-01-01
        • 2018-06-18
        • 1970-01-01
        相关资源
        最近更新 更多