how to do type safe money operation in java?

Viewed 103

I'm new in Java, and I try to make type-safe money operation with generics.
For example, there is a function add, I want to be sure that I can't add dollars to euros, and that if I add dollars I'll get only dollars.

In my snippet, I don't know how to convert from BigDecimal to T type.
Could you please help me with that, or maybe to propose another design.
Thanks in advance

package com.company;
import java.math.BigDecimal;

public class Main {

    public static void main(String[] args) {

        class Money extends BigDecimal{

            public Money(int val) {
                super(val);
            }
        }

        class Euro extends Money {
            public Euro(int val) {
                super(val);
            }

        }

        class Usd extends Money {
            public Usd(int val) {
                super(val);
            }
        }

        class Operation{
            public <T extends Money> T add(T a, T b){
                return (T) a.add(b);
            }
        }

        Operation operation = new Operation();
        Usd usdResult = operation.add(new Usd(5), new Usd(5));
        Euro euroResult = operation.add(new Euro(5), new Euro(5));
        System.out.println(usdResult);
        System.out.println(euroResult);
    }
}

1 Answers

First, I would suggest [Composition over Inheritance], because BigDecimal methods return BigDecimal instances, not Money, so inheriting from it doesn't have much value. You'd have to write your own add method that returns a Money either way...

Next, to achieve type safety, I suggest making Money generic, and making the currency types unrelated to Money. Yes, they "are a kind of" Money, but it's not type safe that way. Money.add would return an instance of Money, not a Euro, so Euro can't inherit Money.add.

This is how it would look:

class Money<T extends Currency> {
    private BigDecimal value;

    public Money(BigDecimal value) {
        this.value = value;
    }

    public BigDecimal getValue() {
        return value;
    }

    public Money<T> add(Money<T> other) {
        return new Money<>(value.add(other.value));
    }

    // plus other operations...
}

interface Currency {}
final class Euro implements Currency { private Euro() {} }
final class Usd implements Currency { private Usd() {} }

Because generics are invariant, you can't add a Money<Usd> to a Money<Euro>.

Related