Java17来啦!全文分享更新内容

Java 17 有什么新功能?

微信搜索关注《Java学研大本营》,加入读者群,分享更多精彩

Java 17 于 2021 年 9 月发布,仅比之前的 LTS Java 11 晚了 3 年。它带来了很多新的变化,比如重新实现了整个 TCP 和 UDP Socket API 以及引入了两个新的垃圾收集器:ZGC 和 Shenandoha。

此版本中引入的语言功能包括:

  • 切换表达式

  • 文本块和格式化字符串

  • instanceof 的模式匹配

  • 记录

  • 密封类

  • 本地类型

  • 内部类静态成员 此外还引入了 jpackage,这是一个用于创建本地安装程序的有趣命令行工具。

切换表达式

与经典的 Switch 相比,Switch 表达式只执行激活的 case 内的代码,并且可以返回一个值。

public class SwitchExpressions {
    private static final boolean LOVE_COLD = false;

    public enum Season {SPRING, SUMMER, AUTUMN, WINTER}

    public static String seasonMessage(Season season) {
        return switch (season) {
            case SPRING, AUTUMN -> "It is a good season.";
            case SUMMER -> "Beach time!";
            case WINTER -> {
                if (LOVE_COLD) {
                    yield "Snow time!";
                }
                throw new RuntimeException("Ice cubes everywhere!");
            }
        };
    }
}

返回的值类型必须在所有情况下和默认分支中都兼容。它可以直接在表达式 分支中返回(第 8-9 行)或在块分支中使用yield关键字(第 10-15 行)。

在块分支中,yield 与方法体中的 return 具有相同的功能。这意味着,如果某个条件流运算符用于在块分支内生成多个分支,则每个分支都必须产生一个值或抛出异常。

与普通 Switch 不同,switch 表达式始终需要默认分支,除非输入类型是枚举并且涵盖了所有情况,或者没有返回值。

文本块和格式化字符串

现在使用字符串更容易了。

public class TextBlocks {
    public static void main(String[] args) {
        var block = """
                This Text has \
                two lines.
                  "Text" is automatically escaped.""";
        System.out.println(block);
        /*
        |This Text has two lines.
        |  "Text" is automatically escaped.
        */
        var formatted = "A string can be formatted using %s method".formatted(".formatted()");
        System.out.println(formatted);
        /*
        A string can be formatted using .formatted() method
         */
    }
}

文本块允许编写不需要转义的字符串,例如 JSON。最左边字符之前的所有空格都会被自动删除。

另一方面,Formatted 是一种新的字符串实例方法,它允许直接格式化字符串而不使用String.format(...).

instanceof 的模式匹配

instanceof 的模式匹配与 if 语句一起用于减少样板文件并提高可读性。

public class PatternMatchingInstanceof {
    public static void main(String[] args) {
        Object o = "I'm a string.";
        // old way
        if (o instanceof String && !((String) o).isBlank()) {
            System.out.println(((String) o).toUpperCase());
        }
        // new way
        if (o instanceof String s && !s.isBlank()) {
            System.out.println(s.toUpperCase());
        }
    }
}

新语法允许定义匹配类型的变量并使用它。新变量的范围由编译器计算并取决于检查的完成方式。在这种情况下,从检查后的第 9 行开始,到 if 正文结束的第 11 行结束。

public class GuessANumber {
    private static final Random random = new Random();

    public static void main(String[] args) {
        Object o = random.nextInt(10) + 1;

        while (!guessANumber(o)) {}

        System.out.println("I won.");
    }

    private static boolean guessANumber(Object o) {
        if (!(o instanceof Integer i)) {
            throw new IllegalArgumentException("I can guess only numbers.");
        }
        if (i < 1 || i > 10) {
            throw new IllegalArgumentException("I can guess only numbers between 1 and 10.");
        }
        var guess = random.nextInt(10) + 1;
        System.out.println("Trying with " + guess);
        return i.equals(guess);
    }
}

在第二种情况下,在第 15 行之后,可以使用变量i ,因为o只能是整数。

记录

记录用于删除样板,给定 POJO 类:

final class POJOPoint {
    private final double x;
    private final double y;

    POJOPoint(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double x() {
        return x;
    }

    public double y() {
        return y;
    }

    @Override
    public boolean equals(Object o) {
        return true; // my dummy example implementation
    }

    @Override
    public int hashCode() {
        return 0; // my dummy example implementation
    }

    @Override
    public String toString() {
        return "Point{x=%f, y=%f}".formatted(x, y);
    }
}

可以替换为:

record RecordPoint(double x, double y) {
}

Records 允许以紧凑的方式定义一个不可变的类:

  • 规范的所有参数构造函数

  • 访问器方法(field()而不是getField())

  • equals(),hashCode()和toString()默认实现

然而,可以为任何这些方法和规范构造函数本身提供自定义实现。

public record Point(double x, double y, String label) implements Serializable {
    public static final long serialVersionUID = 1;

    public Point {
        if (label == null || label.isBlank()) {
            throw new IllegalArgumentException("Label cannot be blank.");
        }
        label = label.toUpperCase();
    }

    public Point(double x, double y) {
        this(x, y, "POINT");
    }

    @Override
    public String toString() {
        return "%s(%f, %f)".formatted(label, x, y);
    }

    public static Point origin() {
        return new Point(0, 0 , "origin");
    }

    public double distanceFrom(Point p) {
        double xDistance = Math.pow(x - p.x, 2);
        double yDistance = Math.pow(y - p.y, 2);
        return Math.sqrt(xDistance + yDistance);
    }
}

此外,记录可以:

  • 实现任何接口,但不能扩展类(第 1 行)

  • 在调用规范构造函数之前定义一个紧凑的构造函数来操作数据(第 4-9 行)

  • 定义任何不同于 all args 的构造函数,但必须使用 all args 调用this(...)(第 11-13 行)

  • 添加静态成员或方法(第 2 行,第 20-22 行)

  • 添加实例方法,但不添加其他实例成员(第 24-28 行)

对于使用 Lombok 的人来说,它基本上是一个带有@Value注释的类。

密封类

密封类/接口限制了哪些其他类/接口可以直接实现/扩展它们。

sealed interface Shape permits Triangle, Rectangle, Polygon {
}

final class Triangle implements Shape {
}

sealed class Rectangle implements Shape permits Square {
}

final class Square extends Rectangle {
}

non-sealed class Polygon implements Shape{
}

permit子句中定义的每个类/接口都必须扩展/实现密封的类/接口。默认情况下,专利和子项必须在同一个包中,但在模块化应用程序中,可以在同一模块内的任何位置定义。

扩展/实现密封类/接口的类/接口必须是以下之一:

  • final:不允许子类。

  • 密封:必须定义允许的子类/子接口。

  • non-sealed:这相当于一个普通的类/接口,没有任何限制。

本地类型

本地意味着在方法体内部,在 Java 11 中已经可以定义类,在 Java 17 中这种可能性已扩展到接口和枚举。

public class LocalTypes {
    public static void main(String[] args) {
        interface PrimaryColor { boolean isPrimary(); }

        enum Color implements PrimaryColor {
            RED(true), GREEN(true), BLUE(true),
            BLACK(false), WHITE(false), YELLOW(false);

            private final boolean primary;

            Color(boolean primary) { this.primary = primary; }

            @Override
            public boolean isPrimary() { return primary; }
        }

        Stream.of(Color.values())
                .filter(PrimaryColor::isPrimary)
                .forEach(System.out::println);
    }
}

需要指出的是,即使现在有可能,但这并不意味着必须这样做。在编写代码时,始终考虑可读性和可维护性是很重要的。

内部类静态成员

内部类,作为顶级类和嵌套类,现在可以定义静态成员(字段和方法)。

public class InnerClassStaticMembers {
    public static void main(String[] args) {
        Nested.print();
        Inner.print();
    }
    
    class Inner {
        static String message = "Static inside inner is new!";

        static void print() {
            System.out.println(message);;
        }
    }

    static class Nested {
        static String message = "Static inside nested is old!";

        static void print() {
            System.out.println(message);;
        }
    }
}

在 Java 17 之前,内部类和嵌套类都可以声明实例成员,但只有嵌套类可以声明静态成员。至于局部类型,内部类和嵌套类在某些情况下可能很方便,但它们的使用应该反映一个原因,它们不应该仅仅因为它们可以使用而使用。

jpackage

这个新的命令行工具为独立的 Java 应用程序创建安装程序。换句话说,给定一个 jar 应用程序和一个 JRE(Java 运行时环境),它将为不同的操作系统创建一个本机安装程序:

  • 视窗:exe、msi

  • Linux:rpm、deb

  • Mac:pkg,dmg

让我们以模式匹配中的GuessANumber示例为例,假设包是my.example。作为第一步,我们需要将源代码编译并打包到一个 jar 文件中:

javac my/example/*.java
jar cfe GuessANumber.jar my.example.GuessANumber my/example/*.class

这相当于mvn compile package在 maven 项目中运行。

现在应用程序已准备就绪,可以使用以下命令创建安装程序:

jpackage --input . \
    --main-jar GuessANumber.jar \
    --main-class my.example.GuessANumber

安装程序和 JRE 类型由当前环境自动推断。给定一个 linux,创建一个 deb 文件。有了它,就可以安装包了,安装的可执行文件可以在/opt/guessanumber/bin/GuessANumber找到。

垃圾收集器

从 Java 9 开始,默认的垃圾收集器是G1。它不能在应用程序运行时工作,但可以根据用户需要修剪暂停。更短的暂停为 GC 提供更多的 CPU 时间,更大的暂停让应用程序工作更长时间而不会停止。然而,如果工作量随着时间的推移而增加,性能就会下降。

Shenandoha是在 java 12 中引入的,它相对于G1的优势在于它能够与应用程序线程同时执行其工作。当它能够使用 Brooks 指针与应用程序同时重定位对象时,这是可能的。Brook 指针是堆中的每个对象都具有指向该对象的引用。

ZGC的目标是低延迟、可扩展性和易用性。作为Shenandoha,它允许主应用程序在执行垃圾收集的同时继续运行。此外,它可以将 Java 堆从数百 MB 扩展到 TB,保持低暂停时间。可预测的暂停简化了开发过程:无需设计避免垃圾收集的方法或垃圾收集器的性能专业知识。

结论

一个接一个地发布 Java 正试图成为一种更现代、更轻量级的编程语言。它需要与在 JVM 行 Kotlin 上运行的其他现代语言保持更新,同时,不要失去习惯它并且不想改变他们所知道的一切的旧开发人员。

从我的角度来看,它做得很好,添加了非常有趣的新工具,有助于以更优雅和可读的方式构建代码。我很期待看到下一个 LTS 中会发生什么。

参考

  • OCP Oracle Certified Professional Java SE 17 开发人员学习指南:考试 1Z0–829

  • jpackage 指南

  • 了解 jdks 新的超高速垃圾收集器

推荐书单

《项目驱动零起点学Java》

《项目驱动零起点学Java》共分 13 章,围绕 6 个项目和 258 个代码示例,分别介绍了走进Java 的世界、变量与数据类型、运算符、流程控制、方法、数组、面向对象、异常、常用类、集合、I/O流、多线程、网络编程相关内容。《项目驱动零起点学Java》总结了马士兵老师从事Java培训十余年来经受了市场检验的教研成果,通过6 个项目以及每章的示例和习题,可以帮助读者快速掌握Java 编程的语法以及算法实现。扫描每章提供的二维码可观看相应章节内容的视频讲解。

《项目驱动零起点学Java》贯穿6个完整项目,经过作者多年教学经验提炼而得,项目从小到大、从短到长,可以让读者在练习项目的过程中,快速掌握一系列知识点。

马士兵,马士兵教育创始人,毕业于清华大学,著名IT讲师,所讲课程广受欢迎,学生遍布全球大厂,擅长用简单的语言讲授复杂的问题,擅长项目驱动知识的综合学习。马士兵教育获得在线教育“名课堂”奖、“最受欢迎机构”奖。

赵珊珊,从事多年一线开发,曾为国税、地税税务系统工作。拥有7年一线教学经验,多年线上、线下教育的积累沉淀,培养学员数万名,讲解细致,脉络清晰。

《项目驱动零起点学Java》(马士兵,赵珊珊)【摘要 书评 试读】- 京东图书京东JD.COM图书频道为您提供《项目驱动零起点学Java》在线选购,本书作者:,出版社:清华大学出版社。买图书,到京东。网购图书,享受最低优惠折扣!icon-default.png?t=N3I4https://item.jd.com/13607758.html

精彩回顾

部署Spring Boot应用程序

Java Spring Boot 3.0.0 RC1 震撼登场!

微信搜索关注《Java学研大本营》

访问【IT今日热榜】,发现每日技术热点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值