It's been a while since I've done any programming in Java. My day job has been all Javascript, Python, and Go. I found myself with about an hour to kill and decided to dust off the ol' Java skills.
Of course, as soon as I decided this, my brain stopped working and my mind went blank.
Because I had limited time and really just wanted to get back into the Java syntax, I thought: "just create anything, no matter how simple."
What's more simple than a calculator? Cool, don't have to think too much. Just have to get warmed up.
Part 1: The "Regular" Way (or the sane Java programmer way)
After figuring out which JVM I had on my MacBook, I opened up IntelliJ (been a minute for that, too) and quickly produced exactly what you might expect.
public class RegularCalculator {
public RegularCalculator() {
}
public double add(double a, double b) {
return a + b;
}
public double sub(double a, double b) {
return a - b;
}
public double mul(double a, double b) {
return a * b;
}
public double div(double a, double b) {
return a / b;
}
public double pow(double a, double b) {
return Math.pow(a, b);
}
}
And of course, some tests. We're not animals.
class CalculatorTest {
public Calculator calc;
@BeforeEach
void setUp() {
calc = new Calculator();
}
@Test
void add() {
double a = 10;
double b = 5;
double expected = 15;
double actual = calc.add(a, b);
assertEquals(expected, actual);
}
// ... and so on for sub, mul, div, etc.
}
Amazing. I remember Java. The world's most basic program. (Let's just ignore the potential divide-by-zero error one could get into using this program.)
Part 2: Making it "Functional" (Because Why Not?)
If you've read any of my work from previous years, you know I like messing around with functional programming (in Java, because I am a glutton for punishment).
Let's start with a new class.
public FunctionalCalculator {
}
How would I implement this using Java's Functional Interfaces? After a brief search to refresh my memory, I found that Java has an interface already defined for this type of thing.
Meet the DoubleBinaryOperator. As the docs say, it represents an operation on two double values and returns the result as a double value. It's a functional interface, so it can be used for a lambda expression.
Its functional method is applyAsDouble(double left, double right).
OK, this should work.
Since we have this interface, we can picture a function that takes two doubles and returns a value. Simple enough. But how do we map this to the operation?
To solve this, I created a Map that stores a string operator (like * or +) as a key, with the value being the lambda function that does the appropriate operation.
public class FunctionalCalculator {
// The key is the symbol (String), and the value is the operation.
private final Map<String, DoubleBinaryOperator> operations = new HashMap<>();
public FunctionalCalculator() {
// short-hand method reference for (a, b) -> Double.sum(a, b)
operations.put("+", Double::sum);
operations.put("-", (a, b) -> a - b);
operations.put("*", (a, b) -> a * b);
// let's fix the divide by zero problem
operations.put("/", (a, b) -> {
if (b == 0) {
throw new ArithmeticException("Cannot divide by zero!");
}
return a / b;
});
operations.put("^", Math::pow);
}
}
So now, all we need is a calculate function that accepts the operator and our values. I added that (and the error handling) to the class above.
// in our FunctionalCalculator class
public double calculate(String operator, double a, double b) {
if (!operations.containsKey(operator)) {
throw new IllegalArgumentException("Unknown operator: " + operator);
}
// Get our lambda function that maps to the operator
DoubleBinaryOperator op = operations.get(operator);
// And return our implementation of applyAsDouble
return op.applyAsDouble(a, b);
}
Obviously, we need new tests. The great part is we can add tests for our new error handling.
class FunctionalCalculatorTest {
private FunctionalCalculator calculator;
@BeforeEach
void setUp() {
calculator = new FunctionalCalculator();
}
@Test
void testAdd() {
assertEquals(15, calculator.calculate("+", 10, 5));
}
@Test
void testSubtract() {
assertEquals(5, calculator.calculate("-", 10, 5));
}
// ... etc. ...
@Test
void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> {
calculator.calculate("/", 10, 0);
});
}
@Test
void testInvalidOperator() {
assertThrows(IllegalArgumentException.class, () -> {
calculator.calculate("%", 10, 5); // % isn't defined
});
}
}
Tests pass. Nice.
So, What Did We Learn?
Nothing much, other than I convinced myself I still remember some Java syntax. I'm not sure my functional implementation is any "better" for this simple case, but I felt cool doing it.
Sometimes you gotta just mess around and "play."
Anyone else reach for functional interfaces even when they don't have to?
Top comments (0)