泛型:获取实例的名称

5

我有一个方法,它返回一个List<Property<?>>

Property是一个具有一个泛型参数的类型:

public class Property<T extends Comparable<T>> { ... }

我有一个混合类型属性的列表,我无法知道特定元素的类型参数。

我想做这样的事情:

List<Property<?>> list = getList();
for(Property<?> crt : list)
{
    PropertyWrapper<?> crtWrapper = new PropertyWrapper(crt.getGenericType());
    //                        I know this doesn't exist ----^
}

简单来说,我需要PropertyWrapper与当前的Property有相同的通用模板参数。有任何方法可以做到这一点吗?
我可以应用https://dev59.com/vnA75IYBdhLWcg3wKluM#3437930中提到的建议,但即使我这样做了,如何实例化适当的PropertyWrapper<XXX>,只有一个Class<T>实例呢?
如果需要,我可以修改Property<?>。 我也不介意使用反射(我认为需要使用反射)。
编辑:我忘记了一些东西。 实际上,我无法通过以下行实例化包装器:
PropertyWrapper<?> crtWrapper = new PropertyWrapper(crt.getGenericType());

因为我有专门的子类(PropertyWrapper_String)。

现在我看到两种可能性:

1:通过字符串实例化类:

String strGenericType = "";
Class<?> wrapperClass = Class.forName("PropertyWrapper_" + strGenericType);

2: 有没有办法在不创建子类的情况下专门化一个通用类?


非常感谢您提前的建议。


你写 new PropertyWrapper(crt.getGenericType()); 的时候,我们可以理解为你的意思是要写成 new PropertyWrapper<crt.getGenericType()>(); 吗? - John B
@JohnB 不,我认为他想将泛型参数的类类型传递给构造函数,就像他链接的另一个问题中那样。 - Thomas
但是如果他正在创建一个通用类型 PropertyWrapper<?>,那么构造函数使用 <> 运算符似乎是有意义的。 - John B
1
你可以使用 Map<Class<?>, Class<?>> 进行映射,其中键是被包装的类 String,值是包装器 PropertyWrapper_String - John B
有没有办法在不创建子类的情况下专门化一个泛型类? - 你不能以那种方式专门化类,只能是实例。但是,由于实例除了它们所属的类之外没有运行时反射信息,因此您必须将泛型类型类存储在实例中 - 就像我们所有答案已经指出的那样。 - Thomas
显示剩余6条评论
4个回答

7

尝试创建以下方法:

<T> PropertyWrapper<T> createWrapper(Property<T> property){
      return new PropertyWrapper<T>(property);
}

然后将其称为这样。
List<Property<?>> list = getList();
for(Property<?> crt : list)
{
    PropertyWrapper<?> crtWrapper = createWrapper(crt);
}

上述代码之所以有效,是因为泛型类型T从参数中推断出来,并且在整个方法中被锁定。与在循环中使用<?>不同,每个<?>实例都被推断为不同的类型。
编辑:
为了解决基于包装器中的类而有不同类类型的问题,请考虑一个Map,其中键是正在包装的类,而值是包装器。
Map<Class<?>, Class<?>> myMap;

然后你可以像这样做:
Class<?> wrappedType = property.getGenericType();
Class<?> wrapperClass = myMap.get(wrappedType);
PropertyWrapper<?> wrapper = (PropertyWrapper<?>) wrapperClass.newInstance();

如果需要传递一个参数,可能需要做类似这样的操作。

Constructor<?> constructor = wrapperClass.getDeclaredConstructor(property.getClass());
PropertyWrapper<?> wrapper = (PropertyWrapper<?>) constructor.newInstance(property);

谢谢,约翰。你的想法很简单并且可行,但是...我修改了我的问题,因为我认为这种方法行不通。有什么想法吗? - Atmocreations

1
如果您有一个Class<T>,您可以取得该类,通过在类对象上调用newInstance()创建一个实例并将其转换为T
您遇到的问题是获取crt的泛型参数。如果您有Property的具体子类,例如StringProperty extends Property<String>,则可以使用反射获取类型。但是,如果您只创建Property<T>的实例而没有具体的子类,并且不知道列表中的元素是在哪里创建的,则无法获取泛型类型(即使您知道元素在哪里创建,也可能无法获取泛型类型)。
因此,您可能唯一能够让属性包装器知道属性类型的方法是将类型参数(类)存储在属性本身中。然后,包装器可以查询属性的类型/类成员变量。 编辑:对为什么这很难或者说不可能进行了一些解释。
泛型的问题在于,由于类型擦除,当您使用new Property<SomeType>()创建属性时,泛型类型会丢失。这里没有运行时信息可用于检索SomeType
如果您有具体的子类(定义具体的泛型类型),则可以在运行时获得反射信息,了解每个类的泛型参数。然后,您可以获取每个属性的实际类并检索该类的反射数据。
如果您有定义这些类型并返回/持有对属性的引用的方法或字段,则也可以实现此目的。但是,我怀疑您是否拥有该信息,因为您似乎获得了某个列表,不知道该列表的元素是在何处以及如何创建的。
我进一步假设属性的类仅为Property,而不是子类。因此,唯一的方法是通过将类型类的引用作为构造函数参数传递来自己提供运行时信息。

我不知道为什么你写的是正确的。也许是因为你没有回答问题,只是说:“你想要的在技术上是不可能的”。我甚至可以用更激烈的措辞:new Property<String>() 在运行时与 new Property() 相同,因此你既不能获取类型,也不能在运行时实例化参数化类型。但我在这个话题上也曾经受过伤。 - Hauke Ingmar Schmidt
@他,感谢确认:)。其中一个问题是“有什么方法可以做到这一点吗?”,所以我的简短回答是:没有 - 这应该回答了所有的问题,其余只是更详细的解释 :)。 - Thomas

0

好的,我要回答自己了。

现在我把 Class<?> 的一个实例传递给 Property 类。

然后,我提取属性的基本名称,简单地去掉可能会在大多数情况下存在的 "java.lang.",因为我正在处理原始数据类型 - 或者它们的自动包装类。

此外,我只需通过名称实例化包装器的新实例,并将要包装的属性作为参数传递给构造函数即可。

对于感兴趣的人,以下是一些代码:

String template = "..."; // some package definition
for (Property<?> crt : bag)
{
    String className = template + crt.getClassName();
    Class<? extends PropertyWrapper<?>> wrapperClass = null;
    wrapperClass = (Class<? extends PropertyWrapper<?>>) Class.forName(className);
    Constructor<? extends PropertyWrapper<?>> constructor = wrapperClass.getConstructor(new Class<?>[] {Property.class});
    PropertyWrapper<?> wrapper = constructor.newInstance(crt);
    // Further operations using the wrapper
}

为了简单起见,我省略了错误处理部分。

嗯,我不确定我喜欢那个解决方案。那不是真正的重构安全(例如,当更改包时,您可能会忘记调整模板等),而且所有与字符串的摆弄似乎有些脆弱(而且似乎包装器具有与属性相同的简单名称,对吧?)。为什么不使用@JohnB在评论中建议的映射方法,将String.class映射到StringWrapper.class等?这将消除与字符串的摆弄,并提高重构安全性。 - Thomas
这将需要您为每种属性类型编写一个包装类,即一个字符串包装类、一个整数包装类等。这似乎与您只有PropertyWrapper的问题相矛盾。顺便说一句,我仍然不知道您需要这些包装器来做什么。 - Thomas

0

为什么不让Property知道如何创建自己的包装器,而不是从调用站点实例化包装器呢?类似这样:

public class Property<T extends Comparable<T>> {
    PropertyWrapper<T> newWrapper();
}

但这只是帮了一半。你真正的问题在于像你这样的循环:

List<Property<?>> list = getList();
for(Property<?> crt : list)
{
    PropertyWrapper<?> crtWrapper = crt.newWrapper();
}

PropertyWrapper和Property具有“相同”的类型并不是很有帮助,因为该类型只是未绑定的通配符。

我真的不想给出那种“你真正想做什么”的答案,但——你真正想做什么?一般来说,一旦你到达了一个未绑定的通配符,所有的希望就都破灭了(除非你愿意进行不安全、无法检查的强制转换或者通过反射检查费尽周折)。解决这个问题的一种方法是在将该属性放入List<Property<?>>之前,在Property<T>中尽可能多地放置动作。我的newWrapper()就是一个例子:它将创建一个PropertyWrapper<T>的操作放入了Property<T>本身。如果您想要注册当属性改变时的回调,这也是您可能能够在每个实例化非通配符Property<Whatever>的地方完成的事情。例如:

Property<UserLogin> property = new Property<UserLogin>();
SomeListener<UserLogin> listener = whatever();
property.addListener(listener);
wildcardedPropertiesList.add(property);

这个特定的例子可能不会有所帮助,但希望它能给你一些可应用的想法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接