随着 Java 16 的正式公布(以及长期反对版 17 的行将到来),还在用 Java 8 的小伙伴们可能曾经感觉有太多货色要学了。自己将整顿从 Java 9 到 Java 16 带来的次要新个性,依照分类陆续展现供大家参考。

本文针对的是语法元素的变动。它们不会影响代码逻辑,但理解它们有助于用更简洁的形式编写代码。本文的代码示例基本上都是来自 JEP 文档中的例子。

JEP 286: Local-Variable Type Inference(本地变量类型推断)

公布版本:Java 10

该个性可能简化本地变量的申明。可推断类型的变量申明语句不需以类型结尾,而是用关键字 var 代替。

例子:

ArrayList<String> list = new ArrayList<>();  // 不应用本地变量类型推断var list = new ArrayList<String>();           // 应用本地变量类型推断

不实用的状况:上面一些本地变量的申明不实用类型推断:

// 上面是谬误应用 var 关键字的示例var x;                // 没有初始化表达式var x = null;        // 初始化为 nullvar x = () -> {};      // 无奈确定对应哪种函数式接口var x = this::f;    // 无奈确定对应哪种函数式接口var x = {1, 2};        // 初始化数组必须严格申明元素类型,如 var x = new int[]{1, 2};

JEP 361: Switch Expressions(Switch表达式)

公布版本:Java 14

该个性可能简化 switch 语句的编写,并防止因为遗记在分支上用 break; 语句完结而造成 BUG。

例子:

// 旧语法switch (day) {    case MONDAY:    case FRIDAY:    case SUNDAY:        System.out.println(6);        break;    case TUESDAY:        System.out.println(7);        break;    case THURSDAY:    case SATURDAY:        System.out.println(8);        break;    case WEDNESDAY:        System.out.println(9);        break;}// 新语法switch (day) {    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);    case TUESDAY                -> System.out.println(7);    case THURSDAY, SATURDAY     -> System.out.println(8);    case WEDNESDAY              -> System.out.println(9);}

此外,switch 自身也能够作为一个表达式来输入值。例如:

T result = switch (arg) {    case L1 -> e1;    case L2 -> e2;    default -> e3;};// 留神:如果 switch 表达式的一个分支是代码块,则须要用 yield 关键字返回后果。// 这里不能用 return,因为 return 关键字曾经被用于从以后办法返回。int j = switch (day) {    case MONDAY  -> 0;    case TUESDAY -> 1;    default      -> {        int k = day.toString().length();        int result = f(k);        yield result;    }};// 上面是一个谬误应用 switch 表达式的例子:int i = switch (day) {    case MONDAY -> {        System.out.println("Monday");         // 谬误!该分支需蕴含 yield 申明    }    default -> 1;};

JEP 378: Text Blocks(文本块)

公布版本:Java 15

该个性使得多行字符串常量的表达方式更加直观。

例子:

// 旧语法String html = "<html>\n" +              "    <body>\n" +              "        <p>Hello, world</p>\n" +              "    </body>\n" +              "</html>\n";// 新语法String html = """              <html>                  <body>                      <p>Hello, world</p>                  </body>              </html>              """;

留神:

  • 文本块的开始和完结都是 """,同一文本块的开始和结束符不能在同一行。
  • 文本块中每行的行首空白字符被视为“缩进”,编译时取缩进大小最小的行作为整个文本块的左边界。
  • 文本块中每行的行尾空白字符会被疏忽掉。
  • 不管源代码文件中的换行符是 \r\n 还是 \r\n,编译进去对立以 \n (LF)作为换行符。
  • 如果文本块中蕴含 """,则须要本义为 \""""\""""\"。例如:

    System.out.println("""     1 "     2 ""     3 ""\"     4 ""\""     5 ""\"""     6 ""\"""\"     7 ""\"""\""     8 ""\"""\"""     9 ""\"""\"""\"    10 ""\"""\"""\""    11 ""\"""\"""\"""    12 ""\"""\"""\"""\"""");
  • 如果某行以 \ 结尾,则它与下一行内容之间没有换行符。例如:

    // 上面两个表达式后果雷同String s = "abc";String s = """    a\    b\    c""";
  • 如果想要用空格填充使得每行长度统一,能够用 \s 填充:

    String s = """    red  \s    green\s    blue \s    """;
  • 如果想在文本块内退出变量,能够间接用 +

    String type = getType();String code = """    public void print(""" + type + """     o) {        System.out.println(Objects.toString(o));    }    """;

    然而为了代码的可读性和可维护性,还是倡议放弃文本块本身的连贯。例如:

    String source = """    public void print(%s object) {        System.out.println(Objects.toString(object));    }    """.formatted(type);

JEP 394: Pattern Matching for instanceof(instanceof 操作的匹配模板)

公布版本:Java 16

该个性简化了 “类型判断+强制类型转换” 逻辑的代码,特地是在编写 equals() 办法的时候。

例子:

// 旧语法if (obj instanceof String) {    String s = (String) obj;    flag = s.contains("jdk");}// 新语法if (obj instanceof String s) {    flag = s.contains("jdk");}// 当前面跟着 && 时,新的变量 s 能够在前面的判断表达式中立刻应用if (obj instanceof String s && s.length() > 5) {    flag = s.contains("jdk");}// 反之则不然if (obj instanceof String s || s.length() > 5) {    // 谬误!    ...}

equals() 办法的代码能够失去极大简化。例如:

// 旧语法public boolean equals(Object o) {    if (!(o instanceof Point))        return false;    Point other = (Point) o;    return x == other.x        && y == other.y;}// 新语法public boolean equals(Object o) {    return (o instanceof Point other)        && x == other.x        && y == other.y;}

留神:应用本个性所定义的模板变量也是一种本地变量。和其余本地变量一样,它可能会笼罩所在类的成员名。上面是一个例子:

class Example2 {    Point p;    void test2(Object o) {        if (o instanceof Point p) {            // p 指的是本地模板变量            ...        } else {            // p 指的是所在类的成员            ...        }    }}

下面的例子中,不同 if 分支下的变量 p 指代的是齐全不同的对象。因而请审慎命名,免得造成了解上的凌乱。

JEP 395: Records(记录类)

公布版本:Java 16

该个性简化了只读数据传输对象的定义。留神,它并不能代替现有的 java bean 概念,因为 java bean 的属性是可写的。

例子:

// 旧语法class Point {    private final int x;    private final int y;    Point(int x, int y) {        this.x = x;        this.y = y;    }    int x() { return x; }    int y() { return y; }    public boolean equals(Object o) {        if (!(o instanceof Point)) return false;        Point other = (Point) o;        return other.x == x && other.y == y;    }    public int hashCode() {        return Objects.hash(x, y);    }    public String toString() {        return String.format("Point[x=%d, y=%d]", x, y);    }}// 新语法record Point(int x, int y) { }// 如果要对参数进行校验和解决record Point(int x, int y) {    Point {        if (x < 0) throw new IllegalArgumentException("x不能为正数");        y = Math.abs(y);    }}// 记录类的应用Point p = new Point(1, 1);System.out.println("x=" + p.x() + ", y=" + p.y());

应用限度:

  • 记录类不可从其余类继承,因为所有的记录类都是 java.lang.Record 的子类。一个记录类甚至不能够是另一个记录类的子类。
  • 记录类不能够是形象的,也不能够有子类。
  • 记录类的成员都是 final 的,即不可从新赋值。
  • 记录类不能够再定义额定的成员,也不能够再定义额定的构造方法。

除此之外,记录类和一般 class 一样:

  • 应用 new 关键字来创立实例;
  • 能够实现其余接口;
  • 能够是内部的,能够是外部的,也能够蕴含泛型;
  • 能够定义静态方法、动态成员和非静态方法;

本地记录类:你能够在一个办法内申明本地记录类,以帮忙实现业务逻辑。例如:

List<Merchant> findTopMerchants(List<Merchant> merchants, int month) {    // 申明本地记录类    record MerchantSales(Merchant merchant, double sales) {}    return merchants.stream()        .map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))        .sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))        .map(MerchantSales::merchant)        .collect(toList());}

留神,外部记录类(在类中申明)和本地记录类(在办法中申明)都是动态的。

代码兼容性问题:

因为默认包下新增了 java.lang.Record 类,所以任何其余包下的 Record 类的应用都会受到影响。例如你有一个类叫做 a.b.Record,那么如果代码中应用 import a.b.*;,这种状况下是无奈应用你的 Record 类的。你必须改为 import a.b.Record; 能力编译通过。