题 Java是否支持默认参数值?


我遇到了一些具有以下结构的Java代码:

public MyParameterizedFunction(String param1, int param2)
{
    this(param1, param2, false);
}

public MyParameterizedFunction(String param1, int param2, boolean param3)
{
    //use all three parameters here
}

我知道在C ++中我可以为参数指定一个默认值。例如:

void MyParameterizedFunction(String param1, int param2, bool param3=false);

Java是否支持这种语法?有没有理由说这两步语法更可取?


1313
2018-06-15 18:04


起源


不会。但是,Builder模式可以提供帮助。 - Dave Jarvis
我真的很想念这个功能。在修改现有代码以将额外参数带入函数或构造函数时,它会有很大帮助 - Jatin
@Jatin使用Eclipse“更改方法签名”重构,您可以添加参数并提供现有调用者将使用的默认值。 - Erwin Bolwidt
@ErwinBolwidt谢谢。我正在使用Android Studio,它还可以选择重构方法并提供默认值。非常有用。 - Jatin


答案:


不,您找到的结构是Java如何处理它(即,使用重载而不是默认参数)。

对于构造函数, 请参阅“有效的Java:编程语言指南” 项目1提示(如果重载变得复杂,请考虑静态工厂方法而不是构造函数)。对于其他方法,重命名某些情况或使用参数对象可能会有所帮助。这是因为你有足够的复杂性,难以区分。一个明确的情况是你必须使用参数的顺序区分,而不仅仅是数字和类型。


758
2018-06-15 18:14



这是在2009年, static 在2015年,任何事情都被认为是有害的。 输入安全流利 Builder 现在,实施完整有效的建筑合同的实例是一个更好的解决方案。 - feeling unwelcome
@JarrodRoberson:静态工厂方法没有比它更有害 new。他们是 用过的 一切 时间 在新的代码中。简单值对象的构建器通常是过度工程的结果。 - Lii
@JarrodRoberson:通过编译器强制正确使用的有趣方式,感谢分享!对未来帖子的友好建议:对于大多数人来说,300行未注释的源代码可能有点消化(毕竟代码比阅读更难阅读)。再次感谢! - Christian Aichinger
@ChristianAichinger - 我现在使用的是一种更严格的高级技术,它将通用接口与抽象类与私有构造函数结合起来,我正在撰写博客文章。我的博客链接在我的个人资料中。 另外,自我记录代码不需要评论:) - feeling unwelcome
@JarrodRoberson:很好,期待着它!我想要传达的内容:作为您博客的读者,一个50行示例,其中包含对正在发生的事情的简短文本描述,将帮助我超过300行没有上下文。 - Christian Aichinger


不,但你可以使用 生成器模式,如中所述 这个Stack Overflow回答

如链接答案中所述,Builder Pattern允许您编写类似的代码

Student s1 = new StudentBuilder().name("Eli").buildStudent();
Student s2 = new StudentBuilder()
                 .name("Spicoli")
                 .age(16)
                 .motto("Aloha, Mr Hand")
                 .buildStudent();

其中某些字段可以具有默认值,或者是可选的。


544
2018-06-15 19:20



最后是一个不到2页的Builder模式示例。 - nevvermind
我很好奇,为什么我们在使用构建器模式时需要构建器类。我在想学生s1 =新学生()。名字(“Spicolo”)。年龄(16).motto(“Aloha,Mr Hand”); - ivanceras
@ivanceras:当类具有必需字段时,它是相关的,并且您不希望能够在无效状态下实例化这些类。所以如果你刚才说的话 Student s1 = new Student().age(16); 然后那会让你没有名字的学生,这可能是坏事。如果它不坏,那么你的解决方案很好。 - Eli Courtwright
@ivanceras:另一个原因是你可能希望你的类在构造之后是不可变的,所以你不希望它中的方法改变它的值。 - Jules
@ivanceras:我使用了Builders 3件事 - 消除了多参数和流畅的初始化,不变性,最重要的是我觉得在build()方法中验证域对象。为什么创建一个对象实例如果无效。你也可以重载静态工厂方法如上例中的buildFreshman(),buildSenior()等 - Abhijeet Kushe


有几种方法可以在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. 可变参数。

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

    可选为调用者显示方法契约,但是,人们可能会发现这样的签名过于冗长。

  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 = (String)parameters.get("a");
        } else if (parameters.containsKey("b")) { 
            //... 
        }
        //...
    }
    
    foo(ImmutableMap.<String, Object>of(
        "a", "a",
        "b", 2, 
        "d", "value")); 
    

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


349
2017-11-01 02:03



很好的解释。我从来没有见过像这样使用的返回值。 5)什么做的 return this 做?另外,没有 FooBuilder().setA("a").build(); 因为(根据定义)首先调用构造函数 FooBuilder() 返回一个值,这不是意思 .setA("a"): 没有机会被召唤? - Celeritas
@Celeritas return this 返回调用该方法的同一对象(在示例中, FooBuilder)。这允许在一个语句中链接作用于同一对象的方法: new FooBuilder().setA(..).setB(..).setC(..) 等,而不是在单独的语句中调用每个方法。 - ADTC
@Celeritas new FooBuilder() 返回一个 FooBuilder 对象 setA 方法被调用。如 setB 不叫, this.b 保留默认值。最后 build 方法被调用 FooBuilder 目的。该 build 方法创建并返回一个 Foo 设置为变量的对象 Foo foo。请注意 FooBuilder 对象不存储在任何变量中。 - ADTC
注释也可用于创建默认参数,并且在多态集合需要时最有用。 docs.oracle.com/javase/tutorial/java/annotations/declaring.html - Martin Spamer
两个问题的相同答案超过900个赞成票。我印象深刻: stackoverflow.com/questions/965690/java-optional-parameters/... - AdamMc331


可悲的是没有。


197
2018-06-15 18:05



难过吗?这样做会引入可能模糊的功能签名。 - Trey
@Trey:具有默认参数的语言通常会丢弃函数重载,因为它不那么引人注目。所以没有歧义。此外,Scala在2.8中添加了该功能,并以某种方式解决了歧义问题(因为出于兼容性原因他们保持了重载)。 - PhiLho
我没有看到参数默认值如何阻止函数重载。例如,C#允许覆盖,并允许默认初始化。似乎任意选择,不限制是原因。 - FlavorScape
是的,让我们进行权衡,让编译器做一些额外的工作,而是让我们都写出100000次重载,以便为我们的库用户提供便利。好主意。
@ user562566:每当我处理一个Java项目时,我都会觉得Java开发人员是按照他们每天生成的代码行数来支付/衡量的 - Mark K Cowan


不幸的是,是的。

void MyParameterizedFunction(String param1, int param2, bool param3=false) {}

可以用Java 1.5编写为:

void MyParameterizedFunction(String param1, int param2, Boolean... params) {
    assert params.length <= 1;
    bool param3 = params.length > 0 ? params[0].booleanValue() : false;
}

但是,你是否应该依赖于你对编译器生成的感觉

new Boolean[]{}

每次通话。

对于多个默认参数:

void MyParameterizedFunction(String param1, int param2, bool param3=false, int param4=42) {}

可以用Java 1.5编写为:

void MyParameterizedFunction(String param1, int param2, Object... p) {
    int l = p.length;
    assert l <= 2;
    assert l < 1 || Boolean.class.isInstance(p[0]);
    assert l < 2 || Integer.class.isInstance(p[1]);
    bool param3 = l > 0 && p[0] != null ? ((Boolean)p[0]).booleanValue() : false;
    int param4 = l > 1 && p[1] != null ? ((Integer)p[1]).intValue() : 42;
}

这符合C ++语法,它只允许参数列表末尾的默认参数。

除了语法之外,还有一个区别在于它对传递的默认参数进行了运行时类型检查,而C ++类型在编译期间检查它们。


76
2018-05-26 21:51



聪明,但是varargs(...)只能用于最终参数,这比支持默认参数的语言更有限。 - CurtainDog
与C ++版本相比,这很聪明但有点乱 - Someone Somewhere
Java肯定需要可选的默认参数,因为C#和其他允许...语法很明显,我认为他们可以相当简单地实现这一点,即使只是编译所有可能的组合......我无法想象为什么他们没有将它添加到语言中然而! - jlarson
一个人永远不应该使用 assert 在生产代码中。抛出异常。 - anthropomorphic
-1这真的不是varargs的用途。这是一个黑客。 - 在这种情况下,使用重载将更具可读性(这是不幸的,因为三个额外的字符比5个额外的源代码行更可读...)。 - 但Java不支持默认参数。 - BrainSlugs83


不,但你可以很容易地模仿它们。 C ++中的内容是:

public: void myFunction(int a, int b=5, string c="test") { ... }

在Java中,它将是一个重载函数:

public void myFunction(int a, int b, string c) { ... }

public void myFunction(int a, int b) {
    myFunction(a, b, "test");
}

public void myFunction(int a) {
    myFunction(a, 5);
}

前面提到过,默认参数导致函数重载的模糊情况。这根本不是真的,我们可以看到C ++的情况:是的,也许它可以创建模糊的情况,但这些问题可以很容易地处理。它简直不是用Java开发的,可能是因为创建者需要一种更简单的语言,因为C ++是 - 如果他们有正确的话,则是另一个问题。但是我们大多数人并不认为他使用Java是因为它简单。


35
2017-11-04 09:12



C#默认值表示法的主要思想正是为了避免这种样板编码而只有一个构造函数而不是很多。 - Kolya Ivankov
@KolyaIvankov我不知道C#,但我知道C ++的推理是一样的。我不知道什么是更好的,但我认为,实际上,在C ++ / C#的情况下,编译器会生成相同的样板代码,并且它将进入最终的二进制文件。 - peterh
每种编程语言(特别是)都是避免汇编程序样板的手段,我错了吗?问题只在于它是否提供了便利的功能。 - Kolya Ivankov
@KolyaIvankov我真的不明白你的问题。如果您有疑问,请将此问题作为问题。 - peterh
第一句话是一个修辞问题。第二句中的“问题”一词与第一句中的修辞问题无关。 - Kolya Ivankov


您可以在Scala中执行此操作,Scala在JVM上运行并与Java程序兼容。 http://www.scala-lang.org/

class Foo(var prime: Boolean = false, val rib: String)  {}

19
2017-07-21 16:26



使用全新的语言来获得一个不那么常见的功能? - om-nom-nom
@ om-nom-nom:Java永远不会存在。说没有使用某个功能相当于没人需要它就是说Java在发明之前并不流行意味着Gosling不应该开始设计它。 - Val
@Val只是说这就像用大炮射击鸟 - om-nom-nom
这与OP的问题无关 - destan


我可能会在这里说明显而易见但是为什么不自己简单地实现“默认”参数?

public class Foo() {
        public void func(String s){
                func(s, true);
        }
        public void func(String s, boolean b){
                //your code here
        }
}

默认情况下,您将使用以太

func(“我的字符串”);

如果你不想使用默认值,你会使用

func(“my string”,false);


11
2018-02-18 13:59



海报询问是否可以避免这种(相当丑陋的)模式... ;-)在更现代的语言(如c#,Scala)中,您不需要额外的重载,这只会产生更多的代码行。在某些时候你可以在此期间使用varargs(static int max(int ... array){}),但它们只是一个非常难看的解决方法。 - Offler
重载并不丑陋并且具有许多好处,例如具有不同签名的不同方法调用可以执行不同的功能。 //This is better public class Foo() { /* This does something */ public void func(String s){ //do something } /* This does something else with b */ public void func(String s, boolean b){ // b was passed } }  //Than this public class Foo() { /* This does something unless b = value, then it does something else */ public void func(String s, boolean b = value){ If (b){ // Do Something } else{ // Do something else } } } - Antony Booth
好吧,如果一个人想要不同的行为。如果唯一的区别是计算的细微变化等,那么创建多个签名肯定会浪费精力。默认值在您需要它们的地方有意义......并且缺少它不应被归类为“无用”的要求。 - Kapil
@Offler默认参数与“现代语言”无关。我在20年前的Delphi中使用它们,它们可能已经存在于Turbo Pascal中。 - The incredible Jan


不。通常,Java没有太多(任何)语法糖,因为他们试图制作一种简单的语言。


6
2018-06-15 18:49



不完全的。令人痛苦的事实是,球队的时间紧迫,没有时间用于语法糖。为什么会这样呢 const 和 goto 保留关键字哪些没有实现? - 特别是 const 是我痛苦的想念 - final 没有替代品他们知道。 - 如果你做了 Concious酒店 决定永不执行 goto 您不需要保留关键字。 - 后来在Java团队中,以制作Label为基础作弊 break 和 continue 像帕斯卡一样强大 goto。 - Martin
“简单,面向对象和熟悉”确实是一个设计目标 - 请参阅 oracle.com/technetwork/java/intro-141325.html - mikera
tomjen说:“不。通常,Java没有太多(任何)语法糖,因为他们试图制作一种简单的语言”。所以你说从C ++中删除许多不必要的功能使得Java成为一种简单的语言,然后告诉我为什么Java确实有可变方法?为什么它有varargs?如果你只是简单地使用一个对象数组就不需要了,对吗?因此,可以从语言中删除varargs,因为它是不必要的。这将使Java比现在更简单。我对吗?也可以删除重载,因为每个方法都有无限的名称。 - Farewell Stack Exchange
@Martin有什么地方可以阅读Java背后的故事吗?我很想知道你的评论中有更多。 - Daniel Soutar