Java SE 10 新增特性

Java SE 10 新增特性

作者:Grey

原文地址:Java SE 10 新增特性

源码

源仓库: Github:java_new_features

镜像仓库: GitCode:java_new_features

类型推断

无需定义变量类型,通过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 10Collections的最新定义,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来实现这一点,也就是说,创建完全不能修改的集合。

第一个APIcopyOf,用来制作集合的不可修改的副本。

    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是生成视图(源集合改变会影响视图)。

第二个APIStream包中的Collectors类增加的三个新方法。现在你可以使用toUnmodifiableListtoUnmodifiableSettoUnmodifiableMap在生成一个不可修改的集合。代码如下

    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会查询主机操作系统来获得这些信息,这就造成了一个问题。

例如,假设你想创建一个基于Javadocker镜像,其中运行的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正朝着更加异构的内存系统发展。

一个直接的用例是能够在非易失性DIMMNVDIMM)模块上分配堆,这在大数据应用中是常用的。

另一个用例是在同一台机器上运行许多JVM进程。在这种情况下,让那些需要较低读取延迟的进程映射到DRAM上,其余的进程映射到NVDIMM上。

可以在你的启动参数中添加如下标志

-XX:AllocateHeapAt=<path>

这里的path通常是一个内存映射的目录。

更多

Java SE 7及以后各版本新增特性

参考资料

Java Language Updates

Java 新特性教程

What’s New in Java 10?

Java 10 Features (with Examples)

Java 10 Features and Enhancements

New Features in Java 10

graalvm

JEP 310: Application Class-Data Sharing

Java 10

相关文章

摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠...
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠...
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个...
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:...
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程...