我如何使方法返回types通用?

考虑这个例子(典型的OOP书籍):

我有一个Animal课,每个Animal可以有很多朋友。
DogDuckMouse等子类添加特定的行为,如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); } } 

以下是一些包含大量types转码的代码片段:

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

有什么办法可以使用generics的返回types来摆脱types转换,所以我可以说

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

下面是一些初始代码,返回types作为一个从未使用的parameter passing给方法。

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

有没有办法找出运行时返回types没有使用instanceof额外的参数? 或者至less通过传递一个类的类而不是一个虚拟实例。
我知道generics是编译时types检查,但有没有解决方法呢?

你可以这样定义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(); 

这段代码有没有产生任何编译器警告的好处。 当然,这实际上只是从通用date开始的更新版本,并没有增加额外的安全性。

编号不知道什么types的jerry.callFriend("spike")会返回。 另外,你的实现只隐藏了方法中的强制types,而没有任何额外的types安全性。 考虑这个:

 jerry.addFriend("quaker", new Duck()); jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast 

在这个特定的情况下,创build一个抽象的talk()方法并在子类中适当地覆盖它将为您提供更好的服务:

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

你可以像这样实现它:

 @SuppressWarnings("unchecked") public <T extends Animal> T callFriend(String name) { return (T)friends.get(name); } 

(是的,这是合法的代码;请参阅Javagenerics:generics仅定义为返回types 。)

返回types将从调用者推断。 不过,请注意@SuppressWarnings注释:它告诉你这个代码不是types安全的 。 你必须自己validation它,否则你可能会在运行时得到ClassCastExceptions

不幸的是,你使用它的方式(没有把返回值赋给一个临时variables),让编译器开心的唯一方法就是这样调用它:

 jerry.<Dog>callFriend("spike").bark(); 

虽然这可能比铸造好一点,但如同David Schmitt所说,你最好给Animal课一个抽象的talk()方法。

这个问题与Effective Java中的第29项非常相似 – “考虑types安全的异构容器”。 拉兹的答案是最接近布洛赫的解决scheme。 然而,为了安全,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。

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

 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(); 

不完美,但是这与Javagenerics相差无几。 有一种使用超级types令牌来实现Typesafe Heterogenous Containers(THC)的方法 ,但是这又有其自身的问题。

不可能。 如果Map只知道一个String键,它应该知道Animal的哪个子类呢?

唯一的方法是如果每只动物只接受一种types的朋友(那么它可能是Animal类的一个参数),或者callFriend()方法有一个types参数。 但是它看起来像是遗漏了inheritance点:只能使用超类方法时才能统一处理子类。

基于与超级types令牌相同的想法,您可以创build一个types化的ID来代替string:

 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]; } } 

但我认为这可能会失败的目的,因为你现在需要为每个string创build新的id对象,并保留它们(或用正确的types信息重build它们)。

 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中的types参数,尽pipe这意味着如果你愿意的话,你可以稍后从标识符中获取types。

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

另外你可以让方法以这种方式返回给定types的值

 <T> T methodName(Class<T> var); 

Oracle Java文档中的更多示例

我写了一篇文章,其中包含概念validation,支持类和testing类,演示了如何在运行时通过类获取超types令牌。 简而言之,它允许您根据调用者传递的实际generics参数将代理委托给其他实现。 例:

  • TimeSeries<Double>委托给使用double[]的私有内部类
  • TimeSeries<OHLC>委托给使用ArrayList<OHLC>的私有内部类

请参阅: 使用TypeTokens检索通用参数

谢谢

理查德·戈麦斯 – 博客

“有没有办法找出运行时的返回types没有额外的参数使用instanceof?

作为替代解决scheme,您可以像这样使用Visitor模式 。 使动物抽象,使其实现可视化:

 abstract public class Animal implements Visitable { private Map<String,Animal> friends = new HashMap<String,Animal>(); public void addFriend(String name, Animal animal){ friends.put(name,animal); } public Animal callFriend(String name){ return friends.get(name); } } 

可见只是意味着一个动物实施愿意接受一个访客:

 public interface Visitable { void accept(Visitor v); } 

访客实现可以访问动物的所有子类:

 public interface Visitor { void visit(Dog d); void visit(Duck d); void visit(Mouse m); } 

所以例如一个狗的实现将如下所示:

 public class Dog extends Animal { public void bark() {} @Override public void accept(Visitor v) { v.visit(this); } } 

这里的技巧是,由于Dog知道它是什么types,它可以通过传递“this”作为参数来触发访问者v的相关重载访问方法。 其他的子类将以完全相同的方式实现accept()。

想要调用子类特定方法的类必须像这样实现Visitor接口:

 public class Example implements Visitor { public void main() { Mouse jerry = new Mouse(); jerry.addFriend("spike", new Dog()); jerry.addFriend("quacker", new Duck()); // Used to be: ((Dog) jerry.callFriend("spike")).bark(); jerry.callFriend("spike").accept(this); // Used to be: ((Duck) jerry.callFriend("quacker")).quack(); jerry.callFriend("quacker").accept(this); } // This would fire on callFriend("spike").accept(this) @Override public void visit(Dog d) { d.bark(); } // This would fire on callFriend("quacker").accept(this) @Override public void visit(Duck d) { d.quack(); } @Override public void visit(Mouse m) { m.squeak(); } } 

我知道这是比你讨价还价的更多的接口和方法,但它是一个标准的方式来获得每个特定的子types的句柄,具有零实例检查和零types转换。 这一切都是以标准的语言不可知的方式完成的,所以它不仅适用于Java,而且任何面向对象的语言都应该是一样的。

 Here is the simpler version public <T> T callFriend(String name) { return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do } ====================== Fully working code public class Test { public static class Animal { private Map<String,Animal> friends = new HashMap<>(); public void addFriend(String name, Animal animal){ friends.put(name,animal); } public <T> T callFriend(String name){ return (T) friends.get(name); } } public static class Dog extends Animal { public void bark() { System.out.println("i am dog"); } } public static class Duck extends Animal { public void quack() { System.out.println("i am duck"); } } public static void main(String [] args) { Animal animals = new Animal(); animals.addFriend("dog", new Dog()); animals.addFriend("duck", new Duck()); Dog dog = animals.callFriend("dog"); dog.bark(); Duck duck = animals.callFriend("duck"); duck.quack(); } } 

不是真的,因为正如你所说,编译器只知道callFriend()正在返回一个动物,而不是一个狗或鸭。

你能不能给Animal添加一个抽象的makeNoise()方法,通过子类来实现它作为树皮或者庸医?

你在这里找的是抽象。 代码更多的接口,你应该做less铸造。

下面的例子是在C#中,但概念保持不变。

 using System; using System.Collections.Generic; using System.Reflection; namespace GenericsTest { class MainClass { public static void Main (string[] args) { _HasFriends jerry = new Mouse(); jerry.AddFriend("spike", new Dog()); jerry.AddFriend("quacker", new Duck()); jerry.CallFriend<_Animal>("spike").Speak(); jerry.CallFriend<_Animal>("quacker").Speak(); } } interface _HasFriends { void AddFriend(string name, _Animal animal); T CallFriend<T>(string name) where T : _Animal; } interface _Animal { void Speak(); } abstract class AnimalBase : _Animal, _HasFriends { private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>(); public abstract void Speak(); public void AddFriend(string name, _Animal animal) { friends.Add(name, animal); } public T CallFriend<T>(string name) where T : _Animal { return (T) friends[name]; } } class Mouse : AnimalBase { public override void Speak() { Squeek(); } private void Squeek() { Console.WriteLine ("Squeek! Squeek!"); } } class Dog : AnimalBase { public override void Speak() { Bark(); } private void Bark() { Console.WriteLine ("Woof!"); } } class Duck : AnimalBase { public override void Speak() { Quack(); } private void Quack() { Console.WriteLine ("Quack! Quack!"); } } } 

我知道这是一个完全不同的东西,那个问。 解决这个问题的另一种方法是反思。 我的意思是,这不会从generics中受益,但是它可以让你在某种程度上效仿你想要执行的行为(制作一个狗吠,制作鸭子等)

 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"); } } 

关于什么

 public class Animal { private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>(); public <T extends Animal> void addFriend(String name, T animal){ friends.put(name,animal); } public <T extends Animal> T callFriend(String name){ return friends.get(name); } 

}

我在我的自由软件中做了以下工作:

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

子类:

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

至less可以在当前类中使用,并且具有强types引用。 多inheritance的作品,但变得非常棘手,然后:)

还有另一种方法,你可以缩小返回types,当你重写一个方法。 在每个子类中,您将不得不覆盖callFriend以返回该子类。 成本将是callFriend的多个声明,但是你可以将公共部分隔离到一个叫做internal的方法。 这似乎比上面提到的解决scheme简单得多,并且不需要额外的参数来确定返回types。

这里有很多很好的答案,但这是我为一个Appiumtesting所采取的方法,在单个元素上执行操作可能导致根据用户的设置转到不同的应用程序状态。 虽然它不遵循OP的例子,但我希望它能帮助别人。

 public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //signInButton.click(); return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver); } 
  • MobilePage是types扩展的超类,这意味着你可以使用它的任何一个子(duh)
  • type.getConstructor(Param.class等)允许你与types的构造函数进行交互。 这个构造函数在所有期望的类之间应该是相同的。
  • newInstance需要一个你想传递给新对象构造函数的声明variables

如果你不想抛出错误,你可以像这样捕获它们:

 public <T extends MobilePage> T tapSignInButton(Class<T> type) { // signInButton.click(); T returnValue = null; try { returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver); } catch (Exception e) { e.printStackTrace(); } return returnValue; } 
 public <X,Y> X nextRow(Y cursor) { return (X) getRow(cursor); } private <T> Person getRow(T cursor) { Cursor c = (Cursor) cursor; Person s = null; if (!c.moveToNext()) { c.close(); } else { String id = c.getString(c.getColumnIndex("id")); String name = c.getString(c.getColumnIndex("name")); s = new Person(); s.setId(id); s.setName(name); } return s; } 

你可以返回任何types,并直接接收。 不需要强制转换。

 Person p = nextRow(cursor); // cursor is real database cursor. 

这是最好的,如果你想定制任何其他types的logging,而不是真正的游标。