【问题标题】:Using Raw types to dynamically apply type parameters for a reflected subclass使用原始类型为反射子类动态应用类型参数
【发布时间】:2019-01-08 01:55:05
【问题描述】:

重要提示:

我目前拥有的代码符合我的期望。它做我想让它做的事情。我的问题是关于我让它工作的方式是否错误。我问这个的原因是因为我已经看到了大量关于原始类型的堆栈溢出结果以及它们基本上应该如何被使用。

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

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

加载属性文件

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();

这两个类及其实例是实现DataParserImplementation 的两个独立的DataParsers。这些接收FileParser 提供的Strings 数组,并创建对象/将数据处理为所需的任何内容。它列出了这些数据的集合。 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);

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

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

通过调用反射实例上的方法来获取 Map 结果。然后打印出这张地图的内容。目前似乎地图参数也得到了,因为地图内的对象在调用reflectiveFinalMap.get(key).toString() 时正确使用了它们的 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

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

我试图不使用原始类型的事情。

我已经尝试在反射的crossreferencer Class 中实际获取CrossReferenceImplementation 的参数化类型,我现在通过调用

Class arrayparametertype = (Class)((ParameterizedType)crossreferencer.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

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

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

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

//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 具有原始类型,因此 Parse 的结果被擦除”。 请注意,此处的SalesRep 是将数据作为该对象的字段保存在其中的对象。该对象被操纵并放入各种集合中。在DataParserImplementations的许多方法中也可以通过反射访问它

在指定 Map 的参数类型时出现了类似的错误消息和问题(我不希望这样做,因为它使反射毫无意义,我希望 map 返回结果是通用的并由实现类指定)。

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

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

运行代码确实让我确认.CrossReference 方法的结果已被删除,而其他一切运行正常。

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

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

目标。

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

要理解为什么 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 包含在接口或集合类型名称中。这与以某种不使用原始类型但不破坏反射点的方式相反,以解耦这些类之间的交互。在这种情况下,使用硬代码指定参数会“破坏反射”。另外,我想了解为什么在上面的 pusdocode 中为这些 RAW 类型指定参数(即使我知道那不是我要做的)会导致问题中上面列出的错误,即为什么是结果向方法返回的RAWCollectionType 提供实际参数时,CallingAMethod 被擦除?根本问题是,当我在声明时向RAWCollectionType 提供类型参数时,它拒绝被CallingAMethod 返回的内容更新,我不明白为什么。它接受返回值,但如果 CallingAMethod 方法的主体将返回值作为参数传入,在方法内部更新然后返回,则我收到的返回没有更新。 CallingAMethod 在这个例子中就像我有一个类似的列表:

[1,2,3]

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

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

然后我返回了列表,指定参数时得到的返回值为 [1,2,3],而使用原始类型时,返回值为 [2,3,4],如我所愿。我问这个是因为我听说过关于使用原始类型的坏事。

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

糟糕的设计(应该使用不同的方式来反射性地调用我的方法,并使用使用泛型接口的反射类)

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

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

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

【问题讨论】:

标签: java generics reflection interface raw-types


【解决方案1】:

Java 具有类型擦除功能,因此您在运行时的Map&lt;A,B&gt; 只是一个Map,同样CrossReferenceImplementation&lt;Map&lt;String, SalesRep&gt;,Map&lt;String, SalesRep&gt;,Map&lt;String, SalesRep&gt;&gt; 只是一个CrossReferenceImplementation
这也意味着您可以将任何映射转换为Map,然后将任何您想要的对象放入其中,这样您就可以拥有实际上存储Map&lt;Cookie, Fish&gt; 类型对象的Map&lt;String, Long&gt;,这就是您需要小心的原因带有原始类型和反射。

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

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

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

然后,在您需要使用该字段之前,您已经完成了所有需要的验证,并且它已经返回了有效的类型。因此,您可以获得一些有效类型的值并将其添加到普通的通用 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&lt;String, Long&gt;,因为这可能以后很难调试。 (除非有人将此访问器用作原始类型,因为 .get 未经过验证 - 但您可以通过将 definingClass 传递给构造函数并在 get 方法中检查对象实例来添加它)

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

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

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

【讨论】:

    猜你喜欢
    • 2017-03-29
    • 1970-01-01
    • 2013-06-01
    • 2017-09-26
    • 2013-02-02
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多