How to Use Optional Parameters in Java

How do I use optional parameters in Java?

There are several ways to simulate optional parameters in Java:

  1. Method overloading.

    void foo(String a, Integer b) {
    //...
    }

    void foo(String a) {
    foo(a, 0); // here, 0 is a default value for b
    }

    foo("a", 2);
    foo("a");

One of the limitations of this approach is that it doesn't work if you have two optional parameters of the same type and any of them can be omitted.

  1. Varargs.

a) All optional parameters are of the same type:

    void foo(String a, Integer... b) {
Integer b1 = b.length > 0 ? b[0] : 0;
Integer b2 = b.length > 1 ? b[1] : 0;
//...
}

foo("a");
foo("a", 1, 2);

b) Types of optional parameters may be different:

    void foo(String a, Object... b) {
Integer b1 = 0;
String b2 = "";
if (b.length > 0) {
if (!(b[0] instanceof Integer)) {
throw new IllegalArgumentException("...");
}
b1 = (Integer)b[0];
}
if (b.length > 1) {
if (!(b[1] instanceof String)) {
throw new IllegalArgumentException("...");
}
b2 = (String)b[1];
//...
}
//...
}

foo("a");
foo("a", 1);
foo("a", 1, "b2");

The main drawback of this approach is that if optional parameters are of different types you lose static type checking. Furthermore, if each parameter has the different meaning you need some way to distinguish them.

  1. Nulls. To address the limitations of the previous approaches you can allow null values and then analyze each parameter in a method body:

    void foo(String a, Integer b, Integer c) {
    b = b != null ? b : 0;
    c = c != null ? c : 0;
    //...
    }

    foo("a", null, 2);

Now all arguments values must be provided, but the default ones may be null.

  1. Optional class. This approach is similar to nulls, but uses Java 8 Optional class for parameters that have a default value:

    void foo(String a, Optional bOpt) {
    Integer b = bOpt.isPresent() ? bOpt.get() : 0;
    //...
    }

    foo("a", Optional.of(2));
    foo("a", Optional.absent());

    Optional makes a method contract explicit for a caller, however, one may find such signature too verbose.

    Update: Java 8 includes the class java.util.Optional out-of-the-box, so there is no need to use guava for this particular reason in Java 8. The method name is a bit different though.

  2. Builder pattern. The builder pattern is used for constructors and is implemented by introducing a separate Builder class:

    class Foo {
    private final String a;
    private final Integer b;

    Foo(String a, Integer b) {
    this.a = a;
    this.b = b;
    }

    //...
    }

    class FooBuilder {
    private String a = "";
    private Integer b = 0;

    FooBuilder setA(String a) {
    this.a = a;
    return this;
    }

    FooBuilder setB(Integer b) {
    this.b = b;
    return this;
    }

    Foo build() {
    return new Foo(a, b);
    }
    }

    Foo foo = new FooBuilder().setA("a").build();
  3. Maps. When the number of parameters is too large and for most of the default values are usually used, you can pass method arguments as a map of their names/values:

    void foo(Map<String, Object> parameters) {
    String a = "";
    Integer b = 0;
    if (parameters.containsKey("a")) {
    if (!(parameters.get("a") instanceof Integer)) {
    throw new IllegalArgumentException("...");
    }
    a = (Integer)parameters.get("a");
    }
    if (parameters.containsKey("b")) {
    //...
    }
    //...
    }

    foo(ImmutableMap.<String, Object>of(
    "a", "a",
    "b", 2,
    "d", "value"));

    In Java 9, this approach became easier:

    @SuppressWarnings("unchecked")
    static <T> T getParm(Map<String, Object> map, String key, T defaultValue) {
    return (map.containsKey(key)) ? (T) map.get(key) : defaultValue;
    }

    void foo(Map<String, Object> parameters) {
    String a = getParm(parameters, "a", "");
    int b = getParm(parameters, "b", 0);
    // d = ...
    }

    foo(Map.of("a","a", "b",2, "d","value"));

Please note that you can combine any of these approaches to achieve a desirable result.

Why should Java 8's Optional not be used in arguments

Oh, those coding styles are to be taken with a bit of salt.

  1. (+) Passing an Optional result to another method, without any semantic analysis; leaving that to the method, is quite alright.
  2. (-) Using Optional parameters causing conditional logic inside the methods is literally contra-productive.
  3. (-) Needing to pack an argument in an Optional, is suboptimal for the compiler, and does an unnecessary wrapping.
  4. (-) In comparison to nullable parameters Optional is more costly.
  5. (-) The risk of someone passing the Optional as null in actual parameters.

In general: Optional unifies two states, which have to be unraveled. Hence better suited for result than input, for the complexity of the data flow.

calling method with Optional parameter in Java 8

Optional is not a optional parameter as var-args is.

Optional is a container object which may or may not contain a non-null value.

So you could invoke the method as :

test("...", Optional.of(true));

or

test("...", Optional.empty());

Note that with var-args :

public static Boolean test(String str, Boolean... test) {    
//...
}

this would be valid :

test("hello")

But var-args is not the correct way to pass an optional parameter as it conveys 0 or more objects and not 0 or 1 object.

Method overload is better :

public static Boolean test(String str, Boolean test) {
// ...
}

public static Boolean test(String str) {
// ...
}

In some other cases, the @Nullable constraints (JSR-380) may also be interesting.

Does Java support default parameter values?

No, the structure you found is how Java handles it, (that is, with overloading instead of default parameters).

For constructors, See Effective Java: Programming Language Guide's Item 1 tip (Consider static factory methods instead of constructors)

If the overloading is getting complicated. For other methods, renaming some cases or using a parameter object can help.

This is when you have enough complexity that differentiating is difficult. A definite case is where you have to differentiate using the order of parameters, not just number and type.

How to pass optional arguments in java

Maybe you can make a private method to check if is possible to retrieve the value, or get a default value, like this:

public static void main(String[] args){
String token = getFromArgsOrDefault(args,0,"defaultToken");
String projectName = getFromArgsOrDefault(args,1,"defaultProjectName");
String branch = getFromArgsOrDefault(args,2,"defaultBranch");
String language = getFromArgsOrDefault(args,3,"defaultLanguage");
String PRString = getFromArgsOrDefault(args,4,"4");
int PR = Integer.parseInt(PRString);

System.out.println(token +" "+ projectName +" "+ branch +" "+ language +" "+ PRString +" "+ PR);
}

private static String getFromArgsOrDefault(String[] args, int index, String defaultValue){
if(args.length > index) {
return args[index];
}
return defaultValue;
}

How do I make List string as optional parameter?

Java doesn't support optional parameters. You can provide multiple signatures with different parameters, like this:

public SESReqErrorIdAndArgs(String errorIdentifier, List<String> arguments) {
this.errorIdentifier = errorIdentifier;
this.args = arguments;
}

public SESReqErrorIdAndArgs(String errorIdentifier) {
this(errorIdentifier, new ArrayList<>());
}

Note that (1) there's no point in assigning a new ArrayList<>() in your args initializer, as it will always be immediately discarded, and (2) it's typically best to make a defensive copy of items passed in this way, so this is likely better (since Java 10):

public SESReqErrorIdAndArgs(String errorIdentifier, List<String> arguments) {
this.errorIdentifier = errorIdentifier;
this.args = List.copyOf(arguments);
}

public SESReqErrorIdAndArgs(String errorIdentifier) {
this(errorIdentifier, Collections.emptyList());
}

Finally, depending on your use case varargs might be a better option:

public SESReqErrorIdAndArgs(String errorIdentifier, String... arguments) {
this.errorIdentifier = errorIdentifier;
this.args = List.of(arguments);
}

Optional method parameter in Java

Java doesn't have default values like Python, C++, VBA, Delphi, and many languages. Create new constructors with the alternate signature.

public static int httpGet(String url, StringBuilder response) throws IOException {
return httpGet(URL, response, 5000)
}

public static int httpGet(String url, StringBuilder response, int readTimeout) throws IOException {
return http(url,(http)->http.setRequestMethod("GET"), response, readTimeout);
}

private static int http(String url, httpMethod method, StringBuilder response) throws IOException {
return http(url, method, response, 5000);
}

private static int http(String url, httpMethod method, StringBuilder response, int readTimeout) throws IOException {
HttpURLConnection http = (HttpURLConnection)new URL(url).openConnection();
http.setConnectTimeout(5000);
http.setReadTimeout(readTimeout;
method.doMethod(http);
int status = 404;
......
......
return status;
}

passing optional unknown parameters at runtime

When the number of arguments after the first int argument is not defined & they might have different types, you can use varargs method signature

public Class Test{
public void Foo(int x, Object... args) {

}
}

The only problem with this approach is that you need to handle arguments carefully inside your Foo method

For ex. to iterate arguments you can use loop and check the type instance:

for (Object item : args) {
if (item instanceof String) {
// do stuff
}
}

The other way is to get arguments length & get theirs particular values one by one:

final int maxArgs = args.length;
if (maxArgs > 0) {
final Object arg0 = args[0];
if (arg0 instanceof String) {
final String strArg0 = (String) arg0;
//do stuff
}
}

Basically it depends on your demands

Structuring a class with optional parameters

The way I see it there are some disadvantages in addition to the fact that validation might become much more cumbersome, as you already pointed out.

As noted in the comments to your question, your approach will only work if your optional parameters all have the same value. If they do not, you will have to resort to using Map<String,Object>. That in turn will make it hard to deal with these parameters, as you lost all type information.

Secondly, and I think this is the main issue with your approach: When utilizing a method such as updateParameter(String key, int value) the knowledge about which parameters the object being built requires is transferred from the builder to the client using this builder. Compare:

new Builder.updateParameter("calories", 0)
.updateParameters("fat", 1)
.updateParameters("carbohydrates",0)
.build();

new Builder.calories(0)
.fat(1)
.carbohydrates(0)
.build();

With the second approach the client will know that it is possible to set carbohydrates, because the builder provides a public method for it. It will not be possible to set the parameter mumbojumbo, because no such method exists.

In the first approach, will the client know if there is an additional parameter proteins? What happens if you mistype? What happens if a provided parameter is not actually used by the object being built? All these questions don't even pose themselves when sticking to the approach without the map.

In summary: Your approach provides seemingly more convenience (at first glance) at the cost of safety, readability and maintainability.



Related Topics



Leave a reply



Submit