【问题标题】:java generics - "extends" keyword -> not supporting add elementsjava泛型-“扩展”关键字->不支持添加元素
【发布时间】: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 你可以抑制警告。当你知道自己在做什么时,你不应该担心它。

标签: java generics collections


【解决方案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;
    }
    

    ... 或在本地尝试/捕获异常。

    【讨论】:

    • 好朋友。唯一的缺点是让自己受制于反射......也可以与带参数的构造函数一起使用,如下所示:stackoverflow.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,我正在寻找一种更通用的方法,而不需要显式转换元素。
    • 如果你说这是不可能实现的,那么我最终会接受答案并进一步进行显式转换:-(
    猜你喜欢
    • 2012-12-27
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    • 2012-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多