Java 12中的新功能
最近的长期支持(LTS)版本是Java 12,这是自Java 11以来的第一个 “临时 “版本,它于2019年3月19日发布。在Java的LTS版本11之后,Java 12被发布。6个月的发布周期适用于JDK 12。2019年3月19日,Java 12–一个没有长期支持的非LTS版本–被推出。与早期版本相比,Java 12的修改相当小。语言本身自Java 7以来第一次没有变化。
Java 12是Java 12 SE平台的一个开源参考实现的名称。 甲骨文公司为JDK 12提供了符合GPL标准的生产用二进制文件,其他供应商的二进制文件也将很快提供。
在Java JDK 12中没有很多新的API功能。JDK 12的主要进步涉及到Java/JVM的内部。JEP是一项新功能,包含在JDK 12中。
根据普通开发者工作的重要性,我们对修改进行了分类。我们将首先讨论对类库的改进。之后包括性能提升、实验性功能和预览功能,以及作为开发者可能看不到的微小变化
此外,我们将谈论Java 12的新功能,并研究它为程序员准备了什么。
Java 12中的功能
Java 12的关键功能包括:。
- JEP 189、JEP 346、JEP 344和JEP 230涉及对JVM的修改。
- 改变表达方式
- 文件不匹配()技术
- 紧凑的数字格式
- 使用流API的Teeing收集器
- Indent()、transform()、describeConstable()和resolveConstantDesc是新的Java Strings方法()。
- 用于JVM常量的API,JEP 334
- JEP 305:JDK 12中删除了原始字符串字数模式匹配的内容
1) JVM的变化
1.JEP 189 – 一个低脉冲时间的垃圾收集器。Shenandoah(实验性):
Shenandoah垃圾收集器是由RedHat启动的,以缩短GC的暂停时间。该计划是与活跃的Java线程同时运行GC。无论堆的大小如何,它都会尝试可预测的、一致的短暂停。因此,如果堆的大小是15MB或15GB,这并不重要。这是Java 12中的一个测试功能。
一种实验性的垃圾收集(GC)机制,称为Shenandoah。 目前在标准的Java 12版本中没有这个功能。
通过与活跃的Java线程同时进行疏散工作,它减少了GC的停止时间。这意味着,无论堆的大小如何,使用Shenandoah的暂停时间应该是恒定的。一个200GB的垃圾堆应该和一个2GB的垃圾堆有类似的低暂停行为。
从15版开始,Shenandoah将被纳入主要的JDK构建中。
2.JEP 346:快速从G1返回已承诺的未使用的内存:
根据Java 12,当一个应用程序不被使用时,G1现在会检查Java Heap RAM并将其返回给操作系统。这是一种预防技术,以保存和利用自由内存。
3.JEP344中的G1的可中止混合集合:
现在,如果G1混合集合有可能超过指定的暂停阈值,将可以中止,这将提高G1的效率。为了做到这一点,混合收集集被分为强制性和选择性两类。为了完成暂停时间的目标,G1采集器可以优先采集所需的集合。
4.JEP230和344:
由于Microbenchmark Suite,JEP 230功能的存在,JDK源代码现在包括了一个最小的微观基准集合。开发人员可以轻松构建新的微基准,并因此而运行他们已有的微基准。JEP 344, One AArch64 Port, Not Two, 保留了32位ARM和64位arch64端口,同时删除了与arm64端口有关的所有资源。因此,开发人员可以集中精力创建一个单一的 64 位 ARM 实现。
5.JEP 341的默认CDS档案:
这改进了JDK的构建过程,使其能够在64位平台上使用默认的类列表创建类数据共享(CDS)存档。其目的是减少启动时间。从Java 12开始,CDS被默认打开。在禁用CDS的情况下执行你的程序,请执行以下操作。
java -Xshare:off HelloWorld.java
2) 新的字符串和文件方法。
在Java 11的Files和一些新的String方法被引入后。对于Java 12,JDK开发者再次增强了这两个类,增加了readString()和writeString()功能。
1. indent():
我们以前不得不创建一个微小的辅助方法,在字符串前添加所需的空格数,以便缩进它。如果要在许多行上操作,这个过程就会演变成一个更复杂的过程。
在Java 12中集成了一个方法,叫做String.indent()。下面是一个如何将一个多行字符串缩进四个空格的例子。
String str = "The courses\n available \n at javatpoint.";
System.out.println(str);
System.out.println(str.indent(4));
2. transform():
新的String.transform()方法使用任何函数对一个字符串进行转换,并返回函数的结果。这里有几个例子。
String lowercase = "ABCDE".transform(String::toLowerCase);
Integer inter = "12345".transform(Integer::valueOf);
BigDecimal bigger = "1234567891011121314151617181920".transform(BigDecimal::new);
正如你从String.transform()的源代码中看到的那样,这并不涉及任何火箭科学。一旦方法的引用被转化为一个函数,String就被提供给方法的apply()方法。
public <R> Rer transform(Function<? super String, ? extends Rer> fun) {
return fun.apply(this);
}
那么,为什么不直接写下,而要使用transform()呢?
String lowercase = "ABCDE".toLowerCase;
Integer inter = "12345".valueOf;
BigDecimal bigger =new BigDecimal( "1234567891011121314151617181920);
与后者在编译时固定转换的符号相比,String.transform()允许在运行时动态地确定要应用的函数。
3. mismatch():
要比较两个文件的内容,使用Files.mismatch()方法。
如果两个文件都匹配,该程序返回-1。如果不匹配,则返回两个文件相差的第一个字节。如果其中一个文件在发现差异之前就过期了,则返回该文件的长度。
(A JDK enhancement proposal does not include definitions for the new string and files methods.)
- 发球收集器
如果你的要求需要,你可能想在用两个而不是一个收集器终止一个流时,将两个收集器的结果结合起来。
为了避免被指责为有 “代码气味”,下面的示例源代码旨在寻找随机数流中最大和最小的数字之间的差异。
Stream nums = new Random().ints(100).boxed();
int minimum = nums.collect(Collectors.minimumBy(Integer::compareTo)).orElseThrow();
int maximum = nums.collect(Collectors.maximumBy(Integer::compareTo)).orElseThrow();
long ranges = (long) maximum - minimum;
除了一个例外,程序建立了,但在执行过程中崩溃了。
我们从异常文本中得知,我们只能终止一个流一次。
那么,我们如何才能完成这项任务?
一种不同的方法是开发一个独特的收集器,将最小值和最大值存储在一个2元的int数组中:
Stream nums = new Random().ints(100).boxed();
int[] ans =
nums.collect(
() -> new int[] {Integer.MAX_VALUE, Integer.MIN_VALUE},
(minimumMaximum, i) -> {
if (i < minimumMaximum[0]) minimumMaximum[0] = i;
if (i > minimumMaximum[1]) minimumMaximum[1] = i;
},
(minimumMaximum1, minimumMaximum2) -> {
if (minimumMaximum2[0] < minimumMaximum1[0]) minimumMaximum1[0] = minimumMaximum2[0];
if (minimumMaximum2[1] > minimumMaximum1[1]) minimumMaximum1[1] = minimumMaximum2[1];
});
long ranges = (long) ans[1] - ans[0];
这个策略的可读性不强,而且相当曲折。
使用Java 12引入的 “Teeing Collector”,我们可以更快速地完成它。我们可以定义两个下游采集器(将两个下游采集器的输出合并的采集器)和一个合并函数。
Stream nums = new Random().ints(100).boxed();
long ranges =
nums.collect(
Collectors.teeing(
Collectors.minimumBy(Integer::compareTo),
Collectors.maximumBy(Integer::compareTo),
(minimum, maximum) -> (long) maximum.orElseThrow() - minimum.orElseThrow()));
更加可读和优雅,是吗?
在这种情况下,”Teeing Collector “是什么意思?
由于收藏家的图形描述类似于……。”T”,这个名字来自英文字母 “T “的发音。
(There is also no proposal for the Teeing Collector JDK upgrade.)
- 性能的改进
以下改进确保了我们的Java应用程序启动更快,垃圾收集器延迟更低,内存占用更少。
默认的CDS档案:
为了创建classes.jsa共享归档文件,你以前必须为每个Java安装运行一次java -Xshare:dump。
随着 JDK Enhancement Proposal 341 的实施,所有 64 位 JDK 端口现在都提供了这个文件,从而消除了对 Java 应用程序执行 java -Xshare:dump 的要求,而默认使用默认的 CDS 归档。
可放弃的G1的混合收藏:
G1 Gargabe收集器的目标之一是坚持为清理活动设定的最大暂停时间,它不能与程序同时进行,也就是说,不要让应用程序停止的时间超过允许的时间。
通过-XX:MaxGCPauseMillis参数,你可以为G1设置这个时间段。如果你去掉这个参数,允许的最大暂停周期是200毫秒。
为了选择一组要在这个停止-世界阶段进行清理的堆区,G1采用了一种启发式方法(称为 “收集集”)。
启发式有可能确定一个太大的收集集,这导致应用程序比计划的时间更长,特别是在 “混合收集 “的情况下(即在清理年轻一代和老年一代的区域时)。
如果持续超过最大暂停持续时间,JDK增强提案344优化了混合集合,将集合分为必选部分和可选部分。直到达到最大暂停持续时间,可选的部分以小的增量完成,而必须的部分则不间断地进行。
该算法试图在这期间修改启发式,以便快速选择它能在指定暂停期内处理的集合。
提示从G1返回未使用的承诺内存:
在你只为你实际使用的内存付费的情况下,垃圾收集器应该及时将不需要的内存返回给操作系统。
内存可以被G1垃圾收集器返回,但只有当垃圾正在被收集的时候。如果堆分配或当前对象分配的速度没有启动垃圾收集周期,则不返回内存。
假设我们有一个程序,除了每天运行一次内存密集型的批处理程序外,其他时间基本不活动。结果,在批处理操作完成后,不需要垃圾收集周期,我们为包含一天中大部分时间未使用的对象的RAM付费(红色高亮区域)。
JEP 346对这个问题提供了一个答案。当应用程序不使用时,会定期启动一个并发的垃圾收集周期,释放任何可能不再需要的内存。
默认情况下,这个功能是停用的。你可以通过给G1一个毫秒的间隔来激活它,用-XX:G1PeriodicGCInterval参数来检查是否需要开始这样的循环。它将以这种方式迅速地检索内存。
- 紧凑的数字格式化。
介绍CompactNumberFormat,Java 12中一个新的数字格式。基于特定位置提供的模式,它旨在以更短的格式来表达一个数字。
通过NumberFormat类的getCompactNumberInstance方法,我们可以获得其实例。
public static NumberFormat CompactNumber(Locale locale, NumberFormat.Style formatStyle)
如前所述,locale参数负责提供适当的格式样式。格式样式有两个选项。SHORT和LONG。让我们以美国地区的数字1000为例来更好地理解格式样式。LONG格式将是 “10,000”,而SHORT格式将是 “10K”。
让我们看一个插图,它使用两种不同的风格来压缩这篇文章的喜欢数量。
public void givenNumber_thenCompactValues() {
NumberFormat sShort =
NumberFormat.CompactNumber(new Locale("en", "US"), NumberFormat.Style.SHORT);
sShort.setMaximumFractionDigits(2);
assertEquals("2.59K", sShort.format(2592));
NumberFormat sLong =
NumberFormat.CompactNumber(new Locale("en", "US"), NumberFormat.Style.LONG);
sLong.setMaximumFractionDigits(2);
assertEquals("2.59 thousand", sLong.format(2592));
}
transform(Function f):
使用transform(Function f)方法将指定的函数应用于该字符串。只有一个字符串值可以被传递给指定的函数,然后该函数将输出对象。为了更好地理解这种方法,让我们看一个例子。
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.function.Function;
public class Transform {
public static void main(String[] args) {
String str = "JAVATPOINT,student learning";
List li_data = str.transform(new spiltFunction());
System.out.println(li_data);
}
}
class spiltFunction implements Function> {
public List apply(String s) {
return Arrays.asList(s.split(","));
}
}
- 实验和预览功能。
现在我们要讨论的是实验性和预览性功能,即仍在开发中的功能,在最终发布前可能会根据Java社区的反馈进行修改。
我们将把发布相应功能的Java版本称为 “生产就绪”,而不是详细介绍这些功能。
切换表达式(预览):
让我们对比一下之前的和新的开关语句,作为一个例子。基于LocalDate实例的DayOfWeek枚举,我们将利用它们来区分工作日和周末。
JDK增强提案325允许我们利用箭头符号和逗号来分隔众多情况,从而消除开关语句中容易出错的break语句。
DayinWeek day = LocalDate.now().getDayinWeek();
String Daytype = "";
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
Daytype = "The working day";
break;
case THURSDAY:
case FRIDAY:
Daytype = "The working day";
break;
case SATURDAY:
case SUNDAY:
Daytype = "Holiday";
}
现在让我们用其他的表达方式来研究同样的逻辑。
Daytype = switch (Weekday) {
case MONDAY, TUESDAY, WEDNESDAY -> "The Working Day", THURSDAY, FRIDAY -> "The Working Day";
case SATURDAY, SUNDAY -> "Holiday";
};
新的switch语句不仅更短,更容易阅读。对break语句的要求也被它们消除了。在初始匹配后,代码执行不会失败。
另一个重要的区别是,我们可以直接对变量应用切换语句。以前,这是不可行的。
开关表达式在不产生结果的情况下运行代码也是可行的。
switch (Weekday) {
case MONDAY, TUESDAY, WEDNESDAY ->System.out.println("The Working Day"), THURSDAY, FRIDAY -> System.out.println("The Working Day");
case SATURDAY, SUNDAY -> System.out.println("Holiday");
}
值得注意的是,我们可以选择使用旧的或新的语法。Java 12中的开关表达式只是一种补充,它们并不取代任何东西。
开关表达式将在Java 14中准备投入生产。关于开关表达式的主要文章有关于它们的所有信息。
在Java 12中,必须在IDE中启用Switch Expressions(IntelliJ允许你通过FileProject StructureProject SettingsProjectProject language level来完成),或者在执行javac和java命令时使用-enable-preview参数。
Unicode 11:
在Java 11启用了对Unicode 10的支持后,Java 12将这种支持升级到包括Unicode 11。这意味着,特别是String和Character类必须能够处理Unicode 11增加的新字符、代码块和脚本。
请看之前提到的关于Unicode 10的部分来说明。
(Unicode 11 compatibility is not included in any JDK enhancement proposals.)
微观基准测试套件
在这之前,JDK类库的微观测试一直作为一个独立的项目来处理。例如,这些测试用于检查 JDK 方法是否随着新的 Java 版本的发布而变得更慢。它们定期评估JDK类库的性能。
为了促进测试的执行和发展,JDK 增强提案 230 将当前的微观测试集整合到 JDK 源代码中。
- JVM常量API
在编译a.java文件过程中出现的常量被包含在a.class文件的常量池中。这些常量包括被引用的类和方法的名称(如 “java/lang/System”、”out “和 “println”),以及在Java代码中定义的常量,如字符串 “Hello world!”每个常量都被赋予一个数字,在.class文件的字节码中被引用。
由于JDK增强提案334,开发读取或写入JVM字节码的Java程序将变得更加简单。它提供了新的类和接口来表示常量池中的元素,以达到这个目的。
作为类和它们的方法的引用的常量增加了复杂性。由于我们只知道被引用的类和方法的名称、参数和返回值,我们无法使用反射类Class和MethodHandle。
类ClassDesc和MethodHandleDesc以及MethodTypeDesc现在可以用于这个目的,除此之外,还可以分别识别一个类和一个方法。
总结
Java 12中的修改是相当容易处理的。现在可以使用Teeing Collector,它使我们能够在两个收集器上终止一个流并合并它们的结果,还有一些额外的String和Files方法。
由于64位系统上有classes.jsa共享存档文件,现在默认情况下,类数据共享是打开的。
如果混合收集花费的时间太长,G1垃圾收集器就会停止。未使用的内存会迅速返回到操作系统中。
Shenandoah垃圾收集器和Switch表达式是另外两个实验性或预览性功能,已经进入了Java 12。