Java is often described as a simple programming language. While this is arguably true, it has still retained the ability to surprise me after using it full time for years.

This blog post describes four features that are obscure enough that they’ve surprised either me or one of my seasoned colleagues.

Abstract over thrown exception type

Yes, throws clauses may contain type variables. This means that this sort of thing is admissable:

interface ExceptionalSupplier<T, E extends Throwable> {
    T supply() throws E;
}

class FileStreamSupplier
  implements ExceptionalSupplier<FileInputStream, IOException> {
    @Override
    public FileInputStream supply() throws IOException {
        return new FileInputStream(new File("foo.txt"));
    }
}

As the example suggests, this pattern is frequently useful if you want to do functional abstraction without having to rethrow exceptions as RuntimeException everywhere. The place people are most likely to encounter this for the first time is when looking at the Throwables utilities in Guava.

Intersection types

This refers to the ability to write down a type that is the set intersection of two other types – so the type A & B is only inhabited by values that are instances of both A and B. Java lets you use this sort of type within generic bounds, but not anywhere else:

class IntersectionType {
    public <T extends List & Iterator> void consume(T weirdThing) {
        weirdThing.iterator().next();
        weirdThing.next();
    }
}

One place this comes up in practice is where you need to know that something is both of some useful type and also, for resource managment purposes, Closeable.

Constructor type parameters

Constructors are somewhat analagous to static methods. Did you know that just like static methods, constructors can take type arguments? Observe:

class ConstructorTyArgs {
    private final List<String> strings;

    <T> ConstructorTyArgs(List<T> xs, Function<T, String> f) {
        strings = xs.stream().map(f).collect(Collectors.toList());
    }

    public static void useSite() {
        new <Integer> ConstructorTyArgs(
        	Arrays.asList(1, 2, 3),
        	x -> x.toString() + "!");
    }
}

This feature is useless enough that I’ve never felt any desire to do this. In fact, I only noticed it when I was reading a formal grammar for Java.

Note that the type parameters you write here are not the same as the type parameters of the enclosing class (if any). This means that unfortunately there is no way to write the static create method below as a constructor, since it requires refining the bounds on the class type parameters:

class Comparablish<T> {
    private final T value;
    private final Comparator<T> comparator;

    public Comparablish(T value, Comparator<T> comparator) {
        this.value = value;
        this.comparator = comparator;
    }
    
    static <T extends Comparable<T>> Comparablish<T> create(T value) {
        return new Comparablish<T>(value, Comparator.naturalOrder());
    }
}

Inline classes

I’m sure everyone is aware that you can declare anonymous inner classes within a method body like this:

class AnonymousInnerClass {
    public int method() {
        return new Object() {
            int foo() { return 1; }
        }.foo();
    }
}

But did you know that you can also declare named inner classes too?

class InlineClass {
    public int method() {
        class MyIterator implements Iterator<Integer> {
            private int i = 0;
            @Override public Integer next() { return i++; }
            @Override public boolean hasNext() { return false; }
        }

        return new MyIterator().next() + new MyIterator().next();
    }
}

The inner class (which may not be static) can close over local variables and is subject to the same scoping rules as a variable. In particular, this means that a named inner class can use itself recursively from within it’s definition, but you can’t declare a mutually recursive group of multiple classes like this:

public int rec() {
    class A {
        public int f() { return new B().g(); }
    }
    class B {
        public int g() { return new A().f(); }
    }

    return new B().g();
}