Benji's Blog

Anonymous Types in Java

Java allows casting to an intersection of types, e.g. (Number & Comparable)5. When combined with default methods on interfaces, it provides a way to combine behaviour from multiple types into a single type, without a named class or interface to combine them.

Let’s say we have two interfaces that provide behaviour for Quack and Waddle.

interface Quacks {
    default void quack() {
        System.out.println("Quack");
    }
}
interface Waddles {
    default void waddle() {
        System.out.println("Waddle");
    }
}

If we wanted something that did both we’d normally declare a Duck type that combines them, something like

interface Duck extends Quacks, Waddles {}

However, casting to an intersection of types we can do something like

with((Anon & Quacks & Waddles)i->i, ducklike -> {
    ducklike.quack();
    ducklike.waddle();
});

What’s going on here? Anon is a functional interface compatible with the identity lambda, so we can safely cast the identity lambda i->i to Anon.

interface Anon {
    Object f(Object o);
}

Since Quacks and Waddles are both interfaces with no abstract methods, we can also cast to those and there’s still only a single abstract method, which is compatible with our lambda expression. So the cast to (Anon & Quacks & Waddles) creates an anonymous type that can both quack() and waddle().

The with() method is just a helper that also accepts a consumer of our anonymous type and makes it possible to use it.

static  void with(T t, Consumer consumer) {
    consumer.accept(t);
}

This also works when calling a method that accepts an intersection type. We might have a method that accepts anything that quacks and waddles.

public static  void doDucklikeThings(Ducklike ducklike) {
    ducklike.quack();
    ducklike.waddle();
}

We can now invoke the above method with an intersection cast

doDucklikeThings((Anon & Quacks & Waddles)i->i);

Source code

You can find complete examples on github

Enhancing existing objects

Some have asked whether one can use this to add functionality to existing objects. This trick only works with lambdas, but we can make use of a delegating lambda, for common types like List that we might want to enhance.

Let’s say we are fed up of having to call list.stream().map(f).collect(toList()) and wish List had a map() method on it directly.

We could do the following

List stringList = asList("alpha","bravo");
with((ForwardingList & Mappable)() -> stringList, list -> {
    List strings = list.map(String::toUpperCase);
    strings.forEach(System.out::println);
});

Where Mappable declares our new method

interface Mappable extends DelegatesTo> {
    default  List map(Function mapper) {
        return delegate().stream().map(mapper).collect(Collectors.toList());
    }
}

And DelegatesTo is a common ancestor with our ForwardingList

interface DelegatesTo {
    T delegate();
}
interface ForwardingList extends DelegatesTo>, List {
    default int size() {
        return delegate().size();
    }
    //... and so on
}

Here’s a full example