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

时间:2018-07-31 15:42:19

标签: java generics reflection interface raw-types

重要提示:

我当前拥有的代码正在按我的期望运行。它完成了我想要的操作。我的问题是关于我使之发挥作用的方式是否错误。我之所以这样问,是因为我已经看到大量有关原始类型以及从不应该使用它们的堆栈溢出结果。

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

当前,我正在动态创建通用接口的具体子类,该类在构造类时会接受参数。当我创建此类的实例并使用其返回的对象调用各种方法时,我将使用原始类型,因为它可用于我要尝试执行的操作。这是我的代码中使用原始类型的示例。该代码是从上到下的顺序,即在代码块之间没有代码。

加载属性文件

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

这两个类及其实例是实现DataParsers的两个单独的DataParserImplementation。它们采用Strings给出的FileParser数组并创建对象/将数据操纵到所需的内容中。它将收集此数据。 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);

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

CrossReferencerImplementation

通过在反射实例上调用方法来获取Map结果。然后将打印此地图的内容。当前似乎也获得了地图参数,因为在调用Class<? extends CrossReferenceImplementation> crossreferencer = Class.forName(prop.getProperty("CrossReferenceImplementation")).asSubclass(CrossReferenceImplementation.class); CrossReferenceImplementation crossReferencerInstance = (CrossReferenceImplementation) crossreferencer.getDeclaredConstructors()[0].newInstance(); 时,地图内的对象正确使用了它们的toString方法。 这使我相信它会按预期工作。

reflectiveFinalMap.get(key).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 CrossReferenceImplementation中的crossreferencer的参数化类型
Class

然后我像这样创建Class arrayparametertype = (Class)((ParameterizedType)crossreferencer.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 的实例时尝试传递那个arrayparameter

crossreferencer

这没有用,因为可变参数类型显然不是问题。 我试图手动指定具体的反射类的特定参数(无论如何我都不希望这样做,因为它破坏了整个反射点,可以通过使用实现适当接口的anythng将类彼此分离)。这导致出现此警告,并且代码未实际运行应该执行的方法:

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

警告: “ Dataset1具有原始类型,因此将删除解析结果”。 请注意,这里的//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()); 是将数据保存为该对象字段的对象。该对象被操纵并放入各种集合中。 SalesRep s的许多方法也可以通过反射来访问它

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

DataParserImplementation

指定地图结果的实际参数化类型时,错误消息为: “ {//where the parameterized type was specified Map reflectiveFinalMap = (Map<String,SalesRep>) crossReferencer.CrossReference(Dataset1.Parse(), Dataset2.Parse()); 具有原始类型,因此crossReferencer的结果将被删除”。

运行代码确实为我确认了CrossReference方法的结果已被清除,而其他所有情况都很好。

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

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

目标。

要了解我当前使用的原始类型,所以我知道我做对了。 “正确”是指与我下面定义的“错误”方式相反的含义。我寻求“了解”的一个例子是:

要了解为什么puesdo遵循以下代码:

.CrossReference

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

CallingAMethod

在方法中我有类似的东西:

[1,2,3]

然后返回列表,指定参数时返回的返回值为[1,2,3],使用原始类型时返回的返回值为[2,3,4]。我之所以这样问,是因为我听说过有关使用原始类型的不好的事情。

此外,我想确保我对原始类型的使用不是很错误,并且可以正常工作,因为它可以正常工作。也许我刚刚对反射和泛型有所了解,并找到了原始类型的有效用法,或者我做的太可怕了,值得逮捕。那就是我打算找出的。为了澄清,我的意思是错误的:

不良的设计(应该使用其他方式来反射性地调用我的方法,并且还应该使用使用通用接口的反射性类)

低效的设计(明智的时间复杂度,明智的代码行或可维护性)

有更好的方法,您甚至不应该一开始就这样做

如果在阅读此代码时出现任何上述原因或我错过的东西,请告诉我。否则,请解释一下为什么我对原始类型的使用有效并且不违反以下问题:[link] What is a raw type and why shouldn't we use it?

1 个答案:

答案 0 :(得分:1)

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

然后,在使用该字段之前,您已具有所有必需的验证,并且该字段将返回有效类型。因此,您可以获得一些有效类型的值并将其添加到普通的通用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仍然有点“空”-无法建立创建自己的泛型类型实例或检查它们是否可分配的方法。 (像gson这样的库就是这样做的,因此它们可以反序列化地图和其他泛型类型,它们拥有自己的Java泛型类型表示接口的实现,例如ParameterizedType并实现了自己的方法来检查给定类型是否可分配)

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

我希望这能回答您的问题。