为什么允许使用 "extends T" 而不是 "implements T"?

364

在Java中,为什么在定义类型参数边界时总是使用“extends”而不是“implements”?是否有特殊原因?

例如:

public interface C {}
public class A<B implements C>{} 

被禁止,但是

public class A<B extends C>{} 

这是正确的。那是什么原因?


23
我不知道为什么人们认为铁人鬼的回答真正回答了问题。 它基本上是用学术词汇重新表述了OP的观察结果,但没有给出任何推理。“为什么没有'implements'?” - “因为只有'extends'”。 - ThomasR
2
ThomasR:这是因为问题不在于“允许”,而在于意义:无论约束来自接口还是祖先类型,你编写使用约束类型的泛型的方式都没有区别。 - Tetsujin no Oni
我已添加了一个答案(https://dev59.com/k3NA5IYBdhLWcg3wZ8_U#56304595),并解释了为什么 implements 不会带来任何新东西,反而会进一步复杂化事情。我希望这对你有所帮助。 - Andrew Tobilko
这篇文章可能会有所帮助! https://veejnas.medium.com/how-to-write-a-generic-comparator-in-java-31605bc854fe - sanjeev
9个回答

347

在泛型约束语言中,“实现(implements)”和“扩展(extends)”之间没有语义差异。约束的可能性是“扩展(extends)”和“超级(super)” - 即,这个类是否能够分配到另一个类(extends),或者这个类是否可以从那个类分配(super)。


@KomodoDave(我认为答案旁边的数字标记它是正确的,我不确定是否有其他标记方式,有时其他答案可能包含额外信息-例如,我有一个特定的问题无法解决,当你搜索它时,谷歌会将你带到这里。) @Tetsujin no Oni(是否有可能使用一些代码来澄清?谢谢:)) - ntg
@ntg,这是一个非常好的寻找示例问题的例子 - 我会在评论中链接它,而不是在此时将其嵌入答案。https://dev59.com/wVnUa4cB1Zd3GeqPb5uq#6828257 - Tetsujin no Oni
1
我认为原因至少是我想要一个通用的构造函数和方法,可以接受任何既扩展基类又表现出接口的类,而不仅仅是扩展接口的接口。然后,对Genric的实例化进行接口存在性测试,并将实际类指定为类型参数。理想情况下,我希望class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }需要在编译时进行检查。 - peterk
1
@peterk,您可以通过RenderableT扩展Renderable、Draggable和Droppable来实现这一点……除非我没有理解您希望擦除泛型为您提供的内容。 - Tetsujin no Oni
@TetsujinnoOni,您不了解我的要求是希望在编译时强制只接受实现某些接口的类,并且能够在泛型中引用对象类(展示这些接口),并知道至少在编译时(并且最好在运行时)分配给泛型的任何内容可以安全地转换为指定接口中的任何一个。 这在Java当前的实现方式下不是这种情况。 但这会很好 :) - peterk
https://docs.oracle.com/javase/tutorial/java/generics/bounded.html - Paulo Moreira

117
答案在这里
 

为了声明一个有界类型参数,需要列出该类型参数的名称,然后是extends关键字,接着是它的上限[...]。请注意,在这个语境中,extends一般用于表示extends(如类)或implements(如接口)。

所以,以上就是答案,虽然有点混乱,但 Oracle 心知肚明。

5
为了增加混淆,getFoo(List<? super Foo> fooList) 只适用于直接由 Foo 扩展的类,例如 class Foo extends WildcardClass。在这种情况下,List<WildcardClass> 将是可接受的输入。但是,任何 Foo 实现的类都不行,例如 class Foo implements NonWorkingWildcardClass 并不意味着 List<NonWorkingWildcardClass>getFoo(List<? super Foo> fooList) 中是有效的。完全清楚! - Ray

20

可能是因为对于B和C双方而言,只有类型是相关的,而不是实现方式。在您的示例中

public class A<B extends C>{}

B也可以是一个接口。 "extends"用于定义子接口以及子类。

interface IntfSub extends IntfSuper {}
class ClzSub extends ClzSuper {}

我通常认为'Sub extends Super'是指'Sub类似于Super,但具有额外的功能',而 'Clz implements Intf'是指'Clz实现了Intf接口'。在您的示例中,这将匹配: B类似于C,但具有额外的功能。这里相关的是功能,而不是实现。


13
考虑 <B extends D & E>。E 必须不是一个类。 - Tom Hawtin - tackline
除非你使用类型参数(“E”),否则似乎不能在那个和号字符后面放置一个接口文字,这样Java才能相信它是一个接口。我很想在这一点上得到纠正。 - Jaroslav Záruba
1
@JaroslavZáruba 说得没错。实际错误是“意外类型,需要:Class,找到:类型参数 E”(我强调“Class”)。然而,原帖作者(不幸的是)将类型名称以及类型参数用单个字母表示= E 应该代表一个实际的类型名称(我想 - 我在十年前写了那条评论)。 - Tom Hawtin - tackline
@TomHawtin-tackline 真糟糕,我本来还希望能有一个变通方法 :) - Jaroslav Záruba

8
这是一个更为复杂的例子,展示了 extends 可以使用的情况,也可能是你需要的情况:
``` public class A> ```

7

可能基础类型是一个泛型参数,因此实际类型可能是接口或类。例如:

class MyGen<T, U extends T> {

从客户端代码的角度来看,接口和类几乎无法区分,但对于子类型而言,这一点非常重要。


7

我们习惯于

class ClassTypeA implements InterfaceTypeA {}
class ClassTypeB extends ClassTypeA {}

任何与这些规则稍有偏差都会使我们混淆不清。

类型边界的语法定义如下:

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

(JLS 12 > 4.4. Type Variables > TypeBound)

如果我们对它进行更改,肯定会添加 implements 案例。

TypeBound:
    extends TypeVariable 
    extends ClassType {AdditionalBound}
    implements InterfaceType {AdditionalBound}

最终会得到两个完全相同的处理子句

ClassOrInterfaceType:
    ClassType 
    InterfaceType

(JLS 12 > 4.3. 引用类型和值 > ClassOrInterfaceType)

不过我们还需要考虑 implements,这会让事情更加复杂。我相信这就是为什么使用 extends ClassOrInterfaceType 而不是 extends ClassTypeimplements InterfaceType 的主要原因 - 为了在复杂的概念中保持简单性。问题是我们没有合适的词来涵盖 extendsimplements,我们绝对不想引入一个新词。

<T is ClassTypeA>
<T is InterfaceTypeA>

尽管 extends 在与接口一起使用时会带来一些混乱,但它是一个更广泛的术语,可以用于描述两种情况。试着将思维调整到扩展类型的概念上(而不是 扩展类实现接口)。您通过另一个类型限制类型参数,无论该类型实际上是什么都无所谓。重要的是它是其上限和其超类型


5

使用哪种术语有些随意,可以任选其一。也许语言设计师认为“extends”是最基本的术语,“implements”是接口的特殊情况。

但我认为,“implements”稍微更合理一些。我认为这样更能传达参数类型不必在继承关系中,而可以处于任何子类型关系中。

Java词汇表表达了类似观点


0
在中使用“extends”是一个承诺,即数据类型将直接实现Comparable,或者将扩展实现Comparable的类。您可能编写了另一个类A的子类B,该类实现了Comparable,如果您声明数据类型,则在实例化类时可以使用A或B作为数据类型。

0
事实上,在接口中使用泛型时,关键字也是extends。以下是代码示例:
有两个类实现了Greeting接口:
interface Greeting {
    void sayHello();
}

class Dog implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Greeting from Dog: Hello ");
    }
}

class Cat implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Greeting from Cat: Hello ");
    }
}

测试代码如下:

@Test
public void testGeneric() {
    Collection<? extends Greeting> animals;

    List<Dog> dogs = Arrays.asList(new Dog(), new Dog(), new Dog());
    List<Cat> cats = Arrays.asList(new Cat(), new Cat(), new Cat());

    animals = dogs;
    for(Greeting g: animals) g.sayHello();

    animals = cats;
    for(Greeting g: animals) g.sayHello();
}

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