Default method parameters in Java

3 min read Original article ↗

Java does not offer default method parameters like many languages do. While there are some inelegant workarounds (see this stack overflow thread for details) design patterns are no substitute for the simplicity of Python (and most other languages') default parameter syntax:

def method(a, b, c=1, d=1):
  return a+b+c+(2*d)

method(1, 1) # Returns 5
method(1, 1, d=2) # Returns 7

This post is to show two tools to write more fluent, usable Java using a custom annotation I wrote as an extension to lombok and Google Guava collections. (Guava is actually not necessary for this example, but I think the fluent maps pair well with this feature.)

Say you are writing a method in Java, foo, whose method signature is:

private static void foo(int a, int b, int c, String d, boolean e, double f, Optional<String> g);

This is fine, but it is often the case that a client does not care about every parameter. Take a look at the read_csv method from pandas for instance. In that case, they just want to pass the parameters they care about and sensible defaults will be used for the params not passed. Hence the need for an annotation in Java to indicate a parameter is optional. A client would then be free to not pass it at all, or pass it by name if they would like to use something other than the default. This behavior is not so easy in Java, as the stack overflow thread linked above shows. However, we could achieve Python/Ruby style default values by overloading the method foo with the following two methods:

private static void foo(int a, int b) {
  foo(a, b, <default>, <default>, <default>, <default>, <default>);
}

private static void foo(int a, int b, Map<String, Object> paramsMap) {
  foo(a, b, <values from map if present, else defaults>...);
}

We could write these methods ourselves, but that's time-consuming, error-prone, and requires we update three methods every time we update the method signature of foo. It would be nice to autogenerate these methods. Such a thing is now possible with the @Def annotation. Consider DefExample.java below. This code will actually compile and run exactly as you'd expect.

When you compile DefExample.java with this fork of lombok in the classpath, it will automatically inject the correct two methods seen below into the same class file.

javac -cp lombok.jar:guava.jar DefExample.java
private static void foo(int a, int b) {
  foo(a, b, 99, "default", true, 99.9, Optional.empty());
}

private static void foo(int a, int b, final java.util.Map<String, Object> paramsMap) {
	foo(a, 
		b, 
		paramsMap.containsKey("c") ? (int)paramsMap.get("c") : 99, 
		paramsMap.containsKey("d") ? (String)paramsMap.get("d") : "default", 
		paramsMap.containsKey("e") ? (boolean)paramsMap.get("e") : true, 
		paramsMap.containsKey("f") ? (double)paramsMap.get("f") : 99.9, 
		paramsMap.containsKey("g") ? (Optional<String>)paramsMap.get("g") : Optional.empty());
}

Now you can run DefExample:

java -cp <path to DefExample.class>:guava.jar DefExample

All defaults:
a: 1
b: 2
c: 99
d: default
e: true
f: 99.9
g: was not passed

Overriding a couple defaults:
a: 1
b: 2
c: 99
d: not default!
e: false
f: 99.9
g: was not passed

Passing all parameters:
a: 1
b: 2
c: 3
d: 4
e: false
f: 5.0
g: not default!

@Def can be used on all primitives (except for char), as well as Strings and Java 8 Optionals. If using Optionals, no default value needs to be provided, as Optional.empty() will automatically be used.

So there you have it. Optional parameters in Java.