考虑下面的例子(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的额外参数的情况下在运行时找出返回类型?或者至少通过传递该类型的类而不是虚拟实例。 我知道泛型是用于编译时类型检查的,但是对此有解决方法吗?


当前回答

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

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

See:

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

谢谢

Richard Gomes -博客

其他回答

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

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

“有没有一种方法可以在不使用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,而且适用于任何面向对象语言。

不可能的。如果只给一个String键,Map怎么知道它将得到Animal的哪个子类呢?

唯一可能的方法是每个Animal只接受一种类型的friend(那么它可以是Animal类的一个参数),或者callFriend()方法获得了一个类型参数。但是,看起来您确实忽略了继承的要点:在独占地使用超类方法时,只能统一地对待子类。

我知道这和刚才那个人问的完全不同。解决这个问题的另一种方法是反思。我的意思是,这并没有从泛型中获益,但它让你在某种程度上模拟你想要执行的行为(让狗叫,让鸭子嘎嘎叫,等等),而不考虑类型转换:

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

    }

}

由于这个问题是基于假设的数据,这里有一个很好的例子,返回一个扩展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"));
    }
}