4

I have a method toString( Object ) which delegates the conversion to handlers. The handlers are defined like this:

public interface IToStringService<T> {
    public String toString( T value );
}

The code looks like this:

// (1) How can I say that these two wildcards must in fact be the same type?
private Map<Class<?>, IToStringService<?>> specialHandlers = Maps.newHashMap();

// Generic method, must accept Object (any type really)
@Override
public String toString( Object value ) {

    if( null == value ) {
        return "null";
    }

    Class<?> type = value.getClass();
    if( type.isArray() ) {
        return arrayToString( value );
    }

    // (2) How can I get rid of this SuppressWarnings?
    @SuppressWarnings( "unchecked" )
    IToStringService<Object> handler = (IToStringService<Object>) specialHandlers.get( type );

    if( null != handler ) {
        return handler.toString( value );
    }

    return value.toString();
}

public <T> void addSpecialHandler( Class<T> type, IToStringService<T> handler ) {
    specialHandlers.put( type, handler );
}

And one implementation looks like this:

@SuppressWarnings( "rawtypes" ) // Can't add generics to "Class" or I get compile errors when calling DefaultToStringService.addSpecialHandler() :-(
public class ClassToStringService implements IToStringService<Class> {

    @Override
    public String toString( Class value ) {
        return value == null ? "null" : value.getName();
    }
}

I have several problems here:

  1. How can I say that the handlers in the specialHandlers map must match the type used as the key?

  2. How can I use the same information inside the method to avoid casting and @SuppressWarnings?

  3. When I change ClassToStringService to implement IToStringService<Class<?>>, I get a compile error when calling addSpecialHandler( Class.class, new ClassToStringService() ); How do I solve this?

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 1
    You want to enforce that the Generics of the Key is the same as the Generics of the Value. Well, you cannot do that in a Map. But since you are controlling what is put into the Map, do you really that restriction? – Luciano Mar 05 '12 at 15:49
  • @Luciano: Thought so but sometimes, someone knows a trick. What about the last point? – Aaron Digulla Mar 05 '12 at 15:54
  • 1
    possible duplicate of [Generic Map of Generic key/values with related types](http://stackoverflow.com/questions/2208317/generic-map-of-generic-key-values-with-related-types) – Louis Wasserman Mar 05 '12 at 16:03
  • 1
    I don't know about the last point, maybe you hit the limits of Generics (which are far from perfect). – Luciano Mar 05 '12 at 16:13
  • 1
    Your question is somewhat related to Item 29 of Effective Java by J. Bloch. I could not come up with anything better than what you did. I concur with @Luciano: you are probably doing as well as you can with generics and you control all access to your map, so all the warning suppression is not an issue. – toto2 Mar 05 '12 at 17:14

1 Answers1

1

You can't do this in Java. I'll explain why. ? extends are called existential types in type theory but Java has a limited form of them. ? extends X means that there exist Y such that Y extend X. We can rewrite Map<Class<?>, IToStringService<?>> as:

Map<Class<(exists T1 extends Object)>, IToStringService<(exist T2 extends Object)>> 

But we want:

Map<(exists T extends Object Class<T>, IToStringService<T>> 

We can't express this in Java because existential quantifier can't be specified explicitly in Java. However, In Scala you can do this.

toto2
  • 5,306
  • 21
  • 24
Konstantin Solomatov
  • 10,252
  • 8
  • 58
  • 88