如何在Java中使用可选参数?

1010

哪个规范支持可选参数?

17个回答

1909

在Java中,有几种模拟可选参数的方法:

  1. 方法重载。

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

    void foo(String a) { foo(a, 0); // 这里,0是b的默认值 }

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

这种方法的局限性之一是,如果您有两个相同类型的可选参数,并且其中任何一个可以省略,则此方法无法使用。

  1. Varargs。

a) 所有可选参数都是相同类型:

    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) 可选参数的类型可能不同:
    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");

这种方法的主要缺点在于,如果可选参数是不同类型的,则会失去静态类型检查。此外,如果每个参数具有不同的含义,则需要某种方式来区分它们。
为解决先前方法的限制,您可以允许空值,然后在方法体中分析每个参数:
void foo(String a, Integer b, Integer c) { b = b != null ? b : 0; c = c != null ? c : 0; //... }
现在必须提供所有参数值,但默认值可以为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"));
    
请注意,您可以结合这些方法来达到理想的结果。

8
尽管其中一个选项中使用了Optional作为参数,但这篇回答非常好。以下是提供反对使用Optional作为参数的另一种链接:此处 - Dherik
3
这可能是最好的答案——涵盖了所有内容。 - xproph
1
投票建造者方法,比Maps更类型安全。当为可能被重复使用的组件设计API时,将额外的精力放入适当的构建器中会产生很大的差异。 - NicuMarasoiu
我有一个名为User的类,其中包含大约15个字段。其中近10个字段是可选的,根据最终用户的输入可能为空或非空。与JS相比,创建User对象需要编写大量额外的代码。对于这样的类,最佳解决方案是什么? - Amirhosein Al
如果可选参数是单个的,varargs变量的变体是否存在某些限制?据我所知,以下两个调用将产生相同的结果:someMethod("test", 1)someMethod("test", 1, 2, 3) - hohserg
显示剩余3条评论

589

可变参数可以以某种方式实现该功能。除此之外,方法声明中的所有变量都必须提供。如果要使一个变量是可选的,可以使用不需要该参数的签名重载该方法。

private boolean defaultOptionalFlagValue = true;

public void doSomething(boolean optionalFlag) {
    ...
}

public void doSomething() {
    doSomething(defaultOptionalFlagValue);
}

5
在我看来,方法重载是一个很好的解决方案,而可变参数则是一个非常糟糕的答案。请删除有关可变参数的评论,或者解释可能会导致多个返回值的严重缺点。 - Andreas Vogl
1
添加全局变量可能会引起其他问题。 - Helen Cui

145

Java 5.0中有可选参数。只需像这样声明函数:

public void doSomething(boolean... optionalFlag) {
    //default to "false"
    //boolean flag = (optionalFlag.length >= 1) ? optionalFlag[0] : false;
}

现在你可以使用 doSomething(); 或者 doSomething(true); 来调用函数。


21
这实际上是正确的答案。它简单而紧凑。只需记住,您可能会获得多个参数,因此Java会将它们放在数组中。例如,要检索单个参数,您需要先检查数组的内容: 'code' boolean flag = (optionalFlag.length < 1)?false:optionalFlag[0]; - Salvador Valencia
79
不,这不是正确的答案,因为它不允许单个可选参数,但允许任意数量的可选参数。虽然这接近 OP 所需的内容,但并不完全相同。接受比所需参数更多的参数可能会导致错误和误解。 - sleske
6
如果你只有一个可选参数,这种方法可以工作,但你需要检查数组的长度等,所以它并不是非常简洁 :| 如果你想要“一个”可选参数,那么最好就声明两个方法(重载一个doSomething() { doSomething(true); }),没有数组需要处理,也没有歧义。 - rogerdpack
@sleske的问题是在哪里,它只想要一个参数? - codemonkey
重载方法时,可变参数(varargs)是一个不好的选择。 - ammills01
显示剩余2条评论

105
你可以使用类似这样的方法:
public void addError(String path, String key, Object... params) { 
}

params变量是可选的。它被视为一个可为空的对象数组。

奇怪的是,在文档中找不到任何关于这个的信息,但它确实有效!

这是Java 1.5及以上版本的“新”功能(不支持Java 1.4或更早版本)。

我也看到用户bhoot在下面提到了这一点。


77

Java中没有可选参数。你可以重载函数并传递默认值来实现相同的效果。

void SomeMethod(int age, String name) {
    //
}

// Overload
void SomeMethod(int age) {
    SomeMethod(age, "John Doe");
}

Java现在有可选参数。 - z atef

28

之前提到了VarArgs和重载函数。另一个选项是使用Bloch Builder模式,代码会类似于这样:

 MyObject my = new MyObjectBuilder().setParam1(value)
                                 .setParam3(otherValue)
                                 .setParam6(thirdValue)
                                 .build();
尽管该模式最适合在构造函数中需要可选参数的情况下使用。

我想为此添加参数依赖性。比如说,我只有在设置param1时才想设置param3。例如,我只想在设置进度可见时设置进度消息。isProgressVisible().setProgressMessage("loading")。我该怎么做? - Harshal Bhatt
@HarshalBhatt 我知道这已经晚了6年,但对于其他人来说。您可以在构建方法中使用检查来在运行时强制执行它。您还可以创建一个生成器类,其接口更有限,从setProgressVisible方法返回。但很多事情取决于您希望构建语义是什么样的。 - David Bradley

17

在JDK>1.5中,您可以像这样使用它;

public class NewClass1 {

    public static void main(String[] args) {

        try {
            someMethod(18); // Age : 18
            someMethod(18, "John Doe"); // Age & Name : 18 & John Doe
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void someMethod(int age, String... names) {

        if (names.length > 0) {
            if (names[0] != null) {
                System.out.println("Age & Name : " + age + " & " + names[0]);
            }
        } else {
            System.out.println("Age : " + age);
        }
    }
}

这是正确的,if () {} else {} 对于代码维护来说不好。在我看来,方法重载才是正确的答案。 - Gwang-Jin Kim

10

你可以使用方法重载来做这件事,就像这样。

 public void load(String name){ }

 public void load(String name,int age){}

您还可以使用@Nullable注释

public void load(@Nullable String name,int age){}

只需将第一个参数设置为null即可。

如果您要传递相同类型的变量,则可以使用这个方法

public void load(String name...){}

9

简短版:

使用三个点

public void foo(Object... x) {
    String first    =  x.length > 0 ? (String)x[0]  : "Hello";
    int duration    =  x.length > 1 ? Integer.parseInt((String) x[1])     : 888;
}   
foo("Hii", ); 
foo("Hii", 146); 

(基于@VitaliiFedorenko的回答)

(该句为注释,无需翻译)

5

函数重载是可以的,但如果有很多需要默认值的变量,你最终会得到:

public void methodA(A arg1) {  }    
public void methodA(B arg2) {  }
public void methodA(C arg3) {  }
public void methodA(A arg1, B arg2) {  }
public void methodA(A arg1, C arg3) {  }
public void methodA(B arg2, C arg3) {  }
public void methodA(A arg1, B arg2, C arg3) {  }

所以我建议使用Java提供的可变参数


1
使用可变参数被认为是一种不良实践。如果系统需要这样的方法,那么您应该考虑新的设计,因为类的设计看起来很糟糕。 - Diablo
5
为Java的缺陷编造借口,并因注意到这些不便并设计解决方法的人而指责其存在不良实践,这种做法更加糟糕。 - BrianO
2
请在您的答案中添加所有相关信息,而不是链接到外部来源 - 给定的链接已失效。 - Nico Haase
像这样的代码是一种代码异味,应该进行重构——可以使用引入参数对象或转换为FunctionalInterface并使用Builder模式。 - awgtek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接