1

A Formatter<T> knows how to format T to string:

public interface Formatter<T> {
  String format(final T t);
}

I would like to have a Map of formatters, one for Integer, one for Date etc:

Map<Class, Formatter> formatters;

The intended use would be:

formatters.put(Integer.class, new Formatter<Integer>() {
    public String format(Integer i) {
      return i.toString;
    }
});

Is there some way to enforce that the key-values will agree on the Class type? That if I say put(Integer.class, new Formatter<Integer>(){...}) it will work but put(Integer.class, new Formatter<Date>(){...}) will not?

What I am trying now is to use ? as the type:

Map<Class<?>, Formatter<?>> formatters;

But then I cannot use formatters inside this map:

Object obj = Integer.valueOf(15);
formatters.get(obj.getClass()).format(obj);
Error: The method format(capture#3-of ?) in the type Formatter<capture#3-of ?> is not applicable for the arguments (Object)

Any clarification would be welcome.

vektor
  • 3,312
  • 8
  • 41
  • 71

3 Answers3

2

Unfortunately you can't create such relationship between keys and values without some earlier declared <T> generic type. Also such type shouldn't be fixed for class so it should be able to change in each put invocation.

If that is an option consider using method with generic type like

@SuppressWarnings("unchecked")
public static <T> void putSafe(Class<T> key, Formatter<T> value) {
    formatters.put(key, (Formatter<Object>) value);
}

which will put some values into map which could be for instance private field in your class

private static Map<Class<?>, Formatter<Object>> formatters 
                  = new HashMap<Class<?>, Formatter<Object>>();

Also you can't use format method from Formatter<?> reference. This kind of reference can point to Formatter<Integer> or Formatter<Date> or any other kind of Formatter and compiler will not be able to determine which you are using so there is a risk that you will use Formatter<Date> on Integer object (it is same problem why Java will not let you use add method in List<?> reference - since we don't know what kind of list reference holds allowing executing add(new Banana) could cause problems if list in in fact list of Apples List<Apple>)

To solve this problem you can explicitly say that map will store Formatter<Object> and thanks to that format will be able to accept any kind of data as long as it extend Objects. Only problem with this approach is that Formatter<Object> can't be reference to Formatter<Integer>[1] so you have to explicitly cast passed Formatters to Formatter<Object> which generally could be unsafe[2] and you will be warn by compiler about it but in this case you shouldn't be fine so you can suppress this warning.


[1] Like List<Fruit> can't be reference to List<Apple> because via list of fruits you would be able to add Banana to list of Apples
[2] Casting List<Apple> to List<Banana> is generally not best idea and compiler will warn us about such approach

Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • Your solution is very promising, thanks. I am still getting the error with `capture#3-of ?` though - any idea what to do? `map` is now using question marks, therefore any formatter in it will not even accept an `Object`... – vektor Apr 22 '14 at 08:48
  • I am not sure what you mean. This code works fine for me. Also using `?` doesn't prevent `Object` from being put in map. Maybe post some code example of how you are using my answer. You can post it on http://pastebin.com/ and share a link. – Pshemo Apr 22 '14 at 08:58
  • Line 3 http://pastebin.com/pEH9d94S The same error as in the Question text here. – vektor Apr 22 '14 at 09:01
  • 1
    This problem can be solved by changing signature of `format` method in `Formatter` interface to `String format(final Object obj);` but I will try to find more generic way to solve it. – Pshemo Apr 22 '14 at 09:07
  • Yep that works, but then my `Formatter` implementations are weird - have to accept an `Object` and cast it. Thanks a lot! – vektor Apr 22 '14 at 09:09
  • @vektor You can try this way http://pastebin.com/F0X66JWA. I forgot that `Formatter>>` can be reference to Formatter of any type so using its method based on `?` type like `format(? ourData)` could be unsafe (in case of `Map, Formatter>>` Java doesn't know about contract we are trying to create between key and value so it thinks that we would be able pass Integer to Formatter which should handle other type like Date which has nothing to do with Integer and compiler wouldn't be able to detect it). – Pshemo Apr 22 '14 at 09:41
  • this solution does not help when calling `putSafe()` - in the `Formatter` implementations I still have to write `public String format(Object t) {...` instead of, say, `public String format(Integer t) {...`. – vektor Apr 22 '14 at 10:11
  • With my previous example you wouldn't have to use `format(Object)`, `format(T t)` would be fine. Take a look at http://ideone.com/QYQsfd to see example. – Pshemo Apr 22 '14 at 10:33
  • Oh I see now. Thanks a lot man, if you update your answer with the info from comments, I will accept it. – vektor Apr 22 '14 at 10:38
1

That can't be done at language level because of generics erasure. You can't access generics type in runtime. (I'm not sure if I'm using all the terms properly) The problem is, if you pass an Integer.class then it would need to be evaluated in runtime that there should be a Formatter<Integer>. Generics in Java only work for enforcing type checking during compile time and are 'erased' at runtime. There are ways how to access the a generic type in runtime but it's a bit of a hacking using reflection.

It won't work as you'd imagined.

How to get a class instance of generics type T

Community
  • 1
  • 1
MarianP
  • 2,719
  • 18
  • 17
1

My solution requires a suppress warning, but it would ensure, the generic types are the same:

public class Example {

    private static Map<Class<?>, Formatter<?>> FORMATTERS =
            synchronizedMap(new HashMap<Class<?>, Formatter<?>>());

    public static <T> void addFormatter(Class<T> clazz, Formatter<T> formatter) {
        FORMATTERS.put(clazz, formatter);
    }
}

interface Formatter<T> {
    String format(final T value);
}

UPDATE: I added the solution by Pshemo which in the first place gave me a compiler error, but now works. Curious, but however. The code shown above is now the merge of both solutions, which removes the ugly @SuppressWarnings("unchecked").

Community
  • 1
  • 1
Harmlezz
  • 7,972
  • 27
  • 35