Jakob Jenkov 2019-08-18
Java 的 Properties 类(java.util.Properties)类似于一个键值对均为 Java String 的 Java Map。Properties 类可以将这些键值对写入磁盘上的属性文件,也可以从属性文件中重新读取它们。这是一种常用于存储 Java 应用程序简单配置信息的机制。
创建 Properties 实例
要使用 Java 的 Properties 类,首先需要通过其构造函数和 new 关键字创建一个 Properties 实例。例如:
Properties properties = new Properties();
设置属性
使用 setProperty() 方法可以向 Properties 实例中添加属性。例如:
properties.setProperty("email", "john@doe.com");
此示例将键为 "email" 的属性设置为值 "john@doe.com"。
获取属性
通过 getProperty() 方法并传入属性键,可以从 Properties 对象中获取属性值。例如:
String email = properties.getProperty("email");
删除属性
使用 remove() 方法并传入要删除的属性键,可以从 Properties 实例中移除某个属性。例如:
properties.remove("email");
遍历属性
可以通过获取 Properties 实例的键集合(key set)并对其进行遍历来访问所有键。例如:
Properties properties = new Properties();
properties.setProperty("key1", "value1");
properties.setProperty("key2", "value2");
properties.setProperty("key3", "value3");
Iterator keyIterator = properties.keySet().iterator();
while(keyIterator.hasNext()){
String key = (String) keyIterator.next();
String value = properties.getProperty(key);
System.out.println(key + " = " + value );
}
输出结果为:
key1 = value1
key2 = value2
key3 = value3
将属性保存到文件
可以使用 store() 方法将 Properties 对象的内容写入属性文件,以便后续再次读取。例如:
Properties properties = new Properties();
properties.setProperty("property1", "value1");
properties.setProperty("property2", "value2");
properties.setProperty("property3", "value3");
try(FileWriter output = new FileWriter("data/props.properties")){
properties.store(output, "These are properties");
} catch (IOException e) {
e.printStackTrace();
}
属性文件编码
默认情况下,Java 属性文件使用 ISO-8859-1 (Latin-1) 编码。然而现在更常用的是 UTF-8。可以在创建 FileWriter 时指定编码。例如:
try(FileWriter output = new FileWriter("data/props.properties", Charset.forName("UTF-8"))){
properties.store(output, "These are properties");
} catch (IOException e) {
e.printStackTrace();
}
属性文件格式
Java 属性文件每行包含一个 key=value 键值对。例如:
#These are properties
#Thu Jul 04 21:29:20 CEST 2019
property2=value2
property1=value1
property3=value3
以 # 开头的行是注释。注意第一行注释就是调用 store() 方法时传入的注释字符串。
从文件加载属性
可通过 load() 方法将属性文件中的内容加载回 Properties 对象。例如:
Properties properties = new Properties();
try(FileReader fileReader = new FileReader("data/props.properties")){
properties.load(fileReader);
} catch (IOException e) {
e.printStackTrace();
}
加载属性文件时的编码
load() 方法默认假设文件使用 ISO-8859-1 编码。如果使用了其他编码(如 UTF-8),需在 FileReader 中指定。例如:
try(FileReader fileReader = new FileReader("data/props.properties", Charset.forName("UTF-8"))){
properties.load(fileReader);
} catch (IOException e) {
e.printStackTrace();
}
将属性保存为 XML 文件
Properties 类也支持通过 storeToXML() 方法将键值对写入 XML 文件。例如:
Properties properties = new Properties();
properties.setProperty("property1", "value1");
properties.setProperty("property2", "value2");
properties.setProperty("property3", "value3");
try(FileOutputStream output = new FileOutputStream("data/props.xml")){
properties.storeToXML(output, "These are properties");
} catch (IOException e) {
e.printStackTrace();
}
XML 属性文件编码
XML 属性文件默认使用 UTF-8 编码(与普通属性文件相反)。如需使用其他编码(如 ISO-8859-1),可作为第三个参数传入:
try(FileOutputStream output = new FileOutputStream("data/props.xml")){
properties.storeToXML(output, "These are properties", Charset.forName("ISO-8859-1"));
} catch (IOException e) {
e.printStackTrace();
}
XML 属性文件格式
上述代码生成的 XML 文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>These are properties</comment>
<entry key="property2">value2</entry>
<entry key="property1">value1</entry>
<entry key="property3">value3</entry>
</properties>
注意:注释被包裹在 <comment> 元素中,而非 XML 注释 <!-- -->。
从 XML 文件加载属性
使用 loadFromXML() 方法可从 XML 属性文件加载属性。例如:
Properties properties = new Properties();
try(FileInputStream fileInputStream = new FileInputStream("data/props.xml")){
properties.loadFromXML(fileInputStream);
} catch(IOException e){
e.printStackTrace();
}
加载 XML 属性文件时的编码
loadFromXML() 默认使用 UTF-8 编码。若使用其他编码(如 ISO-8859-1),必须在 XML 文件头部声明:
<?xml version="1.0" encoding="ISO-8859-1"?>
从类路径加载属性
也可以从类路径(classpath)加载属性文件(该文件可能位于 JAR 包内或类路径下的目录中)。
首先获取一个 Class 实例(以下示例使用包含 main() 方法的类):
Class aClass = PropertiesExample.class;
然后调用 getResourceAsStream() 获取输入流:
InputStream inputStream = aClass.getResourceAsStream("/myProperties.properties");
文件应位于类路径根目录下;若在子目录中,路径应相应调整,如 /subdir/myProperties.properties。
接着使用 load() 或 loadFromXML() 加载(取决于文件格式):
Class aClass = PropertiesExample.class;
InputStream inputStream = aClass.getResourceAsStream("/myProperties.properties");
Properties fromClasspath = new Properties();
try {
fromClasspath.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
ResourceBundle 属性
Java 提供了一种特殊的属性类型:ResourceBundle。它是一组文件的集合,通常每个文件针对一种语言,包含相同的键但值为对应语言的翻译。ResourceBundle 常用于应用程序国际化。
默认属性
Properties 类支持为未定义的键提供默认值。有两种方式:
- 在调用
getProperty()时提供默认值。 - 在创建
Properties实例时传入一个包含默认值的Properties实例。
使用 getProperty() 提供默认值
getProperty() 有一个重载版本接受默认值参数:
Properties properties = new Properties();
String preferredLanguage = properties.getProperty("preferredLanguage", "Danish");
若 properties 中没有 "preferredLanguage" 键,则返回 "Danish",而不是 null。
使用默认 Properties 实例
创建 Properties 时可传入另一个包含默认值的 Properties 实例:
Properties defaultProperties = new Properties();
defaultProperties.setProperty("preferred Language", "Danish");
Properties newProperties = new Properties(defaultProperties);
String language = newProperties.getProperty("preferredLanguage");
System.out.println("Preferred language: " + language);
由于 newProperties 中未设置 "preferredLanguage",它会从 defaultProperties 中查找。
系统属性
Java 的系统属性是一个特殊的 Properties 实例,包含 JVM 等系统级设置。可通过 System.getProperties() 获取:
Properties systemProperties = System.getProperties();
也可使用 System.getProperty() 和 System.setProperty() 快捷方法:
System.setProperty("key1", "value1");
String value1 = System.getProperty("key1");
// 等价于:
Properties systemProperties = System.getProperties();
systemProperties.setProperty("key1", "value1");
String value1 = systemProperties.getProperty("key1");
通过命令行设置系统属性
启动 Java 应用时可通过 -D 参数设置系统属性:
java -Dkey1=value1 -cp . com.jenkov.MyApp
该命令会执行 com.jenkov.MyApp 类,并将系统属性 key1 设为 value1。
在程序内部仍可通过 System.getProperty("key1") 访问该属性。
Properties 是 Hashtable 的子类 —— 这是个错误!
Java 的 Properties 类继承自 Hashtable,但实际上这是一个设计失误!这是面向对象编程中“Is-a / Has-a”原则误用的经典案例。
由于继承自 Hashtable,你可以调用其 get() 和 put() 方法,而这些方法允许使用非字符串类型的键和值,这违背了 Properties 作为字符串-字符串映射的设计初衷。例如:
Properties asProperties = new Properties();
asProperties.put(123, 456);
asProperties.put("abc", 999);
更严重的是,混合使用会导致不可预测的行为:
Properties asProperties = new Properties();
asProperties.put("abc", 999);
String abc = asProperties.getProperty("abc"); // 返回 null!
只有当 put() 的两个参数都是字符串时,getProperty() 才能正常工作:
asProperties.put("abc", "999");
String abc = asProperties.getProperty("abc"); // 返回 "999"
建议:不要使用 put() 和 get() 方法!始终使用 setProperty() 和 getProperty()。
正确的做法应该是:Properties 内部包含一个 Hashtable(即 “Has-a”),而不是继承它(“Is-a”)。这样既能复用 Hashtable 的功能,又不会暴露不合适的接口。