不使用 instanceof 的重载方法的动态调度(运行时多态性)
Posted
技术标签:
【中文标题】不使用 instanceof 的重载方法的动态调度(运行时多态性)【英文标题】:Dynamic dispatch (runtime-polymorphism) with overloaded methods without using instanceof 【发布时间】:2018-04-12 18:52:42 【问题描述】:我想将Arc
和Line
的对象保存在一个ArrayList中,然后得到两者的交集。问题是我如何将i
和j
转换为其原始类。我知道instanceof
有效,但那将是最肮脏的方法。
public class Intersection
public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2)
for (Curve i : list1)
for (Curve j : list2)
if (i.intersection(j).length > 0)
return true;
return false;
public abstract class Curve
public Point[] intersection(Curve c)
return new Point[] ;
public class Line extends Curve
public Point[] intersection(Line l)
// returns intersection Point of this and l
public Point[] intersection(Arc a)
// returns intersection Point(s)
public class Arc extends Curve
public Point[] intersection(Line l)
// return intersection Point(s) of this and l
public Point[] intersection(Arc a)
// returns intersection Point(s)
感谢您的帮助!
【问题讨论】:
我认为使用 instanceof 没有问题。您会在同一个 ArrayList 中有不同的对象类型,还是每个 ArrayList 实例只有一种类型的对象?如果没有,您可以使用通配符。 您确实意识到Arc
和Line
正在重载父方法而不是真正覆盖它?
你没有覆盖交点(曲线)你只是重载它,注意代码不会像你期望的那样工作
@McTi 跟进问题。 Line
和 Arc
是否有 Curve
中不存在的其他方法?也就是说,它们有不同于Curve
的操作还是intersection
唯一的操作?
@CKing 是的,他们还有其他方法
【参考方案1】:
有两种方法可以处理这样的用例:
1.实现Multiple dispatch:
首先将Curve
设为一个接口,并将intersect
的两个重载版本添加到该接口,从而使它们成为合约的一部分。接下来,让每个子类中的intersection(Curve c)
方法将调用委托给适当的重载表单。 (又名Visitor pattern)
interface class Curve
public Point[] intersection(Curve c);
public Point[] intersection(Line l);
public Point[] intersection(Arc c);
class Line extends Curve
public Point[] intersection(Curve c)
return c.intersection(this);
@Override
public Point[] intersection(Line l)
System.out.println("line interesection with line");
return new Point[0];
@Override
public Point[] intersection(Arc c)
System.out.println("line intersection with arc");
return new Point[0];
class Arc extends Curve
public Point[] intersection(Curve c)
return c.intersection(this);
@Override
public Point[] intersection(Line l)
System.out.println("arc interesection with line");
return new Point[0];
@Override
public Point[] intersection(Arc c)
System.out.println("arc interesection with arc");
return new Point[0];
然后您可以在 Intersection
类中调用您的 intersection
方法,而无需任何显式转换:
public class Intersection
public static boolean intersect(ArrayList<Curve> list1,
ArrayList<Curve> list2)
for (Curve i : list1)
for (Curve j : list2)
if (i.intersection(j).length > 0)
return true;
return false;
public static void main(String[] args)
Curve line1 = new Line();
Curve arc1 = new Arc();
Curve line2 = new Line();
Curve arc2 = new Arc();
ArrayList<Curve> list1 = new ArrayList<>();
ArrayList<Curve> list2 = new ArrayList<>();
list1.add(line1);
list1.add(arc1);
list2.add(line2);
list2.add(arc2);
Intersection.intersect(list1, list2);
Extras:看看this实现Visitor模式的替代方法。
2。使直线和曲线遵守同一个界面(契约):
如果Line
和Arc
遵循Curve
的接口,您的代码将不再需要intersect
方法的重载版本。如果我们说Line
是Curve
并且Arc
也是Curve
,那么这两个类都应该具有与Curve
相同的接口(接口我的意思是他们支持的操作列表)。如果这些类没有与Curve
相同的接口,这就是问题所在。 Curve
中的方法应该是 Line
和 Arc
类需要的唯一方法。
有几种策略可以消除子类具有父类中不存在的方法的需要:
如果子类与超类相比需要额外的输入,请通过构造函数提供这些输入,而不是创建对这些输入进行操作的单独方法。 如果子类需要超类不支持的其他行为,请通过组合(阅读策略模式)支持此行为,而不是添加方法来支持其他行为。一旦您不再需要在子类中使用超类中不存在的专用方法,您的代码就会自动消除对instanceof
或类型检查的需要。这与Liskov substitution principle 一致。
【讨论】:
【参考方案2】:由于每个子类都必须知道其他子类(例如,Arc
必须知道 Line
类才能实现 Arc
和 Line
交集),所以有使用instanceof
没有错。
在每个子类中,您可以重写基类的 public Point[] intersection(Curve c)
方法并将实现分派给重载方法之一。
例如:
public class Arc extends Curve
@Override
public Point[] intersection(Curve c)
if (c instanceof Line)
return instersection ((Line) c);
else if (c instanceof Arc)
return intersection ((Arc) c);
else
return an empty array or null, or throw some exception
public Point[] intersection(Line l)
// return intersection Point(s) of this and l
public Point[] intersection(Arc a)
// returns intersection Point(s)
这样您就不必更改 public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2)
方法中的任何内容。
【讨论】:
对不起,我在看到你的想法之前就发布了我的想法 :) @McTi 如果我错了,请纠正我,但你说,“我知道 instanceof 有效,但那将是最肮脏的方法。” 暗示你正在寻找解决方案不使用instanceof
?我给了你两个很好的选择,以避免在我的回答中使用instanceof
。我挠头想知道这是怎么接受的答案? (此答案的作者无意冒犯。我试图从 OPs 帖子中寻求不使用 instanceof
的解决方案,然后接受使用的答案)
@Eran 我看到这个解决方案有两个问题 1)intersection(Curve c)
方法中的代码需要在所有子类中重复,这很好但是 2)这很容易成为维护的噩梦如果必须引入更多子类(例如 Elipse、Parabola 等),则需要向所有子类添加新的 else if
条件和 instanceof
检查。即使除了 Line 或 Curve 不需要支持任何东西,重复 instanceof
检查即使是一个额外的子类也似乎是一种代码味道。【参考方案3】:
另一种方法是使用Class
类的isAssignableFrom
方法。下面是一个例子:
Exception e = new Exception();
RuntimeException rte = new RuntimeException();
System.out.println(e.getClass().isAssignableFrom(RuntimeException.class));
System.out.println(rte.getClass().isAssignableFrom(Exception.class));
System.out.println(rte.getClass().isAssignableFrom(RuntimeException.class));
Here'sisAssignableFrom
方法的 javadoc,它是这么写的:
确定此 Class 对象表示的类或接口是否 要么是相同的,要么是超类或超接口, 由指定的 Class 参数表示的类或接口。它 如果是,则返回 true;否则返回false。如果这个 Class 对象 表示原始类型,如果指定,此方法返回 true Class 参数就是这个 Class 对象;否则返回 假的。
【讨论】:
【参考方案4】:首先考虑:您是否需要将i
和j
从Curve
转换为Arc
或Line
?
例如,请看这里:
What's the need to use Upcasting in java?
如果您确定确实需要向上转换,不幸的是没有神奇的彩蛋 - 您无法避免使用 instanceof
来决定要向上转换的类。
你可以将责任委托给另一个班级,但基本上你无法避免。
对不起!
【讨论】:
【参考方案5】:好的,所以我发现的一个解决方案是在Curve
中使用抽象方法,在子类中使用if-else
链。但是我对这个解决方案并不满意。
public abstract class Curve
public abstract Point[] intersection(Curve c);
public class Line extends Curve
public Point[] intersection(Curve c)
if (c instanceof Line)
return this.intersection((Line) c);
else if (c instanceof Arc)
return this.intersection((Arc) c);
private Point[] intersection(Line l)
// returns intersection Point of this and l
private Point[] intersection(Arc a)
// returns intersection Point(s)
【讨论】:
【参考方案6】:将Curve
更改为接口。保持ArrayList<Curve>
不变,而是将intersection
方法提取到一个单独的类,并让它在Curves
上运行。
您需要在此处使用instanceof
检查,但由于使用继承,您的设计会更简洁。
public interface Curve
...
public class Line extends Curve
...
public class Arc extends Curve
...
public class IntersectionUtility
public static boolean intersects(ArrayList<Curve> list1, ArrayList<Curve> list2)
for (Curve i : list1)
for (Curve j : list2)
if (i.intersection(j).length > 0)
return true;
return false;
public Point[] intersection(Curve a, Curve b)
if (a.instanceof(Line.class))
if (b.instanceof(Line.class))
return findIntersection((Line) a, (Line) b); // two Lines
else
return findIntersection((Line) a, (Arc) b); // a Line and an Arc
else
if (b.instanceof(Line.class))
return findIntersection((Line) b, (Arc) a); // a Line and an Arc
else
return findIntersection((Arc) a, (Arc) b); // two Arcs
public Point[] findIntersection(Line a, Line b)
// returns intersection Point of two Lines
public Point[] findIntersection(Arc a, Arc b)
// returns intersection Point(s) of two Arcs
public Point[] findIntersection(Line a, Arc b)
// returns intersection Point(s) of an Line and an Arc
【讨论】:
不确定如何编译?没有带Curve
参数的findIntersection
?【参考方案7】:
如果您不想使用instanceof
,那么替代方法是使用组合来获取类型。以下方法不会使用instanceof
,只会使用首选Class.cast
操作:
public static class Intersection
public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2)
for (Curve i : list1)
Optional<Line> il = i.get(Line.class);
Optional<Arc> ia = i.get(Arc.class);
for (Curve j : list2)
Optional<Line> jl = j.get(Line.class);
Optional<Arc> ja = j.get(Arc.class);
Point[] intersection = null;
if ( il.isPresent() )
if ( jl.isPresent() ) intersection = il.get().intersection( jl.get() );
else if ( ja.isPresent() ) intersection = il.get().intersection( ja.get() );
else if ( ia.isPresent() )
if ( jl.isPresent() ) intersection = ia.get().intersection( jl.get() );
else if ( ja.isPresent() ) intersection = ia.get().intersection( ja.get() );
if ( intersection != null && intersection.length > 0 ) return true;
return false;
public static abstract class Curve
public abstract <T extends Curve> Optional<T> get(Class<T> clazz);
public static class Line extends Curve
public <T extends Curve> Optional<T> get(Class<T> clazz)
return clazz.equals(Line.class) ? Optional.of( clazz.cast(this) ) : Optional.empty();
public Point[] intersection(Line l)
return new Point[] ;
public Point[] intersection(Arc a)
return new Point[] ;
public static class Arc extends Curve
public <T extends Curve> Optional<T> get(Class<T> clazz)
return clazz.equals(Arc.class) ? Optional.of( clazz.cast(this) ) : Optional.empty();
public Point[] intersection(Line l)
return new Point[] ;
public Point[] intersection(Arc a)
return new Point[] ;
【讨论】:
以上是关于不使用 instanceof 的重载方法的动态调度(运行时多态性)的主要内容,如果未能解决你的问题,请参考以下文章
Java面向对象(OOP)--面向对象三大特性之一:多态(多态方法绑定instanceof和类型转换(重写重载和多态的关系))