首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >什么时候需要Some<E扩展Some<E>>而不是Some<E扩展Some>?

什么时候需要Some<E扩展Some<E>>而不是Some<E扩展Some>?
EN

Stack Overflow用户
提问于 2014-08-18 14:20:38
回答 4查看 226关注 0票数 8

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

我试图找出一个优势,将一个类声明为:

代码语言:javascript
复制
public class Some<E extends Some<E>>

而不是宣布它为:

代码语言:javascript
复制
public class Some<E extends Some>

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

你能给我看一种方法来证明这个额外的<E>的优势吗?我假设有一个是因为JDK声明而存在的:<E extends Comparable<? super E>>

例如,对有关SO的其他问题的答复如下:

使用附加的构造,您知道任何扩展Enum的类都只能与其自身具有可比性。

但是,我很容易就打破了这个理论:

代码语言:javascript
复制
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" !!!

尽管存在泛型递归,但i仍然可以将狗与猫进行比较。

代码语言:javascript
复制
Dog dog = new Dog();
dog.compare(new Cat());

转换理论:

您知道,任何扩展动物的类都只能与其自身相比较。

这是错误的-我组成了类狗,它扩展了猫的动物,而不是自己。

EN

回答 4

Stack Overflow用户

发布于 2014-08-19 08:34:44

很少有像class Some<E extends Some<E>>这样的绑定是必要的。在大多数情况下,代码中并没有使用绑定,而且class Some<E>也能正常工作。

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

代码语言:javascript
复制
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>)的原始类型确实带有警告(您可以忽略这个警告)。然而,原始类型意味着可以使用它做不安全的事情。

想出一个例子来证明它是不安全的,这需要付出一些努力。这里有一个:

代码语言:javascript
复制
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

票数 3
EN

Stack Overflow用户

发布于 2014-08-19 10:04:41

关于声明

每次我尝试删除额外的-没有新的错误/警告出现。

情况不应如此。它应该打印一个警告,因为您使用的是原始类型Some,其结果是缺少类型安全性,如新会计回答所示。

Dog/Cat示例有点做作,而且有一些缺陷。你建议宣布一个类

代码语言:javascript
复制
public class Dog extends Animal<Cat> { } // Note "Cat" !!!

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

事实上,在很多情况下,这些自引用的泛型类型是必要的。其中一个例子是用这一常见问题条目:描述的,它声明了一个节点结构(即树),其中类型参数可以用来将树结构的定义与节点的实际类型分离开来:

代码语言:javascript
复制
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;
    }

}

但根据我个人的经验,我可以说,当你认为你需要创造这样一个类型,你应该彻底考虑的好处和缺点。后者主要是指可读性的降低。当您在以下方法之间进行选择时,如

代码语言:javascript
复制
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) { ... }

类型安全的方法,如

代码语言:javascript
复制
Node doSomething(Node parent, Node child) { ... }

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

票数 2
EN

Stack Overflow用户

发布于 2014-08-18 23:22:50

区别在于,Some<E extends Some>意味着两件事:

  • E是一种原始类型。简单地说,原始类型从类中删除了所有泛型信息,这反过来会产生意外行为。
  • E可以是任何类别的Some

相反,使用Some<E extends Some<E>>意味着:

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

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

代码语言:javascript
复制
// 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类都可以,但是类SomeTB会引发编译错误:

类型参数SomeTA不在类型变量E的界限之内

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

如果您期望参数和返回类型与类本身相同,这一点很重要,这通常是类型化类所需要的。

被原始类型限制的类型不是什么问题,因为它仍然是一个Some,但是在某些情况下,它可能会产生影响(我现在想不出任何情况)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25365589

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档