How do I make the method return type generic?

前端 未结 19 2426
無奈伤痛
無奈伤痛 2020-11-22 06:16

Consider this example (typical in OOP books):

I have an Animal class, where each Animal can have many friends.
And subclasses like

相关标签:
19条回答
  • 2020-11-22 06:23

    "Is there a way to figure out the return type at runtime without the extra parameter using instanceof?"

    As an alternative solution you could utilise the Visitor pattern like this. Make Animal abstract and make it implement Visitable:

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

    Visitable just means that an Animal implementation is willing to accept a visitor:

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

    And a visitor implementation is able to visit all the subclasses of an animal:

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

    So for example a Dog implementation would then look like this:

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

    The trick here is that as the Dog knows what type it is it can trigger the relevant overloaded visit method of the visitor v by passing "this" as a parameter. Other subclasses would implement accept() exactly the same way.

    The class that wants to call subclass specific methods must then implement the Visitor interface like this:

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

    I know it's a lot more interfaces and methods than you bargained for, but it's a standard way to get a handle on every specific subtype with precisely zero instanceof checks and zero type casts. And it's all done in a standard language agnostic fashion so it's not just for Java but any OO language should work the same.

    0 讨论(0)
  • 2020-11-22 06:26

    There are a lot of great answers here, but this is the approach I took for an Appium test where acting on a single element can result in going to different application states based on the user's settings. While it doesn't follow the conventions of OP's example, I hope it helps someone.

    public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //signInButton.click();
        return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
    }
    
    • MobilePage is the super class that the type extends meaning you can use any of its children (duh)
    • type.getConstructor(Param.class, etc) allows you to interact with the constructor of the type. This constructor should be the same between all expected classes.
    • newInstance takes a declared variable that you want to pass to the new objects constructor

    If you don't want to throw the errors you can catch them like so:

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 06:26

    As the question is based in hypothetical data here is a good exemple returning a generic that extends Comparable interface.

    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"));
        }
    }
    
    0 讨论(0)
  • 2020-11-22 06:26

    I know this is a completely different thing that the one asked. Another way of resolving this would be reflection. I mean, this does not take the benefit from Generics, but it lets you emulate, in some way, the behavior you want to perform (make a dog bark, make a duck quack, etc.) without taking care of type casting:

    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");
    
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 06:27

    Not possible. How is the Map supposed to know which subclass of Animal it's going to get, given only a String key?

    The only way this would be possible is if each Animal accepted only one type of friend (then it could be a parameter of the Animal class), or of the callFriend() method got a type parameter. But it really looks like you're missing the point of inheritance: it's that you can only treat subclasses uniformly when using exclusively the superclass methods.

    0 讨论(0)
  • 2020-11-22 06:29

    This question is very similar to Item 29 in Effective Java - "Consider typesafe heterogeneous containers." Laz's answer is the closest to Bloch's solution. However, both put and get should use the Class literal for safety. The signatures would become:

    public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
    public <T extends Animal> T callFriend(String name, Class<T> type);
    

    Inside both methods you should check that the parameters are sane. See Effective Java and the Class javadoc for more info.

    0 讨论(0)
提交回复
热议问题