Java 9 新特性

主要特性

  1. 接口中支持定义 private 方法
  2. try-with-resource 方式优化
  3. 不可以使用 “_”下划线命名变量
  4. @Deprecated 注解支持指定废弃版本(since), 以及标记未来版本是否删除(forRemoval)
  5. String字符串的变化
  6. 模块化
  7. jshell

接口定义private方法

import java.util.concurrent.TimeUnit;

/**
 * @author imyzt
 * @date 2023/12/16
 * @description java 9 在接口中可以定义 private 方法, 只可在本接口中调用
 */
public interface InterfaceFuture {

    void foo();

    /**
     * java 8
     */
    default void foo1() throws InterruptedException {
        sleep();
    }

    /**
     * java 8
     */
    static void foo2() throws InterruptedException {
        sleep2();
    }

    /**
     * java 9
     */
    private void sleep() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
    }

    /**
     * java 9
     */
    private static void sleep2() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1);
    }
}

try-with-resource 方式优化

package top.imyzt.jdk.features.jdk9;


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

/**
 * @author imyzt
 * @date 2023/12/16
 * @description 1. try 代码块简化, 2. 不可以再使用 "_"(下划线)命名变量
 */
public class TryWithResource {

    public static void main(String[] args) throws FileNotFoundException {

        // java 8
        try (FileInputStream fis1 = new FileInputStream("");
             FileOutputStream fos1 = new FileOutputStream("")) {
            fis1.read();
        } catch (Exception e) {

        }

        // java 9
        // 可以将变量写到 try 代码块中, 让代码块更简洁
        FileInputStream fis2 = new FileInputStream("");
        FileOutputStream fos2 = new FileOutputStream("");
        try (fis2; fos2) {

        } catch (Exception e) {

        }


        // java 9 运行报错 As of Java 9, '_' is a keyword, and may not be used as an identifier
        // String _ = "123";

    }

    /**
     * `@Deprecated` 注解支持指定废弃版本(since), 以及标记未来是否废弃(forRemoval)
     */
    @Deprecated(since = "9", forRemoval = true)
    private void test() {

    }
}


@Deprecated 注解升级

例如java.lang.Object#finalize方法就在 Java9 中被标记废弃,并且在未来可能会被删除。

@Deprecated(since="9", forRemoval=true)
protected void finalize() throws Throwable { }

String字符串的变化

在 Java9 之前的版本中,String内部使用char数组存储,对于使用英语的人来说,一个字符用一个byte就能存储,使用char存储字符会浪费一半的内存空间,因此在 Java9 中将String内部的char数组改成了byte数组,这样就节省了一半的内存占用。

char c = 'a'; // 2个字节
byte b = 97; // 1个字节

String中增加了2个成员变量

  1. static final boolean COMPACT_STRINGS;:判断是否压缩,默认为true,如果=false,则不压缩,使用UTF-16编码。
  2. private final byte coder;:用来区分使用的字符编码
    • LATIN1,值为0,存储英文
    • UTF-16,值为1,存储中文,或夹杂中文的英文(为了区分字符边界,如果英文使用1byte,则不利于字符串的sub等操作,也无法计算长度)
@Native static final byte LATIN1 = 0;
@Native static final byte UTF16  = 1;

public int length() {
    // 如果开启压缩,`coder()=0`,不进行位移,返回value数组长度
    // 如果开启压缩,`coder()=1`,右移1=除以2,因为中文存储1个字符占用2个byte的空间
    return value.length >> coder();
}
byte coder() {
    // 如果开启压缩,则`coder=LATIN1=0`,否则`coder=UTF16=1`
    return COMPACT_STRINGS ? coder : UTF16;
}

byte数组如何存储中文?通过源码 java.lang.StringUTF16#toBytes(char[], int, int) 可以看到,1个中文会被存储到byte数组中的两个元素上 ,即存储一个中文,byte数组长度为2,存储2个中文,byte数组长度为4.

string_

StringUTF16 部分源码截取,高八位和低八位分别存储,占用2个byte数组的空间:

static final int HI_BYTE_SHIFT;
static final int LO_BYTE_SHIFT;
static {
    // CPU架构大小端
    if (isBigEndian()) {
        HI_BYTE_SHIFT = 8;
        LO_BYTE_SHIFT = 0;
    } else {
        HI_BYTE_SHIFT = 0;
        LO_BYTE_SHIFT = 8;
    }
}
@IntrinsicCandidate
// intrinsic performs no bounds checks
static void putChar(byte[] val, int index, int c) {
    assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
    index <<= 1;
    val[index++] = (byte)(c >> HI_BYTE_SHIFT);
    val[index]   = (byte)(c >> LO_BYTE_SHIFT);
}

@IntrinsicCandidate
// intrinsic performs no bounds checks
static char getChar(byte[] val, int index) {
    assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
    index <<= 1;
    return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                    ((val[index]   & 0xff) << LO_BYTE_SHIFT));
}

模块化

Java8 和之前的版本中,主要源代码是放在 rt.jar 中的,但是其中很多包是我们平时不会用到的,比如 java.awt 如果全载入到内存中,会造成一定的浪费。
所以在 Java9 开始,将 rt.jar 分成了不同的模块,一个模块下可以包含多个包,模块之间存在依赖关系。其中 java.base 为基础模块(包含java.lang,java.util..),不依赖其他模块。
模块与包类似,只不过一个模块下可以包含多个包。

Java8文档
Java8文档
Java9文档
Java9文档

.jar包中含有.class文件,配置文件。
.jmod除了上述文件外,还包含navite librarylegal licenses等。
两者主要区别是.jmod主要用在编译器和链接期,并非运行期,对于开发者来说,运行期任然需要使用jar包。

模块化的优点

  • 精简JVM运行所需加载的class类,提升加载速度。
  • 对包更精细的控制,提高安全。

关键字

  • exports: 声明导出包,正常可使用,反射可以使用。
  • opens:声明导出包,只有反射可以使用,正常编写代码编译无法通过,报错 Package 'package_name' is declared in module 'develop', which does not export it to module 'test'
  • requires:声明依赖包。

示例工程

  1. 创建项目
  2. 创建模块 developtest
  3. 在模块 develop 下创建 Cat.java, Apple.java,创建 module-info.java,声明 export develop
package top.imyzt.learing.jdkfuture.dev1;


/**
 * @author imyzt
 * @date 2023/12/16
 * @description Cat
 */
public class Cat {

    public void eat() {
        System.out.println("吃鱼");
    }
}

package top.imyzt.learing.jdkfuture.dev2;


/**
 * @author imyzt
 * @date 2023/12/16
 * @description Apple
 */
public class Apple {
    public Apple() {
        System.out.println("Apple Constructor");
    }
}

module develop {
    // 导出包
    exports top.imyzt.learing.jdkfuture.dev1;
    // 导出包, 只能通过反射访问
    opens top.imyzt.learing.jdkfuture.dev2;
}

  1. 在模块 test 下创建 Test.java,创建 module-info.java,声明 requie develop
package main.top.imyzt.learing.jdkfuture.test;


import top.imyzt.learing.jdkfuture.dev1.Cat;
// Package 'top.imyzt.learing.jdkfuture.dev2' is declared in module 'develop', which does not export it to module 'test'
// import top.imyzt.learing.jdkfuture.dev2.Apple;

/**
 * @author imyzt
 * @date 2023/12/16
 * @description 描述信息
 */
public class Test {

    public static void main(String[] args) throws Exception {
        Cat cat = new Cat();
        cat.eat();

        Class<?> clazz = Class.forName("top.imyzt.learing.jdkfuture.dev2.Apple");
        clazz.getDeclaredConstructor().newInstance();
    }
}

module test {
    requires develop;
}

项目结构图

项目结构图

源代码

示例项目

jshell

作用不大,主要对于初学者可以学习语法。

➜  jshell
|  欢迎使用 JShell -- 版本 21.0.1
|  要大致了解该版本, 请键入: /help intro

jshell> System.out.println("hello world");
hello world

jshell> int a = 1;
a ==> 1

jshell> int b = 2;
b ==> 2

jshell> System.out.println(a+b);
3

jshell> /exit
|  再见