1. 概述

在 Java 中,并不像 C++ 或 Scala 那样原生支持方法参数的默认值。也就是说,你不能像这样写代码:

void makeTea(String name, int sugar = 2) { ... } // ❌ Java 不支持

✅ 但我们可以用方法重载(method overloading)来模拟默认参数行为。本文将通过一个实际例子,展示如何简单粗暴地实现这一效果,避免踩坑。

2. 实际示例:泡杯茶

我们以“泡茶”为例。先定义一个 Tea 类,作为数据载体:

public class Tea {

    static final int DEFAULT_TEA_POWDER = 1;

    private String name; 
    private int milk;
    private boolean herbs;
    private int sugar;
    private int teaPowder;

    // standard getters
    public String getName() {
        return name;
    }

    public int getMilk() {
        return milk;
    }

    public boolean isHerbs() {
        return herbs;
    }

    public int getSugar() {
        return sugar;
    }

    public int getTeaPowder() {
        return teaPowder;
    }
}

参数设计说明:

  • name:必填,茶总得有个名字吧。
  • teaPowder:虽然也必填,但我们希望默认是 1 勺,不传就用默认值 → 这就是我们要模拟的“默认参数”
  • milkherbssugar:可选,不传就视为 0 或 false

解决方案:构造器重载 + 构造器链(Constructor Chaining)

我们通过多个构造器层层调用,把缺失的参数补上默认值:

public Tea(String name, int milk, boolean herbs, int sugar, int teaPowder) {
    this.name = name;
    this.milk = milk;
    this.herbs = herbs;
    this.sugar = sugar;
    this.teaPowder = teaPowder;
}

public Tea(String name, int milk, boolean herbs, int sugar) {
    this(name, milk, herbs, sugar, DEFAULT_TEA_POWDER);
}

public Tea(String name, int milk, boolean herbs) {
    this(name, milk, herbs, 0);
}

public Tea(String name, int milk) {
    this(name, milk, false);
}

public Tea(String name) {
    this(name, 0);
}

✅ 这种方式利用了 constructor chaining,从最简构造器一路传参到全参构造器,逻辑清晰,调用方便。

测试验证

@Test
public void whenTeaWithOnlyName_thenCreateDefaultTea() {
    Tea blackTea = new Tea("Black Tea");

    assertThat(blackTea.getName()).isEqualTo("Black Tea");
    assertThat(blackTea.getMilk()).isEqualTo(0);
    assertThat(blackTea.isHerbs()).isFalse();
    assertThat(blackTea.getSugar()).isEqualTo(0);
    assertThat(blackTea.getTeaPowder()).isEqualTo(Tea.DEFAULT_TEA_POWDER);
}

测试通过,说明默认值生效了。

3. 其他替代方案

虽然方法重载是最直观的方式,但还有几种其他思路可以实现类似效果,各有优劣:

✅ 方案一:使用 Builder 模式

  • 适合参数多、可选参数复杂的场景
  • 代码更清晰,可读性强
  • 推荐用于构造复杂对象
Tea tea = new TeaBuilder()
    .name("Green Tea")
    .milk(100)
    .sugar(1)
    .build();

具体实现略,可参考《Effective Java》中的 Builder 模式。

✅ 方案二:使用 Optional(谨慎使用)

public Tea(String name, Optional<Integer> milk, Optional<Boolean> herbs, Optional<Integer> sugar) {
    this.name = name;
    this.milk = milk.orElse(0);
    this.herbs = herbs.orElse(false);
    this.sugar = sugar.orElse(0);
    this.teaPowder = DEFAULT_TEA_POWDER;
}

⚠️ 但注意:Optional 用作方法参数是反模式(见 Oracle 官方建议),容易让调用方写起来很别扭,比如:

new Tea("Oolong", Optional.of(50), Optional.empty(), Optional.of(2));

❌ 不推荐用于 public 接口。

✅ 方案三:允许 null 值作为“未传”标记

public Tea(String name, Integer milk, Boolean herbs, Integer sugar, Integer teaPowder) {
    this.name = name;
    this.milk = milk == null ? 0 : milk.intValue();
    this.herbs = herbs == null ? false : herbs.booleanValue();
    this.sugar = sugar == null ? 0 : sugar.intValue();
    this.teaPowder = teaPowder == null ? DEFAULT_TEA_POWDER : teaPowder.intValue();
}

调用示例:

new Tea("Chai", 150, null, 2, null); // herbs 默认 false, teaPowder 默认 1

⚠️ 缺点:

  • null 容易引发歧义,IDE 不友好
  • 容易出 NPE,除非你严格校验
  • 可读性不如重载

✅ 仅建议在内部方法或兼容旧接口时使用。

4. 总结

方案 优点 缺点 推荐场景
方法重载 简单直接,调用清爽 参数多时构造器爆炸 ✅ 一般推荐
Builder 模式 可读性强,扩展性好 模板代码多 ✅ 参数 > 4 个时
Optional 参数 语义明确 调用繁琐,反模式 ❌ 不推荐
null 标记 实现简单 易出错,不直观 ⚠️ 仅限内部使用

🔚 结论:对于大多数场景,方法重载是最干净、最简单的方案,尤其适合参数不多(≤5)的情况。代码清晰,调用方便,也不需要引入额外模式。

所有示例代码已托管至 GitHub: 👉 https://github.com/example-java/tutorials/tree/main/core-java-lang-2


原始标题:Java Default Parameters Using Method Overloading