考虑下面的例子(OOP书籍中的典型例子):

我有一个动物班,每个动物都有很多朋友。 和子类,如狗,鸭,老鼠等,其中添加特定的行为,如bark(), quack()等。

下面是Animal类:

public class Animal {
    private Map<String,Animal> friends = new HashMap<>();

    public void addFriend(String name, Animal animal){
        friends.put(name,animal);
    }

    public Animal callFriend(String name){
        return friends.get(name);
    }
}

下面是一些带有大量类型转换的代码片段:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();

有没有办法我可以使用返回类型的泛型来摆脱类型转换,这样我就可以说

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

下面是一些初始代码,其中返回类型作为从未使用过的参数传递给方法。

public<T extends Animal> T callFriend(String name, T unusedTypeObj){
    return (T)friends.get(name);        
}

是否有一种方法可以在不使用instanceof的额外参数的情况下在运行时找出返回类型?或者至少通过传递该类型的类而不是虚拟实例。 我知道泛型是用于编译时类型检查的,但是对此有解决方法吗?


当前回答

我在我的lib kontraktor中做了以下事情:

public class Actor<SELF extends Actor> {
    public SELF self() { return (SELF)_self; }
}

子类化:

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

至少这在当前类内部和具有强类型引用时是有效的。多重继承工作,但真的很棘手:)

其他回答

我已经写了一篇文章,其中包含了概念的证明、支持类和一个测试类,它演示了类如何在运行时检索Super Type令牌。 简而言之,它允许您根据调用者传递的实际泛型参数委托其他实现。例子:

TimeSeries<Double>委托给使用Double[]的私有内部类 TimeSeries<OHLC>委托给一个私有内部类,该类使用ArrayList<OHLC>

See:

使用TypeTokens检索通用参数(web存档) 使用TypeTokens检索泛型参数(blog)

谢谢

Richard Gomes -博客

基于与超级类型令牌相同的思想,你可以创建一个类型化的id来代替字符串:

public abstract class TypedID<T extends Animal> {
  public final Type type;
  public final String id;

  protected TypedID(String id) {
    this.id = id;
    Type superclass = getClass().getGenericSuperclass();
    if (superclass instanceof Class) {
      throw new RuntimeException("Missing type parameter.");
    }
    this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
  }
}

但我认为这可能会破坏目的,因为现在需要为每个字符串创建新的id对象并保留它们(或使用正确的类型信息重新构造它们)。

Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};

jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());

但是您现在可以按照您最初想要的方式使用该类,而不需要强制类型转换。

jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();

这只是将类型参数隐藏在id中,尽管这意味着如果您愿意,稍后可以从标识符中检索类型。

如果希望能够比较一个id的两个相同实例,还需要实现TypedID的比较和散列方法。

正如你所说,通过一门课程就可以了,你可以这样写:

public <T extends Animal> T callFriend(String name, Class<T> clazz) {
   return (T) friends.get(name);
}

然后像这样使用它:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

这并不完美,但这几乎是您使用Java泛型所能达到的极限。有一种方法可以使用超级类型令牌来实现类型安全异构容器(THC),但这也有它自己的问题。

这个问题与Effective Java中的第29项非常相似——“考虑类型安全的异构容器”。拉兹的答案最接近布洛赫的答案。但是,为了安全起见,put和get都应该使用Class字面值。签名将变成:

public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);

在这两个方法中,您应该检查参数是否正常。有关更多信息,请参阅有效Java和类javadoc。

你可以这样定义callFriend:

public <T extends Animal> T callFriend(String name, Class<T> type) {
    return type.cast(friends.get(name));
}

那么就这样称呼它吧:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

这段代码的好处是不会生成任何编译器警告。当然,这实际上只是前泛型时代的强制转换的更新版本,并没有增加任何额外的安全性。