Sobrecarga de método.
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");
Uma das limitações dessa abordagem é que ela não funcionará se você tiver dois parâmetros opcionais do mesmo tipo e qualquer um deles puder ser omitido.
Varargs.
a) Todos os parâmetros opcionais são do mesmo tipo:
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) Os tipos de parâmetros opcionais podem ser diferentes:
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");
A principal desvantagem dessa abordagem é que, se os parâmetros opcionais forem de tipos diferentes, você perderá a verificação de tipo estático. Além disso, se cada parâmetro tiver um significado diferente, você precisará de alguma maneira de distingui-lo.
Nulos. Para abordar as limitações das abordagens anteriores, você pode permitir valores nulos e, em seguida, analisar cada parâmetro em um corpo de método:
void foo(String a, Integer b, Integer c) {
b = b != null ? b : 0;
c = c != null ? c : 0;
//...
}
foo("a", null, 2);
Agora todos os valores dos argumentos devem ser fornecidos, mas os padrão podem ser nulos.
Classe opcional. Essa abordagem é semelhante a nulos, mas usa a classe Java 8 Optional para parâmetros que possuem um valor padrão:
void foo(String a, Optional<Integer> bOpt) {
Integer b = bOpt.isPresent() ? bOpt.get() : 0;
//...
}
foo("a", Optional.of(2));
foo("a", Optional.<Integer>absent());
Opcional torna um contrato de método explícito para um chamador, no entanto, pode-se achar essa assinatura muito detalhada.
Padrão do construtor. O padrão do construtor é usado para construtores e é implementado através da introdução de uma classe Builder separada:
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();
Maps. Quando o número de parâmetros é muito grande e, para a maioria deles, os valores padrão geralmente são usados, você pode passar argumentos de método como um mapa de seus nomes / valores:
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 = (String)parameters.get("a");
} else if (parameters.containsKey("b")) {
//...
}
//...
}
foo(ImmutableMap.<String, Object>of(
"a", "a",
"b", 2,
"d", "value"));
Observe que você pode combinar qualquer uma dessas abordagens para obter um resultado desejável.