I've been evaluating JSR-275 - Units and Quantities tonight in relation to JSR-310. Its not been pretty.
JSR-275 - Units and Quantities
JSR-275 is a follow on from JSR-108, and aims to become part of the standard Java library in the JDK. It provides a small API for quantities and units, and a larger implementation of physical units, conversions and formatting. The goal is to avoid using ints for quantities, with their potential for mixing miles and metres.
So where's the problem?
Well, it starts with naming. There is a class called Measure which is the key class. But its what I would call a quantity - a number of units, such as 6 metres or 32 kilograms.
Measure
is generified by two parameters.
The first is the value type.
You'd expect this to be a Number
, but weirdly it is any class you like.
You'd expect the second generic parameter to be the unit type, right? Er, no. It's the Quantity. So, not only is the quantity class not called Quantity, there is another class which doesn't have much to do with a quantity that is called Quantity. Confused yet?
The unit class is at least called Unit.
Unit
also has a generic parameter, and again its Quantity
.
So, what is this mysterious Quantity class in JSR-275?
Well, its the concept of length or temperature or mass. Now actually that is a very useful thing for JSR-275 to have in the API. By having it in the API, it allows the generic system to prevent you from mixing a length with a mass. Which must be a Good Thing, right?
Measure<Integer, Length> distance = Measure.valueOf(6, KILO(METRES)); int miles = distance.intValue(MILES);
The first thing to notice is that the variable is held using a Length generic parameter. There is no support in JSR-275 to specify the variable to only accept metres or miles. Personally, I think thats a problem.
The second thing to notice is how easy conversion is. It just happened when it was requested. But is that what you necessarily want? We just converted from kilometres to miles without a thought. What about lost accuracy? Are those units really compatible?
Well now consider dates and times:
Measure<Integer, Duration> duration = Measure.valueOf(3, MONTHS); int days = duration.intValue(DAYS);
Er, how exactly did we manage to convert between months and days? What does that really mean? Well, assuming I've understood everything, what it does is use a definition of a year as 365 days, 5 hours, 49 minutes, and 12 seconds, and a month being one twelth of that. It then goes ahead and does the conversion from months to days using that data.
I doubt that the result will be what you want.
In fact, perhaps what has happened here is that we have substituted the unsafe int with a supposedly safe class. With JSR-275 we relax and assume all these exciting conversions will just work and nothing will go wrong. In short, we have a false sense of security. And thats perhaps one of the worst things an API can do.
I then looked at the proposal for integrating JSR-275 and JSR-310. One idea was for the JSR-310 duration field classes (Seconds, Minutes, Days, Months, etc) to subclass Measure. But that isn't viable as Measure defines static methods that are inappropriate for the subclasses. Measure also defines too many regular methods, including some that don't make sense in the context of JSR-310 (or at least the JSR-310 I've been building).
Looking deeper, what I see is that JSR-275 is focussed on conversions between different units. There is quite a complex API for expressing the relationships between different units (multipliers, powers, roots, etc). This then feeds right up to the main public API, where a key 'service' provided by the API is instant conversion.
But while conversion is important, for me it has come to dominate the API at the expense of what I actually wanted and expected. My use case is a simple model to express a quantity - a Number plus a Unit.
Alternative
So, here is my mini alternative to JSR-275:
public abstract class Quantity<A extends Number, U extends Unit> { public abstract A amount(); public abstract U unit(); } public abstract class Unit { // grams or celcius public abstract String name(); public abstract Scale scale(); } public abstract class Scale { // mass or temperature public abstract String name(); } Quantity<Integer, MilesUnit> distance = ...
So, what have we lost? Well, all the conversion for a start.
In fact, all we actually have is a simple class, similar to Number
, but for quantities.
And the unit is very bare-bones too.
We've also lost some safety. There is now no representation of the distance vs duration vs temperature concept. And that could lead us to mix mass and distance. Except that the API will catch it at runtime, so its not too bad.
And what have we gained? Well its a much smaller and simpler API. But less functional. However, by being simpler, its easier for other teams like JSR-310 to come along and add the extra classes and conversions we want. For example, implementations of Quantity called Days and Months that don't provide easy conversion.
In essence, this is just the quantity equivalent of Number
- thus its a class which the JDK lacks.
I'm sure there are many holes in this mini-proposal (I did code it and try to improve on it, but ran into generics hell again). The main thing is to emphasise simplicity, and to separate the whole reams of code to do with conversion and formatting of units. I'm highly unconvinced at the moment that the conversion/formatting code from JSR-275 belongs in the JDK.
Summary
Having spent time looking at JSR-275 I'm just not feeling the love. Its too focussed on conversion for my taste, and prevents the simple subclassing I actually need.
Opinions welcome on JSR-275!