【问题标题】:Use generic to store common supertype in Java在 Java 中使用泛型存储常见的超类型
【发布时间】:2013-08-18 21:12:30
【问题描述】:

假设我有一个方法“mix”,它接受两个可能不同类型 T 和 S 的列表,并返回一个包含两者元素的列表。为了类型安全,我想指定返回的 List 是 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)

但是,如果我想在 List2&lt;T&gt; 类上将 mix 设为实例方法而不是静态方法怎么办?

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

List2 的实例上隐藏&lt;T&gt;,所以这不好。

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

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

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

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

我可以将参数移动到类本身,例如List2&lt;R, T extends R, S extends R&gt;,但是类型信息在实例级别上确实没有用,因为它只用于一个方法调用,你必须重新转换每次你想在不同的参数上调用方法时对象。

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

【问题讨论】:

  • 为什么你有S extends T而不是S extends R
  • 另外,如果你能展示你的方法的主体会更好 - mix。你到底在做什么?你真的需要那些类型参数吗?看起来您可以改用有界通配符。
  • 其实我不明白R类型参数的必要性。既然你只是打算返回一个List&lt;Number&gt;,那么就把它作为返回类型。坦率地说,我在这里没有看到任何泛型方法的使用。
  • R 旨在成为 T 和 S 共有的一些超类。对于 Double 和 Integer,它可以是 Number。对于 StringBuilder 和 String,它可以是 CharSequence。对于 Scanner 和 Comparator,它可以是 Object。
  • +1 这个不错。我不认为有办法做到这一点。除非你在List2 类型的声明中声明了TR 的关系。这只是将方法声明中的类型参数移动到类声明中:class List2&lt;R, T extends R, S extends R&gt;,我认为这不是您想要的。

标签: java generics bounded-wildcard


【解决方案1】:

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

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

当然,R super Tis not allowed by the language(请注意,polygenelubricants 在链接帖子上的回答是错误的 - 正如您的问题所表明的那样,这种语法有一些用例)。

这里没有办法取胜 - 您只能选择以下几种解决方法之一:

  • 使用原始类型的签名。不要这样做。
  • mix 保留为静态方法。这实际上是一个不错的选择,除非由于与多态性相关的原因它需要成为类接口的一部分,或者您计划将 mix 用作如此常用的方法,以至于您认为将其保持为静态是不可接受的。
  • 解决mix 的签名过于严格,并记录调用者需要某些未经检查的强制转换。这类似于GuavaOptional.or 必须做的事情。从该方法的文档中:

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

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

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

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

很遗憾,将List2&lt;? extends T&gt; 转换为List2&lt;T&gt; 并不总是安全的。例如,将List2&lt;Integer&gt; 转换为List2&lt;Number&gt; 可能允许将Double 添加到仅应包含Integers 的内容中,并导致意外的运行时错误。如果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;
}

那么你可能有以下调用站点:

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

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

    如果你真的想这样做,你必须将 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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多