2010-10-18

Designing APIs with the Java 5 language features

The following is an almost verbatim copy of my post in a thread. I’m replicating it here in order to not lose this content.

While implementing a mid-sized framework, I've discovered that the following Java 5 language features can help improve the usability of APIs.
  • Generics can help avoid casts in more ways than one, especially if instances of Class are used (see my blog post on this subject).
  • Generics and constructors: Generic constructors always need a type argument, while static methods (such as factory methods) can infer type arguments from their actual (method) parameters, leading to shorter code. This is an example:
    public class Foo<X> {
        public static <X> Foo<X> create(Class<X> elementClass) {
            return new Foo<X>(elementClass);
        }
        public Foo(Class<X> elementClass) { }
        public X getElement() { return null; }
        public static void main(String[] args) {
            // Wrong type at right-hand side:
            Foo<String> foo1 = new Foo(String.class);
            // Proper way of doing it:
            Foo<String> foo2 = new Foo<String>(String.class);
            // Correctly infers the type:
            Foo<String> foo3 = Foo.create(String.class);
        }
    }
    
  • Interface Iterable enables the simplified for-each loops. This suggests that whenever you return an interface Iterator, you should also consider returning an interface Iterable. It is unfortunate that Java does not allow for-each to be applied to iterators. One can also use the following adapter:
    public class IterableWrapper<Elem> implements Iterable<Elem> {
        private Iterator<Elem> _iter;
        public IterableWrapper(Iterator<Elem> iter) {
            _iter = iter;
        }
        public Iterator<Elem> iterator() {
            return _iter;
        }
    }
    
  • Varargs are good whenever the API asks for 0 or more "things". In the past, there were often two variants of a method, one with the optional argument (be it a list or an array), the other one without it. With varargs, you can unify both ways of invocation under one signature. Additionally, many data structures can be cleanly created with varargs, for example:
    public static final <T> Set<T> makeSet(T... elements) {
        return new HashSet<T>(Arrays.asList(elements));
    }
    
  • Enums: Make it easy to discover the possible options for an argument. Compare this to the class SWT inside the Eclipse framework of the same name. Practically every GUI widget depends on the constants defined in this class, which makes looking up what constants apply in one particular case more difficult than it should be. Other advantages of Enums: Each enum constant is a full-blown object and one can perform all kinds of book keeping along with defining them; enums can be examined with a switch statement; the classes EnumSet and EnumMap provide memory-saving means for storing enums.
  • Interface Appendable: Unifies all objects “to which char sequences and values can be appended”. The list of implementing classes is long and includes StringWriter and StringBuffer. Consequence: Whenever you have an argument to which you want to "append" strings in a stream-like fashion, consider using this interface.

4 comments:

Fatih Coşkun said...

Your iterator wrapping iterable class has a shortcoming. Instances of it can only be used once in a foreach loop.

It is not explicitely specified to require multiple iterations to be allowed. Though this was the cause of much trouble I had in the past.

An iterable is implicitely thought of as being capable of multiple iterations. This is also the reason, why Java's foreach loop does not accept Iterator instances.

Fatih Coşkun said...

I didn't actively know the Appendable interface. I was very enthusiastic to try it out today.

While I wanted to replace some StringBuilder occurrences in my code, I noticed the shortcoming of the Appendable interface. It throws IOException.

For me it is not worth the additional complexity dealing unnecessarily with IOException. I switched back to StringBuilder.

Axel Rauschmayer said...

@Fatih: Python can iterate over both iterables and iterators, and I always found it really useful (granted, you have to be a little careful, but still). Sometimes in loops, the first and/or last element one iterates over are handled differently (e.g. when printing commas between elements). If you have an iterator, you can use isNext() do determine if there are more elements after the current one.

Axel Rauschmayer said...

@Fatih2: Yes, the IOException is annoying.

Web Analytics