在不使用 instanceof 的情况下使用重载方法进行动态调度(运行时多态性)
Dynamic dispatch (runtime-polymorphism) with overloaded methods without using instanceof
我想把Arc
和Line
的对象保存在一个ArrayList中,然后求两者的交集。问题是如何将 i
和 j
转换为原来的 class。我知道 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)
}
}
感谢您的帮助!
首先考虑:是否需要将i
和j
从Curve
转换(upcast)为Arc
或Line
?
例如,请看这里:
What's the need to use Upcasting in java?
如果您决定确实需要向上转型,不幸的是没有魔法蛋 - 您无法避免使用 instanceof
来决定要向上转型的 class。
你可以把责任委托给另一个人class,但基本上你无法避免。
对不起!
另一种方法是使用 Class 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's isAssignableFrom
方法的 javadoc,它是这样说的:
Determines if the class or interface represented by this Class object
is either the same as, or is a superclass or superinterface of, the
class or interface represented by the specified Class parameter. It
returns true if so; otherwise it returns false. If this Class object
represents a primitive type, this method returns true if the specified
Class parameter is exactly this Class object; otherwise it returns
false.
因为每个子 class 已经知道其他子 class(例如,Arc
必须知道 Line
class 为了实现 Arc
和 Line
交集),使用 instanceof
.
没有错
在每个子 class 中,您可以覆盖基础 class 的 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)
方法中的任何内容。
将Curve
更改为界面。保持 ArrayList<Curve>
不变,而是将 intersection
方法提取到单独的 class,并让它在 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
中使用抽象方法,在子类中使用 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)
}
}
有两种方法可以解决这种用例:
1.实施 Multiple dispatch :
首先让 Curve
成为一个接口,然后将 intersect
的两个重载版本添加到这个接口中,从而使它们成为契约的一部分。接下来,让每个子 class 中的 intersection(Curve c)
方法将调用委托给适当的重载形式。 (a.k.a 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
class 中调用您的 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
,那么这两个 class 应该具有相同的 interface as Curve
(我所说的接口是指它们支持的操作列表)。如果这些 class 没有与 Curve
相同的界面,这就是问题所在。 Curve
中存在的方法应该是 Line
和 Arc
class 中唯一需要的方法。
有几种策略可以消除子classes 拥有超class 中不存在的方法的需要:
- 如果与超级class 相比,子class 需要额外的输入,请通过构造函数提供这些输入,而不是创建对这些输入进行操作的单独方法。
- 如果 subclass 需要 superclass 不支持的额外行为,请通过组合(阅读策略模式)支持此行为,而不是添加方法来支持额外行为。
一旦您不再需要 subclass 中的专门方法,而 superclass 中不存在,您的代码将自动消除 instanceof
或类型检查的需要。这符合 Liskov substitution principle.
如果您不想使用 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[] {};
}
}
我想把Arc
和Line
的对象保存在一个ArrayList中,然后求两者的交集。问题是如何将 i
和 j
转换为原来的 class。我知道 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)
}
}
感谢您的帮助!
首先考虑:是否需要将i
和j
从Curve
转换(upcast)为Arc
或Line
?
例如,请看这里:
What's the need to use Upcasting in java?
如果您决定确实需要向上转型,不幸的是没有魔法蛋 - 您无法避免使用 instanceof
来决定要向上转型的 class。
你可以把责任委托给另一个人class,但基本上你无法避免。
对不起!
另一种方法是使用 Class 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's isAssignableFrom
方法的 javadoc,它是这样说的:
Determines if the class or interface represented by this Class object is either the same as, or is a superclass or superinterface of, the class or interface represented by the specified Class parameter. It returns true if so; otherwise it returns false. If this Class object represents a primitive type, this method returns true if the specified Class parameter is exactly this Class object; otherwise it returns false.
因为每个子 class 已经知道其他子 class(例如,Arc
必须知道 Line
class 为了实现 Arc
和 Line
交集),使用 instanceof
.
在每个子 class 中,您可以覆盖基础 class 的 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)
方法中的任何内容。
将Curve
更改为界面。保持 ArrayList<Curve>
不变,而是将 intersection
方法提取到单独的 class,并让它在 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
中使用抽象方法,在子类中使用 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)
}
}
有两种方法可以解决这种用例:
1.实施 Multiple dispatch :
首先让 Curve
成为一个接口,然后将 intersect
的两个重载版本添加到这个接口中,从而使它们成为契约的一部分。接下来,让每个子 class 中的 intersection(Curve c)
方法将调用委托给适当的重载形式。 (a.k.a 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
class 中调用您的 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
,那么这两个 class 应该具有相同的 interface as Curve
(我所说的接口是指它们支持的操作列表)。如果这些 class 没有与 Curve
相同的界面,这就是问题所在。 Curve
中存在的方法应该是 Line
和 Arc
class 中唯一需要的方法。
有几种策略可以消除子classes 拥有超class 中不存在的方法的需要:
- 如果与超级class 相比,子class 需要额外的输入,请通过构造函数提供这些输入,而不是创建对这些输入进行操作的单独方法。
- 如果 subclass 需要 superclass 不支持的额外行为,请通过组合(阅读策略模式)支持此行为,而不是添加方法来支持额外行为。
一旦您不再需要 subclass 中的专门方法,而 superclass 中不存在,您的代码将自动消除 instanceof
或类型检查的需要。这符合 Liskov substitution principle.
如果您不想使用 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[] {};
}
}