考虑下面的例子(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的额外参数的情况下在运行时找出返回类型?或者至少通过传递该类型的类而不是虚拟实例。
我知道泛型是用于编译时类型检查的,但是对此有解决方法吗?
这里有很多很好的答案,但这是我在Appium测试中采用的方法,在该测试中,对单个元素的操作可能导致基于用户设置的不同应用程序状态。虽然它没有遵循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是该类型扩展的超类,这意味着您可以使用它的任何子类(胡说)
type.getConstructor(Param.class等)允许您与
类型的构造函数。这个构造函数在所有期望的类之间应该是相同的。
newInstance接受一个声明的变量,您希望将该变量传递给new objects构造函数
如果你不想抛出错误,你可以像这样捕获它们:
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;
}
基于与超级类型令牌相同的思想,你可以创建一个类型化的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的比较和散列方法。
不。编译器无法知道jerry.callFriend("spike")将返回什么类型。此外,您的实现只是将强制转换隐藏在方法中,而没有任何额外的类型安全。考虑一下:
jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast
在这种情况下,创建一个抽象的talk()方法并在子类中适当地重写它会更好地为你服务:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
我知道这和刚才那个人问的完全不同。解决这个问题的另一种方法是反思。我的意思是,这并没有从泛型中获益,但它让你在某种程度上模拟你想要执行的行为(让狗叫,让鸭子嘎嘎叫,等等),而不考虑类型转换:
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");
}
}