首页 / Java 入门教程 / 命令行参数与可变参数

Java 入门教程

命令行参数与可变参数

本教程共 100 篇 · 第 28 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

JavaJava 入门教程命令行参数可变参数varargs

28. 命令行参数与可变参数

本节目标:搞懂 main 方法的 String[] args 从哪来、怎么传,并掌握可变参数(varargs)让一个方法能接收任意多个同类型实参的写法。

程序启动时常需要外部传点信息进来,比如要处理哪个文件、用什么模式跑。写死在代码里,改一次就得重新编译一次,显然不合适。

Java 用两套机制接收「数量不确定」的输入:命令行参数负责程序外部传入,可变参数负责方法之间传递。两者底层都是数组。

命令行参数:main 的 args

从第一天写 Hello World 起,你就在写 public static void main(String[] args),那个 args 一直没派上用场。它就是命令行参数,运行程序时跟在类名后面。

public class ArgsDemo {
    public static void main(String[] args) {
        System.out.println("收到 " + args.length + " 个参数");
        for (String s : args) {
            System.out.println(s);
        }
    }
}
```bash

编译后用空格把参数跟在类名后面:

```bash
javac ArgsDemo.java
java ArgsDemo hello world 2026

输出:

收到 3 个参数
hello
world
2026
```bash

参数按书写顺序装进数组,`args[0]` 是 `hello`,不是类名。这点和 C 语言不同——C 的 `argv[0]` 是程序名,Java 的 `args[0]` 直接就是第一个真实参数。

## args 是空数组,不是 null

不传任何参数时,`args` 是一个**长度为 0 的数组**,不是 `null`:

```bash
java ArgsDemo

输出 收到 0 个参数,循环一次都不进,不会抛异常。

这是 JVM 的保证,所以下面这种防御性判空纯属多余:

if (args != null && args.length > 0) { }   // 前半句白写
if (args.length > 0) { }                   // 够了
```text

**下标访问必须判长度**。用户没传参数时直接读 `args[0]`:

```text
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
	Index 0 out of bounds for length 0

这是命令行程序最常见的崩溃。取参数前先检查个数,或者给个默认值。

参数永远是字符串

不管你在命令行里敲的是什么,进到 args 里全是 String。要当数字用必须自己转:

public class SumArgs {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("用法:java SumArgs 数字1 数字2");
            return;
        }
        int a = Integer.parseInt(args[0]);
        int b = Integer.parseInt(args[1]);
        System.out.println(a + b);
    }
}
```java

`Integer.parseInt` 把字符串转成 int。转不了会抛异常,比如 `java SumArgs abc 1`:

```text
Exception in thread "main" java.lang.NumberFormatException:
	For input string: "abc"
Note

转数字用 Integer.parseInt(要 int)或 Integer.valueOf(要 Integer 对象)。new Integer("1") 这种构造方法从 Java 9 起就被标记废弃,Java 16 之后更是明确不该再用,历史兼容,不推荐

带空格的参数要加引号

参数之间靠空格分隔,所以参数自身含空格时必须用引号包起来:

java ArgsDemo "hello world" 2026
```bash

这样是 2 个参数;不加引号就变成 3 个。引号由操作系统的 shell 处理,Windows 的 cmd、PowerShell 和 Linux/macOS 的 bash 规则略有差异,但双引号在三者都能用。

## 单文件直接运行

Java 11 起可以跳过 `javac`,直接运行源文件,参数照传:

```bash
java ArgsDemo.java hello world

JVM 会在内存里编译再执行,不生成 .class 文件。写小脚本、做快速验证时很方便,正式项目还是走编译流程。

main 的几种合法写法

main 的签名并不像想象中那么死板。下面几种都能被 JVM 认作程序入口:

public static void main(String[] args)     // 标准写法
public static void main(String... args)    // 可变参数版,同样合法
static public void main(String[] args)     // 修饰符顺序可换
public static void main(String[] 随便叫)    // 参数名任意
```java

关键是三点:`public static`、返回 `void`、参数是一个 String 数组。第二种能行,正是因为可变参数编译后就是数组——这两个概念在这里接上了头。

Java 25 LTS 转正的紧凑源文件特性(JEP 512)还允许省掉外层类和 `static`:

```java
void main() {
    System.out.println("Hello");
}

这是为降低初学门槛做的简化,写小程序很省事。但它拿不到 args,需要命令行参数时还是得写完整签名。工作中读到的绝大多数代码仍是标准写法,两种都要认识。

可变参数:方法自己收多个值

写一个求和方法,调用方有时传 2 个数,有时传 5 个,怎么办?

老办法是让调用方自己封数组,很别扭:

static int sum(int[] nums) { }
sum(new int[]{1, 2, 3});   // 每次都要 new,烦
```java

Java 5 引入的**可变参数(varargs)**解决了这个问题,语法是在类型后加三个点 `...`:

```java
public class VarargsDemo {
    static int sum(int... nums) {   // nums 在方法内部就是 int[]
        int total = 0;
        for (int n : nums) {
            total += n;
        }
        return total;
    }

    public static void main(String[] args) {
        System.out.println(sum(1, 2, 3));   // 6
        System.out.println(sum(10, 20));    // 30
        System.out.println(sum());          // 0,一个都不传也行
    }
}

int... nums 在方法体里就是一个 int[],该怎么遍历怎么遍历。调用时直接把值列出来,编译器自动打包成数组塞进去。

一个都不传时,nums 是长度为 0 的空数组,不是 null。这和 mainargs 是同一套逻辑。

可变参数的规则

规则只有两条,但都是硬性的。

一个方法最多一个可变参数,而且必须放在参数列表末尾

static void print(String prefix, int... nums) {   // 正确
    System.out.print(prefix + ":");
    for (int n : nums) {
        System.out.print(n + " ");
    }
}
```java

放错位置直接编译不过:

```java
static void bad(int... a, String b) { }
error: varargs parameter must be the last parameter
```bash

道理不难想:编译器要判断「从哪个实参开始该打包进数组」。如果可变参数后面还有别的参数,这个边界就没法确定了。

## 可变参数就是数组

理解了这一点,很多行为就不用死记。可变参数方法可以直接接收数组:

```java
sum(1, 2, 3);                 // 列值,编译器帮你打包
sum(new int[]{1, 2, 3});      // 直接给数组,效果完全一样

反过来也成立:不能同时定义可变参数版和数组版的重载,它们编译后签名一模一样:

static int f(int... a) { return 0; }
static int f(int[] a)  { return 0; }
```java

```text
error: cannot declare both f(int[]) and f(int...) in class Demo
Warning

String...null 要当心。f(null) 会把整个 null 当成数组本身传进去,方法里遍历就抛空指针,编译器只给一句 warning:non-varargs call of varargs method with inexact argument type for last parameter。想传「一个 null 元素」得写 f(new String[]{null})

重载时谁优先

同时存在精确匹配和可变参数版本时,编译器优先选精确匹配

static void f(int a, int b) { System.out.println("两参数版"); }
static void f(int... a)     { System.out.println("可变参数版"); }

f(1, 2);      // 两参数版
f(1, 2, 3);   // 可变参数版
```bash

可变参数是最后的兜底选项。编译器会先找参数个数和类型完全对上的,找不到才考虑装箱、再找不到才考虑可变参数。这个顺序是为了兼容 Java 5 之前的老代码——加了 varargs 重载不会悄悄改变已有调用的行为。

正因如此,重载里混用可变参数很容易让人读不懂调用了哪个。工具方法里用它很好,重载体系里尽量少掺和。

## 两者对照

| 项目 | 命令行参数 `args` | 可变参数 `int...` |
|------|------------------|------------------|
| 来源 | 程序外部,运行时传入 | 程序内部,方法调用传入 |
| 类型 | 固定是 `String[]` | 任意类型都行 |
| 数量为 0| 空数组,不是 null | 空数组,不是 null |
| 本质 | 数组 | 数组 |
| 位置要求 | main 的唯一参数 | 必须是参数列表最后一个 |

## 常见疑问

**问:可变参数会不会影响性能?**

每次调用都会 new 一个数组,高频调用的热点路径上有一点开销。JDK 的做法是给最常见的 0~2 个参数场景加固定参数的重载,其余交给可变参数版兜底。日常业务代码不用为此纠结,可读性收益远大于这点开销。

**问:泛型可变参数为什么会有警告?**

写 `static <T> void f(T... args)` 时,编译器会提示 `Possible heap pollution from parameterized vararg type T`。因为泛型数组本身是不安全的,往里塞错类型编译期查不出来。确认自己的方法只读不写这个数组,就在方法上加 `@SafeVarargs` 消除警告。

**问:JDK 里哪些常用方法用了可变参数?**

非常多。`String.format(fmt, ...)`、`Arrays.asList(...)`、`List.of(...)`、`String.join(分隔符, ...)` 都是。看到方法签名里的三个点,就知道它接收任意多个同类型实参。