使用泛型在 Java 中存储通用超类型

2022-09-04 22:32:22

假设我有一个方法“mix”,它采用两个可能不同类型的T和S的列表,并返回一个包含两者元素的单个列表。对于类型安全,我想指定返回的列表是 R 类型,其中 R 是 T 和 S 通用的超类型。例如:

List<Number> foo = mix(
    Arrays.asList<Integer>(1, 2, 3),
    Arrays.asList<Double>(1.0, 2.0, 3.0)
);

要指定这一点,我可以将方法声明为

static <R, T extends R, S extends R> List<R> mix(List<T> ts, List<S> ss)

但是,如果我想在类上创建一个实例方法而不是静态方法怎么办?mixList2<T>

<R, T extends R, S extends R> List<R> mix ...

阴影上的实例,所以这不好。<T>List2

<R, T extends S&T, S extends R> List<R> mix ...

解决了重影问题,但编译器不接受

<R super T, S extends R> List<R> mix ...

被编译器拒绝,因为下限通配符不能存储在命名变量中(仅在表达式中使用)? super X

我可以将参数移动到类本身,例如,但类型信息实际上在实例级别上没有业务,因为它仅用于一个方法调用,并且每次想要在不同的参数上调用方法时,都必须重新转换对象。List2<R, T extends R, S extends R>

据我所知,没有办法用泛型来做到这一点。我能做的最好的事情就是返回一个原始数据并将其投射到调用站点,就像在引入泛型之前一样。有没有人有更好的解决方案?List2


答案 1

如问题和评论中所述,以下签名将是理想的:

<R super T, S extends R> List<R> mix(List<S> otherList)

但是,当然,语言不允许这样做(请注意,多基因在链接帖子上的答案是错误的 - 正如您的问题所显示的那样,此语法有用例)。R super T

这里没有办法获胜 - 您只有几种解决方法之一可供选择:

  • 诉诸于对原始类型使用签名。别这样。
  • 保留静态方法。这实际上是一个不错的选择,除非出于多态性相关的原因,它需要成为类接口的一部分,或者您计划成为一种常用的方法,以至于您认为保持静态是不可接受的。mixmix
  • 使用过于限制的签名进行结算,并记录调用方需要某些未经检查的强制转换。这类似于番石榴可选或必须做的事情。从该方法的文档:mix

关于泛型的注意事项:签名限制性过强。但是,理想的签名 ,不是合法的 Java。因此,一些涉及子类型的合理操作是编译错误:public T or(T defaultValue)public <S super T> S or(S)

Optional<Integer> optionalInt = getSomeOptionalInt();
Number value = optionalInt.or(0.5); // error

作为一种解决方法,将 一个 转换为 始终是安全的。将 [上述实例] 转换为(其中是所需的输出类型)可以解决问题:Optional<? extends T>Optional<T>OptionalOptional<Number>Number

Optional<Number> optionalInt = (Optional) getSomeOptionalInt();
Number value = optionalInt.or(0.5); // fine

不幸的是,对于您来说,投射到 并不总是安全的。例如,将 a 强制转换为 可能允许将 a 添加到只应包含 s 并导致意外运行时错误的内容中。例外情况是 if 是不可变的(如 ),但这似乎不太可能。List2<? extends T>List2<T>List2<Integer>List2<Number>DoubleIntegerList2Optional

不过,如果你小心谨慎,并记录类型不安全的代码并附有解释,你就可以逃脱这样的强制转换。假设有以下签名(和实现,为了好玩):mix

List<T> mix(final List<? extends T> otherList) {

    final int totalElements = (size() + otherList.size());
    final List<T> result = new ArrayList<>(totalElements);

    Iterator<? extends T> itr1 = iterator();
    Iterator<? extends T> itr2 = otherList.iterator();
    while (result.size() < totalElements) {
        final T next = (itr1.hasNext() ? itr1 : itr2).next();
        result.add(next);
        final Iterator<? extends T> temp = itr1;
        itr1 = itr2;
        itr2 = temp;
    }

    return result;
}

然后,您可能有以下呼叫站点:

final List2<Integer> ints = new List2<>(Arrays.asList(1, 2, 3));
final List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);

final List<Number> mixed;
// type-unsafe code within this scope
{
    @SuppressWarnings("unchecked") // okay because intsAsNumbers isn't written to
    final List2<Number> intsAsNumbers = (List2<Number>)(List2<?>)ints;
    mixed = intsAsNumbers.mix(doubles);
}

System.out.println(mixed); // [1, 1.5, 2, 2.5, 3, 3.5]

同样,静态的解决将更干净,并且不会对类型安全构成风险。我会确保有很好的理由不保持这种状态。mix


答案 2

在你的问题中,我唯一不确定的是你是否已经知道这些子类扩展了哪种超类型,或者你想要一个完全泛型的方法,在这个方法中,你可以传递任何给定超类的两个子类型。

在第一种情况下,我最近做了类似的事情,使用抽象类和几个子类型:

public <V extends Superclass> List<Superclass> mix(List<V> list1, List<V> list2) {
  List<Superclass> mixedList;

  mixedList.addAll(list1);
  mixedList.addAll(list2);
}

后一种情况要复杂得多。我建议你重新考虑你的设计,因为混合方法在超类或知道超类及其子类型的类中更有意义,因为你返回的是超类的列表。

如果你真的想这样做,你必须将List2重构为List2,并执行以下操作:

public <R, V extends R> List<R> mix(List<V> list1, List<V> list2) {
    List<R> mixedList;

    mixedList.addAll(list1);
    mixedList.addAll(list2);

    return mixedList;
}

推荐