How do I make the method return type generic?
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 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 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?
Answer by David Schmitt for How do I make the method return type generic?
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();
Answer by sk. for How do I make the method return type generic?
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?
Answer by Michael Myers for How do I make the method return type generic?
You could implement it like this:
@SuppressWarnings("unchecked") public 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.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.
Answer by Michael Borgwardt for How do I make the method return type generic?
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.
Answer by laz for How do I make the method return type generic?
You could define callFriend
this way:
public T callFriend(String name, Class 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.
Answer by Fabian Steeg for How do I make the method return type generic?
As you said passing a class would be OK, you could write this:
public T callFriend(String name, Class 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.
Answer by Craig P. Motlin for How do I make the method return type generic?
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 void addFriend(String name, Class type, T animal); public Animal callFriend(String name, Class type);
Inside both methods you should check that the parameters are sane. See Effective Java and the Class javadoc for more info.
Answer by Mike Houston for How do I make the method return type generic?
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 { 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 spike = new TypedID("spike") {}; TypedID quacker = new TypedID("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.
Answer by Richard Gomes for How do I make the method return type generic?
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 usesdouble[]
TimeSeries
delegates to a private inner class which usesArrayList
See: Using TypeTokens to retrieve generic parameters
Thanks
Richard Gomes - Blog
Answer by user743489 for How do I make the method return type generic?
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> friends = new HashMap>(); private Map theFriends = new HashMap(); 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"); } }
Answer by Xerosigma for How do I make the method return type generic?
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(string name) where T : _Animal; } interface _Animal { void Speak(); } abstract class AnimalBase : _Animal, _HasFriends { private Dictionary friends = new Dictionary(); public abstract void Speak(); public void AddFriend(string name, _Animal animal) { friends.Add(name, animal); } public T CallFriend(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!"); } } }
Answer by gafadr for How do I make the method return type generic?
what about
public class Animal { private Map> friends = new HashMap>(); public void addFriend(String name, T animal){ friends.put(name,animal); } public T callFriend(String name){ return friends.get(name); }
}
Answer by Antti Siiskonen for How do I make the method return type generic?
"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 friends = new HashMap(); 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.
Answer by R.Moeller for How do I make the method return type generic?
I did the following in my lib kontraktor:
public class Actor { public SELF self() { return (SELF)_self; } }
subclassing:
public class MyHttpAppSession extends Actor { ... }
at least this works inside the current class and when having a strong typed reference. Multiple inheritance works, but gets really tricky then :)
Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72
0 comments:
Post a Comment