Java doesn't have a default argument feature **, so I've summarized the implementations to replace it.
TL;DR I think that there is no problem if it is implemented with the following policy.
Number of arguments you want to default | Solution |
---|---|
~ 4 | Overload |
5 ~ | Builder pattern |
The default argument in this article is if the value is not set as an actual argument when calling the method. Instead, it is a mechanism to initialize with the default value. (I think it's a syntax that is mostly introduced in modern languages)
Below is a sample of the default arguments written in Groovy.
def foo(a = 0, b = "undefined") {
println "a = $a, b = $b"
}
foo(23, "hoge") // => a = 23, b = hoge
foo() // => a = 0, b = undefined
You can easily implement it with the pattern you see most often.
static void foo(int a, String b) {
System.out.println("a = " + a + ", b = " + b);
}
static void foo(int a) { foo(a, "undefined"); } //Only argument b is the default argument
static void foo(String b) { foo(0, b); } //Only argument a is the default argument
static void foo() { foo(0, "undefined"); } //Argument a,Both b are default arguments
public static void main(String[] args) {
foo(23, "hoge"); // => a = 23, b = hoge
foo(23); // => a = 23, b = undefined
foo("hoge"); // => a = 0, b = hoge
foo(); // => a = 0, b = undefined
}
In the sample, the arguments declared by foo (int a, String b)
are different types, so
Overloading allows you to set default values for all arguments.
If the arguments a and b are of the same type, it is not possible to declare "default argument only for argument a" and "default argument only for argument b" at the same time.
In that case, there is no choice but to implement it with the Builder pattern described later or passing Null.
The part that becomes a variable length argument has a constraint that it is the last of the method arguments If nothing is specified in the last argument, an empty array (length = 0) will be passed automatically. Therefore, in such cases, there is no need to overload.
static void foo(String a, int... b) {
int bSum = IntStream.of(b).sum();
System.out.println("a = " + a + ", sum = " + bSum);
}
public static void main(String[] args) {
foo("hoge", 23, 42); // => a = hoge, sum = 65
foo("hoge"); // => a = hoge, sum = 0
}
In addition to overloading, there is a commonly used pattern that uses the Builder pattern to set default arguments. Overloading is nice because you can easily set the default value, but if the number of arguments becomes large Replacing it with the Builder pattern makes the code easier to read. SonarQube's rule states that it is desirable for the threshold to have no more than four arguments. Java: Methods should not have too many parameters | SonarQube - Rules
In the sample, it is written to set the default value in the Builder pattern for the method with two arguments.
import lombok.Builder;
import lombok.Value;
public class BuilderPattern {
static void foo(Foo foo) {
System.out.println("foo = " + foo);
}
public static void main(String[] args) {
var specifiedFoo = Foo.builder().a(23).b("hoge").build();
var defaultFoo = Foo.builder().build();
foo(specifiedFoo); // => foo = Foo(a=23, b=hoge)
foo(defaultFoo); // => foo = Foo(a=0, b=undefined)
}
}
@Value
@Builder
class Foo {
@Builder.Default
Integer a = 0;
@Builder.Default
String b = "undefined";
}
You can do the same thing with the Builder pattern in Map, but if you want to do this, I think you should write it in Builder.
static void foo(Map<Foo, Object> params) {
var a = (int) params.getOrDefault(Foo.A, 0);
var b = (String) params.getOrDefault(Foo.B, "undefined");
System.out.println("a = " + a + ", b = " + b);
}
public static void main(String[] args) {
foo(Map.of(Foo.A, 23, Foo.B, "hoge")); // => a = 23, b = hoge
foo(Map.of()); // => a = 0, b = undefined
}
enum Foo {
A, B
}
As mentioned in the overload description, if the declared arguments are of the same type, In some cases, the default value could not be set for each argument. The workaround is to set a default value within the method. However, it is not possible to determine which variable has the default value based on the method signature alone. Method users need to look at the Javadoc or the method implementation, which is a pattern they don't want to use much.
static void foo(String a, Integer b, Integer c) {
b = Objects.requireNonNullElse(b, 0);
c = Objects.requireNonNullElse(c, 0);
System.out.println("a = " + a + ", b = " + b + ", c = " + c);
}
public static void main(String[] args) {
foo("hoge", 23, 42); // => a = hoge, b = 23, c = 42
foo("hoge", null, null); // => a = hoge, b = 0, c = 0
}
As a problem with the above-mentioned Null passing pattern "It is not possible to determine which variable has the default value based on the method signature alone." It's easy to wonder, "Well, maybe the Optional type can be used?", But this seems to be quite criticized. Because the Optional type is a mechanism to tell the caller that the value originally returned from the method may be null. If you use the Optional type as an argument, the code becomes more verbose and the number of meaningless codes increases. Therefore, the following writing style is strictly prohibited.
static void foo(String a, Optional<Integer> bOpt) {
var b = bOpt.orElse(0);
System.out.println("a = " + a + ", b = " + b);
}
public static void main(String[] args) {
foo("hoge", Optional.of(23)); // => a = hoge, b = 23
foo("hoge", Optional.empty()); // => a = hoge, b = 0
foo("hoge", null); // => NullPointerException
}
Java optional parameters | Stack Overflow Java: Methods should not have too many parameters | SonarQube - Rules Why should Java 8's Optional not be used in arguments | Stack Overflow
Recommended Posts