考虑下面的例子(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的额外参数的情况下在运行时找出返回类型?或者至少通过传递该类型的类而不是虚拟实例。
我知道泛型是用于编译时类型检查的,但是对此有解决方法吗?
你可以这样实现它:
@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
return (T)friends.get(name);
}
(是的,这是法律代码;参见Java泛型:仅定义为返回类型的泛型类型。)
返回类型将从调用者推断出来。但是,请注意@SuppressWarnings注释:它告诉您这段代码不是类型安全的。您必须自己验证它,或者您可以在运行时获得classcastexception。
不幸的是,你使用它的方式(没有将返回值分配给临时变量),唯一让编译器高兴的方法是像这样调用它:
jerry.<Dog>callFriend("spike").bark();
虽然这可能比强制转换要好一点,但正如David Schmitt所说,最好给Animal类一个抽象的talk()方法。
“有没有一种方法可以在不使用instanceof的额外参数的情况下在运行时找出返回类型?”
作为另一种解决方案,您可以像这样利用访问者模式。使动物抽象,使其实现可访问:
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);
}
}
可访问的意思是Animal的实现愿意接受一个访问者:
public interface Visitable {
void accept(Visitor v);
}
而一个visitor实现能够访问动物的所有子类:
public interface Visitor {
void visit(Dog d);
void visit(Duck d);
void visit(Mouse m);
}
例如,Dog的实现是这样的:
public class Dog extends Animal {
public void bark() {}
@Override
public void accept(Visitor v) { v.visit(this); }
}
这里的技巧是,当Dog知道它是什么类型时,它可以通过传递“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(); }
}
我知道它的接口和方法比您所期望的要多得多,但是它是一种标准的方法,可以用精确的零instanceof检查和零类型强制转换来处理每个特定的子类型。而且这一切都是以一种标准语言不可知的方式完成的,因此它不仅适用于Java,而且适用于任何面向对象语言。
不。编译器无法知道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();
由于这个问题是基于假设的数据,这里有一个很好的例子,返回一个扩展Comparable接口的泛型。
public class MaximumTest {
// find the max value using Comparable interface
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // assume that x is initially the largest
if (y.compareTo(max) > 0){
max = y; // y is the large now
}
if (z.compareTo(max) > 0){
max = z; // z is the large now
}
return max; // returns the maximum value
}
//testing with an ordinary main method
public static void main(String args[]) {
System.out.printf("Maximum of %d, %d and %d is %d\n\n", 3, 4, 5, maximum(3, 4, 5));
System.out.printf("Maximum of %.1f, %.1f and %.1f is %.1f\n\n", 6.6, 8.8, 7.7, maximum(6.6, 8.8, 7.7));
System.out.printf("Maximum of %s, %s and %s is %s\n", "strawberry", "apple", "orange",
maximum("strawberry", "apple", "orange"));
}
}
你在这里寻找的是抽象。多针对接口编写代码,就应该少做类型转换。
下面的例子是用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!");
}
}
}