使用原始类型为反映的子类动态应用类型参数

Using Raw types to dynamically apply type parameters for a reflected subclass

重要提示:

我目前拥有的代码正在按照我的期望工作。它做我想让它做的事。我的问题是关于我使它工作的方式是否错误。我问这个的原因是因为我已经看到很多关于原始类型的堆栈溢出结果以及它们基本上应该永远不会被使用的方式。

我在做什么以及为什么我使用原始类型

目前,我正在动态创建一个通用接口的具体子class,该接口在构造class 时接收参数。当我创建此 class 的实例并使用其 returned object 调用各种方法时,我使用原始类型,因为它适用于我正在尝试做的事情。这是我的功能代码中使用原始类型的示例。此代码是自上而下的顺序,即代码块之间没有代码。

正在加载属性文件

Properties prop = new Properties();
    try {
    prop.load(ObjectFactory.class.getResourceAsStream("config.properties"));

这是实现 FileParserImplementation 并接收数据并将其放入数组的文件解析器。此代码获取 Class 类型,然后动态生成该类型的实例。

Class<? extends FileParserImplementation> parser = null;
parser = Class.forName(prop.getProperty("FileParserImplementation")).asSubclass(FileParserImplementation.class);
FileParserImplementation ParserInstance = (FileParserImplementation) parser.getDeclaredConstructors()[0].newInstance();

这两个 classes 及其实例是两个单独的 DataParsers 实现 DataParserImplementation。这些接受 FileParser 给出的 Strings 数组,并根据需要创建 objects/manipulates 数据。它输出了这些数据的 Collection。 Fileparser 依赖是通过构造函数注入传入的。这可以在 运行 时通过属性文件进行配置。

Class<? extends DataParserImplementation> dataset1 = Class.forName(prop.getProperty("DataParserImplementation_1")).asSubclass(DataParserImplementation.class);
Class<? extends DataParserImplementation> dataset2 = Class.forName(prop.getProperty("DataParserImplementation_2")).asSubclass(DataParserImplementation.class);
DataParserImplementation Dataset1Instance = (DataParserImplementation) dataset1.getDeclaredConstructors()[0].newInstance(ParserInstance);
DataParserImplementation Dataset2Instance = (DataParserImplementation) dataset2.getDeclaredConstructors()[0].newInstance(ParserInstance);

这是实现CrossReferencerImplementationCrossreferencerclass。它接收两个数据集并以实际具体反映 class 所需的任何方式交叉引用它们。这也可以在 运行 时配置。它在这个 main 中输出一个 Map。 该地图用作数据的最终 collection(我稍后可能会更改)。

Class<? extends CrossReferenceImplementation> crossreferencer = Class.forName(prop.getProperty("CrossReferenceImplementation")).asSubclass(CrossReferenceImplementation.class);
CrossReferenceImplementation crossReferencerInstance = 
(CrossReferenceImplementation) crossreferencer.getDeclaredConstructors()[0].newInstance();

通过在我们的反射实例上调用方法获取 Map 结果。然后打印出这张地图的内容。目前似乎也获得了地图参数,因为在调用 reflectiveFinalMap.get(key).toString() 时,地图内部的 Objects 正确使用了它们的 toString 方法。 这让我相信它会按照我的意图工作。

Map reflectiveFinalMap = (Map) 
crossReferencerInstance.CrossReference(Dataset1Instance.Parse(), Dataset2Instance.Parse());
for (Object key:reflectiveFinalMap.keySet()) {
            System.out.println(key + " { " + 
reflectiveFinalMap.get(key).toString() + " }");
        }
    return reflectiveFinalMap;
}
//catch block goes here

请注意,每次我反射性地创建一个实现我的接口之一的 class 实例时,我都使用该接口作为原始类型。我希望反射在创建具体子 class 时看到此原始类型的参数化类型,因为那是实际指定参数类型的地方。关键是让任何实现这些接口的 class 都通用到它们可以接受几乎任何东西和 return 几乎任何东西的程度。

我尝试不使用原始类型的东西。

我试图通过调用

在反射的 crossreferencer Class 中实际获取 CrossReferenceImplementation 的参数化类型
Class arrayparametertype = (Class)((ParameterizedType)crossreferencer.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

然后我在创建 crossreferencer 的实例时尝试传入 arrayparameter,如下所示:

CrossReferenceImplementation crossReferencer = (CrossReferenceImplementation<<arrayparametertype>>) crossreferencer.getDeclaredConstructors()[0].newInstance();

那是行不通的,因为可变参数类型显然不是问题。 我试图手动指定具体反射的具体参数 class(我不想要这个,因为它打破了这里的整个反射点,通过能够将 Classes 彼此解耦使用任何实现适当接口的东西)。这导致出现此警告并且代码实际上 运行 它应该使用的方法:

//how the parameters were specified. Messy and breaks the reflection.
CrossReferenceImplementation<Map<String, SalesRep>,Map<String, SalesRep>,Map<String, SalesRep>> crossReferencer = (CrossReferenceImplementation) crossreferencer.getDeclaredConstructors()[0].newInstance();

//where the warning occured
Map reflectiveFinalMap = (Map) crossReferencer.CrossReference(Dataset1.Parse(), Dataset2.Parse());

警告: "Dataset1 has raw type so result of Parse is erased"。 请注意,此处的 SalesRep 是 object,其中数据作为 object 的字段保存。这个 object 被操纵并放入各种 collection 中。它也可以通过 DataParserImplementations

的许多方法中的反射来访问

指定地图的参数类型时出现了类似的错误消息和问题(我再次不想这样做,因为它使反射毫无意义我希望地图 return 结果是通用的并被指定通过实施 class)。

//where the parameterized type was specified
Map reflectiveFinalMap = (Map<String,SalesRep>) crossReferencer.CrossReference(Dataset1.Parse(), Dataset2.Parse());

指定地图结果的实际参数化类型时,错误消息是: “crossReferencer 具有原始类型,因此 CrossReference 的结果被擦除”。

运行 代码确实为我确认 .CrossReference 方法的结果被删除,而其他一切 运行 都很好。

在这里提问之前我尝试了哪些互联网搜索

所以我对这两个操作都使用了原始类型,正如在主代码中所见,一切正常。但是见多了"Don't use raw types"。这就是为什么我要问:这是对原始类型的适当使用吗?我应该以不破坏反射的不同方式来做吗?它打破了反射,因为手动指定类型参数不仅使我的代码不是 运行,还意味着只能使用具体的 class。我反映了这样我就可以使用任何实现通用接口的东西。我不想只能使用特定的具体实例。我试过在堆栈溢出中搜索什么在我的标题和其他类似的东西中。我认为这可能与类型擦除有关,但老实说我不确定。没有其他东西能真正解决这个问题,因为没有人同时谈论泛型、参数化类型和反射(我的问题的症结所在)。有人告诉我泛型和反射不能很好地协同工作,但这段代码无论如何都能工作,并且按照我想要的方式工作。它运作良好。我只是想确保我没有做错什么。

目标。

了解我当前对原始类型的使用情况,以便我知道我的做法是正确的。 'Right' 的意思与我在下面定义的 'Wrong' 方式相反。 'Understanding' 我寻求的一个例子是:

理解为什么 puesdo 代码如下:

ConcreteClass forname(myPropertiesFileObject.get(ConcreteClassname)) as subClass of (MyGenericInterface);
MyRAWGenericInterfaceType ConcreteClassInstance = (MyRAWGenericInterfaceType) ConcreteClass.newInstance( Insert generic Type constructor arguments here);
RAWCollectionType someCollection = RAWCollectionType concreteClassInstance.CallingAMethod(Insert generic Type method arguments here);

使用原始类型,其中 RAW 包含在接口或 collection 类型名称中。这与不使用原始类型但不破坏反射点的某种方式相反,以解耦这些 classes 之间的交互。在这种情况下,使用硬代码指定参数将 'break the reflection'。此外,我想了解为什么在上面的 pusedocode 中为这些 RAW 类型指定参数(即使我知道那不是我要做的)会导致上面列出的错误,即为什么CallingAMethod 的结果在向方法 returns 的 RAWCollectionType 提供实际参数时被删除?根本问题是,当我在声明它时向 RAWCollectionType 提供类型参数时,它拒绝被 CallingAMethod returns 更新,我不明白为什么。它采用 return 值,但如果方法 CallingAMethod 的 body 具有作为参数传入的 returned 值,则在方法内部更新,然后 returned,我收到的 return 没有更新。 CallingAMethod 在这个例子中,如果我有一个列表,就像:

[1,2,3]

在方法内部我有类似的东西:

foreach(thing in list){
    thing += 1
}

然后我 return 编辑了列表,在指定参数时我得到的 return 是 [1,2,3] 而在使用原始类型时它是 [2, 3,4] 如我所愿。我问这个是因为我听说过有关使用原始类型的坏消息。

此外,我想确保我对原始类型的使用没有严重错误,并且它可以工作,因为它应该工作。也许我刚刚精通整个反射和泛型,并找到了原始类型的有效用途,或者我可能正在做一些可怕的事情,以至于 运行 我被捕了。这就是我打算找出的。澄清一下,我的意思是错误的:

糟糕的设计(应该使用不同的方式来反射调用我的方法,并且还使用反射 class 使用通用接口的 es)

低效设计(时间复杂度、代码行或可维护性)

有更好的方法,你甚至不应该首先这样做

如果您在阅读此代码时突然想到这些原因中的任何一个或我遗漏的内容,请告诉我。否则请解释为什么我使用原始类型是有效的并且不违反这个问题:[link]What is a raw type and why shouldn't we use it?

Java 有类型擦除,所以你的 Map<A,B> 在运行时只是一个 Map,同样 CrossReferenceImplementation<Map<String, SalesRep>,Map<String, SalesRep>,Map<String, SalesRep>> 只是一个 CrossReferenceImplementation.
这也意味着您可以将任何映射转换为 Map 并在其中放入您想要的任何对象,因此您可以拥有 Map<String, Long> 实际上存储 Map<Cookie, Fish> 类型的对象,这是为什么你需要小心处理原始类型和反射。

你不能真正正常地使用反射和泛型——那时​​你总会有一些未经检查的代码,但你可以将它限制在最低限度,并使它无论如何都是类型安全的。

就像你可以创建自己的方法来获取字段:(这是一些伪代码,我将跳过所有可能的异常等)

public class FieldAccessor<O, T> { 
    final Field field; // + private constructor
    public T get(O object) { return (T) field.get(object); } // unsafe, bu we validated this before constructing this accessor
    public static <O, T> FieldAccessor<O, T> create(Class<? super O> definingClass, Class<? super T> fieldClass, String fieldName) {
        Field field = definingClass.getDeclaredField(fieldName);
        if (field.getType() != fieldClass) {
            throw some exception;
        }
        return new FieldAccessor<>(field);
    }

然后在需要使用该字段之前,您已经完成了所有需要的验证,并且它已经 return 有效类型。因此,您可以获得一些有效类型的值并将其添加到普通通用 Map 实例中。

FieldAccessor<X, A> keyAccessor = FieldAccessor.create(X.class, A.class, "someProperty");
FieldAccessor<Y, B> valueAccessor = FieldAccessor.create(Y.class, B.class, "someOtherProperty");
Map<A, B> myMap = new HashMap<>();
mapMap.put(keyAccessor.get(myXValue), valueAccessor.get(myYValue));

这样你就有了类型安全的代码,它仍然可以在反射上工作——如果你提供无效的类型,它可能仍然会在运行时失败,但至少你总是知道它会在哪里失败——就像这里的 FieldAccessor已经在运行时检查了所有类型,以确保您不会做一些愚蠢的事情,比如将 Integer 添加到 Map<String, Long>,因为这可能以后很难调试。 (除非有人将此访问器用作原始类型,因为 .get 未经过验证 - 但您可以通过将 definingClass 传递给构造函数并在 get 方法中检查对象实例来添加它)

你可以对使用通用类型的方法和字段做类似的事情(比如 Map<X, Y> 类型的字段,这个 FieldAccessor 只允许你检查它是否是某种 Map) - 但它会更难,因为 API 泛型仍然有点 "empty" - 没有构建自己的泛型实例或检查它们是否可分配的方法。 (像 gson 这样的库这样做是为了它们可以反序列化映射和其他泛型类型,它们有自己的 java 泛型类型表示接口的实现,比如 ParameterizedType 并实现了自己的方法来检查给定类型是否可分配)

就在你使用反射时,你需要始终记住并理解你是负责验证类型的人,因为编译器在这里无法帮助你,所以只要你不安全和原始类型的代码就可以了具有验证此代码是否永远不会做真正不安全的事情的逻辑(例如将错误的类型传递给泛型方法,例如 IntegerLong 的映射)。
只是不要在一些普通代码中间扔原始类型和反射,给它添加一些抽象,这样维护这样的代码和项目会更容易。

我希望这能在一定程度上回答您的问题。