How do I make the method return type generic?

前端 未结 19 2407
無奈伤痛
無奈伤痛 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:30

    What you're looking for here is abstraction. Code against interfaces more and you should have to do less casting.

    The example below is in C# but the concept remains the same.

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

    Additionally you can ask the method to return the value in a given type this way

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

    More examples here at Oracle Java documentation

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

    You could define callFriend this way:

    public <T extends Animal> T callFriend(String name, Class<T> type) {
        return type.cast(friends.get(name));
    }
    

    Then call it as such:

    jerry.callFriend("spike", Dog.class).bark();
    jerry.callFriend("quacker", Duck.class).quack();
    

    This code has the benefit of not generating any compiler warnings. Of course this is really just an updated version of casting from the pre-generic days and doesn't add any additional safety.

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

    Here is the simpler version:

    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
    }
    

    Fully working code:

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

    You could implement it like this:

    @SuppressWarnings("unchecked")
    public <T extends Animal> T callFriend(String name) {
        return (T)friends.get(name);
    }
    

    (Yes, this is legal code; see Java Generics: Generic type defined as return type only.)

    The return type will be inferred from the caller. However, note the @SuppressWarnings annotation: that tells you that this code isn't typesafe. You have to verify it yourself, or you could get ClassCastExceptions at runtime.

    Unfortunately, the way you're using it (without assigning the return value to a temporary variable), the only way to make the compiler happy is to call it like this:

    jerry.<Dog>callFriend("spike").bark();
    

    While this may be a little nicer than casting, you are probably better off giving the Animal class an abstract talk() method, as David Schmitt said.

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

    As you said passing a class would be OK, you could write this:

    public <T extends Animal> T callFriend(String name, Class<T> clazz) {
       return (T) friends.get(name);
    }
    

    And then use it like this:

    jerry.callFriend("spike", Dog.class).bark();
    jerry.callFriend("quacker", Duck.class).quack();
    

    Not perfect, but this is pretty much as far as you get with Java generics. There is a way to implement Typesafe Heterogenous Containers (THC) using Super Type Tokens, but that has its own problems again.

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