But now I want to rail against another aspect of interfaces and abstract types (or rather their usage) that I think is by far the worst thing that the Java designers ever messed up. The "Number" type.
First, let's look at three simple reasons why java.lang.Number is so bad.
- it should be an interface -- or rather several interfaces -- but instead it is an abstract type;
- if it is going to be an abstract type, then at least let's have it implement Comparable<Number> --- but it doesn't; all of its sub-classes, say X, implement Comparable<X> but that's not the same thing at all: if you need a generic type that implements Number and Comparable, it can't be done without creating your own type!
- it doesn't even have a method to let you find out if the type is integral or real (forget about complex) -- you have a Number, you can check if it implements Integer, Long, etc. but if somebody creates a new sub-class of Number that happens to be integral, you won't catch it.
Let's think about how we would go about defining an interface (or interfaces) to represent numbers. Sounds pretty straightforward, right? But it isn't quite that simple. There's a world of difference between the integers, where the successor or predecessor operators make perfect sense, and the real numbers where those operators don't make much sense -- while operators such as round are useful. And then there are complex numbers, rational numbers, irrational numbers, etc. etc. In other words, different types of numbers require different methods. In fact, to put it another way by inverting the question, the operators essentially define the number classes. Is there a fundamental set of operators that would apply to all numbers? There really isn't. But a reasonable set that works with most types of number is this: addition, multiplication, negation, perhaps some others, including compare with.
But already we run into problems. If the set of numbers you're modeling is the positive integers, then negation makes no sense.
So, let's start out with something like this:
public interface Numeric extends Comparable<numeric> { Numeric add(Numeric other); Numeric multiply(Numeric other); }
public interface Integral extends Numeric { Numeric negate(Numeric other); }
So far so good. We can now define an IntegralBase class based on the int primitive:
public class IntegralBase implements Integral { private int value; public IntegralBase(int value) { super(); this.value = value; } @Override public Numeric add(Numeric other) { if (other instanceof IntegralBase) return new IntegralBase(this.value+((IntegralBase) other).value); throw new RuntimeException("cannot add non-IntegralBase object"); } @Override public Numeric multiply(Numeric other) { if (other instanceof IntegralBase) return new IntegralBase(this.value*((IntegralBase) other).value); throw new RuntimeException("cannot multiply non-IntegralBase object"); } @Override public int compareTo(Numeric other) { if (other instanceof IntegralBase) return Integer.compare(this.value, ((IntegralBase) other).value); throw new RuntimeException("cannot add non-IntegralBase object"); } @Override public Integral negate(Integral other) { return new IntegralBase(-this.value); } }
But even this is better than the setup that the Java designers gave us. What we have in Java is an abstract type (not an interface) called Number.
public abstract class Number implements java.io.Serializable { public abstract int intValue(); public abstract double doubleValue(); // etc. etc. }
The designers of the math3 package from Apache "commons" have helped somewhat. They do bring in a little mathematics withe the Field and FieldElement interfaces. And they provide a type for rational numbers in BigFraction.
But in my humble opinion, Java, while it is admittedly a general-purpose language, could have done so much better right from the start.
OK, back to work.