无需定义变量类型,通过 var
关键字结合初始化的值,可以推测出变量类型
package git.snippets.jdk10; /** * 类型推断 * * @author Grey * @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;
如下代码
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 Grey * @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() { Listlist = List.of("b", "a", "b", "c"); List c1 = list.stream().collect(Collectors.toUnmodifiableList()); System.out.println(c1); // 会报错 // c1.add("c"); // System.out.println(c1); Set 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
则返回一个不可修改的视图。
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
早在 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
即时编译器( 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
在服务性操作中,比如为所有线程收集堆栈跟踪或执行垃圾回收,当 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
通常是一个内存映射的目录。