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


当前回答

不可能的。如果只给一个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");

    }

}

此外,您还可以通过这种方式要求该方法以给定类型返回值

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

更多示例请参见Oracle Java文档

不完全是,因为如你所说,编译器只知道callFriend()返回的是Animal,而不是Dog或Duck。

你不能给Animal添加一个抽象的makeNoise()方法吗?这个方法将被它的子类实现为树皮或嘎嘎声。

这个问题与Effective Java中的第29项非常相似——“考虑类型安全的异构容器”。拉兹的答案最接近布洛赫的答案。但是,为了安全起见,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> T callFriend(String name) {
    return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}

完全工作的代码:

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

        }
    }