Java Forum / First Aid / May 2008
Can this callback mechanism be generified?
Casper Bang - 02 May 2008 03:51 GMT Hello my fellow Java drinkers. I have a generics issue. Would it be possible to devise an API, which would allow me to register a callback *somewhere* associated with a class. Say I wish to allow a custom formatter to be installed:
1 interface Callback 2 { 3 String format(Object object); 4 } 5 6 class Somewhere 7 { 8 Map<Class, Callback> callbacks = new HashMap<Class, Callback>(); 9 10 public void installCallback(Class clazz, Callback callback) 11 { 12 callbacks.put(clazz, callback); 13 } 14 15 public void doCallback(Object obj) 16 { 17 Callback callback = callbacks.get( obj.getClass() ); 18 if(callback != null) 19 System.out.println( callback.format( obj ) ); 20 } 21 } 22 23 somewhereInstance.installCallback( Date.class, new Callback(){ 24 public String format(Object obj) 25 { 26 Date date = (Date)obj; 27 return SimpleDateFormat.getInstance().format(date); 28 } 29 });
The code works, but it requires casting as it very much revolves around a top-level Object. Is there any way to generify this, particularly the callback itself, since the type actually is known (line 23).
Thanks in advance, Casper
Mark Space - 02 May 2008 05:21 GMT Does this work? I didn't try it. I think the "doCallback()" method is ok without parameterized types... same with putting an unparameterized type into the map.
OTOH doesn't generics just insert the casts for you? I don't think you are saving any execution time here, and the code does not necessarily look more clear to me.
> 1 interface Callback interface Callback<C>
> 2 { > 3 String format(Object object); String format( C Object );
> 4 } > 5 [quoted text clipped - 16 lines] > 22 > 23 somewhereInstance.installCallback( Date.class, new Callback(){ somewhereInstance.installCallback( Date.class, new Callback<Date>(
> 24 public String format(Date obj) public String format(Date obj)
> 25 { > 26 Date date = (Date)obj; Date date = obj;
> 27 return SimpleDateFormat.getInstance().format(date); > 28 } > 29 }); Casper Bang - 02 May 2008 12:14 GMT > Does this work? I didn't try it. I think the "doCallback()" method is > ok without parameterized types... same with putting an unparameterized > type into the map. My problem is that of associating the type (Date.class, line 23) with the type of my callback (line 15 and 24). There doesn't seem to be a way of expressing this. I would be ok with the erasure (implicit casting) issue, if only I got type safety and the developer got shielded from having to do the casting.
However, generifying the callback into:
interface Callback<T> { String format(T object); }
...and trying to use that inside installCallback:
public <T> void installCallback(Callback<T> callback) { callbacks.put(callback.getClass().getTypeParameters()[0].getClass(), callback); }
...all I can get out is "T". Since this is by reflection, it is probably an erasure issue but it could also be because I'm not smart enough.
/Casper
Daniel Pitts - 02 May 2008 16:34 GMT > Hello my fellow Java drinkers. I have a generics issue. Would it be > possible to devise an API, which would allow me to register a callback [quoted text clipped - 37 lines] > Thanks in advance, > Casper This code should also work: interface Callback<T> { String format(T object); }
class Somewhere { Map<Class<?>, Callback<?>> callbacks = new HashMap<Class<?>, Callback<?>>();
public void installCallback(Class clazz, Callback callback) { callbacks.put(clazz, callback); }
public <T> void doCallback(T obj) { final Callback<? super T> callback = getCallback(obj); if(callback != null) System.out.println( callback.format( obj ) ); }
@SuppressWarnings({"unchecked"}) private <T> Callback<? super T> getCallback(T obj) { return (Callback<? super T>) callbacks.get(obj.getClass()); } }
class Main { public static void main(String[] args) { new Somewhere().installCallback( Date.class, new Callback<Date>(){ public String format(Date date) {
return SimpleDateFormat.getInstance().format(date); } }); } }
Of course, getCallback(T obj) should probably walk up the whole inheritance tree, because somewhere.doCallback(new java.sql.Date()) would have unexpected results.
 Signature Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>
Casper Bang - 02 May 2008 18:58 GMT Thanks a lot Daniel for the lesson in wildcards. :)
/Casper
Casper Bang - 02 May 2008 19:58 GMT I managed to make the API just slightly easier to use, by extracting the type from Callback<T> rather than requiring an explicit Class to be supplied along with it. It's not pretty but I'm not sure of how to extract type T, since I guess its not readily available on the account of generics by erasure:
interface Callback<T> { String format(T object); }
class Somewhere { Map<Class<?>, Callback<?>> callbacks = new HashMap<Class<?>, Callback<?>>();
public <T> void installCallback(Callback<T> callback) {
callbacks.put(extractGenericType( callback ), callback); }
private <T> Class extractGenericType(Callback<T> callback){ Class clazz = Object.class; String interfaces = callback.getClass().getGenericInterfaces()[0].toString(); int genTypeStart = interfaces.indexOf("<")+1; int genTypeEnd = interfaces.lastIndexOf(">"); String genType = interfaces.substring( genTypeStart, genTypeEnd);
try{ clazz = Class.forName(genType); } catch (ClassNotFoundException ex){}
return clazz; }
public <T> void doCallback(T obj) { final Callback<? super T> callback = getCallback(obj); if(callback != null) System.out.println( callback.format( obj ) ); }
@SuppressWarnings({"unchecked"}) private <T> Callback<? super T> getCallback(T obj) { return (Callback<? super T>) callbacks.get(obj.getClass()); } }
class Main { public static void main(String[] args) { new Somewhere().installCallback( new Callback<Date>(){ public String format(Date date) { return SimpleDateFormat.getInstance().format(date); } }); } }
/Casper
Mark Space - 02 May 2008 21:33 GMT > I managed to make the API just slightly easier to use, by extracting the > type from Callback<T> rather than requiring an explicit Class to be [quoted text clipped - 8 lines] > class Somewhere { > Map<Class<?>, Callback<?>> callbacks This I still don't get. What's the advantage of Class<?> over Class?
I like your original program better. All the random generics are just starting to obscure the actual design now.
Joshua Cranmer - 02 May 2008 21:59 GMT > This I still don't get. What's the advantage of Class<?> over Class? Class is the raw type, whereas Class<?> is the parameterized type. Start mixing the two, e.g. List<Class>, and you get rare types which causes bad ugliness. Most notably, List<Class<?>> != List<Class>, IIRC. I've been badly caught by this rare type problem before...
 Signature Beware of bugs in the above code; I have only proved it correct, not tried it. -- Donald E. Knuth
Mark Space - 04 May 2008 00:33 GMT >> This I still don't get. What's the advantage of Class<?> over Class? > > Class is the raw type, whereas Class<?> is the parameterized type. Start > mixing the two, e.g. List<Class>, and you get rare types which causes > bad ugliness. Most notably, List<Class<?>> != List<Class>, IIRC. I've > been badly caught by this rare type problem before... Yeah, I can see that would be an issue, from a strictly coding perspective. Does this work:
List<Class<?>> = (List<Class<?>>) (new List());
? Seems like it should, but I haven't played around with casting and parameterized types either. Obviously, it could throw runtime errors if the new list has items that are not Class<?> added somewhere else, but one has to presume some competence on the part of the coder too.
thufir - 02 May 2008 22:05 GMT >> class Somewhere { >> Map<Class<?>, Callback<?>> callbacks > > This I still don't get. What's the advantage of Class<?> over Class? Does the tutorial help?
http://java.sun.com/docs/books/tutorial/extra/generics/wildcards.html
I find generics confusing, but I assume that they exist for good reasons.
-Thufir
Daniel Pitts - 02 May 2008 22:14 GMT >> I managed to make the API just slightly easier to use, by extracting >> the type from Callback<T> rather than requiring an explicit Class to [quoted text clipped - 13 lines] > I like your original program better. All the random generics are just > starting to obscure the actual design now. Class is a raw type, and I believe raw types are deprecated.
 Signature Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>
Casper Bang - 03 May 2008 02:45 GMT > This I still don't get. What's the advantage of Class<?> over Class? > > I like your original program better. All the random generics are just > starting to obscure the actual design now. I understand what you mean, but if you view it from a consumers (of the API) point of view, I think it makes sense. I went from the old Java 1.0 way:
installCallback( Date.class, new Callback(){ public String format(Object obj) { Date date = (Date)obj; return SimpleDateFormat.getInstance().format(date); } });
...to this, where the type of my callback definition flows over to become the argument of the callback (thanks again Daniel) and gives me more type-safety and allows NetBeans to create the anonymous inner class with no need of casting:
installCallback( Date.class, new Callback<Date>(){ public String format(Date date) { return SimpleDateFormat.getInstance().format(date); } });
...to the final version, where the type of my callback is extracted rather than having to be supplied explicitly:
installCallback( new Callback<Date>(){ public String format(Date date) { return SimpleDateFormat.getInstance().format(date); } });
Without closures, I think that's as clean as I can get it. Only rough edge appears to be the way I extract the type from Callback.
/Casper
Mark Space - 04 May 2008 00:27 GMT > ...to the final version, where the type of my callback is extracted > rather than having to be supplied explicitly: [quoted text clipped - 4 lines] > } > }); I agree this part is easier to read, I just don't see the rest of the code much improved (pulling the type by name from the interface name was a pretty good trick. With luck, we'll get reifiable types in Java 7 and then nonsense like that will be unneeded.)
I guess I don't see the advantage of Class<?> over just Class, when you're going to cast it yourself anyway. Given the nature of the problem, the second form seems more clear and correct -- it says "I'm not using generics, I'm tracking the type of Class myself." Class<?> implies to me that you can't and won't resolve the type of Class<C> further.
I might be off base here, I haven't had a need to play around with generic beyond basic functions, so please take this as discussion, not dogma.
Casper Bang - 05 May 2008 03:01 GMT > I agree this part is easier to read, I just don't see the rest of the > code much improved (pulling the type by name from the interface name was > a pretty good trick. With luck, we'll get reifiable types in Java 7 and > then nonsense like that will be unneeded.) I did some reading (Java Generics and Collections) and it turns out there are ways to get to the types, in spite of erasure. So now I do it a bit cleaner, no pulling apart a string and no reflection required:
private <T> Class extractGenericType(Callback<T> callback) { Type[] interfaces = callback.getClass().getGenericInterfaces();
if(interfaces.length != 1 || !(interfaces[0] instanceof ParameterizedType)) throw new RuntimeException("You must supply a generified Callback with a single parameterized type");
Type[] parmTypes = ((ParameterizedType)interfaces[0]).getActualTypeArguments();
return (Class)parmTypes[0]; }
> I might be off base here, I haven't had a need to play around with > generic beyond basic functions, so please take this as discussion, not > dogma. Of course. :) Thanks a lot for the feedback!
Daniel Pitts - 05 May 2008 04:24 GMT Couple of notes, nothing terribly important...
>> I agree this part is easier to read, I just don't see the rest of the >> code much improved (pulling the type by name from the interface name [quoted text clipped - 4 lines] > there are ways to get to the types, in spite of erasure. So now I do it > a bit cleaner, no pulling apart a string and no reflection required: You expect this to return Class<T>, don't you? So why don't you declare it that way.
> private <T> Class extractGenericType(Callback<T> callback) > { > Type[] interfaces = callback.getClass().getGenericInterfaces(); > > if(interfaces.length != 1 || !(interfaces[0] instanceof > ParameterizedType)) Don't assume it should be exactly one, it would probably be better to go through a loop.
> throw new RuntimeException("You must supply a generified > Callback with a single parameterized type"); It would be more appropriate to throw an IllegalArgumentException I think. It would probably be even more appropriate to throw a CallbackException of your own creation.
> Type[] parmTypes = > ((ParameterizedType)interfaces[0]).getActualTypeArguments(); > > return (Class)parmTypes[0]; > } Hope this is useful.
 Signature Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>
Mark Space - 05 May 2008 05:06 GMT > Couple of notes, nothing terribly important...
>> { >> Type[] interfaces = callback.getClass().getGenericInterfaces(); [quoted text clipped - 3 lines] > Don't assume it should be exactly one, it would probably be better to go > through a loop. I noticed that too. Don't assume that Callback must be the first interface. Check the name of the interface too. An object could have several interfaces, only one of them "Callback."
In fact that might be the first thing I do in a real program, make a new object that is also it's own callback. No sense in proliferating objects.
public class EmployeeInfo implements Serializable, Cloneable, CallBack<EmployeeInfo>, Compareable<EmployeeInfo> {
// etc.
}
Casper Bang - 05 May 2008 19:54 GMT > In fact that might be the first thing I do in a real program, make a new > object that is also it's own callback. No sense in proliferating objects. [quoted text clipped - 5 lines] > Compareable<EmployeeInfo> > { Right, that use case should be covered. It's rarely practiced in my experiences), because often you need to implement an interface more than once, which of course you can't - in spite of having apparent different signatures which dispatches to two distinct callback methods:
public class Formatter implements Callback<Date>, Callback<String> ... Error: Repeated interface
/Casper
Lew - 06 May 2008 02:09 GMT > Right, that use case should be covered. It's rarely practiced in my > experiences), because often you need to implement an interface more than [quoted text clipped - 4 lines] > .... > Error: Repeated interface Well, no, they don't dispatch to distinct methods. Were the construct allowed, Formatter instances would have to dispatch to a common erased method. Generics are a compiler convenience, not runtime types.
 Signature Lew
Mark Space - 06 May 2008 05:23 GMT >> In fact that might be the first thing I do in a real program, make a >> new object that is also it's own callback. No sense in proliferating [quoted text clipped - 17 lines] > > /Casper That's not what I meant. Unless I'm totally confused here, you look for interface[0]. Which may or may not be the Callback interface. It could be some other interface at index 0.
Now maybe you know this and were just doing as you say a quick mock up, but I wanted to be clear.
Two parameterized interfaces of the same type aren't possible of course, since erasure means that you've declared the same interface twice. That's totally not what I show above, but again maybe you're already ahead of me here.
Casper Bang - 05 May 2008 19:36 GMT > You expect this to return Class<T>, don't you? So why don't you declare > it that way. Good catch. :)
> Don't assume it should be exactly one, it would probably be better to go > through a loop. Right, it's designed for anonymous inner classes as callbacks but I should obviously cover scenarios where other interfaces apply as well.
> It would be more appropriate to throw an IllegalArgumentException I > think. It would probably be even more appropriate to throw a > CallbackException of your own creation. The truth is I never remember the complete exception hierachy so when mocking up I'm not too interested in the particular exception type. But you're right, IllegalArgumentException is more applicable.
Thanks for the feedback. /Casper
Lew - 06 May 2008 02:16 GMT Daniel Pitts wrote:
>> It would be more appropriate to throw an IllegalArgumentException I >> think. It would probably be even more appropriate to throw a >> CallbackException of your own creation.
> you're right, IllegalArgumentException is more applicable. Perversely, RuntimeExceptions are more about programming mistakes and checked exceptions are more about predicted runtime chaos outside the programmer's control.
This reasoning supports the use of IllegalArgumentException in this scenario. The programmer might not be able to stop 'new FileReader( f )' from throwing a FileNotFoundException, but they sure ought to forestall an illegal argument.
 Signature Lew
Daniel Pitts - 06 May 2008 18:25 GMT > Daniel Pitts wrote: >>> It would be more appropriate to throw an IllegalArgumentException I [quoted text clipped - 11 lines] > )' from throwing a FileNotFoundException, but they sure ought to > forestall an illegal argument. Wait, did we all just agree or not? I'm a little confused because it seems like Lew is countering the suggestion, but appears to come to the same conclusion...
Or were you countering my suggestion to throw a custom CallbackException? In this particular circumstance, I agree with you. I was thinking of something else when I suggested CallbackException :-)
 Signature Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>
Lew - 06 May 2008 23:56 GMT Lew wrote:
>> This reasoning supports the use of IllegalArgumentException in this >> scenario. The programmer might not be able to stop 'new FileReader( f >> )' from throwing a FileNotFoundException, but they sure ought to >> forestall an illegal argument.
> Wait, did we all just agree or not? I'm a little confused because it > seems like Lew is countering the suggestion, but appears to come to the > same conclusion... What part of "this reasoning supports the use of IllegalArgumentException" is confusing?
> Or were you countering my suggestion to throw a custom > CallbackException? In this particular circumstance, I agree with you. I > was thinking of something else when I suggested CallbackException :-) You don't agree with me because I have not stated a conclusion on that matter.
I was simply elucidating the principles whereby one choosed runtime vs. checked exceptions, and showing how the reasoning applied in the given scenario.
I'm sure if you just read the words actually stated, that any confusion will melt away in the face of simply taking what is said at face value.
 Signature Lew
Daniel Pitts - 06 May 2008 23:58 GMT > Lew wrote: >>> This reasoning supports the use of IllegalArgumentException in this [quoted text clipped - 22 lines] > I'm sure if you just read the words actually stated, that any confusion > will melt away in the face of simply taking what is said at face value. I was confused before *both* IllegalArgumentException and RuntimeException are *not* checked exceptions. Your mentioning of checked exceptions was confusing in the context of the choice between the two.
 Signature Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|