DEV Community

Cover image for Duck typing in java
Simon Taddiken
Simon Taddiken

Posted on

Duck typing in java

Disclaimer: please don't take this too serious. It's just fun and games.

Duck typing is an idiom known mostly from dynamically typed languages. It states that you can treat unrelated Objects of type X as Objects of type Y as long as both have the same public interface.

If it looks like duck, moves like duck and makes sounds like a duck then it must be a duck!

Java's static type system on the other hand, imposes strong rules on type compatibility. Just having the same methods doesn't qualify two classes to be "type compatible". Consider this example:

public class Duck {
    public String makeNoise() {
        return "Quak Quak";
    }
}

public class Dog {
    public String makeNoise() {
        return "woof woof";
    }
}

Both classes have the same interface, so can we assign instances to the respective other type?

final Duck duck = new Dog(); // fails
assertEquals("woof woof", duck.makeNoise());

Of course we can't. Java's type system needs explicit definitions of compatible classes using inheritance.

Let's now make our example work using the easiest imaginable change to our code:

public class Dog extends Duck {
    @Override
    public String makeNoise() {
        return "woof woof";
    }
}

Besides everything you learned in biology classes our Dog now is a duck and is thereby assignable to variables with type Duck. This change should have made the test above turn green.

Ok, now our Dog is a Duck, but what about all the other classes in our code base that also have a makeNoise method? Should they all extend the Duck class? That seems impractical and confusing (class Train extends Duck is even stranger than a Dog being a Duck).

Maybe we can build a more dynamic Duck by using reflection. During runtime the JVM has pretty extensive type information about every object. Besides accessing type information, reflection also allows us to dynamically retrieve and call methods on arbitrary objects.

public class DynamicDuck extends Duck {

    private final Object notADuck;

    public DynamicDuck(Object notADuck) {
        this.notADuck = notADuck;
    }

    @Override
    public String makeNoise() {
        // try block is needed because the reflection stuff will 
        // throw all kind of horrible exceptions if you use it wrong
        try {
            // retrieve dynamic type information of the non-duck object
            final Class<?> notADuckType = this.notADuck.getClass();

            // find the method of the class that is not a duck by name
            final Method delegate = notADuckType.getMethod("makeNoise");

            // invoke the method on the object that is not a duck
            return (String) delegate.invoke(this.notADuck);
        } catch (Exception e) {
            throw new IllegalStateException("DynamicDuck error");
        }
    }
}

Using our DynamicDuck we can get a "duck-view" on every non-duck Object. Let's adjust our initial test accordingly (and also remove the extends clause in the Dog class):

final Duck duck = new DynamicDuck(new Dog());
assertEquals("woof woof", duck.makeNoise());

That's a little more dynamic but still doesn't scale well. If the Duck class had more public methods we'd have to delegate every method to the non-duck Object like we did above. And if we decided not to view everything as a Duck, but as a Goose, we need to additionally create a DynamicGoose class. That's all way too cumbersome.

Luckily, Java has another useful concept that comes in handy for our use case. Every class that is available during runtime will be loaded from its byte code representation using a ClassLoader. As this is a pure runtime feature you can make up artificial classes during runtime that did not exist during compile time.

We can dynamically generate the byte code of a class that extends Duck and which automatically delegates every called method to the equivalent method of an arbitrary non-duck Object. The following code uses the popular libraries cglib (for byte code generation) and Objenesis (for instantiating the dynamically generated classes.

import net.sf.cglib.core.DefaultNamingPolicy;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.InvocationHandler;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;

import java.lang.reflect.Method;

public class NotADuck {
    /**
     * The unique callback index to which each method in the proxy object is mapped.
     */
    private static final int CALLBACK_INDEX = 0;

    /**
     * Maps all methods to index {@link #CALLBACK_INDEX}.
     */
    private static final CallbackFilter ZERO_CALLBACK_FILTER = method -> CALLBACK_INDEX;

    // with caching
    private static final Objenesis OBJENESIS = new ObjenesisStd(true);

    public static <T> T asDuck(Object notADuck, Class<T> type) {
        final Enhancer enhancer = new Enhancer();
        final InvocationHandler invocationHandler = new DelegateDuckToNonDuckMethod(notADuck);

        enhancer.setSuperclass(type);
        enhancer.setUseFactory(true);
        enhancer.setNamingPolicy(new DefaultNamingPolicy());
        enhancer.setCallbackFilter(ZERO_CALLBACK_FILTER);
        enhancer.setCallbackType(invocationHandler.getClass());

        final Class<T> proxyClass = enhancer.createClass();
        final T duckInstance = OBJENESIS.getInstantiatorOf(proxyClass).newInstance();
        final Factory factory = (Factory) duckInstance;
        factory.setCallback(CALLBACK_INDEX, invocationHandler);
        return duckInstance;
    }

    private static class DelegateDuckToNonDuckMethod implements InvocationHandler {

        private final Object notADuck;

        private DelegateDuckToNonDuckMethod(Object notADuck) {
            this.notADuck = notADuck;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            final Method nonDuckMethod = notADuck.getClass().getMethod(method.getName(), method.getParameterTypes());
            return nonDuckMethod.invoke(notADuck, args);
        }
    }
}

Using this class, we can create a dynamic, duck-view of a Dog instance:

final Duck duck = NotADuck.asDuck(new Dog(), Duck.class);
assertEquals("woof woof", duck.makeNoise());

The code is no longer restricted to only create Duck objects:

List list = NotADuck.asDuck("I'm a String", List.class);
// both List and String have an isEmpty method
assertFalse(list.isEmpty());

This was just a little experiment. There are likely bugs, uncovered edge cases, performance issues and general lack of practical use cases in this implementation. Nevertheless it's fun to see how far you can go with Java's dynamic type information and materialization of arbitrary byte code.

Don't do this at home!

Top comments (1)

Collapse
 
baso53 profile image
Sebastijan Grabar

Duck typing is one of the most satisfying things that I have encountered in my career. Personally, I used it with TypeScript, and it's such a pleasure not being forced to define interfaces explicitly. It makes the flow sooo good.