我有两个类。
public class Shape1 extends javafx.scene.shape.Path {
public Shape1(PathElement... elements) {
super(elements);
}
}
public class Shape2 extends javafx.scene.shape.Path {
public Shape2(PathElement... elements) {
super(elements);
}
}
它们不一样。它们有不同的领域和方法,但为了简单起见,我没有提及它们
我为每种类型的对象创建两个数组,并且只需要使用一种方法来创建它们。
ArrayList<Shape1> first_shapes = create_array(50, 50, 100, 100, Class.forName("example3.Shape1"));
ArrayList<Shape2> second_shapes = create_array(50, 100, 100, 150, Class.forName("example3.Shape2"));
public static ArrayList create_array(int X1, int Y1, int X2, int Y2, Class my_class) {
var shapes = new ArrayList<>();
shapes.add(my_class.getConstructor(PathElement[].class).newInstance(new MoveTo(X1, Y1), new LineTo(X2, Y2)));
...
return shapes;
}
代码有两个问题。
类定义不合适,因为当您更改类名或包名时,它将不起作用,并且必须手动更改。
实际上,它根本不起作用,因为当您向数组中添加对象时,它会崩溃。
奇怪的是,在上面的表格中,它会显示一个错误。
参数数量错误
但如果我只使用一个参数,
shapes.add(my_class.getConstructor(PathElement[].class).newInstance(new MoveTo(X1, Y1)));
它将显示一个错误。
参数类型不匹配
这是解决我问题的唯一办法。否则我将不得不改变整个算法。
请帮助
谢谢
首先,启用所有编译器警告,并注意它们。您永远不希望在没有泛型类型的情况下使用Class
或ArrayList
;请参阅什么是原始类型以及为什么不应该';我们不用它吗?。
关于反思的最佳策略是避免使用反思。
- 反射很慢。它不太可能被即时编译器优化
- 反射是不安全的。编译器无法检查您是否犯了错误,例如拼写错误或方法签名错误
- 反思很难读懂。您总是需要能够在以后的某个日期理解和维护您的代码。在专业的世界里,其他人可能需要在你离开后很长一段时间内维护你的代码
不要使用反射,而是将每个构造函数视为一个函数。具体来说,一个函数,它将一系列PathElement作为输入,并生成一个特定类的新实例作为输出。在Java中,它被写成:
Function<PathElement[], S>
其中S
本身定义为<S extends Path>
,即从Path继承而来的已知类型。
您可以将这个已知类型和已知构造函数传递给您的方法:
public static <S extends Path> ArrayList<S> create_array(
int X1, int Y1, int X2, int Y2,
Function<PathElement[], S> shapeConstructor) {
var shapes = new ArrayList<S>();
shapes.add(shapeConstructor.apply(new PathElement[] {
new MoveTo(X1, Y1), new LineTo(X2, Y2)
}));
//...
return shapes;
}
现在,您拥有了类型安全的代码,并且能够在编译时捕获拼写或语法中的任何错误。
该方法的调用如下所示:
ArrayList<Shape1> shapeList1 = create_array(10, 10, 50, 50, Shape1::new);
ArrayList<Shape2> shapeList2 = create_array(20, 20, 80, 80, Shape2::new);
- 问题:使用
Shape1.class
而不是Class.forName("example3.Shape1")
。通过这种方式,通过标准的重命名重构来更改pkg和类名,可以在任何地方更改它。Class.forName()
是类解析所必需的,您无法预测,它是动态的,例如来自用户的输入。您只需要对Class<Shape1>
实例的静态Shape1.class
引用
此外,更改方法参数以接受通用Class<T> myClass
并返回相同类型T的列表,如下所示:
public static <T> ArrayList<T> create_array(int X1, int Y1, int X2, int Y2, Class<T> my_class) { ... }
这样,它对泛型是类型安全的,而不会对原始类型发出警告。
- 如果它需要一个数组参数,那么您应该将单个参数封装在一个数组中
参数数量错误意味着调用构造函数时需要一个实际有2个参数的数组参数。显然,Varargs不会自动通过反射封装在数组中。
参数类型不匹配:同样,它需要一个数组PathElement[]
,但您只提供PathElement
替代解决方案
由于各种原因,反思在大多数情况下是最后的手段。在这种情况下,使用反射真的有必要吗?你可以通过继承来解决这个问题。当然,它们都已经扩展了一个超类&有不同的字段和方法,但您可以创建一个几乎为空的中间abstract class BaseShape
,它将包含您的方法(除了Class
参数ofc(和使用的构造函数-它将被继承。
从OOP的角度来看,这绝对是一个比使用反射的静态方法更干净的解决方案。