使用generic来存储Java中的常用超类型

Mat*_*t G 13 java generics bounded-wildcard

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

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

为了指定这个,我可以将方法声明为

static <R, T extends R, S extends R> List<R> mix(List<T> ts, List<S> ss)
Run Code Online (Sandbox Code Playgroud)

但是如果我想在类上创建mix一个实例方法而不是静态List2<T>呢?

<R, T extends R, S extends R> List<R> mix ...
Run Code Online (Sandbox Code Playgroud)

阴影<T>在实例上List2,所以没有好处.

<R, T extends S&T, S extends R> List<R> mix ...
Run Code Online (Sandbox Code Playgroud)

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

<R super T, S extends R> List<R> mix ...
Run Code Online (Sandbox Code Playgroud)

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

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

据我所知,没有办法用泛型做到这一点.我能做的最好的事情就是返回一个raw List2并在callsite上投射它,就像引入泛型之前一样.有人有更好的解决方案吗?

Pau*_*ora 8

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

<R super T, S extends R> List<R> mix(List<S> otherList)
Run Code Online (Sandbox Code Playgroud)

但当然,R super T 语言是不允许的(请注意,polygenelubricants对链接帖子的回答是错误的 - 正如您的问题所示,此语法有用例).

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

  • 使用原始类型的签名.不要这样做.
  • 保持mix静态方法.这实际上是一个不错的选择,除非出于多态相关的原因需要成为类接口的一部分,或者您计划mix成为一种常用的方法,您认为保持静态是不可接受的.
  • mix过度限制的签名结算,并记录调用者需要某些未经检查的强制转换.这是类似于番石榴Optional.or不得已而为之.从该方法的文档:

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

Optional<Integer> optionalInt = getSomeOptionalInt();
Number value = optionalInt.or(0.5); // error
Run Code Online (Sandbox Code Playgroud)

作为一种解决方法,投射Optional<? extends T>到总是安全的Optional<T>.将[上面的Optional实例]转换为Optional<Number>(Number所需的输出类型)解决了问题:

Optional<Number> optionalInt = (Optional) getSomeOptionalInt();
Number value = optionalInt.or(0.5); // fine
Run Code Online (Sandbox Code Playgroud)

不幸的是你,这并不总是安全投List2<? extends T>List2<T>.例如,将a转换List2<Integer>为a List2<Number>可以允许将a Double添加到仅应该保留Integers并导致意外运行时错误的内容.例外情况是if List2是不可变的(比如Optional),但这似乎不太可能.

尽管如此,如果你仔细并记录了带有解释的类型不安全的代码,你仍然可以逃脱这些演员表.假设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;
}
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

再一次,静电的解决方案mix将变得更加清洁并且没有类型安全的风险.我会确保有充分的理由不保持这种方式.