Java 泛型 - 参数中的相等约束

Java Generics - Equality constraint in parameters

设想以下 class 层次结构:

Cake
\- ChocolateCake
  \- StuffedChocolateCake
\- VanillaCake

我想写一个方法来组合 ChocolateCake 和 VanillaCake 的列表,例如:

    public static <T extends Cake> List<T> union(List<T> listA, List<T> listB) {
        List<T> returnObject = new ArrayList<>();
        returnObject.addAll(listA);
        returnObject.addAll(listB);
        return returnObject;
    }

这会抛出一个编译错误,我的理解是:

inferred type does not conform to equality constraint(s)

我尝试通过以下方式删除等式约束:

    public static List<? extends Cake> union(List<? extends Cake> listA, List<? extends Cake> listB) {
        List<? extends Cake> returnObject = new ArrayList<>();
        returnObject.addAll(listA);
        returnObject.addAll(listB);
        return returnObject;
    }

我想这是 Java 泛型的一个相当基本的场景,正确的方法是什么?

提前感谢您的宝贵时间!

将 return 类型更改为 List<Cake>,您的第二个版本应该可以正常工作:

public static List<Cake> union(List<? extends Cake> listA, List<? extends Cake> listB) {
    List<Cake> returnObject = new ArrayList<>();
    returnObject.addAll(listA);
    returnObject.addAll(listB);
    return returnObject;
}

如果您想要 return Cake 的某些常见子类型的选项,您可以将通配符与通用参数结合使用:

public static <T extends Cake> List<T> union(List<? extends T> listA, List<? extends T> listB) {
    List<T> returnObject = new ArrayList<>();
    returnObject.addAll(listA);
    returnObject.addAll(listB);
    return returnObject;
}

您现有的代码只能合并两个完全相同类型的列表。但是,您可以将类型签名(其余代码保持不变)更改为

public static <T extends Cake> List<T> union(
    List<? extends T> listA, List<? extends T> listB
)

在这种情况下,您是说这两个列表可以是任何类型,只要该类型是 T 的子类型,TCake 的子类型。

package cake;

import java.util.ArrayList;
import java.util.List;

public class Main {


    public static List<Cake> unionB(List<Cake> listA, List<Cake> listB) {
        List<Cake> returnObject = new ArrayList<>();
        returnObject.addAll(listA);
        returnObject.addAll(listB);
        return returnObject;
    }

    public static List<Cake> unionC(List<ChocolateCake> listA, List<VanillaCake> listB) {
        List<Cake> returnObject = new ArrayList<>();
        returnObject.addAll(listA);
        returnObject.addAll(listB);
        return returnObject;
    }

    public static List<Cake> unionD(List<? extends Cake> listA, List<? extends Cake> listB) {
        List<Cake> returnObject = new ArrayList<>();
        returnObject.addAll(listA);
        returnObject.addAll(listB);
        return returnObject;
    }

    public static void setB() {

        List<Cake> a = new ArrayList<>();
        a.add(new ChocolateCake());

        List<Cake> b = new ArrayList<>();
        b.add(new VanillaCake());

        unionB(a, b);
    }

    public static void setC() {

        List<ChocolateCake> a = new ArrayList<>();
        a.add(new ChocolateCake());

        List<VanillaCake> b = new ArrayList<>();
        b.add(new VanillaCake());

        unionC(a, b);
    }

    public static void setD() {

        List<ChocolateCake> a = new ArrayList<>();
        a.add(new ChocolateCake());

        List<VanillaCake> b = new ArrayList<>();
        b.add(new VanillaCake());

        unionD(a, b);
    }
}

这三种方法也行。

  1. unionB, unionC, unionD 参数类型取决于你的真实参数,当我们需要像? extends T这样的类型时,一般会有一个容器,容器需要保持不同的类型继承自T。在这种情况下, T是蛋糕。
  2. ? extends T容器无法更新,所以你的第二种方法会导致编译错误。
  3. 您将三个方法 return 类型更改为 List