【问题标题】:Using reflection to use a specific class constructor and create a new object使用反射来使用特定的类构造函数并创建一个新对象
【发布时间】:2019-11-14 22:41:13
【问题描述】:

我有两节课。

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

代码有两个问题。

  1. 类定义不合适,因为更改类名或包名时不起作用,必须手动更改。

  2. 实际上,它根本不起作用,因为当您将对象添加到数组时它会崩溃。

奇怪的是,在上面的表格中它会显示一个错误。

参数数量错误

但如果我只使用一个参数,

shapes.add(my_class.getConstructor(PathElement[].class).newInstance(new MoveTo(X1, Y1)));

它会显示一个错误。

参数类型不匹配

这是解决我的问题的唯一方法。否则我将不得不改变整个算法。

请帮忙

谢谢

【问题讨论】:

  • 除非您使用Class.forName("SomeType") 而不是SomeType.class,否则重命名问题应该根本不是问题。后一种情况应通过 IDE 的重命名功能来处理。此外,我建议改用Function&lt;PathElement[], ? extends Shape&gt;:这更灵活,允许编译器检查适当方法的存在,并且不需要允许反射访问包含的类。此外,它也适用于创建路径的非构造方法:create_array(X1, Y1, X2, Y2, Shape1::new);

标签: java class javafx reflection


【解决方案1】:

首先,启用所有编译器警告,并注意它们。你永远不想在没有泛型类型的情况下使用ClassArrayList;见What is a raw type and why shouldn't we use it?

关于反射的最佳策略是避免使用反射。

  • 反射很慢。即时编译器不太可能对其进行优化。
  • 反射不安全。编译器无法检查您是否犯了错误,例如拼写错误或方法签名错误。
  • 反射很难阅读。您将始终需要能够在以后理解和维护您的代码。在专业领域,其他人需要维护您的代码,可能在您离开很久之后。

不使用反射,而是将每个构造函数视为function。具体来说,一个函数,它将一系列 PathElements 作为输入,并产生一个特定类的新实例作为输出。在Java中,写成:

Function<PathElement[], S>

其中S 本身定义为&lt;S extends Path&gt;,即从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);

【讨论】:

  • 抱歉回复晚了。是的,它有效。谢谢。
【解决方案2】:
  1. 问题:使用Shape1.class 而不是Class.forName("example3.Shape1")。这样,使用标准重命名重构更改 pkg 和类名会在任何地方更改它。 Class.forName() 是您无法预测的类解析所必需的,它是动态的,例如来自用户的输入。您只需要对 Class&lt;Shape1&gt; 实例的静态 Shape1.class 引用。

另外,将方法参数更改为接受泛型 Class&lt;T&gt; myClass 并返回一个相同类型 T 的 List,如下所示:

public static <T> ArrayList<T> create_array(int X1, int Y1, int X2, int Y2, Class<T> my_class) { ... }

这样,泛型是类型安全的,没有关于原始类型的警告。

  1. 也许如果它需要一个数组参数,您应该将单个参数包装在一个数组中。

参数数量错误意味着您调用构造函数时需要一个实际包含 2 个参数的数组参数。很明显,可变参数不会通过反射自动包装在数组中。

参数类型不匹配:同样,它需要一个数组PathElement[],但您只提供PathElement


替代解决方案

出于各种原因,大多数时候反思是最后的手段。在这种情况下真的有必要使用反射吗?你可以通过继承来解决这个问题。当然,它们都已经扩展了一个超类并具有不同的字段和方法,但是您可以创建一个几乎为空的中间 abstract class BaseShape,其中将包含您的方法(Class 参数 ofc 除外)和使用的构造函数 - 它会被继承。

从 OOP 的角度来看,这绝对是比使用反射的静态方法更清洁的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-10
    • 2012-05-15
    • 2012-08-04
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多