在Java Generics中键入泛型类型但不是它自己的类型

时间:2015-12-29 13:32:39

标签: java generics java-8

我需要输入方法签名,因此它接受2个不同特定具体子类型的同等类型参数。

是否可以使用泛型对此进行编码?你会如何解决它? (这个案子绝对是一个例子)

public <T extends List<?>> T<String> sum(T<Integer> sublistOfInts, T<Boolean> sublistOfBooleans){
     /*fusion both lists*/ 
     return sublistOfStrings;
}

编辑:最后,我正在寻找的是编译器通过的方式:

ArrayList<String> myList = sum(new ArrayList<Integer>(), new ArrayList<Boolean>());

但不是:

ArrayList<String> myList = sum(new ArrayList<Double>(), new ArrayList<Boolean>());

,也不

ArrayList<String> myList = sum(new LinkedList<Integer>(), new ArrayList<Boolean>());

(...)
编辑2:我找到了一个更好的例子。想象一个界面Tuple,有子类Duple,Triple&gt; ...,这样的东西会非常好看

<T extends Tuple<?>>  T<String> reset( T<String> input, T<Boolean> listToNull){          
                  T copy = input.copy();
                  for (int i=0; i<input.size();i++){
                       if (listToNull.get(i)){
                             copy.set(i,null);
                       }
                  }  
 }

3 个答案:

答案 0 :(得分:4)

我建议你做什么

首先,摆脱方法参数泛型。当您想要返回ArrayList<Integer>时,没有理由强制呼叫者提供ArrayList<Boolean>ArrayList<String>。只需接受任何List<Integer>List<Boolean>,然后将其留给您的方法,将其转换为相应的返回List

由于您知道要返回某种List String,因此您可以将参数编写为<T extends List<String>>,将返回类型简称为T

这给我们留下了困难的部分:让你的方法实例化一个未知类型的对象。那很难。你不能只做new T();。您需要调用代表您生成T的内容。幸运的是,Java 8为Supplier<T>提供了功能接口。您只需调用get()方法即可获得ArrayList<String>或其他任何您想要的内容。令人痛苦的部分是你的调用者需要提供他们自己的Supplier。但我认为这与Java 8一样好。

以下是代码:

public <T extends List<String>> T sum(
        List<Integer> sublistOfInts,
        List<Boolean> sublistOfBooleans,
        Supplier<T> listMaker) {
    T sublistOfStrings = listMaker.get();
    /*fusion of both lists*/ 

    return sublistOfStrings;
}

至少这个编译:

ArrayList<String> myNewList = thing.<ArrayList<String>>sum(intList, boolList, ArrayList::new);

这不是:

ArrayList<String> myNewList = thing.<ArrayList<String>>sum(intList, boolList, LinkedListList::new);

您甚至可以在调用中省略type参数。这编译:

ArrayList<String> myNewList = thing.sum(intList, boolList, ArrayList::new);

这不是:

ArrayList<String> myNewList = thing.sum(intList, boolList, LinkedListList::new);

为什么你不能只做你要求的

简而言之,这是因为类型参数本身不能参数化。那是因为我们不知道他们自己会采用多少类型的参数,也不知道可能对它们施加的限制。

选择相对模糊的班级RoleList。它扩展了ArrayList<Object>,因此符合List<?>。但它根本不需要类型论证。因此,如果某人使用sum()调用了您的RoleList方法,则在您的示例中需要:

RoleList<Integer> intList = // something
RoleList<Boolean> boolList = // something
RoleList<String> myNewList = thing.sum(intList, boolList);

这显然无法工作,因为它需要一个非参数类型来获取类型参数。如果你像这样取下类型参数:

RoleList intList = // something
RoleList boolList = // something
RoleList myNewList = thing.sum(intList, boolList);

然后,您的方法需要能够接受两个List<Object>参数并返回值List<Object>。这违反了你的基本前提,你可以控制这些事情。

实际上,此处不应允许RoleList,因为您无法保证一个实例仅包含Integer s,另一个实例仅包含Boolean s,以及仅次于String。在这里允许RoleList的编译器必然会有比我们现在更弱的类型检查。

所以最重要的是,你不能做你所要求的,因为Java不是那样构建的。

为什么没关系

您仍然可以使用上面建议的方法在sum()方法中获得完整的类型安全性。您确保传入的List分别仅包含IntegerBoolean值。您确保调用者可以依赖于仅包含List值的String特定子类型的返回。所有有所作为的保证都存在。

答案 1 :(得分:2)

关于上述事情,有两件事让我感到震惊。你如何实例化sublistOfStrings,你期望使用普通旧继承获得哪些优势?

有几种实例化T<String>的方法。您可以让工厂检查您的参数类,并基于此实例化它。或者你可以这样做:

(List<String>)sublistOfInts.getClass().newInstance()

但你不能去new T<String>()。因此,无论如何,你都要根据你的一个论点的类型来确定你的返回类型的实现(除非我没有想到这样的方式)。

通过指定两个参数的类型为&#39; T&#39;并不意味着他们完全属于同一种具体类型&#39; T&#39;无论是。例如

sum((int)1, (long)2L); // valid
sum((int)2, (double)2.0D); // valid ... etc

public <T extends Number> T sum(T a, T b) {
    return a;
}

因此,您并未强制执行sublistOfIntssublistOfBooleans类型为ArrayList,因此您可以返回ArrayList。您仍然需要编写代码来检查您希望根据参数返回的List<?>类型。

我认为你最好不要使用泛型,并使用类似的东西:

public List<String> sum(List<Integer> sublistOfInts, List<Boolean> sublistOfBooleans) {
    // Determine what subclass of list you want to instantiate based on `sublistOfInts` and `sublistOfBools`
    // Call factory method or newInstance to instantiate it.
    // Sum, and return.
}

您仍然可以使用List<?>的子类型调用它。即使Java确实允许你这样做,我也不会相信你可以从泛型中获得任何优势(这是不可能的,因为它不能像T那样参数化$cmd = self::$S3Client->getCommand('GetObject', array('Bucket' => USER_CONTENT_BUCKET_NAME,'Key' => $key)); $request = self::$S3Client->createPresignedRequest($cmd, '+20 minutes'); 这一点)。

答案 2 :(得分:0)

我知道你拥有的只是一个例子,但是如果你只想返回一个包含一组其他列表中所有内容的String值的列表,你可以指定一个采用无限列表的varargs的方法

public List<String> sum(List<?>... lists) {
    List<String> sublistOfStrings = new ArrayList<String>();
    for(List<?> list : lists) {
        for(Object obj : list) {
            sublistOfStrings.add(obj.toString());
        }
    }

    return sublistOfStrings;
}