java泛型-“扩展”关键字->不支持添加元素
Posted
技术标签:
【中文标题】java泛型-“扩展”关键字->不支持添加元素【英文标题】:java generics - "extends" keyword -> not supporting add elements 【发布时间】:2018-09-17 03:52:03 【问题描述】:由于 java 泛型“扩展”关键字,我遇到了一个奇怪的问题。我已经开发了一种通用方法来从一个尽可能通用的方法中获取元素。
当我使用<? extends X>
时,我无法向其中添加任何元素。
在我的例子中,我使用通用模板来限制用户提供的参数并提供返回类型 ac。
class Root
class Sub_1 extends Root
class Sub_2 extends Root
public static <T extends Root> List<T> getSubElements(Class<T> targetClass)
List<T> ls=new ArrayList<>();
if(targetClass.getSimpleName().equals("Sub_1"))
Sub_1 sub_1 = new Sub_1();
ls.add(sub_1);
else if(targetClass.getSimpleName().equals("Sub_2"))
Sub_2 sub_2=new Sub_2();
ls.add(sub_2);
return ls;
在上述情况下,当我向列表中添加元素时出现编译错误。
ls.add(sub_1);
ls.add(sub_2);
现在解决这个问题看起来很有挑战性。如果有人可以在这里提供一些提示,我会很高兴。
谢谢!!
【问题讨论】:
ls.add((T) sub_1);
但是为什么要测试类的简单名称而不是测试类是 Sub_1 还是 Sub_2? targetClass.equals(Sub_1.class)
.
你的实际 T 是多少?
T extends Root
,Sub_1 extends Root
,Sub_2 extends Root
。这并不意味着Sub_1
就是T
。但它是Root
。也许你想要List<Root> ls = ...
?
@JBNizet 是的,我也可以将其用作targetClass.equals(Sub_1.class)
。现在我主要关心的是添加元素。
@ZaksM 你可以抑制警告。当你知道自己在做什么时,你不应该担心它。
【参考方案1】:
我宁愿说答案取决于边界条件。我们可以编写代码来获得必要的功能,但它们可能/可能不遵守各种边界条件,如性能、安全性、完整性等
例如: 以下代码
**T newInstance = targetClass.newInstance();**
**list.add(newInstance);**
也可以实现必要的功能,但可能/可能不遵守性能边界,因为对反射方法的任何调用都会检查安全访问。
上面发布的带有供应商的解决方案也实现了类似的功能,但它可能不遵守安全边界,因为恶意供应商可以提供可能导致缓冲区溢出或 DOS 攻击的值。 (如果你真的想尝试一下,你可以创建一个静态供应商并用 self 初始化。你会得到一个堆栈溢出。java泛型书也提供了一个类似的安全相关示例,你可以参考)。我看到的问题在于当前场景中的 java open 子类型。
还有其他解决方案也可以实现所需的功能(对创建的子类型进行轻微修改),这些解决方案可能/可能不符合这些边界条件。我看到的问题是由于开放的子类型系统造成的,这使得开发人员编写符合所有边界条件的代码变得冗长且困难。
解决方案还完全取决于您的模型结构,无论您是创建数据的子类型还是基于行为,这可能不会反映在这个简短的示例中。
您可以参考 Philiph Wadler 撰写的 Java Generics Book,了解有关使用泛型的更多详细信息,我会推荐。它还提供了以安全方式编写代码的一个重要方面。有趣的是,您可以参考项目 amber(java 11 或更高版本)关于 Java 的数据类,它试图解决其中的一些问题。
【讨论】:
【参考方案2】:您可以通过让调用者传入所需类型的Supplier
而不是该类型的Class
,以类型安全的方式执行此操作,而无需使用反射。 getSubElement
代码然后简单地调用供应商来获取正确的实例:
static <T extends Root> List<T> getSubElements(Supplier<T> s)
List<T> ls = new ArrayList<>();
ls.add(s.get());
return ls;
调用者需要提供一种方法来创建其所需子类的实例。这可能是使用构造函数引用,也可能是对静态工厂方法的引用。如果类层次结构是这样的:
public class Root
public class Sub1 extends Root
public Sub1() ...
public class Sub2 extends Root
public static Sub2 instance() ...
然后调用者可以编写如下代码:
List<Sub1> list1 = getSubElements(Sub1::new);
List<Sub2> list2 = getSubElements(Sub2::instance);
【讨论】:
现在我为自己的反思回答感到尴尬。绝对是更好的(最好的?)解决方案。 +1 @AJNeufeld 不必尴尬。我们都在对 OP 的问题做出假设。就我而言,假设调用者知道他们想要什么类,他们也知道如何获取该类的实例(即供应商)。情况可能并非如此,在这种情况下,需要在类之间建立某种其他类型的关系以及如何获得它——主要是在T
和Supplier<T>
之间——需要建立。这可以保存在 Map 或函数中,这可能涉及反射。
@StuartMarks 非常感谢您提供的解决方案。我正是在寻找这种方法。再次感谢【参考方案3】:
如果你可以接受任何从 Root 派生的类,并且都有一个默认构造函数...
public static <T extends Root> List<T> getSubElements(Class<T> targetClass) throws ReflectiveOperationException
List<T> ls = new ArrayList<>();
T t = targetClass.getDeclaredConstructor().newInstance();
ls.add(t);
return ls;
... 或在本地尝试/捕获异常。
【讨论】:
好朋友。唯一的缺点是让自己受制于反射......也可以与带参数的构造函数一起使用,如下所示:***.com/questions/3574065/… @RannLifshitz 是的,但是参数也需要传递到 getSubElements() 调用中。 OP 只是为两个受支持的类调用默认构造函数。没有透露这是否适用于他们需要支持的每个班级。 是的。在高级构造函数支持方面,可能是解决方案的一个已知限制。【参考方案4】:总而言之,这是一个经过验证的工作实现,使用 an online java compiler 检查:
import java.util.*;
class Root
class Sub_1 extends Root
class Sub_2 extends Root
public class Bla
public static <T extends Root> T factoryMethod(Class<T> targetClass) throws Exception
if (Sub_1.class ==(targetClass))
return (T) (new Sub_1());
else if (Sub_2.class == (targetClass))
return (T) (new Sub_2());
else
throw new Exception("Unsupported class type");
public static List<Root> getSubElements() throws Exception
List<Root> ls=new ArrayList<>();
ls.add(factoryMethod(Sub_1.class));
ls.add(factoryMethod(Sub_2.class));
return ls;
public static void main(String[] args)
try
List<Root> root = getSubElements();
System.out.println(root);
catch (Exception e)
e.printStackTrace();
【讨论】:
您可以毫无问题地将Sub_1
放入List<Root>
。为什么这么复杂的选角?
@Henry:是的。这是一个泛型使用示例。
@Henry, @Rann Lifshitz : 正如我上面提到的,我正在为一个模块开发 API,我想为用户提供获取列表返回类型的灵活性 基于参数中提供的targetClass
类型。 由于这个原因,我不想指定固定的返回类型 List以上是关于java泛型-“扩展”关键字->不支持添加元素的主要内容,如果未能解决你的问题,请参考以下文章