使用generic来存储Java中的常用超类型 [英] Use generic to store common supertype in 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 formix
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'sOptional.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 Integer
s 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屋!