Wednesday, February 11, 2015

Things that Java got wrong, part 3

Last time in this series (Things that Java got wrong, part 2), I talked about interfaces and the lack of an ability to include default method bodies. Happily, this major oversight has been fixed in Java 1.8.

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.
I ran into all of these problems recently while working on a new open-source framework for dealing with fuzzy objects (i.e. objects with uncertainty) and they caused me some big headaches. You can find the project at FuzzyJ.

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);
}
This will work for the positive integers and most other classes. If we want to extend the class to all integers, then we can define the following:
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 we're already beginning to get into difficulties. We don't have anything good to do if we try to add (multiply, or compare) an object which is not Integral (or, more specifically, an IntegralBase). What if the "int" primitive isn't sufficient for our purposes and we need a BigInteger? We could define a BigIntegral class just like the one above. Or we could make Integral generic, except of course that "int" cannot be a generic type because it's a primitive.

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.
}
That's basically all there is apart from longValue, floatValue, etc. There's no good way to find out if the object we are dealing with is a whole number (operable with one set of operations) or a real number (operable with another set, with some overlap).

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.