1. 类型推断

在Java 10中,提供了本地变量类型推断的功能,可以通过var声明变量:

var a = "123";
var b = 123;
System.out.println(a + b);

本地变量类型推断将引入“var”关键字,而不需要显式的规范变量的类型。

其实,所谓的本地变量类型推断,也是Java 10提供给开发者的语法糖。

虽然我们在代码中使用var进行了定义,但是对于虚拟机来说他是不认识这个var的,在java文件编译成class文件的过程中,会进行解糖,使用变量真正的类型来替代var。

2. Switch 增强

在JDK 12中引入了Switch表达式作为预览特性。并在Java 13中修改了这个特性,引入了yield语句,用于返回值。而在之后的Java 14中,这一功能正式作为标准功能提供出来。

  • 引入了Switch表达式。
  • 引入了yield语句,用于返回值。
  • switch支持null。
  • switch支持对enum和sealed Classes完整性校验,对于enum或sealed Classes已约定范围,编译器会进行完整性校验,如果已覆盖所有可能取值,则不再需要default分支。
  • 支持连写case。
public class SwitchTest {
    /**
     * 支持lambda表达式
     *
     * @param a
     */
    public static void test1(String a) {
        switch (a) {
            case "a" -> System.out.println("i guess it is a ~");
            default -> System.out.println("default");
        }
    }

    /**
     * 支持yield返回值
     *
     * @param a
     */
    public static void test2(String a) {
        String result = switch (a) {
            case "a" -> {
                yield "i guess it is a ~";
            }
            // 这里也可以直接省略yield
            default -> "default";
        };
        System.out.println("test2:" + result);
    }

    enum Type {
        TYPE_A, TYPE_B, TYPE_C;
    }

    /**
     * 使用枚举测试
     * 如果已覆盖所有可能取值,则不再需要default分支
     *
     * @param type
     * @return
     */
    public static String test3(Type type) {
        return switch (type) {
            case TYPE_A -> "TYPE_A";
            case TYPE_B -> "TYPE_B";
            case TYPE_C -> "TYPE_C";
        };
    }

    sealed interface ITest permits TestA, TestB {
    }

    static final class TestA implements ITest {
    }

    static final class TestB implements ITest {
    }

    /**
     * 使用密封类测试
     * 如果已覆盖所有可能取值,则不再需要default分支
     *
     * @param test
     * @return
     */
    public static String test4(ITest test) {
        return switch (test) {
            case TestA testA -> "TestA";
            case TestB testB -> "TestB";
        };
    }

    /**
     * null测试
     *
     * @param s
     * @return
     */
    public static String test5(String s) {
        return switch (s) {
            case null -> "null";
            case "a" -> "a";
            default -> "default";
        };
    }

    /**
     * 支持连写case
     *
     * @param s
     * @return
     */
    public static String test6(String s) {
        return switch (s) {
            case "a", "b", "c" -> "abc";
            case "d" -> "d";
            default -> "default";
        };
    }

    public static void main(String[] args) {
        test1("a");
        test1("b");
        test2("a");
        test2("b");
        System.out.println("test3:" + test3(Type.TYPE_A));
        System.out.println("test4:" + test4(new TestA()));
        System.out.println("test5:" + test5(null));
        System.out.println("test6:" + test6("a"));
    }
}

3. Text Blocks

Java 13中提供了一个Text Blocks的预览特性,并且在Java 14中提供了第二个版本的预览。

text block,文本块,是一个多行字符串文字,它避免了对大多数转义序列的需要,以可预测的方式自动格式化字符串,并在需要时让开发人员控制格式。

以最后的"""作为缩进的依据。

    public static void main(String[] args) {
        String str = """
                asdasdqwdqwdqw
                  123123
                asdasd
                """;
        System.out.println(str);
        String str2 = """
                   asdasdqwdqwdqw
                     123123
                   asdasd
                """;
        System.out.println(str2);
    }

4. Records

Java 14 中便包含了一个新特性:EP 359: Records,

Records的目标是扩展Java语言语法,Records为声明类提供了一种紧凑的语法,用于创建一种类中是“字段,只是字段,除了字段什么都没有”的类。

record中的所有字段都是final的,只能在初始化的时候设置。

public class RecordTest {
    record UserRecord(String name, Integer age) {
        UserRecord {

        }
    }

    public static void main(String[] args) {
        UserRecord userRecord = new UserRecord("rabb", 18);
    }
}

5. 封闭类

在JDK17中添加了密封类。密封的类和接口限制了哪些其他类或接口可以拓展或实现它们。密封类这个新的特性在JDK15和JDK16中作为预览功能。现在在JDK17中作为正式的功能,它与JDK16相比没有任何变化。

sealed关键字用来表示这个类是一个密封类,然后permits表示可以继承该类的子类

子类的必须是以下三种类型:

  • final类型
  • 也是密封类类型(sealed)
  • 非密封类类型(non-sealed)

除了通过关键字permits来指定外,也可以直接将密封类允许的子类可以直接写到密封类的源文件中,当以这种方式声明时,密封类可以省略许可子句(permits),Java编译器会从源文件中的声明推断出允许的子类。

public class SealedTest {
    /**
     * 密封类
     */
    abstract sealed class Subject permits SubjectA, SubjectB {
    }

    /**
     * 子类非密封类
     */
    non-sealed class SubjectA extends Subject {
    }

    /**
     * 子类也是密封类
     */
    sealed class SubjectB extends Subject permits SubjectC {
    }

    /**
     * 子类为final类,不可被继承
     */
    final class SubjectC extends SubjectB {
    }
}

6. instanceof 模式匹配

模式匹配可以帮我们减少繁琐的条件状态提取。在进行条件状态提取时,我们问一个与某个对象有关的问题(比如“你是 Foo 吗”),如果答案是肯定的,我们就从对象中提取状态:“if (x instanceof Integer i) { … }”,其中 i 是绑定变量。

public class InstanceTest {
    public static void main(String[] args) {
        test(1);
        test("1");
        test(1L);

        System.out.println(test2(1));
        System.out.println(test2("1"));
        System.out.println(test2(1L));
        System.out.println(test2(1f));
    }

    public static void test(Object object) {
        if (object instanceof String str) {
            System.out.println("String:" + str);
        } else if (object instanceof Integer integer) {
            System.out.println("Integer:" + integer);
        } else {
            System.out.println("unknown");
        }
    }

    public static String test2(Object o) {
        return switch (o) {
            case Integer i -> String.format("int %d", i);
            case Long l -> String.format("long %d", l);
            case Double d -> String.format("double %f", d);
            case String s -> String.format("String %s", s);
            default -> "unknown";
        };
    }
}

7. 模块化开发

模块化在jdk9中加入,模块化的好处就是开发者可以根据需要引用某个依赖的指定部分,而不是引入这个依赖的全部。以此达到减少体积以及提高编效率的目的。

可以使用module-info.java文件来导出或者引入一个模块。

module test {
    exports xyz.lazyrabbit.jdk17.module;
    requires java.net.http;
}

8. 接口的私有方法

在JDK17中,允许interface中携带private方法,这个private方法允许被自身default修饰的方法调用。

public class InterfaceTest {
    interface ITest {
        default void test() {
            System.out.println(test2());
        }

        private String test2() {
            return "test2";
        }
    }
}

9. JShell引擎

Java Shell 工具(简称:JShell)是一个用于学习Java编程语言和构建Java代码原型的交互式工具。JShell是一个Read-Evaluate-Print循环(REPL),它在语法、声明和表达式输入时即对它们进行计算,并立即显示其结果。该 JShell 工具通过命令行来运行。

如果你的设备中安装了JDK9即以上的版本,可以在终端中输入jshell,进入JShell,并编写脚本进行测试,同时按tab键可以调出候补提示。

10. 其他语法增强

  • Stream新增takeWhile方法类似与filter,区别在于takeWhile会在遇到不符合的条件时舍弃后边的所有元素,相对应的还有dropWhile方法
  • Stream新增ofNullable方法
  • ListMap新增of方法,可以快速构造集合
  • 通过Files类快速读写
  • String增加判空方法isBlack方法和isEmpty
List<Integer> integers = List.of(1, 2, 3, 4, 5, 6, 7, 1);
System.out.printf("" + integers.stream().takeWhile(integer -> integer < 4).count());
System.out.printf("" + Stream.ofNullable(null).count());
String s = "123abd";
System.out.println(s.isBlank());
System.out.println(s.isEmpty());
Files.writeString(
        Path.of("./", "tmp.txt"), // 路径
        "hello, jdk11 files api", // 内容
        StandardCharsets.UTF_8); // 编码
String txt = Files.readString(
        Paths.get("./tmp.txt"), // 路径
        StandardCharsets.UTF_8); // 编码
System.out.println(txt);

11. 直接打包成可执行程序

支持将Java程序打包为对应平台的可执行程序

  • linux: deb和rpm
  • mac: pkg和dmg
  • windows: msi和exe

假如我们在lib目录下有一个jar包组成的应用,并且main.jar包含main方法,则可以使用下面的语句产生对应平台的可执行程序

jpackage --name myapp --input lib --main-jar main.jar

如果main.jar的MANIFEST.MF没有指定main函数,则需要在命令行中指定

jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main

12. GC优化

  • JDK9设置G1为JVM默认垃圾收集器
  • JDK10并行Full GC,来优化G1的延迟
  • JDK11新增ZGC垃圾收集器
  • JDK12新增Shenandoah GC垃圾回收算法
  • JDK12G1收集器的优化,将GC的垃圾分为强制部分和可选部分,强制部分会被回收,可选部分可能不会被回收,提高GC的效率
  • JDK13ZGC优化,释放内存还给操作系统
  • JDK16ZGC性能优化