benjiPosted under Java.

We’re used to type erasure ruining our day in Java. Want to create a new T() in a generic method? Normally not possible. We can pass a Class<T> around, but that doesn’t work for generic types. We can’t write List<String>.class

One exception to this was using super type tokens. We can get the generic type of a super class at runtime, so if we can force the developer to subclass our type, then we’re able to find out our own generic types.

It let us do things like

List<String> l1 = new TypeReference<ArrayList<String>>() {}.newInstance();

This was really convenient prior to Java 8, because without lambdas we had to use anonymous inner classes to represent functions, and they could double up as type references.

Unfortunately, the super type token does not work with Lambdas, because Lambdas are not just sugar for anonymous inner classes. They are implemented differently.

However, there’s another trick we can use to get the generic type. It’s far more hacky implementation-wise, so probably not useful in a real scenario, but I think it’s neat nonetheless.

Here’s an example of what we can do, a method that takes a TypeReference<T> and creates an instance of that type

public static <T> T create(TypeReference<T> type) {
    return type.newInstance();
}

So far just the same as the supertype tokens approach. However, to use it we just need to pass an identity lambda.

This prints hello world

ArrayList<String> list = create(i->i);
list.add("hello");
list.add("world");
list.forEach(System.out::println);

This prints hello=1 world=2

LinkedHashMap<String, Integer> map = create(i->i);
map.put("hello", 1);
map.put("world", 2);
map.entrySet().forEach(System.out::println);

We could also use as a variable. This prints String

TypeReference<String> ref = i->i;
System.out.println(ref.type().getSimpleName());

Unfortunately, it won’t work for the main motivation for supertype tokens – we can’t use this TypeReference as a key in a map because it will be a different instance each time.

First Attempt – Casting

For my first attempt I noticed that if we try casting something, and the cast is invalid we’ll get a ClassCastException at runtime that will tell us exactly what the type actually is. This does work with lambdas, since they’re translated into normal methods.

Underneath the above Java snippet with a TypeReference<String> has been translated into something like

private static java.lang.String lambda$main$0(java.lang.String input) {
    return input;
}

As you can see, calling this with a type other than String is going to generate a ClassCastException.

So we could invoke the lambda with a type other than it is expecting and pull the type name from the resulting exception.

This worked, but suggested that a better way is possible since we’re invoking a real method, that we should be able to interrogate with reflection.

Better Attempt – Reflection

If we force our lambda to be Serializable, by having our TypeReference interface implement Serializable we can get access to a SerializableLambda instance. This gives us access to the containing class and the lambda name, and then we’re able to use the normal reflection API to interrogate the types.

Here’s a MethodFinder interface that we can extend our TypeReference from which gives us access to the Parameter types

Parameter Objects

Let’s consider a more concrete example of why lambdas that are aware of their types can be useful. One application is parameter objects. Extract parameter-object is a common refactoring.

It lets us go from a method with too many parameters, to a method that takes a parameter object.

e.g.

List<Customer> customers = listCustomers(dateFrom, includeHidden, companyName, haveOrders);

becomes

List<Customer> customers = listCustomers(customerQuerySpec);

Unfortunately the way this is commonly implemented means simply moves the problem one level up to the constructor of the parameter object.

e.g.

CustomerQuerySpecification customerQuerySpec = new CustomerQuerySpecification(dateFrom, includeHidden, companyName, haveOrders);
List<Customer> customers = listCustomers(customerQuerySpec);

We still have just as many parameters, and still just as hard to follow. Furthermore, we now have to import the CustomerQuerySpecification type. The CustomerQuerySpecification type that your IDE might generate for you is also quite big. So this isn’t ideal.

At this point we might reach for the builder pattern, or a variation thereof, to help us name our parameters. However, there are alternatives.

If we were using JavaScript we might pass an object literal in this scenario, to allow ourselves to have default parameter values and named parameters.

listCustomers({
    includeHidden = true,
    companyName = "A Company"
});

We can achieve something similar in Java using lambdas. (Or we could pass an anonymous inner class and use double-brace initialisation to override values)

First of all we’ll create a Parameter Object to store our parameters. It can also have default values. Instead of using getters/setters and a constructor I’m going to deliberately use public fields.

public static class CustomerQueryOptions {
    public Date from = null;
    public boolean includeHidden = true;
    public String companyName = null;
    public boolean haveOrders = true;
}

Now we want a way of overriding these default values in a given call to our method. One way of doing this is instead of accepting the CustomerQueryOptions directly, accepting a function that mutates the CustomerQueryOptions. If we did this then we can easily specify our overrides at the callsite.

listCustomers(config -> {
    config.includeHidden = true;
    config.companyName = "A Company";
});

You might notice that this lambda looks a lot like a Consumer<CustomerQueryOptions> – it accepts a config and returns nothing.

We could just use a Consumer as is, but we can make life easier for ourselves with a little utility method that just gives us the config back and applies the function to it.

Let’s make a Parameters interface that extends consumer. We’ll add a default method to it that returns our config. It instantiates the config for us, applies the consumer function to it in order to override any default values, and then returns the instantiated config.

First we’ll need a way of creating a type of the CustomerQueryOptions ourselves, this is where our Newable<T> interface comes in. We define a NewableConsumer<T>

interface NewableConsumer<T> extends Consumer<T>, Newable<T> {
    default Consumer<T> consumer() {
        return this;
    }
}

And now we define our Parameters interface extending NewableConsumer

public interface Parameters<T> extends NewableConsumer<T> {
    default T get() {
        T t = newInstance(); // provided by Newable<T>
        accept(t); // apply our config
        return t; // return the config ready to use
    }
}
public static class CustomerQueryOptions {
    public Date from = null;
    public boolean includeHidden = true;
    public String companyName = null;
    public boolean haveOrders = true;
}
public List<Customer> listCustomers(Parameters<CustomerQueryOptions> spec) {
 
    System.out.println(spec.get().haveOrders);
 
    // ...
}

This would even work with generic types.

The following will print hello world.

foo(list -> {
    list.add("Hello");
    list.add("World");
    // list.add(5); this would be a compile failure
});
 
public static void foo(Parameters<ArrayList<String>> params) {
    params.get().forEach(System.out::println);
}

Summary

We can use a hack to make lambdas aware of their generic type. It’s a shame that it’s rather too terrible to use for real, because it would be really useful for some of the reasons outlined above.

Unlike the super type tokens we also cannot use it as a key in a map because we’ll get a different lambda instance each time.

Does anyone have an alternative approach?

This post was inspired by Duncan‘s use of this pattern in Expec8ions

The code from this post is available on github.

Leave a Reply

  • (will not be published)