Java可选参数


Answers:


516

varargs可以做到(以某种方式)。除此之外,必须提供方法声明中的所有变量。如果希望变量是可选的,则可以使用不需要参数的签名来重载该方法。

private boolean defaultOptionalFlagValue = true;

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

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

我认为方法重载是一个很好的答案,而varargs是一个非常糟糕的答案。请删除对varargs的注释,或者说明由多个返回值的可能性引起的严重缺陷。
安德烈亚斯·沃格尔

添加全局变量可能会导致其他一些问题。
崔海伦

1652

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

  1. 方法重载。

    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");

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

  2. 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");

    这种方法的主要缺点是,如果可选参数的类型不同,则会丢失静态类型检查。此外,如果每个参数具有不同的含义,则需要某种方式来区分它们。

  3. 为了解决以前方法的局限性,可以允许使用空值,然后分析方法主体中的每个参数:

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

    现在必须提供所有参数值,但是默认值可以为null。

  4. 可选类。这种方法类似于null,但是对具有默认值的参数使用Java 8 Optional类:

    void foo(String a, Optional<Integer> bOpt) {
        Integer b = bOpt.isPresent() ? bOpt.get() : 0;
        //...
    }
    
    foo("a", Optional.of(2));
    foo("a", Optional.<Integer>absent());

    Optional使方法契约对于调用者是显式的,但是,可能会发现这样的签名过于冗长。

    更新:Java 8包含java.util.Optional开箱即用的类,因此出于特殊原因在Java 8中无需使用番石榴。方法名称有所不同。

  5. 生成器模式。构建器模式用于构造函数,并通过引入单独的Builder类来实现:

     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();
  6. 地图。当参数数量太大并且通常使用大多数默认值时,可以将方法参数作为其名称/值的映射传递:

    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")); 

    在Java 9中,这种方法变得更容易:

        @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"));

请注意,您可以组合使用这些方法中的任何一种以获得理想的结果。


2
@Vitalii Fedorenko Hmm,我想您在#6中犯了一个轻微的复制粘贴错误:尽管您的a变量是String(cast是正确的),但是您正在检查Integer的类型。
未知ID

5
@Aetos,您的链接已死。
罗宾诺

2
@Robino替代链接认为Optional不应该用作此处的参数。尽管Optional在其中一个选项中使用了as参数,但此答案还是很好的。
Dherik

解决上述问题的最常见方法可能是方法重载。尽管我从不真正喜欢它。我个人希望如果他们为配置参数添加某种标识符。
亚历山大·海姆

1
这应该是最好的答案-涵盖全部
-xproph

104

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

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

您可以致电doSomething();doSomething(true);现在致电。


13
这实际上是正确的答案。它既简单又紧凑。请记住,您可以获得多个参数,因此Java将它们放在数组中。例如,要检索单个参数,您将首先检查数组的内容:'code'boolean flag =(optionalFlag.length <1)?false:optionalFlag [0];
萨尔瓦多·巴伦西亚

46
不,这不是正确的答案,因为这不允许单个可选参数,但允许任意数量的可选参数。虽然这接近OP的要求,但事实并非如此。接受比您需要的参数更多的错误和误解可能是潜在的原因。
sleske

2
如果只有一个可选参数,则可以使用这种方法,但是您需要检查数组的长度等,因此它不是超级干净的:如果您想使用“一个参数”作为可选参数,则最好声明两个方法(一个方法重载了doSomething() { doSomething(true); }没有要处理的数组,没有歧义
rogerdpack

如果有多个可选参数,则仅当它们都是相同的数据类型时才有效。好吧,您可以将对象类型设置为Object,但这真的很难看。
周杰伦

正如@sleske所指出的那样,使用varargs的可怕缺点是允许传递多个值。OP明确询问有关“可选”的解决方案,这意味着一个值或零。某些人如何认为这是一个很好的答案,这是我的疑问。
安德烈亚斯·沃格尔

99

您可以使用如下形式:

public void addError(String path, String key, Object... params) { 
}

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

奇怪的是,我在文档中找不到关于此的任何内容,但是它可以工作!

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

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


55

不幸的是Java不直接支持默认参数。

但是,我编写了一组JavaBean注释,其中一个注释支持以下默认参数:

protected void process(
        Processor processor,
        String item,
        @Default("Processor.Size.LARGE") Size size,
        @Default("red") String color,
        @Default("1") int quantity) {
    processor.process(item, size, color, quantity);
}
public void report(@Default("Hello") String message) {
    System.out.println("Message: " + message);
}

注释处理器生成方法重载以正确支持此方法。

参见http://code.google.com/p/javadude/wiki/Annotations

有关完整示例,请访问http://code.google.com/p/javadude/wiki/AnnotationsDefaultParametersExample


53

Java中没有可选参数。您可以做的是重载函数,然后传递默认值。

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

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

23

提到了VarArgs和重载。另一个选择是Builder模式,它看起来像这样:

 MyObject my = new MyObjectBuilder().setParam1(value)
                                 .setParam3(otherValue)
                                 .setParam6(thirdValue)
                                 .build();

尽管该模式最适合在构造函数中需要可选参数的情况。


我想为那增加参数依赖。让我说我只在我设置了param1的情况下才设置param3。例如。我只想在设置可见进度的情况下设置进度消息。isProgressVisible()。setProgressMessage(“ loading”)。我该如何实现?
Harshal Bhatt

14

在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);
        }
    }
}

7

您可以使用这样的方法重载来做事情。

 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...){}

7

简洁版本 :

使用 三个点

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的回答)


2

重载是可以的,但是如果有很多需要默认值的变量,您将得到:

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提供的变量参数。这是一个解释链接


使用varargs被认为是不好的做法。如果系统需要此类(如上所述)方法,则应考虑采用新设计,因为类设计看起来很糟糕。
暗黑破坏神

2
弥补Java缺陷的借口,甚至指责其他人注意到这些不便之处并设计解决方法,这是更糟糕的做法。
BrianO '18

1
请所有相关信息添加到您的答案,而不是链接到外部来源-给定的链接是死的
尼科·哈泽

2

您可以使用类似于生成器的类来包含这样的可选值。

public class Options {
    private String someString = "default value";
    private int someInt= 0;
    public Options setSomeString(String someString) {
        this.someString = someString;
        return this;
    }
    public Options setSomeInt(int someInt) {
        this.someInt = someInt;
        return this;
    }
}

public static void foo(Consumer<Options> consumer) {
    Options options = new Options();
    consumer.accept(options);
    System.out.println("someString = " + options.someString + ", someInt = " + options.someInt);
}

使用方式

foo(o -> o.setSomeString("something").setSomeInt(5));

输出是

someString = something, someInt = 5

要跳过所有可选值,您必须调用它,foo(o -> {});或者如果您愿意,可以创建foo()不采用可选参数的第二种方法。

使用这种方法,您可以以任意顺序指定可选值,而不会产生任何歧义。与varargs不同,您还可以具有不同类的参数。如果您可以使用注释和代码生成来创建Options类,则这种方法会更好。


1

Java现在支持1.8中的可选选项,我一直在android上编程,因此我使用null,直到可以重构代码以使用可选类型为止。

Object canBeNull() {
    if (blah) {
        return new Object();
    } else {
        return null;
    }
}

Object optionalObject = canBeNull();
if (optionalObject != null) {
    // new object returned
} else {
    // no new object returned
}

你能提供例子吗?
sepehr

1

默认参数不能在Java中使用。在C#,C ++和Python中的任何地方,我们都可以使用它们。

在Java中,我们必须使用2个方法(函数),而不要使用具有默认参数的方法。

例:

Stash(int size); 

Stash(int size, int initQuantity);

http://parvindersingh.webs.com/apps/forums/topics/show/8856498-java-how-to-set-default-parameters-values-like-c-


28
较新版本的C#允许使用默认参数。
Sogger 2013年

2
Java也为任何参数提供了“ ...”选项。
Cacho Santa

0

这是一个古老的问题,甚至在引入实际的Optional类型之前可能就已经存在,但是如今,您可以考虑以下几件事:-使用方法重载-使用Optional类型,其优点是避免在Java 8引入之前在可选类型周围引入NULL。来自第三方库(例如Google的Guava)。将可选参数用作参数/自变量可以视为过度使用,因为其主要目的是将其用作返回时间。

参考:https : //itcodehub.blogspot.com/2019/06/using-optional-type-in​​-java.html


0

我们可以通过方法重载或使用数据类型作为可选参数...

| * | 方法重载:

RetDataType NameFnc(int NamePsgVar)
{
    // |* Code Todo *|
    return RetVar;
}

RetDataType NameFnc(String NamePsgVar)
{
    // |* Code Todo *|
    return RetVar;
}

RetDataType NameFnc(int NamePsgVar1, String NamePsgVar2)
{
    // |* Code Todo *|
    return RetVar;
}

最简单的方法是

| * | 数据类型...可以是可选参数

RetDataType NameFnc(int NamePsgVar, String... stringOpnPsgVar)
{
    if(stringOpnPsgVar.length == 0)  stringOpnPsgVar = DefaultValue; 

    // |* Code Todo *|
    return RetVar;
}


8
RetDtaTyp...认真吗?是RetDataType太难打出来?
亚历山大–恢复莫妮卡

您可以为该代码添加更多说明吗?所有这些奇怪的变量会做什么?
Nico Haase

更改了变量名称以便于理解...
Sujay联合国

0

如果您打算使用具有多个参数的接口,则可以使用以下结构模式并实现或覆盖apply-一种根据您的要求的方法。

public abstract class Invoker<T> {
    public T apply() {
        return apply(null);
    }
    public abstract T apply(Object... params);
}

0

如果它是API端点,则一种优雅的方法是使用“ Spring”注释:

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam(required = false, defaultValue = "hello") String id) { 
    return innerFunc(id);
}

请注意,在这种情况下,innerFunc将需要变量,并且由于它不是api端点,因此无法使用此Spring注释将其设为可选。参考:https : //www.baeldung.com/spring-request-param

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.