使用generic来存储Java中的常用超类型 [英] Use generic to store common supertype in Java

查看:173
本文介绍了使用generic来存储Java中的常用超类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Suppose I have a method "mix" that takes two Lists of possibly different types T and S and returns a single List containing the elements of both. For type-safety, I'd like to specify that the returned List is of a type R, where R is a supertype common to both T and S. For example:

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

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

To specify this, I could declare the method as

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

但如果我想制作怎么办? mix List2< T> 上的实例方法而非静态方法?

But what if I want to make mix an instance method instead of static, on the class List2<T>?

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

隐藏< T> List2 的实例上,所以这不好。

shadows the <T> on the instance of List2, so that's no good.

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

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

solves the shadowing problem, but isn't accepted by the compiler

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

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

is rejected by the compiler because lower-bounded wildcards can't be stored in a named variable (only used in ? super X expressions)

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

I could move the arguments to the class itself, like List2<R, T extends R, S extends R>, but the type information really has no business being on the instance level, because it's only used for one method call, and you would have to re-cast the object every time you wanted to invoke the method on different arguments.

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

As far as I can tell, there's no way to do this with generics. The best I can do would be to return a raw List2 and cast it at the callsite, like before generics were introduced. Does anybody have a better solution?

推荐答案

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

As noted in the question and in the comments, the following signature would be ideal:

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

但当然, R super T 语言不允许 (请注意polygenelubricants的答案在链接的帖子上是错误的 - 有这种语法的用例,正如你的问题所示。)

But of course, R super T is not allowed by the language (note that polygenelubricants's answer on the linked post is wrong - there are use cases for this syntax, as your question demonstrates).

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

There's no way to win here - you only have one of several workarounds to choose from:


  • 使用原始类型的签名。不要这样做。

  • 保持 mix 静态方法。这实际上是一个不错的选择,除非出于多态相关的原因需要成为类的接口的一部分,或者你计划 mix 成为你常用的方法认为保持静态是不可接受的。

  • mix 的签名结算过度限制,并记录某些未经检查的演员表是必要的在来电者方面。这类似于 Guava Optional.or 必须这样做。从该方法的文档:

  • Resort to using a signature with raw types. Don't do this.
  • Keep mix a static method. This is actually a decent option, unless it needs to be part of your class's interface for polymorphism-related reasons, or you plan for mix to be such a commonly used method that you think keeping it static is unnacceptable.
  • Settle with the signature of mix being overly restrictive, and document that certain unchecked casts will be necessary on the part of the caller. This is similar to what Guava's Optional.or had to do. From that method's documentation:

关于泛型的注释:签名 public T或(T defaultValue)过于严格。然而,理想的签名, public< S super T> S或(S),不是合法的Java。因此,涉及子类型的一些合理操作是编译错误:

Note about generics: The signature public T or(T defaultValue) is overly restrictive. However, the ideal signature, public <S super T> S or(S), is not legal Java. As a result, some sensible operations involving subtypes are compile errors:

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

作为解决方法,可以安全地转换 Optional< ;?将T> 扩展为可选< T> 。将[以上可选实例]转换为可选< Number> (其中数字是所需的输出类型)解决问题:

As a workaround, it is always safe to cast an Optional<? extends T> to Optional<T>. Casting [the above Optional instance] to Optional<Number> (where Number is the desired output type) solves the problem:

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


不幸的是,它是不是始终可以安全地投射 List2<?将T> 扩展为 List2< T> 。例如,将 List2< Integer> 投射到 List2< Number> 可以允许添加到仅应该包含 Integer 的内容并导致意外的运行时错误。例外情况是 List2 是不可变的(如可选),但这似乎不太可能。

Unfortunately for you, it's not always safe to cast List2<? extends T> to List2<T>. For example, casting a List2<Integer> to a List2<Number> could permit a Double to be added to something that was only supposed to hold Integers and lead to unexpected runtime errors. The exception would be if List2 was immutable (like Optional), but this seems unlikely.

但是,如果您小心并记录了带有解释的类型不安全代码,那么您可以逃脱此类演员表。假设 mix 具有以下签名(和实现,为了好玩):

Still, you could get away with such casts if you were careful and documented type-unsafe code with explanations. Assuming mix had the following signature (and implementation, for fun):

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;
}

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

Then you might have the following call site:

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 将变得更加清洁,并且没有类型安全的风险。我会确保有充分的理由不这样做。

Again, a settling for a static mix is going to be cleaner and have no risk to type-safety. I would make sure to have very good reasons not to keep it that way.

这篇关于使用generic来存储Java中的常用超类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆