Java命令行参数解析器
从命令行启动Java程序时,有时可能需要将命令行参数传递给程序的main方法。这些命令行参数可能包含程序正确执行其工作所需的各种配置信息。
在本教程中,我将向我们展示如何使用我开发的命令行参数解析器。本教程的结尾列出了命令行参数解析器的完整代码(这只是一个类)。如果我们想在自己的项目中使用它,请随意使用。考虑它是根据Apache许可证2发布的,该许可证允许我们在开放源代码和商业项目中使用它。该代码在GitHub上也可用,以备我们分叉使用。
命令行参数类别
本教程中描述的命令行参数解析器将命令行参数分为两类:
- Targets (目标)
- Switches (开关)
目标是Java程序要处理的内容。例如,这可以是要复制的文件的文件名(或者Java程序对该文件所做的任何操作)。
开关是Java程序的配置参数。开关通常是可选的。
查看以下用于复制文件的Java程序的示例命令行参数:
-overwrite file.txt file2.txt
这些命令行参数的解释可能是复制程序应将file.txt复制到名为file2.txt的文件,并覆盖file2.txt(如果已存在)。
这些命令行参数由一个开关(" -overwrite")和两个目标(" file.txt"和" file.2.txt")组成。在以下各节中将更详细地说明开关和目标。
开关
如上所述,开关通常用于向Java应用程序提供其他配置信息。
开关始终以-
字符开头。例如-port
,-ha
或者--debug
。
开关可能仅由开关本身组成(意味着它存在或者不存在),或者开关可能具有值。查看以下一系列命令行参数:
-ha -port 9999
第一个开关" -ha"是"布尔"开关。它是否存在。第二个开关" -port"后面紧跟着值" 9999"。这意味着开关" -port"的值为" 9999"。它不仅存在还是不存在。稍后我们将看到命令行参数解析器如何区分不同类型的开关。
目标
目标是不是开关的所有命令行参数。删除开关和开关值时的所有其余参数。查看以下命令行参数:
-overwrite file.txt file2.txt
当删除开关-overwrite时,两个参数file.txt和file2.txt保留。这些论点被认为是目标。稍后我们将看到命令行参数解析器如何知道开关值和目标之间的差异。
命令行参数解析器
命令行参数解析器由一个名为CliArgs
的Java类组成。它的标准Java类名称为com.Hyman.cliargs.CliArgs
。
CliArgs类将String数组作为其构造函数的参数。这是一个如何实例化CliArgs
的例子:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); }
一旦有了CliArgs
实例,就可以开始要求它提供开关和目标。
CliArgs类包含以下方法,可用于获取开关信息和值:
- switchPresent()
- switchValue()
- switchLongValue()
- switchDoubleValue()
- switchValues()
要访问命令行参数的目标,可以调用:
- targets()
这些方法中的每一种都将在下面更详细地说明。
switchPresent()
switchPresent()方法可用于询问给定的开关是否存在。例如,前面显示的-overwrite
开关。这是如何检查命令行参数中是否存在-overwrite开关的示例:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); boolean overwrite = cliArgs.switchPresent("-overwrite"); }
如果命令行参数在任何地方都包含-overwrite开关,则switchPresent()方法将返回true。如果不是,则switchPresent()方法将返回false。
switchValue(),switchLongValue()和switchDoubleValue()
switchValue()方法可用于获取开关的值。开关的值是紧随开关本身之后的参数。在以下命令行参数中,开关-port
的值为9999
:
-port 9999
这是一个代码示例,显示了如何使用switchValue()
来读取-port
开关的值:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); String port = cliArgs.switchValue("-port"); }
如果要将开关的值解析为" long"或者" double",则可以使用" switchLongValue()"和" switchDoubleValue()"方法。这是显示如何使用它们的代码示例:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); String port = cliArgs.switchValue("-port"); long portLong = cliArgs.switchLongValue("-port"); long portDouble = cliArgs.switchDoubleValue("-port"); }
这三种方法都存在于采用默认值的版本中,以防不存在该开关。这是一个代码示例,向我们显示使用默认值的样子:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); String port = cliArgs.switchValue("-port", 9999); long portLong = cliArgs.switchLongValue("-port", 9999L); long portDouble = cliArgs.switchDoubleValue("-port", 9999D); }
如果不存在" -port"开关,则上述方法调用将返回值9999. 这是作为第二个参数传递给所有方法调用的默认值。
switchValues()
switchValues()方法用于获取开关的多个值。 switchValues()方法将在切换之后直到遇到下一个切换之前返回所有值(下一个参数以-字符开头)。
这是第一个命令行参数示例集:
-from file1.txt file2.txt -to copy1.txt copy2.txt
这些命令行参数包含两个开关,每个开关具有两个开关值。我们可以像这样读取开关值:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); String[] from = cliArgs.switchValues("-from"); String[] to = cliArgs.switchValues("-two"); }
-from开关的值将是file1.txt和file2.txt。这些都是在" -from"开关和下一个开关(" -to"开关)之间存在的所有值。
-to开关的值将为copy1.txt和copy2.txt。这些都是-to
开关之后的所有值。
switchPojo()
如果Java应用程序可以使用很多命令行参数,则显式检查每个单独的开关可能会使代码有些冗长。相反,我们可以创建一个Swith POJO(普通的旧Java对象)类来保存所有开关。这是一个这样的开关POJO类示例:
public class CliSwitches { public boolean ha = false; public int port = 8080; public byte port2 = 123; public String[] conf = null; }
这个" CliSwitches"类包含4个开关。命令行参数将被解释为属于这4个开关之一(或者成为目标)。例如:
-port 9999 -ha -conf val1 val2
现在,我们可以使用switchPojo()方法将所有开关从命令行直接读取到CliSwitches类的实例中。看起来是这样的:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); CliSwitches switches = cliArgs.switchPojo(CliSwitches.class); //access switches.port, switches.port2, switches.ha and switches.conf }
我们可以创建自己的POJO类。 CliArgs``switchPojo()
方法将使用类中的属性名称来与命令行中的开关进行匹配。开关名称中的短划线(-)将与属性名称中的下划线(_)匹配。
目标()
targets()方法返回不是开关或者开关值的所有参数。这些开关很容易识别。它们以-字符开头。
找出哪些参数是开关值和什么是目标要困难一些。 " CliArgs"类通过假设所有尚未"作为交换器值"被"采用"的参数都必须是目标,来区分交换器值和目标。这意味着,我们必须首先从CliArgs
实例中读取所有可能的开关值,然后作为最后一个操作读取目标。这是代码中的样子:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); int port = cliArgs.switchLongValue("-port"); String[] targets = cliArgs.targets(); }
使用以下命令行参数:
-port 9999 web-root
上面代码中执行的方法targets()将返回String
web-root
方法switchLongValue("-port")
假定交换机只有一个值,因此该交换机仅"采用"以下自变量(9999
)。剩下的最后一个参数是自由的,这意味着它将被解释为目标。
目标可以位于参数列表中的许多位置。查看以下命令行参数列表:
-port 9999 web-root -zip always
我们可以使用以下代码解析这些参数:
public static void main(String[] args) { CliArgs cliArgs = new CliArgs(args); int port = cliArgs.switchLongValue("-port"); String zip = cliArgs.switchValue("-zip"); String[] targets = cliArgs.targets(); }
调用switchLongValue("-port")方法将使用以下参数9999,并且调用switchValue("-zip")方法将始终使用以下参数。因此targets()
方法调用将返回参数web-root
,因为web-root
是唯一尚未被"采用"的参数,即使它位于参数列表的中间。
不管使用不同的切换方法"获取"切换值,还是通过将它们复制到POJO中来获取它们都没有关系。命令行参数一旦被switch方法接受,就将其标记为内部接受,并且不会被视为目标参数的一部分。
命令行参数解析器的完整代码
这是名为" CliArgs"的命令行参数解析器的完整Java代码。我们还可以在GitHub上获取我的CliArgs Java命令行解析器。
public class CliArgs { private String[] args = null; private HashMap<String, Integer> switchIndexes = new HashMap<String, Integer>(); private TreeSet<Integer> takenIndexes = new TreeSet<Integer>(); private List<String> targets = new ArrayList<String>(); public CliArgs(String[] args){ parse(args); } public void parse(String[] arguments){ this.args = arguments; //locate switches. switchIndexes.clear(); takenIndexes.clear(); for(int i=0; i < args.length; i++) { if(args[i].startsWith("-") ){ switchIndexes.put(args[i], i); takenIndexes.add(i); } } } public String[] args() { return args; } public String arg(int index){ return args[index]; } public boolean switchPresent(String switchName) { return switchIndexes.containsKey(switchName); } public String switchValue(String switchName) { return switchValue(switchName, null); } public String switchValue(String switchName, String defaultValue) { if(!switchIndexes.containsKey(switchName)) return defaultValue; int switchIndex = switchIndexes.get(switchName); if(switchIndex + 1 < args.length){ takenIndexes.add(switchIndex +1); return args[switchIndex +1]; } return defaultValue; } public Long switchLongValue(String switchName) { return switchLongValue(switchName, null); } public Long switchLongValue(String switchName, Long defaultValue) { String switchValue = switchValue(switchName, null); if(switchValue == null) return defaultValue; return Long.parseLong(switchValue); } public Double switchDoubleValue(String switchName) { return switchDoubleValue(switchName, null); } public Double switchDoubleValue(String switchName, Double defaultValue) { String switchValue = switchValue(switchName, null); if(switchValue == null) return defaultValue; return Double.parseDouble(switchValue); } public String[] switchValues(String switchName) { if(!switchIndexes.containsKey(switchName)) return new String[0]; int switchIndex = switchIndexes.get(switchName); int nextArgIndex = switchIndex + 1; while(nextArgIndex < args.length && !args[nextArgIndex].startsWith("-")){ takenIndexes.add(nextArgIndex); nextArgIndex++; } String[] values = new String[nextArgIndex - switchIndex - 1]; for(int j=0; j < values.length; j++){ values[j] = args[switchIndex + j + 1]; } return values; } public <T> T switchPojo(Class<T> pojoClass){ try { T pojo = pojoClass.newInstance(); Field[] fields = pojoClass.getFields(); for(Field field : fields) { Class fieldType = field.getType(); String fieldName = "-" + field.getName().replace('_', '-'); if(fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)){ field.set(pojo, switchPresent(fieldName) ); } else if(fieldType.equals(String.class)){ if(switchValue(fieldName) != null){ field.set(pojo, switchValue(fieldName ) ); } } else if(fieldType.equals(Long.class) || fieldType.equals(long.class) ){ if(switchLongValue(fieldName) != null){ field.set(pojo, switchLongValue(fieldName) ); } } else if(fieldType.equals(Integer.class) || fieldType.equals(int.class) ){ if(switchLongValue(fieldName) != null){ field.set(pojo, switchLongValue(fieldName).intValue() ); } } else if(fieldType.equals(Short.class) || fieldType.equals(short.class) ){ if(switchLongValue(fieldName) != null){ field.set(pojo, switchLongValue(fieldName).shortValue() ); } } else if(fieldType.equals(Byte.class) || fieldType.equals(byte.class) ){ if(switchLongValue(fieldName) != null){ field.set(pojo, switchLongValue(fieldName).byteValue() ); } } else if(fieldType.equals(Double.class) || fieldType.equals(double.class)) { if(switchDoubleValue(fieldName) != null){ field.set(pojo, switchDoubleValue(fieldName) ); } } else if(fieldType.equals(Float.class) || fieldType.equals(float.class)) { if(switchDoubleValue(fieldName) != null){ field.set(pojo, switchDoubleValue(fieldName).floatValue() ); } } else if(fieldType.equals(String[].class)){ String[] values = switchValues(fieldName); if(values.length != 0){ field.set(pojo, values); } } } return pojo; } catch (Exception e) { throw new RuntimeException("Error creating switch POJO", e); } } public String[] targets() { String[] targetArray = new String[args.length - takenIndexes.size()]; int targetIndex = 0; for(int i = 0; i < args.length ; i++) { if( !takenIndexes.contains(i) ) { targetArray[targetIndex++] = args[i]; } } return targetArray; } }