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 RootSub_1 extends RootSub_2 extends Root。这并不意味着Sub_1 就是T。但它是Root。也许你想要List&lt;Root&gt; 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 的问题做出假设。就我而言,假设调用者知道他们想要什么类,他们也知道如何获取该类的实例(即供应商)。情况可能并非如此,在这种情况下,需要在类之间建立某种其他类型的关系以及如何获得它——主要是在TSupplier&lt;T&gt; 之间——需要建立。这可以保存在 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&lt;Root&gt;。为什么这么复杂的选角? @Henry:是的。这是一个泛型使用示例。 @Henry, @Rann Lifshitz : 正如我上面提到的,我正在为一个模块开发 API,我想为用户提供获取列表返回类型的灵活性 基于参数中提供的targetClass 类型。 由于这个原因,我不想指定固定的返回类型 List,我正在寻找一种更通用的方法,而不需要显式转换元素。 如果你说这是不可能实现的,那么我最终会接受答案并进一步进行显式转换:-(

以上是关于java泛型-“扩展”关键字->不支持添加元素的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin学习笔记——接口抽象类泛型扩展集合操作符与Java互操作性单例

Java 为什么不支持泛型数组?

java 泛型

聊一聊Kotlin的泛型

Java泛型中的“超级”和“扩展”有啥区别[重复]

深入理解Spring注解机制:合并注解的合成