22

Moving Forward With Money-API, JSR 354: Motivation Behind the API

 5 years ago
source link: https://www.tuicool.com/articles/hit/BfyyQnU
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

According to Wikipedia, money is any item or verifiable record that is generally accepted as payment for goods, services, and/or the repayment of debts in a particular country or socio-economic context. Money is represented by two parts: a numerical value and a currency. We deal with money in our programs every day, but the JDK doesn't provide a standard representation of wealth. What we need to know as developers is what data type is suitable for representing money.

The first attempt would be to use the primitive floating point types (double and float) that are available in the language. The author of Effective Java doesn't recommend using these types when precise values are required.

double val = 1.03 - .42;
System.out.println(val); //0.6100000000000001

As you can see, the result wasn't something that the user would expect. One might ask: is the floating point arithmetic broken in Java? No, it's not, but Java uses native floating point types, and this is how IEEE-754 floating point numbers work. This means that we can't precisely represent base-10 numbers that humans usually use. In Java, double and float are both the double-precision 64-bit IEEE-754 floating point and single-precision 32-bit IEEE-754 floating point data types respectively.

The same book mentions two ways of dealing with money:

The first is using the a long and int , but this requires converting the value to its lower primitive units (e.g., cents). This solution is highly recommended when performance is an issue. However, it is important to worry about the number of decimal places. This book does not support greater representation than nine decimal places.

public static void main(String[] args) {

    int itemsBought = 0;
    int funds = 100;
    for (int price = 10; funds >= price; price += 10) {
         itemsBought++;
         funds -= price;
    }

    System.out.println(itemsBought + " items bought.");
    System.out.println("Money left over: "+ funds + " cents");
  }

One problem with this solution is readability, and another is flexibility. For example, we have to use the value 1200 cents to represent a product that costs 12 USD.

public class Product {
     private String name;
     private int money;
     //getter and setter
}
Product banana = new Product("banana", 12_00);
Product pasta = new Product("pasta", 4_00);
int sum = banana.getMoney() + pasta.getMoney();

It is easy to go wrong with this design where one could easily forget the fact that we have to convert dollars into cents.

Besides the use of int and  longEffective Java recommends using  BigDecimal . This makes our lives more comfortable because it sounds more natural to say that a product is worth twelve dollars rather than twelve hundred cents.

public class Product {
     private String name;
     private BigDecimal money;
     //getter and setter
}
Product banana = new Product("banana", BigDecimal.valueOf(12D));
Product pasta = new Product("pasta", BigDecimal.valueOf(4D));
BigDecimal sum = banana.getMoney().add(paste.getMoney());

Things are getting better, but there is a significant factor missing in our design, and that is currency. If our program deals with a single currency, then we are outstanding. However, this is not the case, most of the time. Therefore, the number 12 has no meaning without a currency.

So, let's add a field of type String to hold the value of the currency.

public class Product {
     private String name;
     private String currency;
     private BigDecimal money;
     //getter and setter
}

Well, this is clearly not a good design because it's not type-safe. The String is not validated and could be anything that is not a valid currency.

Let's make it type-safe by introducing an enum of currencies. However, we need to keep various aspects of internationalization, like ISO-4217 , in mind.

public class Product {
     private String name;
     private Currency currency;
     private BigDecimal value;
     //getter and setter
}
enum Currency {
    REAL, DOLLAR, EURO;
}

There is something similar available in the JDK called java.util.Currency that works with ISO-4217 and solves these two problems:

  • We need to provide a currency
  • This type supports ISO 4217.
public class Product {
     private String name;
     private Currency currency;
     private BigDecimal value;
     //getter and setter
}

Validation is essential here when we do arithmetic operations like sums or discounts. For example, we can't sum up the prices of products that have different currencies.

Product banana = //instance;
Product pasta = //instance;
if(banana.getCurrency().equals(pasta.getCurrency())) {
  BigDecimal value = cellular.getValue().add(notebook.getValue());
 }//exception

We could introduce a utility class that is responsible for this sort of validation.

public class ProductUtils {
public static BigDecimal sum(Product pA, Product pB) {
    if(pA.getCurrency().equals(pB.getCurrency())) {
      return pA.getValue().add(pB.getValue());
    }
    throw new IllegalArgumentException("Currency mismatch");
   }

}
BigDecimal sum = ProductUtils.sum(pasta, banana);

So will this solve all our problems? No, it will not— too many things could go wrong here:

  • We have to force ourselves and our colleagues to always use the utility class, and never forget to do so.
  • We have to define utility classes for different services or introduce a general abstraction.
public class MoneyUtils {
public BigDecimal sum(Currency currencyA, BigDecimal valueA, Currency currencyB, BigDecimal valueB) {
   //...
}
public class ServiceUtils {
...
}
public class WorkerUtils {
...
}

So, adding an abstraction for representing money becomes more and more obvious after we try all these tricks. Martin Fowler wrote an article describing an abstraction that represents money and solves the following issues:

  • Single responsibility, the money class, is the only type that is responsible for dealing with money.
  • There is no need for utility classes because the money class will be the only class responsible for this kind of validation.
public class Money {
   private  Currency currency;
   private  BigDecimal value;
   //behaviour goes here ...
}
Product banana = new Product("banana", new Money(12, dollar));
Product pasta = new Product("pasta", new Money(4, dollar))
Money money = banana.getMoney().add(pineapple.getMoney());

This was the primary motivation behind introducing a specification for a standard API that abstracts the way we deal with money in our Java programs.

For more information, check out this link


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK