Java SE 10 新增特性

类型推断

无需定义变量类型,通过 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 通常是一个内存映射的目录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值