什么时候需要一些<e extends =“”一些<e =“”>&gt;而不是一些<e extends =“”some =“”>?</e> </e>

时间:2014-08-18 14:20:38

标签: java generics recursion enums type-parameter

注意:这个问题与Enum无关,所以它不重复。 Enum的强制只与自身进行比较,因为编译生成了类型参数,不是因为java递归类型参数。

我试图找到宣布课程的优势:

public class Some<E extends Some<E>>

而不是声明:

public class Some<E extends Some>

我尝试提供返回E的方法和返回Some<E>的方法,复杂类层次结构中的不同交叉调用以及每次我尝试删除其他{{1}时 } - 没有出现新的错误/警告

你能告诉我一个证明这个额外<E>的优势的方法吗? 我假设存在一个因为JDK声明:<E>

对SO的其他问题的回答例如:

  

使用附加构造,您知道任何扩展的类   枚举只能与自身相媲美

但是,我可以很容易地打破这个理论:

<E extends Comparable<? super E>>

尽管有通用的递归,我仍然可以比较Dog with Cat

public static class Animal<E extends Animal<E>> {
    public boolean compare(E other) {...}
}

public class Cat extends Animal<Cat> { }
public class Dog extends Animal<Cat> { } // Note "Cat" !!!

转换理论:

  

你知道任何扩展Animal的类只能与其自身相媲美

这是假的 - 我合作了类Dog,它用Cat扩展Animal,而不是它本身。

4 个答案:

答案 0 :(得分:3)

极少数情况下需要绑定class Some<E extends Some<E>>。大多数人写这篇文章时,代码中实际上没有使用绑定,class Some<E>也可以正常工作。

但是,在某些特定情况下,实际使用class Some<E extends Some<E>>中的绑定。例如:

abstract class Some<E extends Some<E>> {
    abstract E foo();
    Some<E> bar() {
        return foo();
    }
}

关于你的问题 - class Some<E extends Some>怎么办?嗯,首先最明显的问题是你使用的是原始类型。永远不要在新代码中使用原始类型。但你不相信。

具有上述类(class Some<E extends Some>的原始类型编译并带有警告(您可以忽略自己的危险)。但是,原始类型意味着可以用它做不安全的事情。

需要一些努力才能得出一个证明它不安全的例子。这是一个:

abstract class Some<E extends Some> {
    abstract E foo();
    Some<E> bar() {
        return foo();
    }
}

class SomeFoo extends Some<SomeFoo> {
    SomeFoo foo() { return this; }
}

class SomeBar extends Some<SomeFoo> {
    SomeFoo foo() { return new SomeFoo(); }
}

class SomeBaz extends Some<SomeBar> {
    SomeBar foo() { return new SomeBar(); }
}

// then in some method:
Some<SomeBar> a = new SomeBaz();
Some<SomeBar> b = a.bar();
SomeBar c = b.foo();

代码编译时出现警告但没有错误,并在运行时抛出ClassCastException

答案 1 :(得分:2)

关于声明

  

每当我尝试删除其他内容时 - 都没有出现新的错误/警告。

不应该是这种情况。它应该打印一个警告,因为您使用的是原始类型 Some,其结果是缺少类型安全性,如answer by newacct中所示。


Dog / Cat示例有点做作,有些瑕疵。你建议宣布一个班级

public class Dog extends Animal<Cat> { } // Note "Cat" !!!

但是在这里,类型参数基本上意味着:“此参数(Cat)是可以将此类(Dog)的对象与”进行比较的类型。因此,您明确陈述 Dog应与Cat相当。毕竟,即使使用复杂的语言和智能编译器,程序员也有责任编写有意义的代码。


实际上,这些自引用泛型类型的必要性并不多。其中一个示例在this FAQ entry:中草绘。它声明了一个节点结构(即树),其中类型参数可用于将树结构的定义与实际结构分离类型节点:

public class RecurringTest {
    public static void main(String[] args) {
        SpecialNode sa = new SpecialNode(null);
        SpecialNode sb = new SpecialNode(sa);
        SpecialNode s = sa.getChildren().get(0);
    }
}

abstract class Node<N extends Node<N>> {
    private final List<N> children = new ArrayList<N>();
    private final N parent;

    protected Node(N parent) {
        this.parent = parent;
        if (parent != null) {
            this.parent.getChildren().add(getThis());
        }
    }

    abstract N getThis();

    public N getParent() {
        return parent;
    }

    public List<N> getChildren() {
        return children;
    }
}

class SpecialNode extends Node<SpecialNode> {
    public SpecialNode(SpecialNode parent) {
        super(parent);
    }

    SpecialNode getThis() {
        return this;
    }

}

但是从我个人的经验来看,我可以说,当你认为你需要创造这样一种类型时,你应该彻底考虑其优点和缺点。后者主要是指可读性降低。当您可以选择

之类的方法时
Node<? extends Node<? extends N, ? extends T>, T> doSomething(
    Node<? super Node<? extends N>, ? extends T> p, 
    Node<? extends Node<? super N>, ? super T> c) { ... }

是类型安全的,或类似

的方法
Node doSomething(Node parent, Node child) { ... }

不是类型安全的(因为原始类型,或者仅仅因为类型没有被泛化),那么我更喜欢后者。代码由人类阅读。

答案 2 :(得分:1)

不同之处在于Some<E extends Some>意味着两件事:

  • E是原始类型。简而言之,原始类型具有从类中剥离的所有通用信息,这反过来可能会产生意外行为
  • E可以是任何类的Some

相反,使用Some<E extends Some<E>>表示:

  • E已键入
  • E必须是与其声明的类相同的

原始部分是一个问题,但更大的含义是类型界限。此代码演示了不同之处,“B”类使用(或尝试使用)“A”类作为泛型类型。

// The raw version
interface Some<E extends Some> {
    E get();
}

class SomeA implements Some<SomeA> {
    public  SomeA get() {
         return new SomeA();
    }
}

// Compiles OK
class SomeB implements Some<SomeA> {
   public SomeA get() {
         return new SomeA();
    }
}

// The typed version
interface SomeT<E extends SomeT<E>> {
    E get();
}

class SomeTA implements Some<SomeTA> {
    public  SomeTA get() {
         return new SomeTA();
    }
}

// Compile error 
class SomeTB implements SomeT<SomeA> {
   public SomeA get() { 
         return new SomeTA();
   }
}

SomeB编译得很好 - E的类型只绑定到Some,所以任何 Some类都可以,但类{{1}抛出编译错误:

  

类型参数SomeTA不在类型变量E

的范围内

SomeTB的类型必须与包含类完全相同。

如果你期望参数和返回类型与类本身相同,这很重要,这通常是你想要的类型类。


由原始类型限制的类型不是问题,因为它仍然是E,但可能存在差异(我现在想不到任何问题)。< / p>

答案 3 :(得分:0)

看起来目前Some<E extends Some<E>>没有超过Some<E extends Some>的广告

根据article与Java冠军和Java Generics FAQ的创建者一起撰写:

文章承认了一些可能性/假设:

  • 由于Enum是一个泛型类,我们只能将它与类型
  • 结合使用
  • Java可能会变得更加严格,原始类型可能会变得非法
  

我认为Enum已经足够了。   然而,正如Philip Wadler向我指出的那样,因为Enum是一个通用的   class,我们应该只将它与一个类型一起使用。在   未来,Java可能变得更加严格,原始类型可能变得更加严格   非法。因此,我们可以选择编写Enum<E extends Enum<E>>   或Enum<E extends Enum<?>>,其中第一个选项更多   准确。 即使编译器目前没有显示我   差异,我可能会在将来看到警告,所以我会遵循这个习惯用法   我的班级