Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic parameters declaration in static member

Tags:

java

generics

Why it is not allowed to define such a static member:

private static final <T extends Object> Map<Class<T>, BiFunction<T, T, Boolean>> SPECIFIC_HANDLERS = new HashMap<>();

Instead it's only allowed to use it unspecified:

private static final Map<Class<?>, BiFunction<?, ?, Boolean>> SPECIFIC_HANDLERS = new HashMap<>();

Is there a workaround so that I can define that both parameters to the BiFunction MUST be of the same type and that the key of the Map MUST be the class type of these parameters?

Updated to clarify (because the suggestion of @Mena is not suitable for me):

I want a map for the array equals method for a generic equals helper. The generic helper receives formally two Objects. If they are arrays I have to pass it to one of the overloaded Arrays.equals() method. I wandted to have a lookup for the correct method (Example 1):

private static final Map<Class<?>, BiFunction<?, ?, Boolean>> ARRAY_EQUALS_HANDLER = new HashMap<>();

static
{
    ARRAY_EQUALS_HANDLER.put( Object[].class,  (l, r) -> Arrays.equals( (Object[])  l, (Object[])  r ));
    ARRAY_EQUALS_HANDLER.put( boolean[].class, (l, r) -> Arrays.equals( (boolean[]) l, (boolean[]) r ));
    ....
}

and then using it like:

boolean equal = ARRAY_EQUALS_HANDLER.get( anObj1.getClass()).apply(anObj1, anObj2);

The construct (according to Mena) does not even compile:

private static <T extends Object> Map<Class<T>, BiFunction<T, T, Boolean>> getSpecificHandlers() 
{
    Map<Class<T>, BiFunction<T, T, Boolean>> result = new HashMap<>();
    result.put( Object[].class,  (l, r) -> Arrays.equals( (Object[])  l, (Object[])  r ));
    result.put( boolean[].class, (l, r) -> Arrays.equals( (boolean[]) l, (boolean[]) r ));
    return result;
}

And if I populate the generated map outside the method:

 @SuppressWarnings( { "unchecked", "rawtypes" })
private static final Map<Class<?>, BiFunction<?, ?, Boolean>> ARRAY_EQUALS_HANDLER = (Map) getSpecificHandlers();

static
{
    ARRAY_EQUALS_HANDLER.put( Object[].class,  (l, r) -> Arrays.equals( (Object[])  l, (Object[])  r ));
    ...
}

then the whole type safety has gone because I have to do a (unchecked) typecast when assigning it to the final static member.

My Example 1 above works, but I have to cast the received lambda when using it :

private static <T extends Object> boolean equalsArray( T anArray, T anOtherArray) {
    Object o = ARRAY_EQUALS_HANDLER.get( anArray.getClass());
    @SuppressWarnings( "unchecked")
    BiFunction<T, T, Boolean> func = (BiFunction<T, T, Boolean>) o;

    Boolean result = func.apply( anArray, anOtherArray);
    return result;
}
like image 619
Heri Avatar asked Mar 03 '16 14:03

Heri


2 Answers

You are using a generic method idiom (the type parameter declaration before the return type), for a constant declaration.

That idiom will not compile.

As mentioned elsewhere too, you cannot use a class generic type in a static context.

What you can do as a workaround is to declare a static method instead of a constant - something in the lines of:

private static final <T extends WhateverBound> Map<Class<T>, 
    BiFunction<T, T, Boolean>> 
    getSpecificHandlers(T t) {

    // return new HashMap<Class<T>, BiFunction<T, T, Boolean>>();
    // much easier on the eye - thanks Andy Turner
    return new HashMap<>();
}

Assuming:

static class WhateverBound{}
static class Foo extends WhateverBound {}

You can then invoke your method as:

Map<Class<Foo>, BiFunction<Foo, Foo, Boolean>> map = 
getSpecificHandlers(new Foo());

Note of course that the final keyword here has a very different meaning, and can probably be omitted altogether.

Or...

You can keep it as a constant and repeat the wildcard/boundary pattern across all type parametrization.

For instance:

private static final Map<Class<? extends WhateverBound>, 
     BiFunction<? extends WhateverBound, ? extends WhateverBound, Boolean>> 
       SPECIFIC_HANDLERS = new HashMap<>();

static {
    SPECIFIC_HANDLERS.put(Foo.class, (f, ff) -> true);
}
like image 88
Mena Avatar answered Oct 07 '22 09:10

Mena


I have a solution, it's a bit ugly, but works as requested.

First thing, note that Map.get() method takes raw Object type as argument, thus it is not possible for compiler to guess return type of this method based on the argument type. Instead of that return type comes from declaration of the field itself, and as it's declared using fixed type parameter (in your case it should be Object) - you get fixed return type for any invocation of .get(). Actually, same thing applies not only for get() method but for all you expect to act as type-parameterized methods.

Secondly, desired structure should add some restrictions on key and value pairs (you want value type to depend on the key type). This is usually done via type parameters. It doesn't work for field declaration but does work for type or method declaration.

So, with those two premnises, I end with the solution: introduce type that extends Map with additional type correlation and with additional generic get() method (you may add more generic methods, of course).

public static class HandlerMap<T> extends HashMap<Class<? extends T>, BiFunction<T, T, Boolean>> {
    @SuppressWarnings("unchecked")
    <U extends T> BiFunction<U, U, Boolean> getStrict(Class<? extends U> key) {
        return (BiFunction<U, U, Boolean>) get(key);
    }
}

private static HandlerMap<Object> ARRAY_EQUALS_HANDLER = new HandlerMap<>();

static {
    ARRAY_EQUALS_HANDLER.put(Object[].class, (l, r) -> Arrays.equals((Object[]) l, (Object[]) r));
    ARRAY_EQUALS_HANDLER.put(boolean[].class, (l, r) -> Arrays.equals((boolean[]) l, (boolean[]) r));

    //WARNING - type safety breaks here
    ARRAY_EQUALS_HANDLER.put(int[].class, (l, r) -> Arrays.equals((boolean[]) l, (boolean[]) r));

}
public static void main(String[] args) throws Exception {
    BiFunction<int[], int[], Boolean> biFunction = ARRAY_EQUALS_HANDLER.getStrict(int[].class);
}

I used HashMap as superclass directly for shorter code. Note that getStrict() method is marked as unsafe (obviously, it is). And note also missing type coersion on put().

I think now, that all that ugliness comes from that you actually want not Map, but something different. Yes, there is a common concept - structure with key-value pairs, but type coersion should work another way. Map used to restrict keys to certain type and values to another certain type, but you don't need it. What you need is something stronger - not only type coersion for keys and values separately, but also binding between key type and value type.

like image 40
Vasily Liaskovsky Avatar answered Oct 07 '22 08:10

Vasily Liaskovsky