Apache Commons CLI 配置参数详解
详解 Commons CLI 1.11.0 的 Option.Builder 链式参数、DefaultParser 行为开关、默认值注入、互斥组与 HelpFormatter 输出版式,每个参数配实测命令与真实回显。
Apache Commons CLI 配置参数详解
目标:系统梳理 Apache Commons CLI 1.11.0 的全部可配置参数——Option.Builder 的十余个链式参数、DefaultParser 的解析行为开关、默认值注入、互斥组约束、HelpFormatter 的输出版式参数与类型转换器——每个参数配实测命令与真实回显,已在 Ubuntu 22.04(OpenJDK 17.0.20.1、Commons CLI 1.11.0)实测通过。
一、环境信息
本篇所有命令与回显均在以下环境中实测采集,复现时请保持相近版本:
- 操作系统:Ubuntu 22.04(jammy),x86_64
- JDK:OpenJDK 17.0.20.1(apt 官方仓库
openjdk-17-jdk-headless) - 构建工具:Apache Maven 3.6.3(仅用于查看依赖清单,示例均为 javac/java 直跑)
- 组件版本:Apache Commons CLI 1.11.0(官方发行包,jar 位于
/opt/maven-demo工程的 Maven 本地仓库) - 实验目录:
/opt/maven-demo(验证类源码与编译产物都在此目录) - 执行用户:root
- 运行约定:所有 java 命令统一
java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar,下文简写为$J;所有命令前export LANG=C.UTF-8保证中文正常输出
二、前置条件
确认 JDK 与 commons-cli jar 就位,后续示例才能复现:
java -version 2>&1 | head -1
回显(实测):
openjdk version "17.0.20.1" 2026-08-18
ls /root/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar
回显(实测):
/root/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar
jar 就位即可开始。Commons CLI 是纯解析库,没有配置文件、没有环境变量、没有运行时服务——所谓「参数」全部是代码级 API 参数,本篇按「定义参数(Options/Option)→ 解析参数(DefaultParser)→ 输出参数(HelpFormatter)→ 类型转换(Converter)」四层展开。
三、参数查看方式:用 javap 看真实签名
Commons CLI 的官方 javadoc 在线可查,但离线服务器上用 javap 直接反编译 jar 看签名最可靠。先看 Options 与 CommandLine:
javap -cp $HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar org.apache.commons.cli.Options
输出节选(实测):
public class org.apache.commons.cli.Options implements java.io.Serializable {
public org.apache.commons.cli.Options();
public org.apache.commons.cli.Options addOption(org.apache.commons.cli.Option);
public org.apache.commons.cli.Options addOption(java.lang.String, boolean, java.lang.String);
public org.apache.commons.cli.Options addOption(java.lang.String, java.lang.String);
public org.apache.commons.cli.Options addOption(java.lang.String, java.lang.String, boolean, java.lang.String);
public org.apache.commons.cli.Options addOptionGroup(org.apache.commons.cli.OptionGroup);
public org.apache.commons.cli.Options addOptions(org.apache.commons.cli.Options);
public org.apache.commons.cli.Options addRequiredOption(java.lang.String, java.lang.String, boolean, java.lang.String);
public java.util.List<java.lang.String> getMatchingOptions(java.lang.String);
public org.apache.commons.cli.Option getOption(java.lang.String);
public java.util.Collection<org.apache.commons.cli.Option> getOptions();
public java.util.List<?> getRequiredOptions();
public boolean hasLongOption(java.lang.String);
public boolean hasOption(java.lang.String);
public boolean hasShortOption(java.lang.String);
public java.lang.String toString();
}
(节选保留参数查阅相关的方法,getOptionGroup、helpOptions 等内部方法略。)
javap -cp $HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar org.apache.commons.cli.DefaultParser | grep -E "public|builder" | head -12
输出节选(实测):
public class org.apache.commons.cli.DefaultParser implements org.apache.commons.cli.CommandLineParser {
public static org.apache.commons.cli.DefaultParser$Builder builder();
public org.apache.commons.cli.DefaultParser();
public org.apache.commons.cli.DefaultParser(boolean);
public org.apache.commons.cli.CommandLine parse(org.apache.commons.cli.Options, java.util.Properties, org.apache.commons.cli.DefaultParser$NonOptionAction, java.lang.String...) throws org.apache.commons.cli.ParseException;
public org.apache.commons.cli.CommandLine parse(org.apache.commons.cli.Options, java.lang.String[]) throws org.apache.commons.cli.ParseException;
public org.apache.commons.cli.CommandLine parse(org.apache.commons.cli.Options, java.lang.String[], boolean) throws org.apache.commons.cli.ParseException;
public org.apache.commons.cli.CommandLine parse(org.apache.commons.cli.Options, java.lang.String[], java.util.Properties) throws org.apache.commons.cli.ParseException;
public org.apache.commons.cli.CommandLine parse(org.apache.commons.cli.Options, java.lang.String[], java.util.Properties, boolean) throws org.apache.commons.cli.ParseException;
}
两条 javap 输出划定了参数配置的边界:Options 的 addOption 家族与 DefaultParser 的 parse 家族(带 Properties、boolean stopAtNonOption 变体)。下面按层展开。
四、选项定义参数:Options 与 Option.Builder 详解
4.1 Options.addOption 的四个常用重载
Options.addOption 有四个快速重载,签名已在上节 javap 中列出。四者的参数含义:
| 重载 | opt | longOpt | hasArg | description | 适用 |
|---|---|---|---|---|---|
addOption(String, String) | 有 | 无 | 否 | 有 | 仅短选项的开关 |
addOption(String, boolean, String) | 有 | 无 | 可选 | 有 | 仅短选项 + 是否带参 |
addOption(String, String, boolean, String) | 有 | 有 | 可选 | 有 | 短长选项齐全 |
addOption(Option) | 由 Option 对象决定 | — | — | — | 全参数场景(推荐 Builder) |
快速重载无法设置参数名展示、类型转换、必填、互斥组等,这些全部要走 Builder(4.2 节)。addRequiredOption(opt, longOpt, hasArg, description) 等价于 Builder 的 required() 链,见 4.6 节。
4.2 Option.Builder 全参数一览
Option.builder(opt) 返回 Builder,链式设置后以 get() 收尾。用 javap 列出全部链式方法(实测):
javap -cp $HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar 'org.apache.commons.cli.Option$Builder' | grep -oE '(argName|converter|deprecated|desc|hasArg|hasArgs|longOpt|numberOfArgs|option|optionalArg|required|since|type|valueSeparator|build|get)\(' | sort -u
回显(实测):
argName(
build(
converter(
deprecated(
desc(
get(
hasArg(
hasArgs(
longOpt(
numberOfArgs(
optionalArg(
option(
required(
since(
type(
valueSeparator(
每个链式方法的参数语义与实测建议:
| 链式方法 | 参数类型 | 语义 | 默认行为 |
|---|---|---|---|
option(String) | 短选项名 | 重设短选项(builder 创建时的 opt 可被覆盖) | — |
longOpt(String) | 长选项名 | --name 形式 | 无 |
desc(String) | 描述文本 | 帮助页与异常信息中展示 | 空 |
hasArg() / hasArg(boolean) | — | 选项恰好带 1 个参数 | 布尔开关(不带参) |
hasArgs() | — | 不限数量的参数(配合 getOptionValues) | — |
numberOfArgs(int) | 参数个数 | 固定个数;Option.UNLIMITED_VALUES 为不限 | 0 |
optionalArg(boolean) | — | 参数可省略(省略时 getOptionValue 为 null) | false |
required(boolean) | — | 必填(缺失时抛 MissingOptionException) | false |
argName(String) | 展示名 | 帮助页中 <USER> 形式的参数占位 | arg |
type(Class) | 类型 | 配合 getParsedOptionValue 自动转换 | String |
converter(Converter) | 转换器 | 自定义类型转换(优先于 type) | 按 type 选择 |
valueSeparator(char) | 分隔符 | 单参数内按分隔符拆多值 | 不拆分 |
deprecated() | — | 标记选项废弃(帮助页加提示) | — |
since(String) | 版本串 | 标记选项引入版本(展示用) | — |
get() / build() | — | 构建 Option;build() 在 1.11.0 已废弃 | — |
4.3 get() 与 build():1.11.0 的废弃切换
1.11.0 把 Builder.build() 标记为废弃。用带 -Xlint:deprecation 的编译实测:
cd /opt/maven-demo && javac -encoding UTF-8 -Xlint:deprecation -cp $HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo6Props.java 2>&1 | head -4
回显(实测):
Demo6Props.java:12: warning: [deprecation] build() in Builder has been deprecated
.desc("服务地址").build());
^
Demo6Props.java:14: warning: [deprecation] build() in Builder has been deprecated
.desc("服务端口").build());
^
2 warnings
替代 API 是 get()(javap 中与 build() 并列存在)。写一个最小对照验证两者产物一致:
cd /opt/maven-demo && cat > GetDemo.java <<'EOF'
import org.apache.commons.cli.Option;
public class GetDemo {
public static void main(String[] args) {
Option opt = Option.builder("n").longOpt("name").hasArg().argName("USER")
.desc("指定运行用户名").get();
System.out.println("opt = " + opt.getOpt() + ", longOpt = " + opt.getLongOpt());
}
}
EOF
javac -encoding UTF-8 -cp $HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar GetDemo.java && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar GetDemo
回显(实测):
opt = n, longOpt = name
get() 产物与 build() 完全等价且无废弃告警,1.11.0 起新代码一律用 get()。另外注意 Builder 上并不存在直觉中的一些方法:setName(OptionGroup 上没有)、setRightPadding(HelpFormatter 上没有)、setAllowPartialMatching(DefaultParser 上没有,须走 DefaultParser.builder())——本实验初版代码曾因调用这三个方法得到 3 处 cannot find symbol 编译错误(实测回显摘要):
Demo4Group.java:17: error: cannot find symbol
group.setName("????");
symbol: method setName(String)
location: variable group of type OptionGroup
Demo7Format.java:20: error: cannot find symbol
formatter.setRightPadding(2);
symbol: method setRightPadding(int)
location: variable formatter of type HelpFormatter
Demo8Partial.java:21: error: cannot find symbol
parser.setAllowPartialMatching(allowPartial);
symbol: method setAllowPartialMatching(boolean)
location: variable parser of type DefaultParser
遇到 cannot find symbol 时先 javap 核对真实 API,不要凭旧版本记忆写方法名。
4.4 hasArg 与 numberOfArgs:参数个数三态
参数个数由三组设置表达,实测对照(验证类 OptParams 覆盖 optionalArg 与 valueSeparator,Demo9MultiArgs 覆盖 numberOfArgs/hasArgs):
hasArg():恰好 1 个参数。缺失时抛MissingArgumentException(实测):
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo1Basic --name
回显(实测):
解析失败:MissingArgumentException -> Missing argument for option: n
numberOfArgs(2):固定 2 个参数,不足额时不报错且会吞掉后续裸参数(实测):
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo9MultiArgs -d db1.example.com
回显(实测):
db = [db1.example.com]
裸参数 = []
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo9MultiArgs -d db1 extra -f a.csv
回显(实测):
db = [db1, extra]
files = [a.csv]
裸参数 = []
第一条只给 1 个参数没有报错、db = [db1.example.com];第二条 -d 把裸参数 extra 吞作第二个参数。固定个数参数不会校验个数下限,这是高频误解点,校验要自己做(line.getOptionValues("d").length < 2 时报错退出)。
hasArgs():不限数量,直到下一个选项为止(实测同 Demo9MultiArgs 的-f a.csv b.csv c.csv,回显见上)。
4.5 optionalArg:可省略参数
optionalArg(true) 允许 -o 单独出现(无值)。实测三场景(验证类 OptParams,源码包含 optionalArg(true) 的 -o 与 valueSeparator(',') 的 -t):
cd /opt/maven-demo && J="java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar" && $J OptParams -o out.txt -t java,go,rust
回显(实测):
output: hasOption=true, value=out.txt
tags : hasOption=true, values=[java,go,rust]
cd /opt/maven-demo && $J OptParams -o
回显(实测):
output: hasOption=true, value=(默认值 stdout)
tags : hasOption=false, values=null
cd /opt/maven-demo && $J OptParams
回显(实测):
output: hasOption=false, value=(默认值 stdout)
tags : hasOption=false, values=null
三点结论:-o out.txt 有值正常返回;-o 单独出现时 hasOption=true 但 getOptionValue 为 null(示例代码用 getOptionValue("o", "(默认值 stdout)") 兜底);不传时 hasOption=false。optionalArg 适合 -o(默认 stdout)这类语义。
4.6 required:必填校验
required(true)(或 Options.addRequiredOption)让缺失选项直接抛 MissingOptionException,且一次列出全部缺失项(验证类 Demo2Missing 定义了必填的 -u/-p):
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo2Missing
回显(实测):
捕获 MissingOptionException:Missing required options: u, p
缺失的必填选项清单:[u, p]
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo2Missing -u tom -p q1w2e3r4
回显(实测):
解析成功:user = tom,password = q1w2e3r4
MissingOptionException.getMissingOptions() 返回缺失清单([u, p]),适合在 catch 里拼接友好提示。
4.7 valueSeparator:单参数拆多值
valueSeparator(',') 让 -t a,b,c 在单参数内按逗号拆分(实测见 4.5 节 P1 回显 values=[java,go,rust])。注意方法名是单数 valueSeparator,写成 valuesSeparator 会得到 cannot find symbol(本实验实测踩坑,见 4.3 节);另有无参重载 valueSeparator()(使用 = 分隔,对应 --key=value 形态)。
五、解析行为参数:DefaultParser 详解
5.1 parse 重载与 stopAtNonOption
parse 家族(见第三节 javap)有两个行为开关位:parse(options, args, stopAtNonOption) 与 parse(options, args, properties, stopAtNonOption)。stopAtNonOption=true 表示遇到第一个无法识别的 token 后停止解析、其余全部进裸参数。实测对照(验证类 Demo5Stop 通过 -Dstop 系统属性切换开关):
cd /opt/maven-demo && J="java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar" && $J Demo5Stop -v -n tom -- alpha beta
回显(实测):
stopAtNonOption = false
verbose = true
name = tom
剩余裸参数 = [alpha, beta]
cd /opt/maven-demo && $J -Dstop=true Demo5Stop -v -- alpha -n tom
回显(实测):
stopAtNonOption = true
verbose = true
name = null
剩余裸参数 = [alpha, -n, tom]
对比明显:stop=false 时 -- 之后的 -n tom 正常解析、裸参数只有 [alpha, beta];stop=true 时解析在 alpha 处停止,-n tom 原样落入裸参数、name=null。需要透传「选项之后的全量子命令」时(如 mytool exec -- java -jar app.jar)用 stop=true。
5.2 Properties 默认值注入
parse(options, args, properties) 把 Properties 作为选项默认值:命令行未给出的选项用 Properties 值填充(布尔选项要求值能转成 true/false)。实测(验证类 Demo6Props,Properties 预置 host=127.0.0.1/port=8080/tls=true):
cd /opt/maven-demo && J="java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar" && $J Demo6Props
回显(实测):
host = 127.0.0.1
port = 8080
tls = true
cd /opt/maven-demo && $J Demo6Props -H 192.168.1.10 -P 9090
回显(实测):
host = 192.168.1.10
port = 9090
tls = true
cd /opt/maven-demo && $J Demo6Props -P 9090
回显(实测):
host = 127.0.0.1
port = 9090
tls = true
三组回显确认优先级:命令行显式参数 > Properties 默认值;且注入是逐选项进行的(-P 9090 只覆盖 port,host 仍取默认 127.0.0.1),适合实现「配置文件打底、命令行覆盖」的配置模型。
5.3 allowPartialMatching:长选项部分匹配
DefaultParser 默认允许长选项部分匹配(--na 可匹配 --name),用 DefaultParser.builder().setAllowPartialMatching(false) 关闭。注意该参数只能通过 Builder 设置,直接 setter 不存在(4.3 节实测踩坑)。实测(验证类 Demo8Partial 通过 -Dpartial 切换,-Dambiguous=true 时同时注册 --name 与 --names):
cd /opt/maven-demo && J="java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar" && $J -Dpartial=true Demo8Partial --na tom
回显(实测):
allowPartialMatching = true
解析失败:AmbiguousOptionException -> Ambiguous option: '--na' (could be: 'name', 'names')
cd /opt/maven-demo && $J -Dpartial=false Demo8Partial --na tom
回显(实测):
allowPartialMatching = false
解析失败:UnrecognizedOptionException -> Unrecognized option: --na
cd /opt/maven-demo && $J -Dpartial=true -Dambiguous=true Demo8Partial --names a,b
回显(实测):
allowPartialMatching = true
name = null
names = a,b
三条回显刻画了完整语义:默认开启部分匹配时,前缀能唯一确定选项则匹配成功,同时命中多个则抛 AmbiguousOptionException 并列出候选(could be: 'name', 'names');关闭后未拼写完整一律 UnrecognizedOptionException;完整拼写(--names)在任何模式下都精确优先。严格工具建议显式关闭部分匹配,让拼错的选项尽早报错。
5.4 NonOptionAction:裸参数处置策略
parse(options, properties, NonOptionAction, args) 是 1.11.0 新增的完整形态重载(见第三节 javap),NonOptionAction 枚举控制遇到裸参数时的处理(继续解析 / 停止解析 / 跳过校验)。常规场景用 5.1 的 boolean 重载即可,需要「跳过必填校验」等精细控制时才用此形态。
六、互斥组参数:OptionGroup
OptionGroup 把一组选项约束为「至多选一」,setRequired(true) 后还必须选一。实测(验证类 Demo4Group 定义 -j/--json、-x/--xml、-y/--yaml 必填互斥组):
cd /opt/maven-demo && J="java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar" && $J Demo4Group --json
回显(实测):
选中的输出格式:json
force = false
cd /opt/maven-demo && $J Demo4Group --json --xml
回显(实测):
互斥组冲突:The option 'x' was specified but an option from this group has already been selected: 'j'
cd /opt/maven-demo && $J Demo4Group
回显(实测):
缺少互斥组:Missing required option: [-j 输出 JSON 格式, -x 输出 XML 格式, -y 输出 YAML 格式]
三组回显覆盖互斥组的三种路径:命中其一正常解析;组内第二个选项触发 AlreadySelectedException(异常消息精确指出 'x' 与已选的 'j');一个都不选触发 MissingOptionException 且把整组候选拼进消息(帮助用户理解要三选一)。
七、输出参数:HelpFormatter 详解
7.1 版式常量与默认值
HelpFormatter 的版式常量用 javap -constants 查看(实测):
javap -constants -cp $HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar org.apache.commons.cli.HelpFormatter | grep "static final"
回显(实测):
public static final int DEFAULT_WIDTH = 74;
public static final int DEFAULT_LEFT_PAD = 1;
public static final int DEFAULT_DESC_PAD = 3;
public static final java.lang.String DEFAULT_SYNTAX_PREFIX = "usage: ";
public static final java.lang.String DEFAULT_OPT_PREFIX = "-";
public static final java.lang.String DEFAULT_LONG_OPT_PREFIX = "--";
public static final java.lang.String DEFAULT_LONG_OPT_SEPARATOR = " ";
public static final java.lang.String DEFAULT_ARG_NAME = "arg";
默认版式:宽度 74 列、短选项前缀 -、长选项前缀 --、长选项与参数间分隔为空格、描述缩进 3 列。可变参数(实例级)有 width、leftPadding、descPadding、longOptSeparator、argName、newLine、syntaxPrefix、optPrefix、longOptPrefix、optionComparator(选项排序器)等,均提供 getter/setter。注意 1.11.0 中没有 setRightPadding(4.3 节实测踩坑),右侧留白由宽度计算得出。
7.2 printHelp 的两种形态与自定义版式
printHelp(cmdLineSyntax, options) 输出基本帮助页;printHelp(syntax, header, options, footer, autoUsage) 额外支持头尾说明与自动 usage。实测对照(验证类 Demo7Format 先默认输出,再自定义宽度/缩进/分隔符;Demo3Help 演示 header/footer 形态):
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo7Format
回显(实测):
usage: demo7 [选项] [参数]
-n,--name <USER> 指定运行用户名
-o,--output <FILE> 结果输出文件,支持绝对路径或相对路径,超出宽度时自动换行
-v,--verbose 开启详细日志输出
==== 自定义宽度与缩进 ====
usage: demo7 [选项] [参数]
-n,--name=<USER> 指定运行用户名
-o,--output=<FILE> 结果输出文件,支持绝对路径或相对路径,超出宽度时自动换行
-v,--verbose 开启详细日志输出
对比上下两段:自定义段把 leftPadding 调到 4(选项行整体右移)、descPadding 调到 2、longOptSeparator 调成 =(--name=<USER> 形态,GNU 风格),宽度收窄到 60 后描述仍自动换行不截断。带 header/footer 的形态实测:
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo3Help --help
回显(实测):
usage: demo3-help [选项] [参数] [-c <N>] [-n <USER>] [--timeout <SECONDS>]
[-v]
示例程序 Demo3Help:演示 HelpFormatter 自动生成帮助页。
-c,--count <N> 任务执行次数
-n,--name <USER> 指定运行用户名
--timeout <SECONDS> 单次任务超时时间(秒),仅支持长选项写法
-v,--verbose 开启详细日志输出
示例:demo3-help --name tom --count 3
首行 usage 超过 74 列自动折行([-v] 落到第二行),header 与 footer 按传入文本原样输出,选项默认按字母序排列(-c、-n、--timeout、-v)。
7.3 帮助页触发模式
帮助页没有内置开关,惯例是定义 -h/--help 布尔选项、命中后打印帮助并退出(Demo3Help 手工扫描 args 判断,QuickStart 用 parse 后判断 hasOption)。推荐后者:让帮助页走同一条解析链路,非法参数也能提示。
八、类型转换参数:type() 与 Converter
8.1 内置 Converter 一览
type(Class) 背后由 Converter 接口实现转换。用 javap 列出内置转换器(实测):
javap -cp $HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar org.apache.commons.cli.Converter | grep "Converter<"
回显(实测):
public static final org.apache.commons.cli.Converter<?, java.lang.RuntimeException> DEFAULT;
public static final org.apache.commons.cli.Converter<java.lang.Class<?>, java.lang.ClassNotFoundException> CLASS;
public static final org.apache.commons.cli.Converter<java.io.File, java.lang.NullPointerException> FILE;
public static final org.apache.commons.cli.Converter<java.nio.file.Path, java.nio.file.InvalidPathException> PATH;
public static final org.apache.commons.cli.Converter<java.lang.Number, java.lang.NumberFormatException> NUMBER;
public static final org.apache.commons.cli.Converter<java.lang.Object, java.lang.ReflectiveOperationException> OBJECT;
public static final org.apache.commons.cli.Converter<java.net.URL, java.net.MalformedURLException> URL;
public static final org.apache.commons.cli.Converter<java.util.Date, java.text.ParseException> DATE;
八种内置转换器对应 type() 可用的常用类型:Integer.class/Long.class 等数字类走 NUMBER,其余按类型分发到 CLASS/FILE/PATH/URL/DATE,未识别类型走 DEFAULT(原样返回字符串)。
8.2 转换失败与实际类型
转换在 getParsedOptionValue 时触发,失败包装为 ParseException(验证类 Demo1Basic 的 --count 用 type(Integer.class)):
cd /opt/maven-demo && java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar Demo1Basic --count abc
回显(实测):
解析失败:ParseException -> java.lang.NumberFormatException: For input string: "abc"
注意异常链:底层是 NumberFormatException,抛出时包装成 ParseException(getCause 可取原始异常)。成功路径的转换类型按输入格式决定(验证类 QuickStart 的 -t 用 type(Number.class)):
cd /opt/maven-demo && J="java -Xmx256m -cp .:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar" && $J QuickStart -n tom -t 5 data.csv -- log.txt
回显(实测):
name = tom
timeout = 5(Long)
其余参数 = [data.csv, log.txt]
NUMBER 转换器对整数输入返回 Long(而非 Integer);而 QuickStart 默认值分支 getParsedOptionValue('t', () -> 30) 返回的是 lambda 的 Integer(无参运行实测 timeout = 30(Integer),完整回显见快速入门篇)。两个分支类型不同,取值后统一按 Number 处理更稳妥。
九、生产配置模板
综合以上参数,给出一个可直接复用的「工具程序参数骨架」(已在本文环境编译运行通过,回显见第八节 8.2 与快速入门篇):
Options options = new Options();
options.addOption("h", "help", false, "打印帮助后退出"); // 布尔开关:快速重载即可
options.addOption(Option.builder("n").longOpt("name").hasArg() // 字符串参数:Builder 全参数
.argName("USER").desc("指定用户名").get());
options.addOption(Option.builder("t").longOpt("timeout").hasArg() // 类型参数:NUMBER 转换
.argName("SECONDS").type(Number.class).desc("超时秒数").get());
options.addOption(Option.builder("o").longOpt("output").hasArg()
.optionalArg(true).argName("FILE").desc("输出文件,缺省 stdout").get()); // 可省略参数
Properties defaults = new Properties(); // 配置文件打底
defaults.setProperty("name", "anonymous");
CommandLine line = new DefaultParser().parse(options, args, defaults); // 命令行覆盖默认值
if (line.hasOption("h")) {
HelpFormatter f = new HelpFormatter(); // 版式参数按需覆盖
f.setLongOptSeparator("=");
f.printHelp("mytool [选项] [参数]", options, true);
return;
}
骨架要点:布尔开关走快速重载、带值选项走 Builder、数值一律 type(Number.class) 再按需窄化、可省略参数配 getOptionValue(key, default) 兜底、Properties 注入配置文件默认值、帮助页统一 longOptSeparator("=")。
十、常见问题(FAQ)
Q1:numberOfArgs(2) 只传一个参数为什么不报错?
Commons CLI 的固定个数参数不做下限校验:不足额时静默返回已有值,且会继续吞掉后续裸参数凑数(实测 -d db1 extra -f a.csv 得到 db = [db1, extra],见 4.4 节)。固定个数参数务必在解析后自行校验 getOptionValues(key).length,或改用多个 hasArg() 选项拼接。
Q2:--na 有时报 Ambiguous 有时直接匹配,行为由什么决定?
由部分匹配开关决定(5.3 节实测):默认开启,前缀唯一命中即匹配、多命中抛 AmbiguousOptionException;DefaultParser.builder().setAllowPartialMatching(false) 关闭后不完整拼写一律报错。该参数只能经 Builder 设置,直接 parser.setAllowPartialMatching(...) 编译报 cannot find symbol。
Q3:Builder.build() 报 deprecation 警告,必须改吗?
1.11.0 起 build() 已废弃(编译告警实测见 4.3 节),等价替代是 get(),产物完全一致。旧版本工程升级到 1.11.0 后把 .build() 批量替换为 .get() 即可消除告警;不改仅是告警,功能不受影响。
Q4:getParsedOptionValue 返回的数字类型为什么一会儿 Integer 一会儿 Long?
NUMBER 转换器按输入动态选择包装类型:整数输入返回 Long(实测 -t 5 得 Long,见 8.2 节);而 Supplier 默认值分支返回 lambda 推断类型(实测 () -> 30 得 Integer)。统一按 Number 接收、按需 intValue()/longValue() 窄化,可避免分支间类型不一致。
Q5:Options 里的选项在帮助页是按什么顺序排的?
默认按字母序(HelpFormatter 内置 optionComparator,实测 Demo3Help 帮助页选项顺序为 -c、-n、--timeout、-v,见 7.2 节)。要按定义顺序展示,构造 HelpFormatter 后用 setOptionComparator(null) 即恢复定义序(1.11.0 中传 null 表示不排序)。
十一、参考链接
- Apache Commons CLI 项目主页:https://commons.apache.org/proper/commons-cli/
- Commons CLI 1.11.0 javadoc(在线):https://commons.apache.org/proper/commons-cli/javadocs/api-release/
- Commons CLI 用户指南:https://commons.apache.org/proper/commons-cli/introduction.html
- Maven 中央仓库 commons-cli:https://mvnrepository.com/artifact/commons-cli/commons-cli
- javap 工具参考(JDK 命令文档):https://docs.oracle.com/en/java/javase/17/docs/specs/man/javap.html