Type Intersection in Java, or: Interest in Interfaces is Invaluable.
There was an interesting philosphical question posted in the comp.lang.java.programmer newsgroup the other day. Patricia, the original poster, had a question about using ArrayList as the declared type. She wanted to convey the fact that she wanted a List which has constant time random access methods, as this is an important property for her algorithm. Many of us live by the rule “Always use an interface, never a concrete type,” and she was questioning this. I personally think this dogma is a little to broad. However, it usually makes sense when using Java collections. As a matter of fact, you’ll find a lot of my code uses Collection instead of Set or List.
I guess Sun also thought it was important to convey constant time random access; they created an interface called RandomAccess just for that. RandomAccess is a marker interface for a List implementation to “indicate that they support fast (generally constant time) random access.”
Thats all well and good, but there’s a problem here.
RandomAccess myList = new ArrayList<Integer>(); // This compiles fine myList.get(0); // Whoops, compile error. RandomAccess doesn't extend List, so no get method;
Java 1.5 *almost* supports what we want… We want to say “Give me an object that is RandomAccess AND List<Integer>”. With the introduction of Generics, Java introduced Type Intersection. Unfortunately, they only introduced it in the context of Generics.
// Processes a RandomAccess List of integers, compiles fine
public <L extends RandomAccess&List<Integer> > void processList(L list) {
// ...
}
// Fails to compile.
public List<Integer> process(Collection<Integer> input) {
RandomAccess&List<Integer> output = new ArrayList<Integer>(input);
// ...
return output;
}
While this is too convoluted to answer Patricia’s philosophical question. It does bring us to an interesting, if incomplete, feature of Java.
In the past, if you wanted to be able to pass an object that implements two specific interfaces, you’d have to either use a concrete type, an interface that extends both, or two object references.
For example, say I had a class Something which implemented both Runnable, and Comparable<Something> and I want to pass two Something objects to a method called runAndCompare.
Without type intersection, I’d have to one of these things:
// Concrete approach:
class Something implements Runnable, Comparable<Something> { ... }
void runAndCompare(Something a, Something b) { ... };
// Super Interface approach:
interface RunnableAndComparable<T> extends Runnable, Comparable<T> {}
class Something implements RunnableAndComparable<Something> { ... }
void runAndCompare(RunnableAndComparable a, RunnableAndComparable b) { ... }
// Duplicate reference approach. (Has some advantages in certain situations)
class Something implements Runnable, Comparable<Something> { ... }
<T> void runAndCompare(Runnable aRunnable, Comparable<T> aComparable , Runnable bRunnable, Comparable<T> bComparable { … };
// Dangerous casting approach
class Something implements Runnable, Comparable<Something> { … }
<T extends Comparable<T>> void runAndCompare(T a, T b) { ((Runnable)a).run(); … }
Here is where Type intersection applies well. We can write runAndCompare so that it accepts two parameters which implement Runnable and Comparable, without any dangerous casting or unnecessary type restrictions.
class Something implements Runnable, Comparable<Something> { ... }
<T extends Runnable&Comparable<T>> void runAndCompare(T a, T b) { a.run(); b.run(); a.compareTo(b); }
As you can see, things are bit cleaner this way. I have an SSCCE below, for those of you who’d like to try it out.
Like I said before, its unfortunate that Type Intersection is only supported in Generic declarations, otherwise Patricia could have simply used
List<Integer>&RandomAccess myList = new ArrayList<Integer>();
. This gives her the flexibility she needs, and conveys the information she wants.
Anyway, here is a working example:
<sscce>
import java.io.InputStreamReader;
import java.io.BufferedReader;
public class TypeIntersectionTest {
public static final BufferedReader isr = new BufferedReader(new InputStreamReader(System.in));
private static class Something implements Runnable, Comparable<Something> {
private String value;
public void run() {
value = readLine();
}
public int compareTo(Something o) {
return value.compareTo(o.value);
}
}
private static String readLine() {
try {
return isr.readLine();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
System.out.println(”Enter to lines: “);
int result = runAndCompare(new Something(), new Something());
System.out.println(result < 0 ? "Less than" : result == 0 ? "Equal" : "Greater than");
}
public static <T extends Runnable&Comparable<T>> int runAndCompare(T a, T b) {
a.run();
b.run();
return a.compareTo(b);
}
}
</sscce>
Tags: generics, interfaces, type intersection

November 30th, 2007 at 11:37 am
[…] written about it before, but I think its worth revisiting. Type intersection would be a highly useful feature if not for […]