Skip to content

Instantly share code, notes, and snippets.

@dsteiner93
Last active June 23, 2021 18:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dsteiner93/c717ee4aac092fe3ba98 to your computer and use it in GitHub Desktop.
Save dsteiner93/c717ee4aac092fe3ba98 to your computer and use it in GitHub Desktop.
Default method parameters in Java

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.

import lombok.Def;
import java.util.Optional;
import com.google.common.collect.ImmutableMap;
public class DefExample {
public static void main(String[] args) {
System.out.println("All defaults:");
foo(1, 2);
System.out.println();
System.out.println("Overriding a couple defaults:");
foo(1, 2, ImmutableMap.of("d", "not default!", "e", false));
System.out.println();
System.out.println("Passing all parameters:");
Optional<String> g = Optional.of("not default!");
foo(1, 2, 3, "4", false, 5.0, g);
}
private static void foo(int a, int b, @Def("99") int c, @Def("default") String d, @Def("true") boolean e, @Def("99.9") double f,
@Def Optional<String> g) {
System.out.println("a: "+a);
System.out.println("b: "+b);
System.out.println("c: "+c);
System.out.println("d: "+d);
System.out.println("e: "+e);
System.out.println("f: "+f);
System.out.println("g: "+g.orElse("was not passed"));
}
}

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.

How it works:

Unlike standard java annotations, lombok annotations do not create an entirely new file. Instead, they insert the code directly into the same class during compilation, so the code generated by these annotations is treated exactly the same way as if you had written it yourself. There are only two classes involved: the annotation itself and the handler class which will insert the appropriate methods into the class during compilation.

How to use:

You'll need two jars in your class path for this example, the lombok jar which holds the actual annotation and the guava jar that holds the ImmutableMap class. (Note that the guava jar is entirely optional, you can pass in any map.)

The lombok jar can only be obtained by cloning this repository and building the jar with ant.

> git clone https://github.com/dsteiner93/lombok.git
> ant

The guava jar can be downloaded here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment