ChatGPT解决这个技术问题 Extra ChatGPT

How do I make the method return type generic?

Consider this example (typical in OOP books):

I have an Animal class, where each Animal can have many friends.
And subclasses like Dog, Duck, Mouse etc which add specific behavior like bark(), quack() etc.

Here's the Animal class:

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

And here's some code snippet with lots of typecasting:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

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

Is there any way I can use generics for the return type to get rid of the typecasting, so that I can say

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

Here's some initial code with return type conveyed to the method as a parameter that's never used.

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

Is there a way to figure out the return type at runtime without the extra parameter using instanceof? Or at least by passing a class of the type instead of a dummy instance.
I understand generics are for compile time type-checking, but is there a workaround for this?


l
laz

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.


... but still has no compile time type checking between the parameters of the callFriend() call.
This is the best answer so far - but you ought to change addFriend in the same way. It makes it harder to write bugs since you need that class literal in both places.
@Jaider, not exactly the same but this will work: // Animal Class public T CallFriend(string name) where T : Animal { return friends[name] as T; } // Calling Class jerry.CallFriend("spike").Bark(); jerry.CallFriend("quacker").Quack();
C
Community

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.


Method chaining was not really an intention. i don't mind assigning the value to a Subtyped variable and using it. Thanks for the solution.
this works perfectly when doing method call chaining!
I really like this syntax. I think in C# it's jerry.CallFriend<Dog>(... which I think looks better.
Interesting that the JRE's own java.util.Collections.emptyList() function is implemented exactly like this, and its javadoc advertises itself as being typesafe.
@TiStrga: It is interesting! The reason Collections.emptyList() can get away with it is that per definition of an empty list, there is no element object of type T. So there is no risk of casting an object to the wrong type. The list objects themselves can work with any types, as long as there are not elements.
A
Andrei Volgin

No. The compiler can't know what type jerry.callFriend("spike") would return. Also, your implementation just hides the cast in the method without any additional type safety. Consider this:

jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast

In this specific case, creating an abstract talk() method and overriding it appropriately in the subclasses would serve you much better:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();

While mmyers method may work, I think this method is better OO programming and will save you some trouble in the future.
This is the correct way get achieve the same result. Notice the goal is to obtain derived class specific behavior at runtime without explicitly writing code yourself to do the ugly type checking and casting. The method proposed by @laz works, but throws type safety out the window. This method requires less lines of code (because method implementations are late bound and looked up at runtime anyway) but still let's you easily define unique behavior for each subclass of Animal.
But the original question isn't asking about type safety. The way I read it the asker just wants to know if there is a way to leverage generics to avoid having to cast.
@laz: yes, the original question--as posed--is not about type safety. That doesn't change the fact that there is a safe way to implement that, eliminating class cast failures. See also weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/…
I don't disagree about that, but we are dealing with Java and all of its design decisions/foibles. I see this question as just trying to learn what is possible in Java generics, not as an xyproblem (meta.stackexchange.com/questions/66377/what-is-the-xy-problem) that needs to be re-engineered. Like any pattern or approach, there are times when the code I provided is appropriate and times when something totally different (like what you have suggested in this answer) is required.
C
Craig P. Motlin

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.


m
mppfiles

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

        }
    }

What does Casting to T not needed in this case but it's a good practice to do. I mean if it's taken care of properly during runtime what does "good practice" mean?
I meant to say explicit casting (T) not necessary since the return type declartion on the method declaration should be sufficient enough
i
isuru chathuranga

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


F
Fabian Steeg

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.


I am sorry but this is the exact same answer laz has, so you are either copying him or he is copying you.
Thats a neat way to pass the Type. But still it aint type safe like Schmitt said. I could still pass a different class and the typecasting will bomb. mmyers 2nd reply to set the Type in Return type seems better
Nemo, if you check the posting time you'll see we posted them pretty much exactly at the same moment. Also, they are not exactly the same, only two lines.
@Fabian I posted a similar answer, but there's an important difference between Bloch's slides and what was published in Effective Java. He uses Class instead of TypeRef. But this is still a great answer.
K
Kothar

Based on the same idea as Super Type Tokens, you could create a typed id to use instead of a string:

public abstract class TypedID<T extends Animal> {
  public final Type type;
  public final String id;

  protected TypedID(String id) {
    this.id = id;
    Type superclass = getClass().getGenericSuperclass();
    if (superclass instanceof Class) {
      throw new RuntimeException("Missing type parameter.");
    }
    this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
  }
}

But I think this may defeat the purpose, since you now need to create new id objects for each string and hold on to them (or reconstruct them with the correct type information).

Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};

jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());

But you can now use the class in the way you originally wanted, without the casts.

jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();

This is just hiding the type parameter inside the id, although it does mean you can retrieve the type from the identifier later if you wish.

You'd need to implement the comparison and hashing methods of TypedID too if you want to be able to compare two identical instances of an id.


A
Antti Siiskonen

"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.


M
Michael Borgwardt

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.


R
Richard Gomes

I've written an article which contains a proof of concept, support classes and a test class which demonstrates how Super Type Tokens can be retrieved by your classes at runtime. In a nutshell, it allows you to delegate to alternative implementations depending on actual generic parameters passed by the caller. Example:

TimeSeries delegates to a private inner class which uses double[]

TimeSeries delegates to a private inner class which uses ArrayList

See:

Using TypeTokens to retrieve generic parameters (web archive)

Using TypeTokens to retrieve generic parameters (blog)

Thanks

Richard Gomes - Blog


Indeed, thank you for sharing your insight, your article really explains it all!
The original blog post (which is awesome!) is available via Internet Archive here
@ScottBabcock : Thanks for letting me know about this broken link. I've posted a link to my new blog as well.
M
MTen

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

To my understanding, this is the best and most elegant way to use the Generics.
s
sk.

Not really, because as you say, the compiler only knows that callFriend() is returning an Animal, not a Dog or Duck.

Can you not add an abstract makeNoise() method to Animal that would be implemented as a bark or quack by its subclasses?


what if the animals have multiple methods which do not even fall under a common action that can be abstracted? I need this for communication between subclasses with different actions where im okay with passing the Type, not an instance.
You've really just answered your own question - if an animal has a unique action, then you have to cast to that specific animal. If an animal has an action that can be grouped with other animals, than you can define an abstract or virtual method in a base class and use that.
N
Nestor Ledon

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

This question has to do with coding not concept.
R
R.Moeller

I did the following in my lib kontraktor:

public class Actor<SELF extends Actor> {
    public SELF self() { return (SELF)_self; }
}

subclassing:

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

at least this works inside the current class and when having a strong typed reference. Multiple inheritance works, but gets really tricky then :)


佚名

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

    }

}

C
Cassio Seffrin

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

F
FeralWhippet

There is another approach, you can narrow the return type when you override a method. In each subclass you would have to override callFriend to return that subclass. The cost would be the multiple declarations of callFriend, but you could isolate the common parts to a method called internally. This seems a lot simpler to me than the solutions mentioned above, and does not need an extra argument to determine the return type.


I'm not sure what you mean by "narrow the return type". Afaik, Java and most typed languages don't overload methods or functions based on return type. For instance public int getValue(String name){} is indistinguishable from public boolean getValue(String name){} from the compilers point of view. You would need to either change the parameter type or add/remove parameters for the overload to be recognized. Maybe I'm just misunderstanding you though...
in java you can override a method in a subclass and specify a more "narrow" (i.e. more specific) return type. See stackoverflow.com/questions/14694852/….
D
Dharman

what about

public class Animal {
    private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>();

    public <T extends Animal> void addFriend(String name, T animal){
        friends.put(name,animal);
    }

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