目录

Java 版本特性

目录

jdk8

  • 速度更快

HashMap,ConcurrentHashMap特性改变(链表+红黑树等) –详细看HashMap部分 jvm永久区去掉,MetaSpace 元空间,使用物理内存

  • 代码更少(增加了新的语法 Lambda 表达式)
  • 强大的 Stream API
  • 并行
  • 最大化减少空指针异常 Optional
  • 接口中的默认方法和静态方法
  • 新时间日期
  • 重复注解与类型注解

Lambda表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

原来的写法与lambda表达式对比

1.匿名内部类

//原来的匿名内部类
@Test
public void test1(){
    Comparator<String> com = new Comparator<String>(){
        @Override
        public int compare(String o1, String o2) {
            return Integer.compare(o1.length(), o2.length());
        }
    };
    
    TreeSet<String> ts = new TreeSet<>(com);
    
    TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>(){
        @Override
        public int compare(String o1, String o2) {
            return Integer.compare(o1.length(), o2.length());
        }
        
    });
}

//现在的 Lambda 表达式
@Test
public void test2(){
    Comparator<String> com = (x, y) -> Integer.compare(x.length(), y.length());
    TreeSet<String> ts = new TreeSet<>(com);
}

2.集合操作

List<Employee> emps = Arrays.asList(
        new Employee(101, "张三", 18, 9999.99),
        new Employee(102, "李四", 59, 6666.66),
        new Employee(103, "王五", 28, 3333.33),
        new Employee(104, "赵六", 8, 7777.77),
        new Employee(105, "田七", 38, 5555.55)
);

//需求:获取公司中年龄小于 35 的员工信息
public List<Employee> filterEmployeeAge(List<Employee> emps){
    List<Employee> list = new ArrayList<>();
    
    for (Employee emp : emps) {
        if(emp.getAge() <= 35){
            list.add(emp);
        }
    }
    
    return list;
}

@Test
public void test3(){
    List<Employee> list = filterEmployeeAge(emps);
    
    for (Employee employee : list) {
        System.out.println(employee);
    }
}

//需求:获取公司中工资大于 5000 的员工信息
public List<Employee> filterEmployeeSalary(List<Employee> emps){
    List<Employee> list = new ArrayList<>();
    
    for (Employee emp : emps) {
        if(emp.getSalary() >= 5000){
            list.add(emp);
        }
    }
    
    return list;
}

//优化方式二:匿名内部类, 需写接口和实现类,具体看java-learning代码
@Test
public void test5(){
    List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
        @Override
        public boolean test(Employee t) {
            return t.getId() <= 103;
        }
    });
    
    for (Employee employee : list) {
        System.out.println(employee);
    }
}

//优化方式三:Lambda 表达式
@Test
public void test6(){
    List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35);
    list.forEach(System.out::println);
    
    System.out.println("------------------------------------------");
    
    List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
    list2.forEach(System.out::println);
}

//优化方式四:Stream API
@Test
public void test7(){
    emps.stream()
        .filter((e) -> e.getAge() <= 35)
        .forEach(System.out::println);
    
    System.out.println("----------------------------------------------");
    
    emps.stream()
        .map(Employee::getName)
        .limit(3)
        .sorted()
        .forEach(System.out::println);
}

lambda语法

  • Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符箭头操作符将 Lambda 表达式拆分成两部分, 其中:

左侧:ambda 表达式的参数列表 右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体

  • 语法格式
  1. 无参数,无返回值 () -> System.out.println("Hello Lambda!")
  2. 有1个参数,无返回值,有1个参数时,小括号可不写 (x) -> System.out.println(x)/ x-> System.out.println(x);
  3. 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {  
     System.out.println("函数式接口");  
     return Integer.compare(x, y);  
};
  1. 若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写 Comparator<Integer> com = (x, y) -> Integer.compare(x, y)
  2. Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断” (Integer x, Integer y) -> Integer.compare(x, y)
  • lambda表达式需要“函数式接口”的支持

函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。 可以使用注解 @FunctionalInterface 修饰,可以检查是否是函数式接口

public class TestLambda2 {
    
    @Test
    public void test1(){
        int num = 0;//jdk 1.7 前,必须是 final
        
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World!" + num);
            }
        };
        
        r.run();
        
        System.out.println("-------------------------------");
        
        Runnable r1 = () -> System.out.println("Hello Lambda!");
        r1.run();
    }
    
    @Test
    public void test2(){
        Consumer<String> con = x -> System.out.println(x);
        con.accept("我和我的祖国!");
    }
    
    @Test
    public void test3(){
        Comparator<Integer> com = (x, y) -> {
            System.out.println("测试多行代码");
            return Integer.compare(x, y);
        };
    }
    
    @Test
    public void test4(){
        Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
    }

    //类型抢断示例
    @Test
    public void test5(){
        String[] strs = {"aaa", "bbb", "ccc"};
        
        List<String> list = new ArrayList<>();
        
        show(new HashMap<>());
    }

    public void show(Map<String, Integer> map){
        
    }
    
    //需求:对一个数进行运算
    @Test
    public void test6(){
        Integer num = operation(100, (x) -> x * x);
        System.out.println(num);
        
        System.out.println(operation(200, (y) -> y + 200));
    }
    
    public Integer operation(Integer num, Function<Integer, Integer> function){
        return function.apply(num);
    }
}

java8内置的四大核心函数式接口

1.Consumer : 消费型接口
void accept(T t)
2.Supplier : 供给型接口
T get()
3.Function<T, R> : 函数型接口
R apply(T t)
4.Predicate : 断言型接口
boolean test(T t)

public class TestLambda3 {
    
    //Predicate<T> 断言型接口:
    @Test
    public void test4(){
        List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
        List<String> strList = filterStr(list, (s) -> s.length() > 3);
        
        for (String str : strList) {
            System.out.println(str);
        }
    }
    
    //需求:将满足条件的字符串,放入集合中
    public List<String> filterStr(List<String> list, Predicate<String> pre){
        List<String> strList = new ArrayList<>();
        
        for (String str : list) {
            if(pre.test(str)){
                strList.add(str);
            }
        }
        
        return strList;
    }
    
    //Function<T, R> 函数型接口:
    @Test
    public void test3(){
        String newStr = strHandler("\t\t\t 我爱我的祖国   ", (str) -> str.trim());
        System.out.println(newStr);
        
        String subStr = strHandler("我爱我的祖国", (str) -> str.substring(2, 5));
        System.out.println(subStr);
    }
    
    //需求:用于处理字符串
    public String strHandler(String str, Function<String, String> fun){
        return fun.apply(str);
    }
    
    //Supplier<T> 供给型接口 :
    @Test
    public void test2(){
        List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
        
        for (Integer num : numList) {
            System.out.println(num);
        }
    }
    
    //需求:产生指定个数的整数,并放入集合中
    public List<Integer> getNumList(int num, Supplier<Integer> sup){
        List<Integer> list = new ArrayList<>();
        
        for (int i = 0; i < num; i++) {
            Integer n = sup.get();
            list.add(n);
        }
        
        return list;
    }
    
    //Consumer<T> 消费型接口 :
    @Test
    public void test1(){
        happy(10000, (m) -> System.out.println("买西瓜花了:" + m + "元"));
    } 
    
    public void happy(double money, Consumer<Double> con){
        con.accept(money);
    }
}

5.其他接口

函数式接口 参数类型 返回类型 用途
BiFunction<T,U,R> T,U R 对类型为T,U参数应用操作,返回R类型的结果。包含方法为R apply(T t,U u);
UnaryOperator(Function子接口) T T 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为T apply(T t);
BinaryOperator(BiFunction子接口) T,T T 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为T apply(T t1,T t2);
BiConsumer<T,U> T,U void 对类型为T,U参数应用操作。包含方法为void accept(T t,U u);
ToIntFunction ToLongFunction ToDoubleFunction T int,long,double 分别计算int、long、double、值的函数
IntFunction LongFunction DoubleFunction int,long,double R 参数分别为int、long、double类型的函数

方法与构造器引用

方法引用

若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用可以将方法引用理解为 Lambda 表达式的另外一种表现形式)

对象的引用 :: 实例方法名
类名 :: 静态方法名
类名 :: 实例方法名
注意:

①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName

 //类名 :: 实例方法名
@Test
public void test5() {
    BiPredicate<String, String> bp = (x, y) -> x.equals(y);
    System.out.println(bp.test("abcde", "abcde"));

    System.out.println("-----------------------------------------");

    BiPredicate<String, String> bp2 = String::equals;
    System.out.println(bp2.test("abc", "abc"));

    System.out.println("-----------------------------------------");


    Function<Employee, String> fun = (e) -> e.show();
    System.out.println(fun.apply(new Employee()));

    System.out.println("-----------------------------------------");

    Function<Employee, String> fun2 = Employee::show;
    System.out.println(fun2.apply(new Employee()));

}

//类名 :: 静态方法名
@Test
public void test4() {
    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
    System.out.println(com.compare(1, 2));

    System.out.println("-------------------------------------");

    Comparator<Integer> com2 = Integer::compare;
    System.out.println(com.compare(2, 2));
}

@Test
public void test3() {
    BiFunction<Double, Double, Double> fun = (x, y) -> Math.max(x, y);
    System.out.println(fun.apply(1.5, 22.2));

    System.out.println("--------------------------------------------------");

    BiFunction<Double, Double, Double> fun2 = Math::max;
    System.out.println(fun2.apply(1.2, 1.5));
}

//对象的引用 :: 实例方法名
@Test
public void test2() {
    Employee emp = new Employee(101, "张三", 18, 9999.99);

    Supplier<String> sup = () -> emp.getName();
    System.out.println(sup.get());

    System.out.println("----------------------------------");

    Supplier<String> sup2 = emp::getName;
    System.out.println(sup2.get());
}

@Test
public void test1() {
    PrintStream ps = System.out;
    Consumer<String> con = (str) -> ps.println(str);
    con.accept("Hello World!");

    System.out.println("--------------------------------");

    Consumer<String> con2 = ps::println;
    con2.accept("Hello Java8!");

    Consumer<String> con3 = System.out::println;
}

构造器引用

构造器的参数列表,需要与函数式接口中参数列表保持一致

类名::new

//构造器引用
@Test
public void test7() {
    Function<String, Employee> fun = Employee::new;

    BiFunction<String, Integer, Employee> fun2 = Employee::new;
}

@Test
public void test6() {
    Supplier<Employee> sup = () -> new Employee();
    System.out.println(sup.get());

    System.out.println("------------------------------------");

    Supplier<Employee> sup2 = Employee::new;
    System.out.println(sup2.get());
}

数组引用

类型[] :: new

//数组引用
@Test
public void test8() {
    Function<Integer, String[]> fun = (args) -> new String[args];
    String[] strs = fun.apply(10);
    System.out.println(strs.length);

    System.out.println("--------------------------");

    Function<Integer, Employee[]> fun2 = Employee[]::new;
    Employee[] emps = fun2.apply(20);
    System.out.println(emps.length);
}

Stream

流(Stream) 到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”

注意:
Stream 自己不会存储元素
Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

创建stream

@Test
public void test1(){
    //1. Collection 提供了两个方法  stream() 与 parallelStream()
    List<String> list = new ArrayList<>();
    Stream<String> stream = list.stream(); //获取一个顺序流
    Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
    
    //2. 通过 Arrays 中的 stream() 获取一个数组流
    Integer[] nums = new Integer[10];
    Stream<Integer> stream1 = Arrays.stream(nums);
    
    //3. 通过 Stream 类中静态方法 of()
    Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
    
    //4. 创建无限流
    //迭代
    Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
    stream3.forEach(System.out::println);
    
    //生成
    Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
    stream4.forEach(System.out::println);
    
}

中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”

方法 描述
filter(Predicate p) 接收Lambda ,从流中排除某些元素
distinct() 筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n) 互补
map(Functionf) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparatorcomp) 产生一个新流,其中按比较器顺序排序
public class TestStreamaAPI {
    
    //1. 创建 Stream
    @Test
    public void test1(){
        //1. Collection 提供了两个方法  stream() 与 parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream(); //获取一个顺序流
        Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
        
        //2. 通过 Arrays 中的 stream() 获取一个数组流
        Integer[] nums = new Integer[10];
        Stream<Integer> stream1 = Arrays.stream(nums);
        
        //3. 通过 Stream 类中静态方法 of()
        Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
        
        //4. 创建无限流
        //迭代
        Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
        stream3.forEach(System.out::println);
        
        //生成
        Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
        stream4.forEach(System.out::println);
        
    }
    
    //2. 中间操作
    List<Employee> emps = Arrays.asList(
            new Employee(102, "李四", 59, 6666.66),
            new Employee(101, "张三", 18, 9999.99),
            new Employee(103, "王五", 28, 3333.33),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(104, "赵六", 8, 7777.77),
            new Employee(105, "田七", 38, 5555.55)
    );
    
    /*
      筛选与切片
        filter——接收 Lambda , 从流中排除某些元素。
        limit——截断流,使其元素不超过给定数量。
        skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
        distinct——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
     */
    
    //内部迭代:迭代操作 Stream API 内部完成
    @Test
    public void test2(){
        //所有的中间操作不会做任何的处理
        Stream<Employee> stream = emps.stream()
            .filter((e) -> {
                System.out.println("测试中间操作");
                return e.getAge() <= 35;
            });
        
        //只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
        stream.forEach(System.out::println);
    }
    
    //外部迭代
    @Test
    public void test3(){
        Iterator<Employee> it = emps.iterator();
        
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
    
    @Test
    public void test4(){
        emps.stream()
            .filter((e) -> {
                System.out.println("短路!"); // &&  ||
                return e.getSalary() >= 5000;
            }).limit(3)
            .forEach(System.out::println);
    }
    
    @Test
    public void test5(){
        emps.parallelStream()
            .filter((e) -> e.getSalary() >= 5000)
            .skip(2)
            .forEach(System.out::println);
    }
    
    @Test
    public void test6(){
        emps.stream()
            .distinct()
            .forEach(System.out::println);
    }
    
    /*
        映射
        map——接收 Lambda , 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
        flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
     */
    @Test
    public void test1(){
        Stream<String> str = emps.stream()
            .map((e) -> e.getName());
        
        System.out.println("-------------------------------------------");
        
        List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        
        Stream<String> stream = strList.stream()
               .map(String::toUpperCase);
        
        stream.forEach(System.out::println);
        
        Stream<Stream<Character>> stream2 = strList.stream()
               .map(TestStreamAPI1::filterCharacter);
        
        stream2.forEach((sm) -> {
            sm.forEach(System.out::println);
        });
        
        System.out.println("---------------------------------------------");
        
        Stream<Character> stream3 = strList.stream()
               .flatMap(TestStreamAPI1::filterCharacter);
        
        stream3.forEach(System.out::println);
    }

    public static Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        
        for (Character ch : str.toCharArray()) {
            list.add(ch);
        }
        
        return list.stream();
    }
    
    /*
        sorted()——自然排序
        sorted(Comparator com)——定制排序
     */
    @Test
    public void test2(){
        emps.stream()
            .map(Employee::getName)
            .sorted()
            .forEach(System.out::println);
        
        System.out.println("------------------------------------");
        
        emps.stream()
            .sorted((x, y) -> {
                if(x.getAge() == y.getAge()){
                    return x.getName().compareTo(y.getName());
                }else{
                    return Integer.compare(x.getAge(), y.getAge());
                }
            }).forEach(System.out::println);
    }

}

终止操作

终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void

查找与匹配
方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicatep) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparatorc) 返回流中最大值
min(Comparatorc) 返回流中最小值
forEach(Consumerc) 内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
public class TestStreamAPI2 {
    
    List<Employee> emps = Arrays.asList(
            new Employee(102, "李四", 59, 6666.66, Employee.Status.BUSY),
            new Employee(101, "张三", 18, 9999.99, Employee.Status.FREE),
            new Employee(103, "王五", 28, 3333.33, Employee.Status.VOCATION),
            new Employee(104, "赵六", 8, 7777.77, Employee.Status.BUSY),
            new Employee(104, "赵六", 8, 7777.77, Employee.Status.FREE),
            new Employee(104, "赵七", 8, 7777.77, Employee.Status.FREE),
            new Employee(105, "田七", 38, 5555.55, Employee.Status.BUSY)
    );
    
    //3. 终止操作
    /*
        allMatch——检查是否匹配所有元素
        anyMatch——检查是否至少匹配一个元素
        noneMatch——检查是否没有匹配的元素
        findFirst——返回第一个元素
        findAny——返回当前流中的任意元素
        count——返回流中元素的总个数
        max——返回流中最大值
        min——返回流中最小值
     */
    @Test
    public void test1(){
            boolean bl = emps.stream()
                .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
            
            System.out.println(bl);
            
            boolean bl1 = emps.stream()
                .anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
            
            System.out.println(bl1);
            
            boolean bl2 = emps.stream()
                .noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
            
            System.out.println(bl2);
    }
    
    @Test
    public void test2(){
        Optional<Employee> op = emps.stream()
            .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
            .findFirst();//有可能为空即封装到Optional里
        
        System.out.println(op.get());
        
        System.out.println("--------------------------------");
        
        Optional<Employee> op2 = emps.parallelStream()
            .filter((e) -> e.getStatus().equals(Employee.Status.FREE))
            .findAny();
        
        System.out.println(op2.get());
    }
    
    @Test
    public void test3(){
        long count = emps.stream()
                         .filter((e) -> e.getStatus().equals(Employee.Status.FREE))
                         .count();
        
        System.out.println(count);
        
        Optional<Double> op = emps.stream()
            .map(Employee::getSalary)
            .max(Double::compare);
        
        System.out.println(op.get());
        
        Optional<Employee> op2 = emps.stream()
            .min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        
        System.out.println(op2.get());
    }
    
    //注意:流进行了终止操作后,不能再次使用
    @Test
    public void test4(){
        Stream<Employee> stream = emps.stream()
         .filter((e) -> e.getStatus().equals(Employee.Status.FREE));
        
        long count = stream.count();
        
        stream.map(Employee::getSalary)
            .max(Double::compare);
    }
}
归约
方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回Optional

备注:map 和reduce 的连接通常称为map-reduce 模式,因Google 用它来进行网络搜索而出名。

/*
    归约
    reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。
 */
@Test
public void test1(){
    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    
    Integer sum = list.stream()
        .reduce(0, (x, y) -> x + y);
    
    System.out.println(sum);
    
    System.out.println("----------------------------------------");
    
    Optional<Double> op = emps.stream()
        .map(Employee::getSalary)
        .reduce(Double::sum);
    
    System.out.println(op.get());
}

//需求:搜索名字中 “六” 出现的次数
@Test
public void test2(){
    Optional<Integer> sum = emps.stream()
        .map(Employee::getName)
        .flatMap(TestStreamAPI1::filterCharacter)
        .map((ch) -> {
            if(ch.equals('六'))
                return 1;
            else 
                return 0;
        }).reduce(Integer::sum);
    
    System.out.println(sum.get());
}
收集
方法 描述
collect(Collector c) 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法 返回类型 作用
toList List 把流中元素收集到List
示例 Listemps=list.stream().collect(Collectors.toList());
toSet Set 把流中元素收集到Set
示例 Setemps=list.stream().collect(Collectors.toSet());
toCollection Collection 把流中元素收集到创建的集合
示例 Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 计算流中元素的个数
示例 longcount=list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数属性求和
示例 inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double 计算流中元素Integer属性的平均值
示例 doubleavg=list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。如:平均值
示例 IntSummaryStatisticsiss=list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 连接流中每个字符串
示例 Stringstr=list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional 根据比较器选择最大值
示例 Optionalmax=list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional 根据比较器选择最小值
示例 Optionalmin=list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing 归约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
示例 inttotal=list.stream().collect(Collectors.reducing(0,Employee::getSalar,Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果转换函数
示例 inthow=list.stream().collect(Collectors.collectingAndThen(Collectors.toList(),List::size));
groupingBy Map<K,List> 根据某属性值对流分组,属性为K,结果为V
示例 Map<Emp.Status, List> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean,List> 根据true或false进行分区
示例 Map<Boolean,List>vd=list.stream().collect(Collectors.partitioningBy(Employee::getManage));
//collect——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
@Test
public void test3(){
    List<String> list = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toList());
    
    list.forEach(System.out::println);
    
    System.out.println("----------------------------------");
    
    Set<String> set = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toSet());
    
    set.forEach(System.out::println);

    System.out.println("----------------------------------");
    
    HashSet<String> hs = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toCollection(HashSet::new));
    
    hs.forEach(System.out::println);
}

@Test
public void test4(){
    Optional<Double> max = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.maxBy(Double::compare));
    
    System.out.println(max.get());
    
    Optional<Employee> op = emps.stream()
        .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
    
    System.out.println(op.get());
    
    Double sum = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));
    
    System.out.println(sum);
    
    Double avg = emps.stream()
        .collect(Collectors.averagingDouble(Employee::getSalary));
    
    System.out.println(avg);
    
    Long count = emps.stream()
        .collect(Collectors.counting());
    
    System.out.println(count);
    
    System.out.println("--------------------------------------------");
    
    DoubleSummaryStatistics dss = emps.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    
    System.out.println(dss.getMax());
}

//分组
@Test
public void test5(){
    Map<Employee.Status, List<Employee>> map = emps.stream()
        .collect(Collectors.groupingBy(Employee::getStatus));
    
    System.out.println(map);
}

//多级分组
@Test
public void test6(){
    Map<Employee.Status, Map<String, List<Employee>>> map = emps.stream()
        .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
            if(e.getAge() >= 60)
                return "老年";
            else if(e.getAge() >= 35)
                return "中年";
            else
                return "成年";
        })));
    
    System.out.println(map);
}

//分区
@Test
public void test7(){
    Map<Boolean, List<Employee>> map = emps.stream()
        .collect(Collectors.partitioningBy((e) -> e.getSalary() >= 5000));
    
    System.out.println(map);
}

//
@Test
public void test8(){
    String str = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.joining("," , "----", "----"));
    
    System.out.println(str);
}

@Test
public void test9(){
    Optional<Double> sum = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.reducing(Double::sum));
    
    System.out.println(sum.get());
}

并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换

//求和
@Test
public void test3() {
    long start = System.currentTimeMillis();

    Long sum = LongStream.rangeClosed(0L, 10000000000L)
            .parallel() //切换并行
            .sum();

    System.out.println(sum);

    long end = System.currentTimeMillis();

    System.out.println("耗费的时间为: " + (end - start)); 
}

Optional

容器类,用于尽量避免空指针异常

  • Optional.of(T t) : 创建一个 Optional 实例
  • Optional.empty() : 创建一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
  • isPresent() : 判断是否包含值
  • orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
  • orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
  • map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
  • flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
//测试类1
public class NewMan {

    private Optional<Godness> godness = Optional.empty();

    private Godness god;

    public Optional<Godness> getGod() {
        return Optional.of(god);
    }

    public NewMan() {
    }

    public NewMan(Optional<Godness> godness) {
        this.godness = godness;
    }

    public Optional<Godness> getGodness() {
        return godness;
    }

    public void setGodness(Optional<Godness> godness) {
        this.godness = godness;
    }

    @Override
    public String toString() {
        return "NewMan [godness=" + godness + "]";
    }

}
//测试类2
public class NewMan {

    private Optional<Godness> godness = Optional.empty();

    private Godness god;

    public Optional<Godness> getGod() {
        return Optional.of(god);
    }

    public NewMan() {
    }

    public NewMan(Optional<Godness> godness) {
        this.godness = godness;
    }

    public Optional<Godness> getGodness() {
        return godness;
    }

    public void setGodness(Optional<Godness> godness) {
        this.godness = godness;
    }

    @Override
    public String toString() {
        return "NewMan [godness=" + godness + "]";
    }

}
//测试3
public class Godness {

    private String name;

    public Godness() {
    }

    public Godness(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Godness [name=" + name + "]";
    }

}
//Optional示例
public class TestOptional {

    @Test
    public void test4() {
        Optional<Employee> op = Optional.of(new Employee(101, "张三", 18, 9999.99));

        Optional<String> op2 = op.map(Employee::getName);
        System.out.println(op2.get());

        Optional<String> op3 = op.flatMap((e) -> Optional.of(e.getName()));
        System.out.println(op3.get());
    }

    @Test
    public void test3() {
        Optional<Employee> op = Optional.ofNullable(new Employee());

        if (op.isPresent()) {//判断是否有值
            System.out.println(op.get());
        }

        Employee emp = op.orElse(new Employee("张三"));
        System.out.println(emp);

        Employee emp2 = op.orElseGet(() -> new Employee());
        System.out.println(emp2);
    }

    @Test
    public void test2() {
        Optional<Employee> op = Optional.ofNullable(new Employee());//of 和 empty的综合
        System.out.println(op.get());

        Optional<Employee> op2 = Optional.empty();
        System.out.println(op2.get());
    }

    @Test
    public void test1() {
        Optional<Employee> op = Optional.of(new Employee());
        Employee emp = op.get();
        System.out.println(emp);
    }

    @Test
    public void test5() {
        Man man = new Man();

        String name = getGodnessName(man);
        System.out.println(name);
    }

    //需求:获取一个名字
    public String getGodnessName(Man man) {
        if (man != null) {
            Godness g = man.getGod();

            if (g != null) {
                return g.getName();
            }
        }

        return "赵小二";
    }

    //运用 Optional 的实体类
    @Test
    public void test6() {
        Optional<Godness> godness = Optional.ofNullable(new Godness("林志玲"));

        Optional<NewMan> op = Optional.ofNullable(new NewMan(godness));
        String name = getGodnessName2(op);
        System.out.println(name);
    }

    public String getGodnessName2(Optional<NewMan> man) {
        return man.orElse(new NewMan())
                .getGodness()
                .orElse(new Godness("赵小二"))
                .getName();
    }
}

接口中默认方法和静态方法

默认方法

Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰
接口默认方法的”类优先”原则:
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突

静态方法

Java8中,接口允许添加静态方法

新时间日期API

旧SimpleDateFormat的线程安全问题

  • 多个线程同时格式化,会抛异常
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<Date>() {
    @Override
    public Date call() throws Exception {
        return sdf.parse("20161121");
    }
    
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    results.add(pool.submit(task));
}
for (Future<Date> future : results) {
    System.out.println(future.get());
}
pool.shutdown();
  • 解决线程安全问题
Callable<Date> task = new Callable<Date>() {
    @Override
    public Date call() throws Exception {
        return DateFormatThreadLocal.convert("20161121");
    }
    
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    results.add(pool.submit(task));
}
for (Future<Date> future : results) {
    System.out.println(future.get());
}
pool.shutdown();

//其中DateFormatThreadLocal
public class DateFormatThreadLocal {

    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyyMMdd");
        }

    };

    public static final Date convert(String source) throws ParseException {
        return df.get().parse(source);
    }

}
  • LocalDate则不存在线程安全问题
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
Callable<LocalDate> task = new Callable<LocalDate>() {
    @Override
    public LocalDate call() throws Exception {
        LocalDate ld = LocalDate.parse("20161121", dtf);
        return ld;
    }
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<LocalDate>> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    results.add(pool.submit(task));
}
for (Future<LocalDate> future : results) {
    System.out.println(future.get());
}
pool.shutdown();

LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息

方法 描述 示例
now() 静态方法,根据当前时间创建对象 LocalDate localDate = LocalDate.now()
LocalTime localTime = LocalTime.now()
LocalDateTime localDateTime = LocalDateTime.now()
of() 静态方法,根据指定日期/时间创建对象 LocalDate localDate = LocalDate.of(2016, 10, 26)
LocalTime localTime = LocalTime.of(02, 22, 56)
LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55)
plusDays
plusWeeks
plusMonths
plusYears
向当前 LocalDate 对象添加几天、几周、几个月、几年
minusDays
minusWeeks
minusMonths
minusYears
从当前 LocalDate 对象减去几天、几周、几个月、几年
plus
minus
添加或减少一个 Duration 或 Period
withDayOfMonth
withDayOfYear
withMonth
withYear
将月份天数、年份天数、月份、年份修改为指定的值并返回新的LocalDate 对象
getDayOfMonth 获得月份天数(1-31)
getDayOfYear 获得年份天数(1-366)
getDayOfWeek 获得星期几(返回一个 DayOfWeek枚举值)
getMonth 获得月份, 返回一个 Month 枚举值
getMonthValue 获得月份(1-12)
getYear 获得年份
until 获得两个日期之间的 Period 对象,或者指定 ChronoUnits 的数字
isBefore
isAfter
比较两个 LocalDate
isLeapYear 判断是否是闰年

Instant 时间戳

用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算

@Test
public void test2() {
    Instant ins = Instant.now();  //默认使用 UTC 时区
    System.out.println(ins);

    OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));//指定时区
    System.out.println(odt);

    System.out.println(ins.toEpochMilli());//转成时间戳

    Instant ins2 = Instant.ofEpochSecond(5);//设置Instant/秒
    System.out.println(ins2);
}

Duration 和 Period

  • Duration:用于计算两个“时间”间隔
  • Period:用于计算两个“日期”间隔
@Test
public void test3() {
    Instant ins1 = Instant.now();

    System.out.println("--------------------");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }

    Instant ins2 = Instant.now();
    Duration between = Duration.between(ins1, ins2);
    System.out.println("所耗费时间为:" + between);
    System.out.println("所耗费时间为:" + between.getSeconds());
    System.out.println("所耗费时间为:" + between.toMillis());

    System.out.println("----------------------------------");

    LocalDate ld1 = LocalDate.now();
    LocalDate ld2 = LocalDate.of(2011, 1, 1);

    Period pe = Period.between(ld2, ld1);
    System.out.println(pe.getYears());
    System.out.println(pe.getMonths());
    System.out.println(pe.getDays());

    Period until = ld2.until(ld1);//同between
    System.out.println(until.getYears());
}

日期的操作

  • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
  • TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现
@Test
public void test4() {
    LocalDateTime ldt = LocalDateTime.now();
    System.out.println(ldt);

    LocalDateTime ldt2 = ldt.withDayOfMonth(10);
    System.out.println(ldt2);

    LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println(ldt3);

    //自定义:下一个工作日
    LocalDateTime ldt5 = ldt.with((l) -> {
        LocalDateTime ldt4 = (LocalDateTime) l;
        DayOfWeek dow = ldt4.getDayOfWeek();

        if (dow.equals(DayOfWeek.MONDAY)) {
            return ldt4.plusDays(7);
        } else if (dow.equals(DayOfWeek.TUESDAY)) {
            return ldt4.plusDays(6);
        } else if (dow.equals(DayOfWeek.SUNDAY)) {
            return ldt4.plusDays(5);
        } else if (dow.equals(DayOfWeek.THURSDAY)) {
            return ldt4.plusDays(4);
        } else if (dow.equals(DayOfWeek.FRIDAY)) {
            return ldt4.plusDays(3);
        } else if (dow.equals(DayOfWeek.SATURDAY)) {
            return ldt4.plusDays(2);
        } else {
            return ldt4.plusDays(1);
        }
    });

    System.out.println(ldt5);

}

解析和格式化

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

  • 预定义的标准格式
  • 语言环境相关的格式
  • 自定义的格式
@Test
public void test5() {
//      DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");

    LocalDateTime ldt = LocalDateTime.now();
    String strDate = ldt.format(dtf);
    System.out.println(strDate);

    LocalDateTime newLdt = ldt.parse(strDate, dtf);
    System.out.println(newLdt);
}

时区的处理

Java8 中加入了对时区的支持,带时区的时间为分别为:

ZonedDate
ZonedTime
ZonedDateTime

其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式例如 :Asia/Shanghai 等

  • ZoneId:该类中包含了所有的时区信息
  • getAvailableZoneIds() : 可以获取所有时区时区信息
  • of(id) : 用指定的时区信息获取 ZoneId 对象
@Test
public void test7() {
    LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
    System.out.println(ldt);

    ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
    System.out.println(zdt);
}

@Test
public void test6() {
    Set<String> set = ZoneId.getAvailableZoneIds();
    set.forEach(System.out::println);
}

与传统日期处理的转换

To 遗留类 From 遗留类
java.time.Instant
java.util.Date
Date.from(instant) date.toInstant()
java.time.Instant
java.sql.Timestamp
Timestamp.from(instant) timestamp.toInstant()
java.time.ZonedDateTime
java.util.GregorianCalendar
GregorianCalendar.from(zonedDateTime) cal.toZonedDateTime()
java.time.LocalDate
java.sql.Time
Date.valueOf(localDate) date.toLocalDate()
java.time.LocalTime
java.sql.Time
Date.valueOf(localDate) date.toLocalTime()
java.time.LocalDateTime
java.sql.Timestamp
Timestamp.valueOf(localDateTime) timestamp.toLocalDateTime()
java.time.ZoneId
java.util.TimeZone
Timezone.getTimeZone(id) timeZone.toZoneId()
java.time.format.DateTimeFormatter
java.text.DateFormat
formatter.toFormat()

重复注解与类型注解

/**
 * 支持重复注解
 */
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "";
}

/**
 *
 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
    MyAnnotation[] value();
}

/**
 * @description: 测试重复注解
 */
public class AnnotatonTest {

    @Test
    public void test() throws NoSuchMethodException {
        Class<AnnotatonTest> clazz = AnnotatonTest.class;
        Method show = clazz.getMethod("show");

        MyAnnotation[] annotationsByType = show.getAnnotationsByType(MyAnnotation.class);
        for (MyAnnotation m : annotationsByType) {
            System.out.println(m.value());
        }
    }

    @MyAnnotation("thank")
    @MyAnnotation("you")
    public void show(){}
}

jdk9

jdk和jre的改变

区别

jdk: java开发工具包

jre: java运行环境

https://img.zhaojq.top/20260728152705285.png
jdk
JDK = JRE + 开发工具集(例如 Javac 编译工具等) JRE = JVM + Java SE 标准类库

jdk8目录结构

graph TD; A[JDK HOME]-->B[bin]; A-->C[include]; A-->D[jre]; A-->E[lib]; D-->F[bin]; D-->G[lib];
目录 说明
bin 目录 包含命令行开发和调试工具,如 javac,jar 和 javadoc。
include 目录 包含在编译本地代码时使用的 C/C++头文件
lib 目录 包含 JDK 工具的几个 JAR 和其他类型的文件。 它有一个 tools.jar 文件,其中包含 javac 编译器的 Java 类
jre/bin 目录 包含基本命令,如 java 命令。 在 Windows 平台上,它包含系统的运行时动态链接库(DLL)。
jre/lib 目录 包含用户可编辑的配置文件,如.properties 和.policy文件。包含几个 JAR。 rt.jar 文件包含运行时的 Java类和资源文件。

jdk9目录结构

graph TD; A[JDK HOME]-->B[bin]; A-->C[conf]; A-->D[include]; A-->E[jmods]; A-->F[legal]; A-->G[lib];
目录 说明
bin 目录 包含所有命令。 在 Windows 平台上,它继续包含系统的运行时动态链接库。
conf 目录 包含用户可编辑的配置文件,例如以前位于 jre\lib 目录中的.properties 和.policy 文件
include 目录 包含要在以前编译本地代码时使用的 C/C++头文件。它只存在于 JDK 中
jmods 目录 包含 JMOD 格式的平台模块。 创建自定义运行时映像时需要它。 它只存在于 JDK 中
legal 目录 包含法律声明
lib 目录 包含非 Windows 平台上的动态链接本地库。 其子目录和文件不应由开发人员直接编辑或使用

模块化:igsaw -> Modularity

模块将由通常的类和新的模块声明文件(module-info.java)组成。该文件是位于 java 代码结构的顶层,该模块描述符明确地定义了我们的模块需要什么依赖关系,以及哪些模块被外部使用

//module-info.java 必须在src目录下指定引入哪些模块及输入哪些模块
module java.learning {
     exports jdk_version.java9ModulDemo.bean;
     requires junit;
     requires commons.io;
}

Java 的 REPL 工具: jShell 命令

  • Java 9 中终于拥有了 REPL 工具:jShell。利用 jShell 在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行 java 的代码,而无需创建 Java 文件,无需跟人解释public static void main(String[] args)这句废话。
  • jShell 也可以从文件中加载语句或者将语句保存到文件中。
  • jShell 也可以是 tab 键进行自动补全和自动添加分号
# jshell调出

# /help intro 获取帮助

# tab键自动补全

# /exit退出

多版本兼容jar包

# 如9版本中只会用到META-INFO下9版本的A.class和B.class, 之前的版本只会用到上面的
jar root
- A.class
- B.class
- C.class
- D.class
- META-INF
    - versions
        - 9 
            - A.class
            - B.class

测试代码: multijar.jar

语法改进:接口的私有方法

接口在java8中增加了default和static的方法,java9开始可以声明private方法

实现类无需实现private方法,且无法调用

语法改进:钻石操作符(Diamond Operator)使用升级

我们将能够与匿名实现类共同使用钻石操作符(diamond operator)

public class DiamondOperatorTest {

    @Test
    public void testDiamondOperator() {
        diamondOperator();
    }

    public void diamondOperator() {
        //创建一个继承于HashSet的匿名子类的对象
//        Set<String> set = new HashSet<String>(){};//java8需写成这样, 否则编译不通过
        Set<String> set = new HashSet<>() {
        };//编译通过
        set.add("MM");
        set.add("JJ");
        set.add("GG");
        set.add("DD");

        for (String s : set) {
            System.out.println(s);
        }

    }
}

语法改进: try语句

public class TryTest {
    //举例1:
    @Test
    public void testTry1() {
        InputStreamReader reader = null;
        try {
            reader = new InputStreamReader(System.in);
            //读取数据的过程:略
            reader.read();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //资源的关闭操作
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //举例2:在举例1基础上,升级的写法。不用显式的处理资源的关闭
    //java 8 中:要求资源对象的实例化,必须放在try的一对()内完成。
    //java 9 中:可以在try()中调用已经实例化的资源对象
    @Test
    public void testTry2() {
        try (InputStreamReader reader = new InputStreamReader(System.in)) {

            //读取数据的过程:略
            reader.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //如下的操作不可以在jdk 8 及之前的版本中使用
    @Test
    public void testTry3() {
        InputStreamReader reader = new InputStreamReader(System.in);
        OutputStreamWriter writer = new OutputStreamWriter(System.out);
        try (reader; writer) {
            //此时的reader是final的,不可再被赋值
//            reader = null;

            //读取数据的过程:略
            reader.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

语法改进:UnderScore(下划线)使用的限制

在 java 8 中,标识符可以独立使用_来命名

但是,在 java 9 中规定_不再可以单独命名标识符了

String存储结构变更

  • String:jdk 8 及之前:底层使用char[]存储;jdk 9 : 底层使用byte[] (encoding flag)
  • StringBuffer:jdk 8 及之前:底层使用char[]存储;jdk 9 : 底层使用byte[]
  • StringBuilder:jdk 8 及之前:底层使用char[]存储;jdk 9 : 底层使用byte[]
  • String:不可变的字符序列;
  • StringBuffer:可变的字符序列;线程安全的,效率低;
  • StringBuilder:可变的字符序列;线程不安全的,效率高(jdk 5.0)

集合工厂方法

  • jdk8及以前

    Collections.unmodifiableList(list)
    Collections.unmodifiableList(Arrays.asList(1, 2, 3))
    Collections.unmodifiableSet(new HashSet<>(Arrays.asList(1, 2, 3)))

  • jdk9

    List.of(1, 2, 3) Set.of(2, 3, 4) Map.of("Tom", 23, "Jerry", 22, "Lilei", 12, "HanMeimei", 18) Map.ofEntries(Map.entry("Tom", 23), Map.entry("Jerry", 21))

增强的Stream API

public class StreamTest {

    //takeWhile: 返回从开头开始的尽量多的元素,一旦满足,后面的全不要
    @Test
    public void t1() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 6, 4, 7, 9, 8);
        Stream<Integer> stream = list.stream();
        stream.takeWhile(x -> x < 5).forEach(System.out::println);
    }

    //dropWhile 与takeWhile相反,互补,一旦不满足,后面的全都要
    @Test
    public void t2() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 6, 4, 7, 9, 8);
        Stream<Integer> stream = list.stream();
        stream.dropWhile(x -> x < 5).forEach(System.out::println);
    }

    //ofNullable: ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。
    @Test
    public void t3() {
        Stream<Integer> stream1 = Stream.of(1, 2, null);
        stream1.forEach(System.out::println);

//        Stream<Object> stream2 = Stream.of(null);//报 NullPointerException

        //ofNullable():允许值为 null
        Stream<Object> stream3 = Stream.ofNullable(null);
        System.out.println(stream3.count());//0

        Stream<String> stream4 = Stream.ofNullable("hello world");
        System.out.println(stream4.count());//1

    }

    //重载的 iterate
    @Test
    public void t4() {
        Stream.iterate(0, x -> x + 1).limit(10).forEach(System.out::println);

        Stream.iterate(0, x -> x < 10, x -> x + 1).forEach(System.out::println);
    }

    //optional 的stream
    @Test
    public void t5() {
        List<String> list = new ArrayList<>();
        list.add("Jerry");
        list.add("Tim");
        Optional<List<String>> optional = Optional.ofNullable(list);
        Stream<List<String>> stream = optional.stream();
        stream.flatMap(x -> x.stream()).forEach(System.out::println);
    }

}

多分辨率图象api

在 Mac 上,JDK 已经支持视网膜显示,但在 Linux 和 Windows 上,它并没有。在那里,Java 程序在当前的高分辨率屏幕上可能看起来很小,不能使用它们。这是因为像素用于这些系统的大小计算(无论像素实际有多大)。毕竟,高分辨率显示器的有效部分是像素非常小。

JEP 263 以这样的方式扩展了 JDK,即 Windows 和 Linux 也考虑到像素的大小。为此,使用比现在更多的现代 API:Direct2D for Windows和 GTK +,而不是 Xlib for Linux。图形,窗口和文本由此自动缩放。

JEP 251 还提供处理多分辨率图像的能力,即包含不同分辨率的相同图像的文件。根据相应屏幕的 DPI 度量,然后以适当的分辨率使用图像

全新的 HTTP 客户端 API

HTTP/1.1和HTTP/2的主要区别是如何在客户端和服务器之间构建和传输数据。HTTP/1.1 依赖于请求/响应周期。 HTTP/2 允许服务器“push”数据:它可以发送比客户端请求更多的数据。 这使得它可以优先处理并发送对于首先加载网页至关重要的数据.

Java 9 中有新的方式来处理 HTTP 调用。它提供了一个新的 HTTP客户端( HttpClient ), 它将替代仅适用于 blocking 模式的HttpURLConnection (HttpURLConnection是在HTTP 1.0的时代创建的,并使用了协议无关的方法),并提供对 WebSocket 和 HTTP/2 的支持。此外,HTTP 客户端还提供 API 来处理 HTTP/2 的特性,比如流和服务器推送等功能.

/**
 * 需在module-info中导入 requires jdk.incubator.httpclient;
 */
public class HttpClientTest {

    public static void main(String[] args) {
        //jdk 9 中 使用 HttpClient替换原有的HttpURLConnection
        try {
            HttpClient client = HttpClient.newHttpClient();

            HttpRequest req = HttpRequest.newBuilder(URI.create("http://www.atguigu.com")).GET().build();

            HttpResponse<String> response = null;
            response = client.send(req, HttpResponse.BodyHandler.asString());
            System.out.println(response.statusCode());
            System.out.println(response.version().name());
            System.out.println(response.body());

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Deprecated相关api

Java 9 废弃或者移除了几个不常用的功能。其中最主要的是Applet API,现在是标记为废弃的。随着对安全要求的提高,主流浏览器已经取消对 Java 浏览器插件的支持。HTML5 的出现也进一步加速了它的消亡。开发者现在可以使用像 Java Web Start 这样的技术来代替Applet,它可以实现从浏览器启动应用程序或者安装应用程序。同时,appletviewer 工具也被标记为废弃

智能java编译工具

智能 java 编译工具( sjavac )的第一个阶段始于 JEP139 这个项目,用于在多核处理器情况下提升 JDK 的编译速度。如今,这个项目已经进入第二阶段,即 JEP199,其目的是改进 Java 编译工具,并取代目前 JDK 编译工具 javac,继而成为 Java 环境默认的通用的智能编译工具。

JDK 9 还更新了 javac 编译器以便能够将 java 9 代码编译运行在低版本 Java 中

统一的jvm日志系统

日志是解决问题的唯一有效途径:曾经很难知道导致 JVM 性能问题和导致 JVM 崩溃的根本原因。不同的 JVM 日志的碎片化和日志选项(例如:JVM 组件对于日志使用的是不同的机制和规则),这使得 JVM 难以进行调试

解决该问题最佳方法:对所有的 JVM 组件引入一个单一的系统,这些 JVM 组件支持细粒度的和易配置的 JVM 日志

javadoc的html5支持

javascript引擎升级:Nashorn

Nashorn 项目在 JDK 9 中得到改进,它为 Java 提供轻量级的Javascript 运行时。Nashorn 项目跟随 Netscape 的 Rhino 项目,目的是为了在 Java 中实现一个高性能但轻量级的 Javascript 运行时。

Nashorn 项目使得 Java 应用能够嵌入 Javascript。它在 JDK 8 中为Java 提供一个 Javascript 引擎。

JDK 9 包含一个用来解析 Nashorn 的 ECMAScript 语法树的API。这个 API 使得 IDE 和服务端框架不需要依赖 Nashorn 项目的内部实现类,就能够分析 ECMAScript 代码。

java的动态编译器

JIT(Just-in-time)编译器可以在运行时将热点编译成本地代码,速度很快。但是 Java 项目现在变得很大很复杂,因此 JIT 编译器需要花费较长时间才能热身完,而且有些 Java 方法还没法编译,性能方面也会下降。AoT 编译就是为了解决这些问题而生的。

在 JDK 9 中, AOT(JEP 295: Ahead-of-Time Compilation)作为实验特性被引入进来,开发者可以利用新的 jaotc 工具将重点代码转换成类似类库一样的文件。虽然仍处于试验阶段,但这个功能使得 Java 应用在被虚拟机启动之前能够先将 Java 类编译为原生代码。此功能旨在改进小型和大型应用程序的启动时间,同时对峰值性能的影响很小

jdk10

局部变量类型推断

public class TenTest {
    @Test
    public void t1() {
        var num = 10;

        var list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);

        for (var i : list) {
            System.out.println(i);
        }
    }

    @Test
    public void t2(){
        //局部变量不赋值,不能推断
//        var num;

        //lambda表达式,左边不能声明为var
        Supplier<Double> supplier = ()->Math.random();
//        var sup = ()->Math.random();

        //方法引用,左边不能为var
        Consumer<String> consumer = System.out::println;
//        var consumer = System.out::println;

        //数组的静态初始化,不能为var
        int[] arr = {1,2,3,4};
//        var arr2 = {1,2,3,4};
    }

    //方法的返回类型,方法参数,构造器参数,属性,catch块 都不能使用
}

copyOf()

//集合中新增的copyOf(),用于创建一个只读的集合
//如果本身是只读,则copyOf()是同一个,反之
@Test
public void t4(){
    var list1 = List.of(1,2,3);
    var list2 = List.copyOf(list1);
    System.out.println(list1 == list2);

    var list3 = new ArrayList<String>();
    var list4= List.copyOf(list3);
    System.out.println(list3==list4);
}

jdk11

jshell

除了表达式外,可以创建java类和方法

局部类型推断

var: 参考jdk10

集合新api

List.of(); List.copyOf(); 参考jdk9,jdk10

Stream 加强

ofNullable(); 参考jdk9

String

@Test
public void t1(){
    String s = "\t \r\n";
    System.out.println(s.isBlank());//是否空白

    s="\t \r\n abc \t";
    System.out.println("---------------------");
    String s1 = s.strip();//去除首尾空白, trim()只能去除英文空格,strip()能去除任何空白字符
    System.out.println(s1);
    System.out.println(s1.length());//0
    System.out.println("---------------------");
    s.stripTrailing();//去除尾部
    s.stripLeading();//去除首部

    System.out.println("---------------------");
    "java".repeat(3);//复制
    System.out.println("---------------------");
    "A\nB\nC".lines().count(); // 行数统计
}

InputStream

//inputStream
@Test
public void t2() throws IOException {
    var classLoader = this.getClass().getClassLoader();
    var inputStream = classLoader.getResourceAsStream("jdk_version/java11/javastack");
    try (var outputStream = new FileOutputStream("src/jdk_version/java11/javastack2")) {
        inputStream.transferTo(outputStream);
    }
    inputStream.close();
}

java异步http客户端

public class HttpClientTest {
    @Test
    public void t1() throws IOException, InterruptedException {
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://javastack.cn"))
                .GET()
                .build();
        var client = HttpClient.newHttpClient();
        // 同步
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
        // 异步
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println);
    }
}

更简化的编译运行程序

java Javastack.java 直接可编译运行

  • 执行源文件中的第一个类, 第一个类必须包含主方法
  • 并且不可以使用别源文件中的自定义类, 本文件中的自定义类是可以使用的

unicode10

Unicode 10 增加了8518个字符, 总计达到了136690个字符. 并且增加了4个脚本.同时还有56个新的emoji表情符号

新的Epsilon垃圾收集器

A NoOp Garbage Collector

JDK上对这个特性的描述是: 开发一个处理内存分配但不实现任何实际内存回收机制的GC, 一旦可用堆内存用完, JVM就会退出.

如果有System.gc()调用, 实际上什么也不会发生(这种场景下和-XX:+DisableExplicitGC效果一样), 因为没有内存回收, 这个实现可能会警告用户尝试强制GC是徒劳

用法 : -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

public class EpsilonTest {
    public static void main(String[] args) {
        boolean flag = true;
        List<Garbage> list = new ArrayList<>();
        long count = 0;
        while (flag) {
            list.add(new Garbage());
            if (list.size() == 1000000 && count == 0) {
                list.clear();
                count++;
            }
        }
        System.out.println("程序结束");
    }
}

class Garbage {
    int n = (int) (Math.random() * 100);

    //gc在清除本对象时会调用一次
    @Override
    public void finalize() {
        System.out.println(this + " : " + n + " is dying");
    }
}
//如果使用选项-XX:+UseEpsilonGC, 程序很快就因为堆空间不足而退出
  • 使用这个选项的原因 :

    提供完全被动的GC实现, 具有有限的分配限制和尽可能低的延迟开销,但代价是内存占用和内存吞吐量.

    众所周知, java实现可广泛选择高度可配置的GC实现. 各种可用的收集器最终满足不同的需求, 即使它们的可配置性使它们的功能相交. 有时更容易维护单独的实现, 而不是在现有GC实现上堆积另一个配置选项

  • 主要用途

    性能测试(它可以帮助过滤掉GC引起的性能假象)

    内存压力测试(例如,知道测试用例 应该分配不超过1GB的内存, 我们可以使用-Xmx1g –XX:+UseEpsilonGC, 如果程序有问题, 则程序会崩溃)

    非常短的JOB任务(对象这种任务, 接受GC清理堆那都是浪费空间)

    VM接口测试

    Last-drop 延迟&吞吐改进

ZGC jdk11最瞩目特性

但是后面带了Experimental, 说明这还不建议用到生产环境

  • GC暂停时间不会超过10ms
  • 既能处理几百兆的小堆, 也能处理几个T的大堆(OMG)
  • 和G1相比, 应用吞吐能力不会下降超过15%
  • 为未来的GC功能和利用colord指针以及Load barriers优化奠定基础
  • 初始只支持64位系统

ZGC的设计目标

支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆

GC是java主要优势之一. 然而, 当GC停顿太长, 就会开始影响应用的响应时间.消除或者减少GC停顿时长, java将对更广泛的应用场景是一个更有吸引力的平台. 此外, 现代系统中可用内存不断增长,用户和程序员希望JVM能够以高效的方式充分利用这些内存, 并且无需长时间的GC暂停时间

public class ZGCTest {
    public static void main(String[] args) {
        List<Garbage2> list = new ArrayList<>();
        int count = 0;
        boolean flag = true;
        while (flag){
            list.add(new Garbage2());
            if(count++==200){
                list.clear();
            }
        }
    }

}
class Garbage2{
    double d1 = 1;
    double d2 = 2;

    @Override
    protected void finalize() throws Throwable {
        System.out.println(this + " is collection");
    }
}

完全支持Docker容器

支持G1上的并行完全垃圾收集

对于 G1 GC,相比于 JDK 8,升级到 JDK 11 即可免费享受到:并行的 Full GC,快速的 CardTable 扫描,自适应的堆占用比例调整(IHOP),在并发标记阶段的类型卸载等等。这些都是针对 G1 的不断增强,其中串行 Full GC 等甚至是曾经被广泛诟病的短板,你会发现 GC 配置和调优在 JDK11 中越来越方便

Low-Overhead Heap Profiling

免费的低耗能飞行记录仪和堆分析仪

通过JVMTI的SampledObjectAlloc回调提供了一个开销低的heap分析方式

提供一个低开销的, 为了排错java应用问题, 以及JVM问题的数据收集框架, 希望达到的目标如下 :

  • 提供用于生产和消费数据作为事件的API
  • 提供缓存机制和二进制数据格式
  • 允许事件配置和事件过滤
  • 提供OS,JVM和JDK库的事件

实现RFC7539中指定的ChaCha20和Poly1305两种加密算法, 代替RC4

Java Flight Recorder

Flight Recorder以前是商业版的特性,在java11当中开源出来,它可以导出事-XX:StartFlightRecording,或者在应用启动之后,使用jcmd来录制件到文件中,之后可以用Java Mission Control来分析。可以在应用启动时配置java -XX:StartFlightRecording,或者在应用启动之后,使用jcmd来录制

jdk12

Shenandoah GC: 低停顿时间的GC

Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针 对 JVM 上的内存收回实现低停顿的需求。该设计将与应用程序线程并发,通过换 CPU 并发周期和空间以改善停顿时间,使得垃圾回收器执行线程能够在 Java 线程运行时进行堆压缩,并且标记和整理能够同时进行,因此避免了在大多数 JVM 垃圾收集器中所遇到的问题。

据 Red Hat 研发 Shenandoah 团队对外宣称,Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。

与其他 Pauseless GC 类似,Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等

STW(stop-the-world)

指的是GC 事件发生过程中,停止所有的应用程序线程的执行

垃圾回收器的任务是识别和回收垃圾对象进行内存清理。为了让垃圾回收器可以正常且高效地执行,大部分情况下会要求系统进入一个停顿的状态。停顿的目的是终止所有应用程序的执行,只有这样,系统中才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于垃圾回收器更好地标记垃圾对象。因此,在垃圾回收时,都会产生应用程序的停顿。停顿产生时整个应用程序会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW

如果Stop-the- World 出现在新生代的Minor GC 中时, 由于新生代的内存空间通常都比较小, 所以暂停时间也在可接受的合理范围之内,不过一旦出现在老年代的Full GC 中时,程序的工作线程被暂停的时间将会更久。简单来说,内存空间越大,执行Full GC 的时间就会越久, 相对的工作线程被暂停的时间也就会更长

到目前为止,哪怕是G1 也不能完全避免Stop-the-world 情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间

垃圾回收器的分类

  • 按线程数分,可以分为串行垃圾回收器和并行垃圾回收器
    • 串行回收指的是在同一时间段内只允许一件事情发生,简单来说,当多个CPU 可用时,也只能有一个CPU用于执行垃圾回收操作,井且在执行垃圾回收时,程序中的工作线程将会被暂停,当垃圾收集工作完成后
    • 才会恢复之前被暂停的工作线程,这就是串行回收。和串行回收相反,并行收集可以运用多个CPU 同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用了“Stop-the-world”机制和复制算法。
  • 按照工作模式分,可以分为并发式回收器和独占式垃圾回收器
    • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。
    • 独占式垃圾回收器( Stop the world)一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束
  • 按碎片处理方式可分为压缩式垃圾回收器和非压缩式垃圾回收器
    • 压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片。
    • 非压缩式的垃圾回收器不进行这步操作
  • 按工作的内存区间,又可分为年轻代垃圾回收器和老年代垃圾回收器

GC性能评估指标

  • 吞吐量:程序的运行时间(程序的运行时间+内存回收的时间)。
  • 垃圾收集开销:吞吐量的补数,垃圾收集器所占时间与总时间的比例。
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
  • 收集频率:相对于应用程序的执行,收集操作发生的频率。
  • 堆空间: Java 堆区所占的内存大小。
  • 快速: 一个对象从诞生到被回收所经历的时间

工作原理

从原理的角度,我们可以参考该项目官方的示意图,其内存结构与 G1 非常相似,都是将内存划分为类似棋盘的region。整体流程与 G1 也是比较相似的,最大的区别在于实现了并发的疏散(Evacuation) 环节,引入的 BrooksForwarding Pointer 技术使得 GC 在移动对象时,对象引用仍然可以访问 https://img.zhaojq.top/20260728154327460.png

上图对应工作周期如下:  
1. Init Mark 启动并发标记阶段   
2. 并发标记遍历堆阶段   
3. 并发标记完成阶段   
4. 并发整理回收无活动区域阶段   
5. 并发 Evacuation 整理内存区域阶段   
6. Init Update Refs 更新引用初始化阶段   
7. 并发更新引用阶段   
8. Final Update Refs 完成引用更新阶段   
9. 并发回收无引用区域阶段  

配置或调试 Shenandoah 的 JVM 参数

-XX:+AlwaysPreTouch:使用所有可用的内存分页,减少系统运行停顿,为避免运行时性能损失。 

-Xmx == -Xmsv:设置初始堆大小与最大值一致,可以减轻伸缩堆大小带来的压力,与 AlwaysPreTouch 参数配 合使用,在启动时提交所有内存,避免在最终使用中出现系统停顿。 

-XX:+ UseTransparentHugePages:能够大大提高大堆的性能,同时建议在 Linux 上使用时将 /sys/kernel/mm/transparent_hugepage/enabled 和 /sys/kernel/mm/transparent_hugepage/defragv 设置为:madvise,同时与 AlwaysPreTouch 一起使 用时,init 和 shutdownv 速度会更快,因为它将使用更大的页面进行预处理。 

-XX:+UseNUMA:虽然 Shenandoah 尚未明确支持 NUMA(Non-Uniform Memory Access),但最好启用此功 能以在多插槽主机上启用 NUMA 交错。与 AlwaysPreTouch 相结合,它提供了比默认配置更好的性能。 

-XX:+DisableExplicitGC:忽略代码中的 System.gc() 调用。当用户在代码中调用 System.gc() 时会强制 Shenandoah 执行 STW Full GC ,应禁用它以防止执行此操作,另外还可以使用 
- XX:+ExplicitGCInvokesConcurrent,在 调用 System.gc() 时执行 CMS GC 而不是 Full GC,建议在有 System.gc() 调用的情况下使用。 

不过目前 Shenandoah 垃圾回收器还被标记为实验项目,如果要使用Shenandoah GC需要编译时--with-jvm- features选项带有shenandoahgc,然后启动时使用参数 
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

JVM常量 api

Java 12 中引入 JVM 常量 API,用来更容易地对关键类文件 (key class-file) 和运行时构件(artefact)的名义描述(nominal description) 进行建模,特别是对那些从常量池加载的常量,这是一项非常技术性的变化,能够以更简单、标准的方式处理可加载常量

引入了ConstantDesc接口( ClassDesc、MethodTypeDesc、MethodHandleDesc这几个接口直接继承了ConstantDesc 接口 )以及Constable接口;ConstantDesc接口定义了resolveConstantDesc方法,Constable接口定义了describeConstable方法;String、Integer、Long、Float、Double均实现了这两个接口,而EnumDesc实现了ConstantDesc接口

微基准测试套件

JMH

JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件。何谓Micro Benchmark呢?简单的来说就是基于方法层面的基准测试,精度可以达到微秒级。当你定位到热点方法,希望进一步优化方法性能的时候,就可以使用JMH对优化的结果进行量化的分析

JMH应用场景

  • 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;
  • 对比接口不同实现在给定条件下的吞吐量;
  • 查看多少百分比的请求在多长时间内完成;

新特性说明

Java 12 中添加一套新的基本的微基准测试套件(microbenchmarks suite),此功能为JDK源代码添加了一套微基准测试(大约100个),简化了现有微基准测试的运行和新基准测试的创建过程。使开发人员可以轻松运行现有的微基准测试并创建新的基准测试,其目标在于提供一个稳定且优化过的基准。 它基于Java MicrobenchmarkHarness(JMH),可以轻松测试JDK性能,支持JMH更新

只保留一个AArch64实现

当前 Java 11 及之前版本JDK中存在两个64位ARM端口。这些文件的主要来源位于 src/hotspot/cpu/arm 和 open/src/hotspot/cpu/aarch64 目录中。尽管两个端口都产生了 aarch64 实现,我们将前者(由Oracle贡献)称为 arm64 ,将后者称为 aarch64

Java 12 中将删除由 Oracle 提供的 arm64端口相关的所有源码,即删除目录 open/src/hotspot/cpu/arm 中关于64-bit 的这套实现,只保留其中有关 32-bit ARM端口的实现,余下目录的 open/src/hotspot/cpu/aarch64 代码部分就成了 AArch64 的默认实现

默认生成类数据共享(CDS)归档文件

我们知道在同一个物理机/虚拟机上启动多个JVM时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。所以Java团队引入了类数据共享机制 (Class Data Sharing ,简称 CDS) 的概念,通过把一些核心类在每个JVM间共享,每个JVM只需要装载自己的应用类即可。好处是:启动时间减少了,另外核心类是共享的,所以JVM的内存占用也减少了

Java 12 针对 64 位平台下的 JDK 构建过程进行了增强改进,使其默认生成类数据共享(CDS)归档,以进一步达到改进应用程序的启动时间的目的,同时也避免了需要手动运行:java -Xshare:dump 的需要,修改后的 JDK 将在${JAVA_HOME}/lib/server 目录中生成一份名为classes.jsa的默认archive文件(大概有18M)方便大家使用

可中断的 G1 Mixed GC

当 G1 垃圾回收器的回收超过暂停时间的目标,则能中止垃圾回收过程

G1 采用一个高级分析引擎来选择在收集期间要处理的工作量,此选择过程的结果是一组称为 GC 回收集(collectionset( CSet ))的区域。一旦收集器确定了 GC 回收集 并且 GC 回收、整理工作已经开始,这个过程是withoutstopping的,即 G1 收集器必须完成收集集合的所有区域中的所有活动对象之后才能停止;但是如果收集器选择过大的 GC 回收集,此时的STW时间会过长超出目标pause time

这种情况在mixed collections时候比较明显。这个特性启动了一个机制,当选择了一个比较大的collection set,Java12 中将把 GC 回收集(混合收集集合)拆分为mandatory(必需或强制)及optional两部分( 当完成mandatory的部 分,如果还有剩余时间则会去处理optional部分 )来将mixed collections从without stopping变为abortable,以更好满足指定pause time的目标

增强G1,自动返回未用堆内存给操作系统

增加项:支持压缩数字格式化

//数字格式压缩
@org.junit.Test
public void t1() {
    var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA, NumberFormat.Style.SHORT);
    System.out.println(cnf.format(1_0000));
    System.out.println(cnf.format(1_9200));
    System.out.println(cnf.format(1_000_000));
    System.out.println(cnf.format(1L << 30));
    System.out.println(cnf.format(1L << 40));
    System.out.println(cnf.format(1L << 50));

    //输出
    1万
    2万
    100万
    11亿
    1万亿
    1126万亿
}

增加项:String 新方法

//String: transform
@org.junit.Test
public void t2() {
    var result = "foo".transform(input -> input + " bar");
    System.out.println(result); // foo bar

    var result2 = "foo".transform(input -> input + " bar").transform(String::toUpperCase);
    System.out.println(result2); // FOO BAR

    System.out.println("======test java 12 transform======");
    List<String> list1 = List.of("Java", " Python", " C++ ");
    List<String> list2 = new ArrayList<>();
    list1.forEach(element -> list2.add(element.transform(String::strip).transform(String::toUpperCase).transform((e) -> "Hi," + e)));
    list2.forEach(System.out::println);

    System.out.println("======test before java 12 ======");
    List<String> list11 = List.of("Java ", " Python", " C++ ");
    Stream<String> stringStream = list11.stream().map(element -> element.strip()).map(String::toUpperCase).map(element -> "Hello," + element);
    List<String> list22 = stringStream.collect(Collectors.toList());
    list22.forEach(System.out::println);
}

//String: indent 该方法允许我们调整String实例的缩进
@org.junit.Test
public void t3() {
    System.out.println("======test java 12 indent======");
    String result = "Java\n Python\nC++".indent(3);
    System.out.println(result);

    /* 输出如下
    ======test java 12 indent======
        Java
        Python
        C++
    

Files

//Files: mismatch 返回首次不匹配的position,完全不匹配返回-1
@org.junit.Test
public void t4() throws IOException {
    FileWriter fileWriter = new FileWriter("src/jdk_version/java12/a.txt");
    fileWriter.write("a");
    fileWriter.write("b");
    fileWriter.write("c");
    fileWriter.close();
    FileWriter fileWriterB = new FileWriter("src/jdk_version/java12/b.txt");
    fileWriterB.write("a");
    fileWriterB.write("1");
    fileWriterB.write("c");
    fileWriterB.close();
    System.out.println(Files.mismatch(Path.of("src/jdk_version/java12/a.txt"), Path.of("src/jdk_version/java12/b.txt")));
}

jdk13

预览版会在jdk14中正式发行

ZGC取消使用未使用的内存

在Java 13中,JEP 351再次对ZGC做了增强,将没有使用的堆内存归还给操作系统。ZGC当前不能把内存归还给操作系统,即使是那些很久都没有使用的内存,也只进不出。这种行为并不是对任何应用和环境都是友好的,尤其是那些内存占用敏感的服务,例如:

  • 按需付费使用的容器环境;
  • 应用程序可能长时间闲置,并且和很多其他应用共享和竞争资源的环境;
  • 应用程序在执行期间有非常不同的堆空间需求,例如,可能在启动的时候所需的堆比稳定运行的时候需要更多 的堆内存

重新实现旧版套接字API

全新实现的 NioSocketImpl 来替换JDK1.0的PlainSocketImpl

  • 它与现有的缓冲区缓存机制集成在一起,这样就不需要为 I/O 使用线程栈。
  • 它使用 java.util.concurrent 锁,而不是 synchronized 同步方法,增强了并发能力。
  • 新的实现是Java 13中的默认实现,但是旧的实现还没有删除,可以通过设置系统属性jdk.net.usePlainSocketImpl来切换到旧版本

jdk14

https://openjdk.java.net/projects/jdk/14/ –openjdk网站我们可以看到有以下16大特性

305: Pattern Matching for instanceof (Preview)

343: Packaging Tool (Incubator)

345: NUMA-Aware Memory Allocation for G1

349: JFR Event Streaming

352: Non-Volatile Mapped Byte Buffers

358: Helpful NullPointerExceptions

359: Records (Preview)

361: Switch Expressions (Standard)

362: Deprecate the Solaris and SPARC Ports

363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector

364: ZGC on macOS

365: ZGC on Windows

366: Deprecate the ParallelScavenge + SerialOld GC Combination

367: Remove the Pack200 Tools and API

368: Text Blocks (Second Preview)

370: Foreign-Memory Access API (Incubator)

305: Pattern Matching for instanceof (Preview) instanceof新特性

//以前我们是这么使用的
if (obj instanceof String) {
    String s = (String) obj;
    // use s
}

//新特性我们可以这么使用,即将强制转换类型绑定给变量,但变量只能在true块中使用
if (obj instanceof String s) {
    // can use s here
} else {
    // can't use s here
}
if (!(obj instanceof String s)) {
    // can't use s here
} else {
    // can use s here
}

//可以在&&后使用, 但不能在||后使用
if (obj instanceof String s && s.length > 10) {
    // can use s here
}

359: Records (Preview) 记录类型

import static java.util.Calendar.*;

//有望替代lombok
public class Test {
    public record User(int x, int y) {
    }

    public static void main(String[] args) {
        User user = new User(1, 2);
        System.out.println(user.x);
        System.out.println(user.y);

    }
}

361: Switch Expressions (Standard) switch语法形式改变

import static java.util.Calendar.*;

public class Test {
    public static void main(String[] args) {
        formatWeek(0);
        formatWeek(1);
        formatWeek(2);
        formatWeek(3);
        formatWeek(4);
        formatWeek(5);
        formatWeek(6);

        System.out.println(formatWeek2(5));
        System.out.println(formatWeek2(6));
    }

    public static void formatWeek(int day) {
        switch (day) {
            case MONDAY -> System.out.println("MONDAY");
            case TUESDAY -> System.out.println("TUESDAY");
            case WEDNESDAY -> System.out.println("WEDNESDAY");
            case THURSDAY -> System.out.println("THURSDAY");
            case FRIDAY -> System.out.println("FRIDAY");
            case SATURDAY -> System.out.println("SATURDAY");
            case SUNDAY -> System.out.println("SUNDAY");
        }
    }

    public static String formatWeek2(int day) {
        String str = switch (day) {
            case MONDAY -> "MONDAY";
            case TUESDAY -> "TUESDAY";
            case WEDNESDAY -> "WEDNESDAY";
            case THURSDAY -> "THURSDAY";
            case FRIDAY -> "FRIDAY";
            case SATURDAY -> "SATURDAY";
            case SUNDAY -> "SUNDAY";
            default -> "NONE";
        };
        return str;
    }
}

368: Text Blocks (Second Preview) 文本块

//old
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";
//new 
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

NullPointException

该特性可以更好地提示哪个地方出现的空指针,需要通过-XX:+ShowCodeDetailsInExceptionMessages开启

弃用ParallelScavenge和SerialOld GC组合

删除CMS垃圾回收器

ZGC on macOS和windows