Java 如何使方法返回类型通用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/450807/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I make the method return type generic?
提问by Sathish
Consider this example (typical in OOP books):
考虑这个例子(OOP 书中的典型例子):
I have an Animal
class, where each Animal
can have many friends.
And subclasses like Dog
, Duck
, Mouse
etc which add specific behavior like bark()
, quack()
etc.
我有一个Animal
班级,每个班级都Animal
可以有很多朋友。
和子类喜欢Dog
,Duck
,Mouse
等里面加如特定行为bark()
,quack()
等等。
Here's the Animal
class:
这是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);
}
}
And here's some code snippet with lots of typecasting:
这是一些带有大量类型转换的代码片段:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();
Is there any way I can use generics for the return type to get rid of the typecasting, so that I can say
有什么方法可以使用泛型作为返回类型来摆脱类型转换,以便我可以说
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
Here's some initial code with return type conveyed to the method as a parameter that's never used.
这是一些初始代码,返回类型作为从未使用过的参数传递给方法。
public<T extends Animal> T callFriend(String name, T unusedTypeObj){
return (T)friends.get(name);
}
Is there a way to figure out the return type at runtime without the extra parameter using instanceof
? Or at least by passing a class of the type instead of a dummy instance.
I understand generics are for compile time type-checking, but is there a workaround for this?
有没有办法在运行时找出返回类型而不使用额外的参数instanceof
?或者至少通过传递一个类型的类而不是一个虚拟实例。
我知道泛型用于编译时类型检查,但是有解决方法吗?
采纳答案by laz
You could define callFriend
this way:
你可以这样定义callFriend
:
public <T extends Animal> T callFriend(String name, Class<T> type) {
return type.cast(friends.get(name));
}
Then call it as such:
然后这样称呼它:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
This code has the benefit of not generating any compiler warnings. Of course this is really just an updated version of casting from the pre-generic days and doesn't add any additional safety.
此代码的好处是不生成任何编译器警告。当然,这实际上只是从前通用时代开始的更新版本,并没有增加任何额外的安全性。
回答by David Schmitt
No. The compiler can't know what type jerry.callFriend("spike")
would return. Also, your implementation just hides the cast in the method without any additional type safety. Consider this:
不。编译器不知道jerry.callFriend("spike")
会返回什么类型。此外,您的实现只是隐藏了方法中的强制转换,而没有任何额外的类型安全。考虑一下:
jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast
In this specific case, creating an abstract talk()
method and overriding it appropriately in the subclasses would serve you much better:
在这种特定情况下,创建一个抽象talk()
方法并在子类中适当地覆盖它会更好地为您服务:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
回答by sk.
Not really, because as you say, the compiler only knows that callFriend() is returning an Animal, not a Dog or Duck.
不是真的,因为正如您所说,编译器只知道 callFriend() 返回的是 Animal,而不是 Dog 或 Duck。
Can you not add an abstract makeNoise() method to Animal that would be implemented as a bark or quack by its subclasses?
你不能向 Animal 添加一个抽象的 makeNoise() 方法,它会被它的子类实现为 bark 或 quack 吗?
回答by Michael Myers
You could implement it like this:
你可以像这样实现它:
@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
return (T)friends.get(name);
}
(Yes, this is legal code; see Java Generics: Generic type defined as return type only.)
(是的,这是合法的代码;请参阅Java 泛型:仅定义为返回类型的泛型类型。)
The return type will be inferred from the caller. However, note the @SuppressWarnings
annotation: that tells you that this code isn't typesafe. You have to verify it yourself, or you could get ClassCastExceptions
at runtime.
将从调用者推断返回类型。但是,请注意@SuppressWarnings
注释:它告诉您此代码不是 typesafe。你必须自己验证,否则你可以ClassCastExceptions
在运行时得到。
Unfortunately, the way you're using it (without assigning the return value to a temporary variable), the only way to make the compiler happy is to call it like this:
不幸的是,您使用它的方式(没有将返回值分配给临时变量),使编译器满意的唯一方法是这样调用它:
jerry.<Dog>callFriend("spike").bark();
While this may be a little nicer than casting, you are probably better off giving the Animal
class an abstract talk()
method, as David Schmitt said.
尽管这可能比强制转换要好一些,但正如 David Schmitt 所说,您最好为Animal
类提供一个抽象talk()
方法。
回答by Michael Borgwardt
Not possible. How is the Map supposed to know which subclass of Animal it's going to get, given only a String key?
不可能。仅给定一个 String 键,Map 如何知道它将获得 Animal 的哪个子类?
The only way this would be possible is if each Animal accepted only one type of friend (then it could be a parameter of the Animal class), or of the callFriend() method got a type parameter. But it really looks like you're missing the point of inheritance: it's that you can only treat subclasses uniformly when using exclusively the superclass methods.
唯一可能的方法是,如果每个 Animal 只接受一种类型的朋友(那么它可以是 Animal 类的参数),或者 callFriend() 方法得到一个类型参数。但看起来您确实错过了继承的重点:当仅使用超类方法时,您只能统一对待子类。
回答by Fabian Steeg
As you said passing a class would be OK, you could write this:
正如你所说的通过一个类就可以了,你可以这样写:
public <T extends Animal> T callFriend(String name, Class<T> clazz) {
return (T) friends.get(name);
}
And then use it like this:
然后像这样使用它:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
Not perfect, but this is pretty much as far as you get with Java generics. There is a way to implement Typesafe Heterogenous Containers (THC) using Super Type Tokens, but that has its own problems again.
不完美,但这与 Java 泛型差不多。有一种方法可以使用 Super Type Tokens来实现Typesafe Heterogenous Containers (THC),但这又存在其自身的问题。
回答by Craig P. Motlin
This question is very similar to Item 29 in Effective Java- "Consider typesafe heterogeneous containers." Laz's answer is the closest to Bloch's solution. However, both put and get should use the Class literal for safety. The signatures would become:
这个问题与Effective Java 中的第 29 条非常相似——“考虑类型安全的异构容器”。Laz 的答案最接近 Bloch 的解决方案。但是,为了安全起见, 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);
Inside both methods you should check that the parameters are sane. See Effective Java and the Classjavadoc for more info.
在这两种方法中,您应该检查参数是否正常。有关详细信息,请参阅 Effective Java 和Classjavadoc。
回答by Mike Houston
Based on the same idea as Super Type Tokens, you could create a typed id to use instead of a string:
基于与 Super Type Tokens 相同的想法,您可以创建一个类型化的 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];
}
}
But I think this may defeat the purpose, since you now need to create new id objects for each string and hold on to them (or reconstruct them with the correct type information).
但我认为这可能会违背目的,因为您现在需要为每个字符串创建新的 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());
But you can now use the class in the way you originally wanted, without the casts.
但是您现在可以按照您最初想要的方式使用该类,而无需进行强制转换。
jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();
This is just hiding the type parameter inside the id, although it does mean you can retrieve the type from the identifier later if you wish.
这只是将类型参数隐藏在 id 中,尽管这确实意味着您可以稍后从标识符中检索类型,如果您愿意的话。
You'd need to implement the comparison and hashing methods of TypedID too if you want to be able to compare two identical instances of an id.
如果您希望能够比较一个 id 的两个相同实例,您还需要实现 TypedID 的比较和散列方法。
回答by Richard Gomes
I've written an article which contains a proof of concept, support classes and a test class which demonstrates how Super Type Tokens can be retrieved by your classes at runtime. In a nutshell, it allows you to delegate to alternative implementations depending on actual generic parameters passed by the caller. Example:
我写了一篇文章,其中包含一个概念证明、支持类和一个测试类,它演示了您的类如何在运行时检索超级类型令牌。简而言之,它允许您根据调用者传递的实际通用参数委托替代实现。例子:
TimeSeries<Double>
delegates to a private inner class which usesdouble[]
TimeSeries<OHLC>
delegates to a private inner class which usesArrayList<OHLC>
TimeSeries<Double>
委托给使用的私有内部类double[]
TimeSeries<OHLC>
委托给使用的私有内部类ArrayList<OHLC>
See: Using TypeTokens to retrieve generic parameters
请参阅: 使用 TypeTokens 检索泛型参数
Thanks
谢谢
Richard Gomes - Blog
理查德戈麦斯 -博客
回答by Richard Gomes
I know this is a completely different thing that the one asked. Another way of resolving this would be reflection. I mean, this does not take the benefit from Generics, but it lets you emulate, in some way, the behavior you want to perform (make a dog bark, make a duck quack, etc.) without taking care of type casting:
我知道这是一个完全不同的问题。解决这个问题的另一种方法是反射。我的意思是,这不会从泛型中受益,但它可以让您以某种方式模拟您想要执行的行为(发出狗吠声、发出鸭子嘎嘎声等),而无需考虑类型转换:
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
abstract class AnimalExample {
private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
private Map<String,Object> theFriends = new HashMap<String,Object>();
public void addFriend(String name, Object friend){
friends.put(name,friend.getClass());
theFriends.put(name, friend);
}
public void makeMyFriendSpeak(String name){
try {
friends.get(name).getMethod("speak").invoke(theFriends.get(name));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public abstract void speak ();
};
class Dog extends Animal {
public void speak () {
System.out.println("woof!");
}
}
class Duck extends Animal {
public void speak () {
System.out.println("quack!");
}
}
class Cat extends Animal {
public void speak () {
System.out.println("miauu!");
}
}
public class AnimalExample {
public static void main (String [] args) {
Cat felix = new Cat ();
felix.addFriend("Spike", new Dog());
felix.addFriend("Donald", new Duck());
felix.makeMyFriendSpeak("Spike");
felix.makeMyFriendSpeak("Donald");
}
}