JDK 18概述
JDK 18是Java SE平台版本18的开源参考实现,如JSR 393在Java社区进程中。 JDK 18在2022年3月22日正式发布。
特性
400: 默认UTF-8
408: 简单Web服务器
413: Java API文档中的代码段
416: 使用方法句柄重新实现核心反射
417: Vector API(第三个孵化器)
418: 互联网地址解析SPI
419: 外部函数和内存API(第二个孵化器)
420: switch的模式匹配(第二次预览)
421: 不建议删除最终确定
JEP 400:默认为UTF-8
总结
指定UTF-8作为标准Java API的默认字符集。通过此更改,依赖于默认字符集的API将在所有实现、操作系统、区域设置和配置中一致地行为。
目标
当Java程序的代码依赖于默认字符集时,使其更可预测和可移植。
澄清标准Java API使用默认字符集的位置。
在整个标准Java API中标准化UTF-8,控制台I/O除外。
非目标
定义新的标准Java API或受支持的JDK API并不是目标,尽管这项努力可能会发现新的便利方法可能会使现有API更平易近人或更易于使用的机会。
不建议使用或删除依赖于默认字符集而不是采用显式字符集参数的标准Java API。
动机
用于读取和写入文件以及处理文本的标准Java API允许将字符集作为参数传递。字符集控制Java编程语言的原始字节和16位字符值之间的转换。支持的字符集包括,例如,US-ASCII、UTF-8和ISO-8859-1。 如果未传递字符集参数,则标准Java API通常使用默认字符集。JDK在启动时根据运行时环境选择默认字符集:操作系统、用户的区域设置和其他因素。 由于默认字符集在任何地方都不一样,使用默认字符集的API会带来许多不明显的危险,即使对有经验的开发人员也是如此。 考虑一个应用程序,它创建了java.io.FileWriter,而不传递字符集,然后使用它向文件写入一些文本。生成的文件将包含使用运行应用程序的JDK的默认字符集编码的字节序列。第二个应用程序在不同的计算机上运行,或由同一计算机上的不同用户运行,在不传递字符集的情况下创建java.io.FileReader,并使用它读取该文件中的字节。生成的文本包含使用运行第二个应用程序的JDK的默认字符集解码的字符序列。如果第一个应用程序的JDK和第二个应用程序的JDK之间的默认字符集不同,则生成的文本可能会被静默损坏或不完整,因为FileReader无法判断它使用了相对于FileWriter的错误字符集解码文本。以下是这种危险的一个示例,在macOS上以UTF-8编码的日语文本文件在Windows上以美国英语或日语语言环境读取时已损坏:
java.io.FileReader(“hello.txt”) -> “こんにちは” (macOS) java.io.FileReader(“hello.txt”) -> “ã?“ã‚“ã?«ã?¡ã? ” (Windows (en-US)) java.io.FileReader(“hello.txt”) -> “縺ォ縺。縺ッ” (Windows (ja-JP)复制
熟悉此类危害的开发人员可以使用显式接受字符集参数的方法和构造函数。但是,必须传递参数可以防止方法和构造函数通过流管道中的方法引用(::)
使用。 开发人员有时会尝试通过在命令行上设置系统属性file.encode(即java-Dfile.encode=...)来配置默认字符集,但这从未得到支持。此外,在Java运行时启动后,尝试以编程方式设置属性(即System.setProperty(...))是不起作用的。 并非所有标准Java API都服从JDK的默认字符集选择。例如,java.nio.file.Files中读取或写入没有Charset参数的文件的方法被指定为始终使用UTF-8。较新的API默认使用UTF-8,而较旧的API默认使用默认字符集,这对于使用混合API的应用程序来说是一个危险。 如果默认字符集在任何地方都被指定为相同,整个Java生态系统都将受益。不关心可移植性的应用程序将不会受到什么影响,而通过传递字符集参数来支持可移植性的应用程序将不会受到影响。UTF-8有长期以来一直是万维网上最常见的字符集,UTF-8是由大量Java程序处理的XML和JSON文件的标准,Java自己的API越来越青睐UTF-8,例如NIO接口以及属性文件因此,将UTF-8指定为所有Java API的默认字符集是有意义的。 我们认识到,此更改可能会对迁移到JDK 18的程序产生广泛的兼容性影响。因此,始终可以恢复JDK 18之前的行为,其中默认字符集取决于环境。
说明
在JDK 17和更早版本中,默认字符集是在Java运行时启动时确定的。在macOS上,它是UTF-8,POSIX C区域设置除外。在其他操作系统上,它取决于用户的区域设置和默认编码,例如,在Windows上,它是基于代码页的字符集,如windows-1252或windows-31j。方法java.nio.charsets.Charset.defaultCharset()返回默认字符集。查看当前JDK默认字符集的快速方法是使用以下命令:
java -XshowSettings:properties -version 2>&1 | grep file.encoding复制
几个标准Java API使用默认字符集,包括: 在java.io包中,InputStreamReader、FileReader、OutputStreamWriter、FileWriter和PrintStream定义构造函数,以创建使用默认字符集编码或解码的读取器、写入器和打印流。 在java.util包中,Formatter和Scanner定义了构造函数,其结果使用默认字符集。 在java.net包中,URLEncoder和URLDecoder定义了使用默认字符集的过时方法。 我们建议更改Charset.defaultCharset()的规范,以说明默认字符集是UTF-8除非通过特定于实现的手段另有配置。(有关如何配置JDK,请参见下文。)UTF-8字符集由RFC 2279;它所依据的转换格式在ISO 10646-1修正案2中规定,并在Unicode标准.不要与修改后的UTF-8. 我们将更新所有使用默认字符集交叉引用Charset.defaultCharset()的标准Java API的规范。这些API包括上面列出的API,但不包括System.out和System.err,它们的字符集将由 Console.charset()定义。
file.encoding和native.encoding系统属性
正如Charset.defaultCharset()规范所设想的那样,JDK将允许将默认字符集配置为UTF-8以外的其他东西。我们将修改系统属性file.encoding的处理,以便在命令行上设置它是配置默认字符集的支持方法。我们将在实施说明中具体说明这一点 System.getProperties()如下: 如果file.encoding设置为“COMPAT”(即java -Dfile.encoding=COMPAT),则默认字符集将是JDK 17及更早版本中算法选择的字符集,基于用户的操作系统、区域设置和其他因素。file.encode的值将设置为该字符集的名称。 如果file.encoding设置为“UTF-8”(即java -Dfile.encoding=UTF-8),则默认字符集将为UTF-8。定义此no-op值是为了保留现有命令行的行为。 未指定“Compat”和“UTF-8”以外的值的处理。它们不受支持,但如果这样的值在JDK 17中有效,那么它很可能会在JDK 18中继续有效。
JDK 17引入了native.encoding 系统属性作为程序获取JDK算法选择的字符集的标准方式,无论默认字符集是否实际配置为该字符集。在JDK 18中,如果在命令行上将file.encoding设置为COMPAT,则file.encoding的运行时值将与 native.encoding的运行时值相同;如果在命令行上将file.encoding设置为UTF-8,则file.encoding的运行时值可能与native.encoding的运行时值不同。 在下面的风险和假设中,我们讨论了如何减轻此更改对file.encoding可能产生的不兼容问题,以及native.encoding系统属性和应用程序建议。 JDK内部使用了三个与字符集相关的系统属性。它们仍然未指明和不支持,但为了完整性,请在此处记录: sun.stdout.encode和sun.stderr.encode-标准输出流(System.out)和标准错误流(System.err)以及java.io.Console API中使用的字符集的名称。 sun.jnu.encode-java.nio.file实现在编码或解码文件名路径时使用的字符集的名称,而不是文件内容。在macOS上,它的值是“UTF-8”;在其他平台上,它通常是默认字符集。
源文件编码
Java语言允许源代码在UTF-16编码,这不受默认字符集选择UTF-8的影响。但是,javac编译器会受到影响,因为它假定.java源文件是使用默认字符集编码的,除非-encoding 另有配置选项。如果源文件是用非UTF-8编码保存的,并使用较早的JDK编译的,则在JDK 18或更高版本上重新编译可能会导致问题。例如,如果非UTF-8源文件具有包含非ASCII字符的字符串文字,则除非使用-encoding ,否则JDK 18或更高版本中的javac可能会误解这些文字。
旧版默认字符集
在JDK 17和更早版本中,名称默认值被识别为US-ASCII字符集的别名。也就是说,Charset.forName("default")产生的结果与Charset.forName("US-ASCII")相同。默认别名是在JDK 1.5中引入的,以确保使用sun.io converters的旧代码可以迁移到JDK 1.4中引入的java.nio.charset框架。 当默认字符集指定为UTF-8时,JDK 18将默认值保留为US-ASCII的别名将非常混乱。当用户通过在命令行上设置 -Dfile.encoding=COMPAT将默认字符集配置为其JDK 18之前的值时,默认值的含义也会令人困惑。将默认值重新定义为不是US-ASCII的别名,而是默认字符集(无论是UTF-8还是用户配置的)的别名,将导致调用 Charset.forName("default")的(少数)程序中的微妙行为更改。 我们认为,继续承认JDK 18的违约将延长一个糟糕的决定。它不是由Java SE平台定义的,也不是由IANA识别为任何字符集的名称或别名。事实上,对于基于ASCII的网络协议,IANA鼓励使用规范名称US-ASCII,而不仅仅是ASCII或模糊别名,如ANSI_X3.4-1968,使用特定于JDK的别名默认值与该建议背道而驰。Java程序可以使用枚举常量StandardCharsets.US_ASCII来明确其意图,而不是将字符串传递给Charset.forName(...)。 因此,在JDK 18中,Charset.forName("default")将引发不支持的UnsupportedCharsetException。这将使开发人员有机会检测使用该成语,并迁移到US-ASCII或Charset.defaultCharset()的结果。
替代方案
保持现状-这并不能消除上述危险。 不建议在Java API中使用默认字符集的所有方法-这将鼓励开发人员使用采用字符集参数的构造函数和方法,但生成的代码将更加详细。 指定UTF-8作为默认字符集,而不提供任何更改它的方法-此更改的兼容性影响将太高。