具有“super”类型的有界泛型方法

7

根据我阅读的文献,我们有多种多样的果汁水果实现了以下接口:

public interface Juicy<T> {
    Juice<T> squeeze();
}

使用有界类型变量,下面的方法将接收一堆水果并将它们都压榨掉:
<T extends Juicy<T>> List<Juice<T>> squeeze(List<T> fruits);

现在我们需要以下的“下级兄弟”也能够正常工作:
class Orange extends Fruit implements Juicy<Orange>;
class RedOrange extends Orange;

所以我期望该方法应该如下所示:
<T extends Juicy<T>> List<Juice<? super T>> squeeze(List<? extends T> fruits);

相反,我发现方法签名如下所示:

<**T extends Juicy<? super T>>** List<Juice<? super T>> squeezeSuperExtends(List<? extends T> fruits);

什么解释了这个差异?

这句话不应该是 public interface Juicy<T extends Juicy<T>> 吗?(以防止 class Orange extends Fruit implements Juicy<Apple> - SLaks
@SLaks:这并不是一个巨大的收益,因为如果Apple实现了Juicy<Apple>,那么您提到的情况就不会被防止。 - Mark Peters
@MarkPeters:你说得对,没有办法阻止这种情况。不过,可以通过class Orange extends Fruit implements Juicy<String>来避免。 - SLaks
@SLaks:"预防"这些事情是没有意义的。它们是完全类型安全的。你为什么在乎? - newacct
2个回答

3

<T extends Juicy<? super T>>中的<? super T>是为了让Juicy<Orange>的子类RedOrange满足其约束条件。

想象一下没有<? super T>

public <T extends Juicy<T>> List<Juice<T>> squeeze(List<T> fruits) {...

现在,T 必须是一个 Juicy<T>。类 Orange 是一个 Juicy<T>,它是一个 Juicy<Orange>。但是类 RedOrange 不是一个 Juicy<T>。它不是一个 Juicy<RedOrange>;它是一个 Juicy<Orange>。因此,当我们尝试调用 squeeze 时:
List<RedOrange> redOranges = new ArrayList<RedOrange>();
List<Juice<RedOrange>> juices = squeeze(redOranges);

我们收到以下编译器错误:
Inferred type 'RedOrange' for type parameter 'T' is not within its bound; should implement 'Juicy<RedOrange>'.

如果我们把 <? super T> 放置在这里,就可以让 Juicy 的类型参数成为 T 的超类。这样就可以使用 RedOrange,因为它是一个 Juicy<Orange>,而 OrangeRedOrange 的超类。
public <T extends Juicy<? super T>> List<Juice<T>> squeeze(List<T> fruits) {...

现在,上面对`squeeze`的调用已经编译通过。
编辑:
但是,如果我们想从`List`中挤压出一个`List>`呢?这有点棘手,但我找到了解决方案:
我们需要第二个类型参数来匹配`squeeze`方法中的`Orange`。
public <S extends Juicy<S>, T extends Juicy<S>> List<Juice<S>> squeeze(List<T> fruits)

这里,S代表Orange,因此我们可以返回List<Juice<Orange>>。现在我们可以说

List<RedOrange> redOranges = new ArrayList<RedOrange>();
List<Juice<Orange>> juices = squeeze(redOranges);

我喜欢这个答案,但请帮我思考一下为什么你应该能够从榨汁List<RedOrange>得到一个List<Juice<RedOrange>>RedOrange会产生Juice<Orange>而不是Juice<RedOrange>,所以squeeze方法是否无法满足(即无法安全/正确地实现)? - Mark Peters
1
@MarkPeters - 有趣。我已经添加了一种获取List<Juice<Orange>>的方法。 - rgettman
我的观点更多的是,如果你可以声明List<Juice<RedOrange>> juices = squeeze(redOranges),那么肯定有问题,因为RedOrange不能产生Juice<RedOrange>。尝试安全地实现squeeze。我怀疑你做不到。 - Mark Peters
嗯,我认为您的编辑仍然可以选择List<Juice<RedOrange>>。我认为您的新解决方案正确地防止了这种情况。但是,T是多余的,因为它可以只是一个通配符。 - Mark Peters
@MarkPeters,我最初尝试获取一个List<Juice<RedOrange>>,但你是对的,那不应该是目标;当调用squeeze时,RedOrange只能返回一个Juice<Orange>,而不是Juice<RedOrange> - rgettman

3

我认为,最简单的思考方式是简单地忽略水果类型和所产生的果汁类型之间的关系。这个链接已经在类声明中建立;我们不需要它来挤出一堆Juicy

换句话说,只需根据Juicy将要产生的Juice类型进行参数化:

<T> List<Juice<T>> squeeze(List<? extends Juicy<? extends T>> fruits);

这里我们根据生产的常见超类Juice(即Juicy的参数)列出了果汁清单,而不是水果的常见超类。

然后我们得到以下内容:

//works
List<Juice<Orange>> b = squeeze(Arrays.asList(new Orange(), new RedOrange()));

//fails as it should 
List<Juice<RedOrange>> d = squeeze(Arrays.asList(new RedOrange()));

+1,你有一个比我更简单的解决方案来生成List<Juice<Orange>> - rgettman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接