What's new from Java?

java各版本文档官网直达链接

java7

一、整型可以用二进制数表示

在Java SE 7中,整型(byte、short、int和long)也可以使用二进制数字系统表示。要指定二进制文字,请在数字前加上前缀0b或0B

public static void main(String[] args) {
    byte b1 = 0b01;
    byte b2 = 0B01;
    short s1 = 0b0101;
    short s2 = 0B0101;
    int i1 = 0b01010101;
    int i2 = 0B01010101;
    long l1 = 0b10101010101;
    long l2 = 0B10101010101;
    int[] ints = {0b010, 0B1010, 0b0101011};
}

二、允许在数字中间任何位置增加下划线

public static void main(String[] args) {
    long creditCardNumber = 1234_5678_9012_3456L;
    long socialSecurityNumber = 999_99_9999L;
    float pi = 	3.14_15F;
    long hexBytes = 0xFF_EC_DE_5E;
    long hexWords = 0xCAFE_BABE;
    long maxLong = 0x7fff_ffff_ffff_ffffL;
    byte nybbles = 0b0010_0101;
    long bytes = 0b11010010_01101001_10010100_10010010;
}

三、支持在switch语句中使用String

public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
     String typeOfDay;
     switch (dayOfWeekArg) {
         case "Monday":
             typeOfDay = "Start of work week";
             break;
         case "Tuesday":
         case "Wednesday":
         case "Thursday":
             typeOfDay = "Midweek";
             break;
         case "Friday":
             typeOfDay = "End of work week";
             break;
         case "Saturday":
         case "Sunday":
             typeOfDay = "Weekend";
             break;
         default:
             throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
     }
     return typeOfDay;
}

switch语句将会把目标与case子句中的字符串进行比较,就像使用String.equals()方法(区分大小写)。使用switch生成的字节码将会比使用大量if..else if...else if...else语句生成的字节码更高效。

四、泛型的自动推断

public static void main(String[] args) {
    //HashMap<String, List<String>> map = new HashMap<String, List<String>>();
    //不需要在后方的尖括号内再次声明泛型的类型
    HashMap<String, List<String>> map = new HashMap<>();
}

五、try-with-resources

try-with-resources语句可以帮你声明一个或多个资源(程序完成时必须关闭资源的对象,如io流)的try语句。try-with-resources能够确保你的资源正常关闭,但是必须实现java.lang.AutoCloseablejava.io.Closeable接口。io的许多对象以对此功能进行增强。

try-finally方式关闭资源

public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader("path"));
    try {
        String line = br.readLine();
    } finally {
        if (br != null) {
            br.close();
        }
    }
}

try-with-resources自动关闭资源

public static void main(String[] args) throws IOException {
    //把资源对象声明在try()内,即可实现资源的自动关闭
    try (BufferedReader br = new BufferedReader(new FileReader("path"));){
        String line = br.readLine();
    }
}

六、catch字句捕获多种异常

在java7以后,单个 catch 块可以处理多种类型的异常,此模式可以消除重复代码

//多种异常类型时需要catch多次
catch (IOException ex) {
     logger.log(ex);
     throw ex;
catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}

在java7之后,在catch字句中使用|来分隔多种异常类型

//一次性catch多种异常类型
catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

需要注意的是:如果在catch字句中捕获了多种异常类型,那么此异常对象会被隐式的声明为final

java 8

官网直达链接

一、Lambda表达式

使用Lambda可以把一个方法当作一个参数传递到另外一个方法之中

语法

Lambda表达式由三部分组成:

  1. 在括号中由逗号分隔的参数列表(param1, param2, param3 ...)(如果只有一个参数可以省略括号)

  2. 箭头令牌->

  3. 由单句表达式或者语句块组成的方法体(当只有一句表达式时可省略大括号)

    基本示例

    public class TestLambda {
    
        interface IntegerMath {
            int operation(int a, int b);
        }
    
        public static void main(String[] args) {
            //实现了IntegerMath.operation并返回a与b的乘积
            IntegerMath integerMath = (a, b) -> a * b;
        }
    }
    

    无参无返回值

    public class TestLambda {
    
        interface IntegerMath {
            void operation();
        }
    
        public static void main(String[] args) {
            IntegerMath integerMath = () -> System.out.println(...);
        }
    }
    

    集合中使用Lambda

    public class TestLambda {
    
        interface IntegerMath {
            void operation();
        }
    
        public static void main(String[] args) {
            List<Integer> integers = new ArrayList<>(16);
            //第一种
            integers.forEach(integer -> System.out.println(integer));
            //第二种
            integers.forEach(System.out::println);
        }
    }
    

二、方法易于阅读的Lambda引用方式

方法引用的种类

类型语法
对静态方法的引用ContainingClass::staticMethodName
对特定对象的实例方法的引用containingObject*::*instanceMethodName
对特定类型的任意对象的实例方法的引用ContainingType*::*methodName
对构造函数的引用ClassName::new

例子

import java.util.function.BiFunction;

public class MethodReferencesExamples {
    public static <T> T mergeThings(T a, T b, BiFunction<T, T, T> merger) {
        return merger.apply(a, b);
    }

    public static String appendStrings(String a, String b) {
        return a + b;
    }

    public String appendStrings2(String a, String b) {
        return a + b;
    }
    
    //定义了一个从一个集合复制元素到另一个集合的方法
    public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements(
            SOURCE sourceCollection,
            Supplier<DEST> collectionFactory) {
        DEST result = collectionFactory.get();
        result.addAll(sourceCollection);
        return result;
    }

    public static void main(String[] args) {
        MethodReferencesExamples myApp = new MethodReferencesExamples();

        //使用 Lambda 表达式调用方法 mergeThings
        System.out.println(MethodReferencesExamples.mergeThings("Hello ", "World!", (a, b) -> a + b));

        //引用静态方法
        System.out.println(MethodReferencesExamples.mergeThings("Hello ", "World!", MethodReferencesExamples::appendStrings));

        //引用特定对象的实例方法
        System.out.println(MethodReferencesExamples.mergeThings("Hello ", "World!", myApp::appendStrings2));

        //对特定类型的任意对象的实例方法的引用
        System.out.println(MethodReferencesExamples.mergeThings("Hello ", "World!", String::concat));
        
        List<Integer> integers = new ArrayList<>(16);
        //对构造函数的引用
        Set<Integer> rosterSet = transferElements(integers, HashSet::new);
    }
}

三、接口的静态、默认方法

在开发过程中,面向抽象开发往往是比面向具体对象开发更方便的。但是这也带来了里国外一个问题:在给接口新增功能时,往往需要给所有的实现类一起新增。比如在一开始的集合类中是没有forEach()方法的,一般的解决方案是在对应的接口中新增此方法,但是新增后势必需要修改实现,很难做到不影响已发布版本的使用,所以就引入了默认方法来屏蔽这些差异,确保代码的兼容性。

public class InterfaceTest {
    public static void main(String[] args) {
        Interface anInterface = new InterfaceImpl();
        //调用实例的默认方法
        anInterface.defaultMethod();
        //调用静态方法
        Interface.staticMethod();
    }
}

interface Interface {
    void method1();

    void method2();

    void method3();

    default void defaultMethod(){
        System.out.println("defaultMethod");
    }

    static void staticMethod(){
        System.out.println("staticMethod");
    }
}

class InterfaceImpl implements Interface {
    @Override
    public void method1() {}

    @Override
    public void method2() {}

    @Override
    public void method3() {}
}

默认方法继承、实现后的操作

  • 不做任何操作,直接使用

  • 可以在实现类重写默认方法

  • 可以在子类把默认方法再次abstract

    interface InterfaceSon extends Interface {
        @Override
        void defaultMethod();
    }
    

静态方法

与类中的静态方法一样,您可以指定接口中的方法定义是静态方法

四、方法参数反射

在java8中新增了Parameter类,可以使用java.lang.reflect.Executable.getParameters()获取任何方法或构造函数的形式参数的名称。

默认情况下,文件不存储正式的参数名称。这是因为许多生成和使用类文件的工具可能不希望包含参数名称的文件占用更大的静态和动态占用空间。

若要将正式参数名称存储在特定文件中,从而使反射 API 能够检索正式参数名称,请在编译时增加-parameters参数

五、Stream APi

java8中新增了java.util.stream工具包,对集合类型操作进行了极大的增强

Stream可以声明式的批量对集合进行操作,如排序、筛选、求和等。

遍历:forEach()

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(5, 3, 6, 7, 1, 8, 5, 4);
        integers.stream().forEach(System.out::print);
    }
}

//输出
53671854

排序:sorted()

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(5, 3, 6, 7, 1, 8, 5, 4);
        //倒序
        integers.stream().sorted((i1, i2) -> i2 - i1).forEach(System.out::print);
    }
}

//输出
87655431

筛选:filter()

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(5, 3, 6, 7, 1, 8, 5, 4);
        //筛选大于4的数
        integers.stream().filter(i -> i > 4).forEach(System.out::print);
    }
}

//输出
56785

归约:reduce()

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(5, 3, 6, 7, 1, 8, 5, 4);
        System.out.println(integers.stream().reduce(Integer::sum).get());
    }
}

//输出
39

六、使用MetaSpace代替PermGen

七、新的DateTime API

JDK 8 中引入的日期时间 API 是一组包,用于对日期和时间的最重要方面进行建模。java.time 包中的核心类使用 ISO-8601 中定义的日历系统(基于公历系统)作为默认日历。其他非ISO日历系统可以使用java.time.chrono包表示,并提供几个预定义的年表,如Hijrah和Thai Buddhist。

详情请看Java Date Time API (oracle.com)

八、HashMap的性能增强

在java8中,修改了存储hash值的数据结构,由链表转为了平衡树。在hash表中当达到某一阈值(8)时,把链表转为平衡树,优化了最坏情况从O(n)到O(logn)。

JEP 180:使用平衡树处理频繁的哈希映射冲突

java7中HashMap.put的源码,

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K, V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K, V> e = table[bucketIndex];
    //若是产生hash冲突则使用链表来存储冲突的键值
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

static class Entry<K, V> implements Map.Entry<K, V> {
    final K key;
    V value;
    Entry<K, V> next;
    int hash;

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K, V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
}

1.8中HashMap.putVal()方法源码(因为put()方法直接调用了putVal(),所以此处不列出put()方法)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //判断hash冲突的链表是否长度大于等于预设值的阈值
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    	//转换为平衡树
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

/*
 * 转为平衡树结构
 */
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

九、其它

java.lang 和 java.util 中的增强功能

  • Parallel Array Sorting(并行数组排序)

    Arrays.parallelSort();

  • Standard Encoding and Decoding Base64(标准的base64编码库)

    java.util.Base64java.util.Base64.Decoderjava.util.Base64.Encoder

  • Unsigned Arithmetic Support(无符号运算支持)

java9

官网直达链接

一、模块化系统

引入了一种新型的 Java 编程组件模块,它是代码和数据的命名、自描述的集合

  • 引入了一个新的可选阶段,即链接时间,即编译时间和运行时之间的阶段,在此期间,可以将一组模块组装并优化为自定义运行时映像
  • 在一个模块下使用module-info.class文件描述此模块
  • 新增JMOD格式,这是一种类似于JAR的打包格式

二、String存储结构更新

在java8及以前,字符串内部存储的都是字符数组,每个字符使用两个字节(十六位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且大多数对象仅包含1字符。此类字符只需要一个字节的存储空间,因此此类对象的内部数组中有一半的空间未被使用,造成了大量的空间浪费。

所以,在java9更新后,把字符数组改为了字节数组,增加了用于对value中的字节进行编码的编码标识符,包括(LATIN1 UTF16)。

JEP 254:紧凑字符串

java8中的String内部存储结构

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** 8及以前的版本时char[]来存储字符 */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
}

java9中的String内部存储结构

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    /**
     * 由char[]更换为了byte[]数组
     */
    @Stable
    private final byte[] value;

    /** 用于标识字节的编码 */
    private final byte coder;

    private int hash; // Default to 0
}

三、Jshell工具

该工具提供了一个交互式命令行界面,用于评估 Java 编程语言的声明、语句和表达式。

jshell 命令文档

JShell API 使应用程序能够利用 REPL 功能。请参阅 jdk.jshell 包。

jshell.png

四、try-with-resources语句增强

try-with-resource语法是在java7新增的一个异常处理机制。try-with-resources 语句确保了每个资源在语句结束时关闭,连接对象必须要实现AutoCloseable接口才可使用此语法。

在java9中增强了在try()中直接使用现有对象的功能

模拟需要关闭资源的对象

/**
 * 连接对象
 */
class Connection implements AutoCloseable {

    public Connection() {
        System.out.println("打开连接成功。。。");
    }

    public void doSomething() {
        //doSomething
    }

    @Override
    public void close() throws Exception {
        System.out.println("关闭连接成功。。。");
    }
}

传统try-catch-finally

public class TestTryWithResource {

    public static void main(String[] args) {
        Connection connection1 = null;
        Connection connection2 = null;
        try {
            connection1 = new Connection();
            connection2 = new Connection();
            connection1.doSomething();
            connection2.doSomething();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //多次try-catch-finally语句出现,还会有嵌套的情况
            if (connection1 != null){
                try {
                    connection1.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (connection2 != null){
                try {
                    connection2.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

java7后try-with-resources方式

public class TestTryWithResource {

    public static void main(String[] args) {
        //java7新增了try-with-resources语法之后
        //在try()中需要给的是新定义的对象,要么new,要么重新赋值
        Connection connection1 = new Connection();
        try (Connection connection3 = connection1;Connection connection4 = new Connection()) {
            connection3.doSomething();
            connection4.doSomething();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

java9的try-with-resources增强

public class TestTryWithResource {

    public static void main(String[] args) {
        //java9增强了可以直接使用现有对象
        Connection connection1 = new Connection();
        Connection connection2 = new Connection();
        try (connection1; connection2) {
            connection1.doSomething();
            connection2.doSomething();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三个测试例子输出的都是:

打开连接成功。。。
打开连接成功。。。
关闭连接成功。。。
关闭连接成功。。。

五、接口私有方法

java9在继承java8的默认方法、静态方法之后,现在还支持在接口定义私有方法

interface InterFaceTest{
	//方便内部使用
    private void privateMethod(){
        //doSomething
    }
}

六、其它

  • _将不能作为标识符

  • 增加了集合类型ListSetMap接口的静态方法,使得创建一个不可变的集合更加便利

    public static void main(String[] args) {
    	//注意:创建的这些集合都是不可变的
        List<Integer> list = List.of(1, 2, 3, 4, 5, 6);
        Set<Integer> set = Set.of(1, 2, 3, 4, 5, 6);
        Map<String, String> map = Map.of("k1", "v1", "k2", "v2", "k3", "v3");
    }
    
  • 改进@Deprecated注释,以提供有关规范中 API 的状态和预期处置的更好信息。添加了两个新元素:

    • @Deprecated(forRemoval=true)表示将在 Java SE 平台的未来发行版中删除该 API
    • @Deprecated(since="version")包含 Java SE 版本字符串,该字符串指示 API 元素何时被弃用,适用于 Java SE 9 及更高版本中已弃用的元素
  • proceshandle类提供进程的本机进程ID、参数、命令、启动时间、累计CPU时间、用户、父进程和后代进程

java10

官网直达链接

一、局部变量类型推断

java10开始,将可以使用如下的声明变量方式:

var list = new ArrayList<String>();  // infers ArrayList<String>
var stream = list.stream();          // infers Stream<String>
for (var i = 0; i < 10; i++) {
    System.out.println(i);			 // infers int
}

注意:这种处理将仅限于具有初始化器的局部变量、增强for循环中的索引以及在传统循环中声明的局部变量for;它不适用于方法形式、构造函数形式、方法返回类型、字段、捕获形式或任何其他类型的变量声明。

var关键字仅限于可推断出的局部变量,如下定义的方式是不允许:

Main.java:81: error: cannot infer type for local
variable x
        var x;
            ^
  (不能在没有初始化程序的变量上使用“var”)

Main.java:82: error: cannot infer type for local
variable f
        var f = () -> { };
            ^
  (lambda 表达式需要一个明确的目标类型) 

Main.java:83: error: cannot infer type for local
variable g
        var g = null;
            ^
  (无法从“null”中推断出类型)

Main.java:84: error: cannot infer type for local
variable c
        var c = l();
            ^
  (推断类型是不可表示的)

Main.java:195: error: cannot infer type for local variable m
        var m = this::l;
            ^
  (方法引用需要明确的目标类型)

Main.java:199: error: cannot infer type for local variable k
        var k = { 1 , 2 };
            ^
  (数组初始化器需要一个明确的目标类型)

官网原文链接

二、CDS应用类数据共享

改善启动和占用空间,扩展现有的类数据共享(“CDS”)功能以允许将应用程序类放置在共享存档中。

  • 通过在不同 Java 进程之间共享公共类元数据来减少占用空间
  • 扩展 CDS 以允许将 JDK 的运行时映像文件 ($JAVA_HOME/lib/modules) 中的归档类和应用程序类路径加载到内置平台和系统类加载器中
  • 扩展 CDS 以允许将归档类加载到自定义类加载器中

官网原文链接

三、G1并行Full GC

G1 垃圾收集器在 JDK 9 中成为默认设置。之前的默认设置是并行收集器,具有并行Full GC。为了尽量减少对经历Full GC的用户的影响,G1 Full GC 也应该并行。

G1 垃圾收集器旨在避免完全收集,但是当并发收集不能足够快地回收内存时,就会自动切换到并行收集,这可以减少在最坏情况下的 GC 速度。

四、JIT即时编译器

从 Linux/x64 平台开始,将 Graal 用作实验性 JIT 编译器。Graal 将使用 JDK 9 中引入的 JVM 编译器接口 (JVMCI)。Graal 已经在 JDK 中,因此将其作为实验性 JIT 启用将主要是测试和调试工作。

要启用 Graal 作为 JIT 编译器,请在命令行上使用以下选项:

-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

五、其它

  • Optional类添加了orElseThrow()方法,是替换get()方法的首选

  • Collector类新增了三个创建不可变集合的方法(都不允许有空值)

    • toUnmodifiableList()
    • toUnmodifiableSet()
    • toUnmodifiableMap()
  • 增强了for循环的字节码生成

    在 for 循环之外声明迭代器变量允许在不再使用时为其分配空值。这使得 GC 可以访问它,然后可以摆脱未使用的内存

  • javah工具已删除,其功能被 javac 中的高级功能所取代

java11

官网直达链接

一、基于嵌套的访问控制

通常,嵌套内部类可以看作与外部类为一个整体,只是由于封装、独立化具体类及其功能的原因才分为两个类,但在逻辑上往往可以看作一个整体。在内部嵌套类中访问外部的私有成员是被允许的,如下:

public class Outer {
    private final int outerI = 10;

    private void method1(){
        System.out.println("Outer::method1()");
    }

    class Inner {
        public void invokeMethod2(){
            System.out.println(outerI);
            method1();
            this.method2();
        }
    }
}

我们能够通过常规方式访问是因为,在编译时,编译器为我们默认创建了桥接方法,我们是通过桥接方法去访问我们真正需要用到的私有成员。

但是,在反射调用时,就会抛出IllegalAccessException异常,在java11中,新增了几个字节码指令来调用此类方法,详情见官网

在class类中,增加了三个用于获取、判断嵌套外部类的方法:

  • Class.getNestHost() 获取此Class的嵌套宿主类,非嵌套内部类则返回自己本身
  • Class.isNestmateOf(Class<?> c) 如果两个类或接口具有相同的嵌套主机,则它们是嵌套对象
  • Class.getNestMembers() 从顶层获取嵌套类的数组,数组包括自己本身

使用例子:

public static void main(String[] args) {
    Class<Outer> outerClass = Outer.class;
    Class<?> outerClassNestHost = outerClass.getNestHost();
    System.out.println(outerClassNestHost.getSimpleName());
    Class<Outer.Inner> innerClass = Outer.Inner.class;
    Class<?> innerClassNestHost = innerClass.getNestHost();
    Class<?>[] innerClassNestMembers = innerClass.getNestMembers();
    System.out.println(innerClassNestHost.getSimpleName());
    System.out.println(innerClass.isNestmateOf(outerClass));
    for (Class<?> innerClassNestMember : innerClassNestMembers) {
        System.out.println(innerClassNestMember.getSimpleName());
    }
}

//输出
Outer
Outer
true
Outer
Inner
public class Outer {
    public Outer() {}

    private void method1(){
        System.out.println("Outer::method1()");
    }

    class Inner {
        public Inner() {}

        public void invokeMethod2(){
            this.method2();
        }

        private void method2(){
            System.out.println("Inner::method2()");
            Class<Outer> outerClass = Outer.class;
            try {
                //反射调用外部类的私有方法
                Method method = outerClass.getDeclaredMethod("method1");
                method.invoke(outerClass.getConstructor().newInstance());
            } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

//java11之前的输出
Inner::method2()
java.lang.IllegalAccessException: class Outer$Inner cannot access a member of classOuter with modifiers "private"
    
//java11输出
Inner::method2()
Outer::method1()

二、标准化的HTTP客户端

HTTP客户端已在 Java11中标准化。作为这项工作的一部分,位于jdk.incubator.http包中的先前孵化的 API 已被删除,标准化的HTTP客户端在java.net.http库下。

基本使用示例:

public static void main(String[] args) {
    HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI("localhost:8080/hello")).GET().build();
    HttpClient httpClient = HttpClient.newHttpClient();
    HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
    System.out.println(response.body());
}

//输出
Hello world

异步请求:

public static void main(String[] args) {
    HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI("localhost:8080/hello")).GET().build();
    HttpClient httpClient = HttpClient.newHttpClient();
    CompletableFuture<HttpResponse<String>> completableFuture = httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString());
    completableFuture.thenApply(HttpResponse::body).thenAccept(System.out::println);
    System.out.println("请求完成");
}

//输出
请求完成
Hello world

三、ZGC(实验性)

官网原文

Z 垃圾收集器,也称为 ZGC,是一种可扩展的低延迟垃圾收集器。

ZGC的主要目标是:

  • GC 暂停时间不应超过 10ms
  • 处理大小从相对较小(几百兆字节)到非常大(兆兆字节)的堆
  • 与使用 G1 相比,应用程序吞吐量减少不超过 15%
  • 利用彩色指针和负载屏障为未来的 GC 功能和优化奠定基础

四、其它

  • 允许var在声明隐式类型 lambda 表达式的形式参数时使用

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.stream().forEach((var i) -> {
            System.out.println(i);
        });
    }
    
  • 升级现有平台 API 以支持Unicode 标准10.0

  • 新的集合默认方法Collection.toArray(IntFunction),使得集合转数组更加方便

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.toArray(Integer[]::new);
    }
    
  • 字符串API增强

    public static void main(String[] args) {
        String str = "   ";
        //trim()之后是否为空
        System.out.println(str.isBlank());
        
        String str1 = "  1 5  6 a ";
        //删除字符串的前后空白字符
        System.out.println(str1.strip());
        
        String str2 = "   qaq 456   qwq  ";
        //删除字符串的前置空白字符
        System.out.println(str2.stripLeading());
        
        String str3 = " qwq 123   ";
        //删除字符串的后置空白字符
        System.out.println(str3.stripTrailing());
        
        String str4 = "qwq\n" +
                "qaq";
        //获取字符串的行流,根据换行\n,回车\r等切割
        Stream<String> lines = str4.lines();
        lines.forEach(System.out::println);
        
        String str5 = "qaq";
        //使字符串重复几次
        System.out.println(str5.repeat(3));
    }
    
    //输出
    true
    1 5  6 a
    qaq 456   qwq  
     qwq 123
    qwq
    qaq
    qaqqaqqaq
    

java12

官网直达链接

一、switch增强(预览版)

由于还是预览版功能,所以想要使用 Java 12 去编译运行就需要打开功能预览参数:

# 编译时
javac --enable-preview -source 12 ./Xxx.java
# 运行时
java --enable-preview Xxx

在下面的代码中,许多break语句使其不必要地冗长,并且这种视觉噪音通常掩盖了难以调试的错误,其中缺少break语句意味着发生意外的失败

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;
}

在java12,上方例子可以改写成如下格式:

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);
}

需要给局部变量赋值的情况

int numLetters;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        numLetters = 6;
        break;
    case TUESDAY:
        numLetters = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        numLetters = 8;
        break;
    case WEDNESDAY:
        numLetters = 9;
        break;
    default:
        throw new IllegalStateException("Wat: " + day);
}

在java12可以改写为:

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};

代码块格式

int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {
        int k = day.toString().length();
        int result = f(k);
        break result;
    }
};

二、紧凑数字格式

NumberFormat的支持增加了对紧凑形式数字格式的支持。紧凑数字格式是指以短的或人类可读的形式表示数字。如:10000在中文格式可以表示为1万,在英文格式中可以表示为10k。

public static void main(String[] args) {
    //中文格式化
    NumberFormat chineseFormat = NumberFormat.getCompactNumberInstance(Locale.CHINESE, NumberFormat.Style.SHORT);
    System.out.println(chineseFormat.format(1234));
    System.out.println(chineseFormat.format(12345));
    System.out.println(chineseFormat.format(123456));
    System.out.println(chineseFormat.format(123456789));

    //小数格式
    chineseFormat.setMaximumFractionDigits(2);
    System.out.println(chineseFormat.format(1234));
    System.out.println(chineseFormat.format(12345));
    System.out.println(chineseFormat.format(123456));
    System.out.println(chineseFormat.format(123456789));

    //英文格式化
    NumberFormat englishFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT);
    System.out.println(englishFormat.format(1234));
    System.out.println(englishFormat.format(12345));
    System.out.println(englishFormat.format(123456));
    System.out.println(englishFormat.format(123456789));

    //小数格式
    chineseFormat.setMaximumFractionDigits(2);
    System.out.println(englishFormat.format(1234));
    System.out.println(englishFormat.format(12345));
    System.out.println(englishFormat.format(123456));
    System.out.println(englishFormat.format(123456789));
}

//输出
1,234
1121亿
1,234
1.2312.351.23亿
1K
12K
123K
123M
1K
12K
123K
123M

三、常量api

在新包java.lang.constant中引入了一个 API 来模拟类文件和运行时工件的名义描述,特别是可从常量池加载的常量。它通过定义一系列基于值的符号引用 (JVMS 5.1) 类型来实现这一点,能够描述每种可加载常量。符号引用以纯名义形式描述可加载常量,与类加载或可访问性上下文分开。一些类可以充当它们自己的符号引用(例如,String);对于可链接常量,添加了一系列符号引用类型(ClassDescMethodTypeDescMethodHandleDescDynamicConstantDesc),其中包含用于描述这些常量的标称信息。

四、Shenandoah垃圾收集器(实验性)

Shenandoah:一种低暂停时间的垃圾收集器。

添加一个名为Shenandoah的新垃圾收集 (GC) 算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah的暂停时间与堆大小无关,这意味着无论您的堆是 200 MB 还是 200 GB,您都将拥有相同的一致暂停时间。

五、默认CDS档案

增强 JDK 构建过程以在 64 位平台上使用默认类列表生成类数据共享 (CDS) 存档。

  • 改善开箱即用的启动时间
  • 无需用户运行-Xshare:dump即可从 CDS 中受益

六、其它

  • java12支持Unicode 11

  • 增强 G1 垃圾收集器在空闲时自动将 Java 堆内存返回给操作系统。

    为了实现向操作系统返回最大内存量的目标,G1 将在应用程序不活动期间定期尝试继续或触发并发循环以确定整体 Java 堆使用情况。这将导致它自动将 Java 堆中未使用的部分返回给操作系统。可选地,在用户控制下,可以执行完整的 GC 以最大化返回的内存量。

  • G1垃圾收集器的可中止混合集合

    如果 G1 混合收集可能超过暂停目标,则使它们可中止

    G1 的目标之一是满足用户为其收集暂停提供的暂停时间目标。G1 使用高级分析引擎来选择收集期间要完成的工作量(这部分基于应用程序行为)。此选择的结果是一组称为集合集的区域. 一旦确定了收集集并开始收集,那么 G1 必须不停地收集收集集的所有区域中的所有活动对象。如果启发式选择过大的集合集,则此行为可能导致 G1 超过暂停时间目标,例如,如果应用程序的行为发生变化,从而启发式处理“陈旧”数据时,就会发生这种情况。这尤其可以在混合收集期间观察到,其中收集集通常可能包含太多旧区域。需要一种机制来检测启发式何时重复为收集选择错误的工作量,如果是这样,让 G1 逐步执行收集工作,在每个步骤之后可以中止收集。这样的机制将允许 G1 更频繁地满足暂停时间目标。

java13

官网直达链接

一、switch更改(预览版)

switch语句增加在java12中已经增加了预览版,后来就使用表达式和增强语句的体验寻求反馈。基于该反馈,此 JEP 对该功能进行了一项更改:

  • switch中的返回值,break语句被删除,使用yield关键字替换

case右侧字句代码块中使用yield返回:

int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {
        int k = day.toString().length();
        int result = f(k);
        //使用yield代替java12中的break返回
        yield result;
    }
};

传统格式使用yield返回:

int result = switch (s) {
    case "Foo": 
        yield 1;
    case "Bar":
        yield 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        yield 0;
};

二、字符串文本块(预览版)

在 Java 中,在字符串文字中嵌入 HTML、XML、SQL 或 JSON 的片段通常需要使用转义和连接进行大量编辑,然后才能编译包含该片段的代码,该片段通常难以阅读且难以维护。

因此,如果有一种语言机制来表示字符串,而不是字符串文字——跨越多行且没有转义的视觉混乱,这将提高一大类 Java 程序的可读性和可写性。本质上是一个二维的文本块,而不是一维的字符序列。

在java13中引入了"""方式来定义一个文本块:

html字符串示例

//传统模式
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

//java13
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

sql示例

//传统模式
String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
               "WHERE `CITY` = 'INDIANAPOLIS'\n" +
               "ORDER BY `EMP_ID`, `LAST_NAME`;\n";

//java13
String query = """
               SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
               WHERE `CITY` = 'INDIANAPOLIS'
               ORDER BY `EMP_ID`, `LAST_NAME`;
               """;

注意事项:

  • 与字符串文字中的字符不同,内容可能直接包含行终止符。\n允许在文本块中使用,但不是必需的或不推荐使用

  • 如果字符串末尾不需要行终止符,则可以将结束分隔符放在内容的最后一行

    String str = """
    			 line 1
    		     line 2
                 line 3"""
    
  • 文本块可以表示空字符串,但不建议这样做,因为它需要两行源代码

    String empty = """
    """;
    
  • 以下是一些格式错误的文本块的示例:

    String a = """""";   // 打开分隔符后没有行终止符
    String b = """ """;  // 打开分隔符后没有行终止符
    String c = """
               ";        // 没有结束分隔符
    String d = """
               abc \ def
               """;      // 未转义的反斜杠
    

更多见JEP355

三、动态CDS档案

官网原文

在java10中,已经增加了 AppCDS 功能,在java13中扩展了这个功能:以允许在 Java 应用程序执行结束时对类进行动态归档。归档的类将包括默认的基础层 CDS 归档中不存在的所有加载的应用程序类和库类。

在 HotSpot 中使用 AppCDS 归档应用程序类相对于 默认 CDS 归档 提供了额外的启动时间和内存优势。但是,目前需要三个步骤才能将 AppCDS 用于 Java 应用程序:

  1. 进行一次或多次试运行以创建Class列表
  2. 使用创建的类列表转储档案
  3. 使用存档运行

在java13中,我们可以在应用程序退出时动态创建共享存档:

java -XX:ArchiveClassesAtExit=<archive filenName> -cp app.jar appName

要使用此动态存档运行相同的应用程序:

java -XX:SharedArchiveFile=<archive filenName> -cp app.jar appName

四、ZGC:取消提交未使用的内存

增强 ZGC 以将未使用的堆内存返回给操作系统。

在java11引入ZGC之后, 目前不会取消提交并将内存返回给操作系统,即使该内存已很长时间未使用。对于所有类型的应用程序和环境,这种行为并不是最佳的,尤其是那些关注内存占用的应用程序和环境。

但在java13开始,ZGC现在默认将未提交的内存返回给操作系统,直到达到指定的最小堆大小。

如果想回到之前的模式请使用如下参数:

  • -XX:-ZUncommit 禁用此功能
  • 设置堆内存最小(-Xms)与最大值(-Xmx)保持一致

五、其它

  • 支持Unicode 12.1

  • java.nio.file.FileSystems.newFileSystem()新方法

    以便更轻松地使用将文件内容视为文件系统的文件系统提供程序