Java SE 10 新增特性
Java SE 10 新增特性
作者:Grey
原文地址:
源码
类型推断
无需定义变量类型,通过var
关键字结合初始化的值,可以推测出变量类型
package git.snippets.jdk10;
/**
* 类型推断
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2022/8/17
* @since 10
*/
public class TypeRefEnhance {
public static void main(String[] args) {
var a = 2; // a表示int
System.out.println(a);
var b = "hello"; // b 表示String
System.out.println(b);
var date = new java.util.Date();
System.out.println(date);
var obj = new Customer("Grey"); // 自定义对象
System.out.println(obj);
var sum = new TypeRefEnhance().add(1, 23);
System.out.println(sum);
var var = 3;
System.out.println(var);
}
public int add(int a, int b) {
return a + b;
}
static class Customer {
String name;
public Customer(String n) {
name = n;
}
@Override
public String toString() {
return "Customer{" +
"name=" + name +
'}';
}
}
}
var
看似好用,但是请谨慎使用,比如
var x = someFunction()
是因为如果不追踪someFunction()
方法的返回类型,读者就不可能知道x的类型。多年来,人们对动态类型语言提出了类似的抱怨。
所以,记住我们的目标是编写可读的代码。
在Java中,var是一个特殊的新类型,你仍然可以在你的代码的其他地方使用var,比如作为一个变量或类名。这使得Java能够与Java 10之前的代码保持向后兼容,所以如下定义是没问题的
var var = 3;
不可变集合 API
如下代码
package git.snippets.jdk10;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 集合API增强
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2021/11/29
* @since 10
*/
public class CollectionEnhance {
public static void main(String[] args) {
var vegetables = new ArrayList<>(List.of("Brocolli", "Celery", "Carrot"));
var unmodifiable = Collections.unmodifiableList(vegetables);
vegetables.set(0, "Radish");
var v = unmodifiable.get(0);
// 以下这行会报错
unmodifiable.set(0, "XXX");
System.out.println(v);
System.out.println(unmodifiable);
}
}
根据Java 10
中Collections
的最新定义,unmodifiableList
返回一个不可修改的视图集合。所以unmodifiable.set(0, "XXX");
会直接报错
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.Collections$UnmodifiableList.set(Collections.java:1308)
at git.snippets.jdk10.CollectionEnhance.main(CollectionEnhance.java:22)
Java 10
增加了两个新的API
来实现这一点,也就是说,创建完全不能修改的集合。
第一个API
是copyOf
,用来制作集合的不可修改的副本。
static void copyOfTest() {
var list = List.of("a", "b", "c");
var copyList = List.copyOf(list);
list.add("d");
// 由于copyList是副本, 所以copyList不会受到list的影响,打印出[a,b,c]
System.out.println(copyList);
System.out.println(list);
// 由于是不可变集合,所以这里会报错
copyList.add("d");
}
这与用Collections.unmodifiableList
包装一个列表是不同的。 copyOf
是创建副本(源集合改变不会影响副本集合),而Collections.unmodifiableList
是生成视图(源集合改变会影响视图)。
第二个API
为Stream
包中的Collectors
类增加的三个新方法。现在你可以使用toUnmodifiableList
、toUnmodifiableSet
和toUnmodifiableMap
在生成一个不可修改的集合。代码如下
static void unmodifiedTest() {
List<String> list = List.of("b", "a", "b", "c");
List<String> c1 = list.stream().collect(Collectors.toUnmodifiableList());
System.out.println(c1);
// 会报错
// c1.add("c");
// System.out.println(c1);
Set<String> c2 = list.stream().collect(Collectors.toUnmodifiableSet());
System.out.println(c2);
// 会报错
// c2.add("a");
// System.out.println(c2);
// 会报错
// c2.add("e");
// System.out.println(c2);
}
注意,虽然这些方法的名字可能会让你想起Collections.unmodifiableList
等,但这些新方法产生的是真正的不可修改的列表,而Collections.unmodifiableList
则返回一个不可修改的视图。
Unicode 语言标签扩展
Java SE 10
实现了最新的LDML 规范中指定的更多的扩展。
主要增加了下面几个扩展方法。
java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider
java.text.DateFormat::get*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of
尝试一下。
package git.snippets.jdk10;
import java.util.Calendar;
import java.util.Currency;
import java.util.Locale;
/**
* unicode扩展
* @since 10
*/
public class UnicodeTest {
public static void main(String[] args) {
Currency chinaCurrency = Currency.getInstance(Locale.CHINA);
Currency usCurrency = Currency.getInstance(Locale.US);
System.out.println("本地货币:" + chinaCurrency);
System.out.println("US.货币:" + usCurrency);
String displayName = Locale.getDefault().getDisplayName();
String displayLanguage = Locale.getDefault().getDisplayLanguage();
String displayCountry = Locale.getDefault().getDisplayCountry();
System.out.println("本地名称:" + displayName);
System.out.println("本地语言:" + displayLanguage);
System.out.println("本地国家:" + displayCountry);
int firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
System.out.println("本地每周第一天:" + firstDayOfWeek);
}
}
输出结果。
本地货币:CNY
US.货币:USD
本地名称:中文 (中国)
本地语言:中文
本地国家:中国
本地每周第一天:1
G1 性能增强
早在 Java 9 时就已经引入了 G1 垃圾收集器,G1 的优点很多。而在 Java 10 中还是做了小小调整,当 G1 的并发收集线程不能快速的完成全 GC 时,就会自动切换到并行收集,这可以减少在最坏情况下的 GC 速度。
类数据共享
Java SE 5
引入了类数据共享(CDS
),以改善小型Java
应用程序的启动时间。
当JVM
第一次启动时,由引导类加载器加载的任何东西都被序列化并存储在磁盘上的一个文件中,可以在JVM
的未来启动中重新加载。这意味着JVM
的多个实例共享类元数据,因此它不必每次都加载它们。
共享数据缓存意味着小型应用程序的启动时间有了很大的改善,因为在这种情况下,核心类的相对大小要大于应用程序本身。
Java SE 10
将此扩展到包括系统类加载器和平台类加载器。为了利用这一点,你只需要添加以下参数
-XX:+UseAppCDS
Java SE 10
还允许你把你自己的应用程序特定的类也存储到类-数据共享缓存中,可能会减少你的启动时间。
基本上,这是一个三步走的过程。第一步是创建应该被归档的类的列表,用适当的标志启动你的应用程序,并指出你希望列表被存储的位置。
java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=myapp.lst \
-cp $CLASSPATH $MAIN_CLASS
然后,用这个清单,你将创建一个CDS
档案
java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=myapp.lst \
-XX:SharedArchiveFile=myapp.jsa \
-cp $CLASSPATH
最后,运行你的应用程序,使用该存档
java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa \
-cp $CLASSPATH $MAIN_CLASS
更多内容参考:JEP 310: Application Class-Data Sharing
新的即时编译器Graal
即时编译器(JIT
)是Java
的一部分,它在运行时将Java
字节码转换为机器代码。最初的JIT
编译器是用C++
编写的,现在被认为相当难以修改。
Java SE 9
引入了一个新的实验性接口,称为JVM
编译器接口或JVMCI
。新接口的设计使得用纯Java
重写JIT
编译器成为可能。Graal
是由此产生的JIT
编译器,完全用Java
编写。
Graal
目前是一个实验性的JIT
编译器。在未来的Java
版本之前,只有Linux/x64
机器可以使用它。要启用Graal
,请在启动应用程序时在命令行参数中添加这些标志。
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
更多内容参考:graalvm
Thread-Local Handshakes
在服务性操作中,比如为所有线程收集堆栈跟踪或执行垃圾回收,当JVM
需要暂停一个线程时,它需要停止所有线程。有时,这些被称为 "stop-the-world"的暂停。这是由于JVM
想要创建一个全局安全点,一旦JVM
完成工作,所有应用线程都可以从这个安全点重新开始。
但在Java SE 10
中,JVM
可以将任意数量的线程放入安全点,并且线程在执行规定的 "握手"后可以继续运行。这导致JVM
一次只需暂停一个线程,而以前则必须暂停所有线程。
更多参考:JEP 312: Thread-Local Handshakes
容器感知
JVM
现在知道它何时在Docker
容器内运行。这意味着应用程序现在拥有关于docker
容器分配给内存、CPU
和其他系统资源的准确信息。
以前,JVM
会查询主机操作系统来获得这些信息,这就造成了一个问题。
例如,假设你想创建一个基于Java
的docker
镜像,其中运行的JVM
被分配了容器所指定的25%
的可用内存。在一个拥有2G
内存的盒子上,运行一个配置为0.5G
内存的容器,Java SE 9
和更早的版本会错误地根据2G
的数字而不是0.5G
来计算Java
进程的堆大小。
但是,现在,在Java SE 10
中,JVM
能够从容器控制组(cgroups
)中查找这些信息。
有一些命令行选项可以指定Docker
容器内的JVM
如何分配内部内存。例如,为了将内存堆设置为容器组的大小,并限制处理器的数量,你可以传入这些参数
-XX:+UseCGroupMemoryLimitForHeap -XX:ActiveProcessorCount=2
随着容器成为部署服务的标准方式,这意味着开发者现在有一种基于容器的方式来控制他们的Java
应用如何使用资源。
指定替代的内存分配
通过允许用户指定替代的内存设备来分配堆,Java
正朝着更加异构的内存系统发展。
一个直接的用例是能够在非易失性DIMM
(NVDIMM
)模块上分配堆,这在大数据应用中是常用的。
另一个用例是在同一台机器上运行许多JVM
进程。在这种情况下,让那些需要较低读取延迟的进程映射到DRAM
上,其余的进程映射到NVDIMM
上。
可以在你的启动参数中添加如下标志
-XX:AllocateHeapAt=<path>
这里的path
通常是一个内存映射的目录。
更多
参考资料
Java 10 Features (with Examples)
Java 10 Features and Enhancements
本文来自博客园,作者:Grey Zeng,转载请注明原文链接:https://www.cnblogs.com/greyzeng/p/16597130.html