Click here to Skip to main content
15,881,605 members
Articles / Web Development

Duck Typing in Java

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
20 Jan 2015CPOL2 min read 14.8K   1   1
Duck typing in Java

In OO programming, duck typing means an object is defined by what it can do, not by what it is. A statement calling a method on an object does not rely on the declared type of an object, only that the object must implement the method called. This concept is a form of inductive reasoning: "when I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck." - James Whitcomb Riley

Several languages advocate it or at least have support for it: Python, Ruby, PHP, C#, etc.. I personally highly prefer static typing for its better compile-time type safety but duck typing may have justified uses too. The ability to use polymorphism without class inheritance constraints may be handy in certain scenarios. Why not keep that option open in Java too? The idea can be easily illustrated through the following example.

The Duck

I expect a duck to be capable of this:

Java
public interface Duck {
    void quack();
}

A few concrete animals (some of them can quack(), but none of them implements the Duck interface):

Java
public class SilentDuck {
    public void quack() {
        System.out.println("quack");
    }
}

public class LoudDuck {
    public void quack() {
        System.out.println("QUACK");
    }
}

public class AverageDog {
    public void bark() {
        System.out.println("bark");
    }
}

public class TalentedDog {
    public void bark() {
        System.out.println("superior bark");
    }

    public void quack() {
        System.out.println("quacklikesound");
    }
}

The Expectations

Since SilentDuck, LoudDuck and TalentedDog all can quack(), I should be able to use those objects through the Duck interface - even though their classes don't implement that interface explicitly.

Java
Duck duck1 = attach(Duck.class, new SilentDuck());
Duck duck2 = attach(Duck.class, new LoudDuck());
Duck duckImpersonator = attach(Duck.class, new TalentedDog());

duck1.quack();
duck2.quack();
duckImpersonator.quack();
List<Duck> duck = Arrays.asList(duck1, duck2, duckImpersonator);

AverageDog can't quack(). I expect to get runtime exception when I try to attach it to the Duck interface.

Java
Duck wannabeDuck = attach(Duck.class, new AverageDog()); // throws exception - no quack()

Risks

While duck typing has benefits, it has many drawbacks too. Type checking takes place at runtime instead of compile time. The concrete classes don't refer to a dynamic interface directly, simply renaming a method in them may cause runtime problems in client code unintentionally. Relying on such coding style calls for different, careful practices.

Implementation

I implemented a simple dynamic interface attachment tool to make the above code sample work:

Java
public class DynamicInterface {
    public static <I> I attach(Class<I> i, Object o) {
        try {
            ensureMethodsExist(i, o);
            ensureIsInterface(i);
            return attachInterface(i, o);
        } catch (Exception e) {
            throw new DynamicInterfaceException(e);
        }
    }

    @SuppressWarnings("unchecked")
    private static <I> I attachInterface(Class<I> i, Object o) {
        Object proxy = Proxy.newProxyInstance(i.getClassLoader(), 
		new Class[]{i}, new DynamicInterfaceHandler(o));
        return (I) proxy;
    }

    private static <I> void ensureMethodsExist(Class<I> i, Object o) throws NoSuchMethodException {
        for (Method method : i.getDeclaredMethods()) {
            if (o.getClass().getMethod(method.getName(), method.getParameterTypes()) == null)
                throw new NoSuchMethodException(method.getName());
        }
    }

    private static <I> void ensureIsInterface(Class<I> i) {
        if (!i.isInterface())
            throw new DynamicInterfaceException(i.getName() + " is not an interface");
    }
}

class DynamicInterfaceHandler implements InvocationHandler {
    private Object o;

    DynamicInterfaceHandler(Object o) {
        this.o = o;
    }

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

public class DynamicInterfaceException extends RuntimeException {
    public DynamicInterfaceException(Throwable t) {
        super(t);
    }

    public DynamicInterfaceException(String m) {
        super(m);
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Norway Norway
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
General2 things. Pin
Paulo Zemek20-Jan-15 13:23
mvaPaulo Zemek20-Jan-15 13:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.