Salim Fadhley
Salim Fadhley

Reputation: 23008

Doubles with constraints in Java?

I am porting a legacy Python system to Java. I'm a python developer primarily so this question concerns how I might take an unsatisfactory pattern in the original Python system but do it better in my Java version.

A system I'm working on deals with prices of products. All products have prices > 0.0.

In the orginal system all prices are represented as Python float objects (roughly like a Java Double). Functions which operate on these prices typically begin with a bunch of asserts to check that the range of the prices are sensible. Since there are a lot of functions there are a lot of assertions which become very repetitive after a while.

These assertions are better than nothing but it would be far better to throw an error the instant that something tried to create a negative price, since that's where the fault lies.

It occurred to me that I might do better in my re-implementation by inventing a new type of numberish thing which behaves exactly like a Double in all ways except that it cannot be constructed with a negative value. If I had such an object then I could rely on Java's type system and not need to write so many asserts.

Other than that (to begin with) it will obey all the normal rules of arethmatic - it will be a subclass of Double.

Later on I might extend this class by adding features that embrace more features of real-world money, e.g. preventing fractions of pennies. That's not actually needed in my initial version, but I'd like any pattern I adopt to accomodate this sort of development over the next few months.

Is this kind of pattern considered acceptable to a Java developer? Is there a better way to preserve the flexibility of having number-like objects but constraints on their possible values?

Upvotes: 3

Views: 451

Answers (5)

Basil Bourque
Basil Bourque

Reputation: 338316

Avoid floating-point

As already noted here, never use floating-point types where accuracy matters such as money. Floating-point trades away accuracy for performance.

Roll your own

You can handle money directly.

Integer count

As noted, one way to do so is to use an integer count of the most basic unit of currency. For example, in the United States, you might keep a count of pennies (a hundredth of a US dollar). Your stakeholders such as accountants may want to track a fraction of that, such as a tenth of a penny, known as a mill in the US (or mil in the UK).

You may need to use a 64-bit integer type to avoid integer overflow with larger amounts of money. In Java that would be the primitive type long, and its wrapper class Long.

When reporting your pennies or mills as dollars, for example, multiply. If you need fractional dollars, for example, multiply using BigDecimal class for accuracy, never float/double. The BigDecimal class provides for various methods of rounding, including Bankers’ Rounding.

Jakarta Validation

To handle checking for valid money fields, you can use an implementation of the Jakarta Validation API (formerly knows as Bean Validation).

While you can define your own validation rules, the specification requires that implementations provide one to check for positive-or-zero number values.

The only implementation for the latest versions of the spec is Hibernate Validator.

Custom class

Of course, you could write a class to represent your money amounts.

But before doing that, consider some existing libraries.

Libraries

Java does include a Currency class. But that class merely represents the concept of a particular currency and its official code/symbol. The class does not represent any amount of money nor does it perform calculations.

There was an effort launched to define a framework for money types into the spec for Java SE. This effort was officially proposed in JSR 354: Money and Currency API, adopted unanimously in 2015, updated in 2020. But for reasons unknown to me, the framework was never incorporated into Java.

Moneta

At least one implementation of JSR 354 is currently maintained: Moneta.

Joda-Money

Another popular library for money is Joda-Money.

Joda-Money is currently maintained. New 2.0 version launched in 2024. Commercial support available from Tidelift, Inc.

Upvotes: 0

Ernest Friedman-Hill
Ernest Friedman-Hill

Reputation: 81684

Well, first of all, no real software that deals with money uses floating-point math, because of the imprecision. See Accuracy problems section of Wikipedia page on floating-point arithmetic. A "Money" class that holds integer numbers of pennies is one common way to deal with this.

Secondly, yes, having a constructor that rejects negative numbers by throwing an exception would be a fine thing to do for such a class.

Here's a good Dr. Dobb's Journal article on the topic, with code.

Upvotes: 6

TC1
TC1

Reputation: 1

Firstly, yes, subclassing something or writing a class for that would be acceptable and there are people who do it, you can't, however, subclass java.lang.Double, it's a final class. Throwing constructors are nowhere near the catastrophe they are in C++ or something.

Secondly, you shouldn't store it in double unless you really need fractions of pennies, which you just said you don't. An integer class would be a much better idea.

Thirdly, I'd suggest subclassing java.lang.Number to get all the benefits of autoboxing.

Upvotes: 1

Thorsten S.
Thorsten S.

Reputation: 4274

Yes, that is indeed not only acceptable, but the very first thing a Java developer would do. With one exception: DO NOT USE FLOATS OR DOUBLES FOR MONETARY VALUES!

I would suggest:

  • That you create an interface with all the methods you would implement.
  • Perhaps an abstract base class which is NOT a subclass of BigDecimal but uses a private value of BigDecimal (no subclassing, but delegation pattern)
  • That you create the final class with implements all money functions, assertions and safeguards.

Upvotes: 1

jtahlborn
jtahlborn

Reputation: 53694

It's a reasonable idea (except for using Double). You can't extend Double since it's a final class. Regardless, you should be using BigDecimal instead of Double (as @Jagger mentioned in the OP comments). However, you're probably better off creating your own Price class and maintaining the BigDecimal internally since this will give you better control over how the value is used..

Upvotes: 1

Related Questions