当前位置:   article > 正文

java基础 -- java8,9,10,11新特性_error:(63,64) java: -source 1.6 中不支持 diamond 运算符

error:(63,64) java: -source 1.6 中不支持 diamond 运算符

连接视频

java8 新特性

Java 8(又称为 jdk 1.8)是 Java 语言开发个一个主要版本

Java 8 是 oracle公司与 2014年3月发布,可以 看成是自 Java 5 以后来最有革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。


接口中的新特性

JDK7及以前:只能定义全局常量和抽象方法

全局常量:public static final的,但是书写时,可以省略不写
抽象方法:public abstract

JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法

Java 8 中,你可以为接口添加 静态方法默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。

静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用静态方法。你可以在标准库中找到对象 Collection/Collections或者 Path/Paths这样对的接口和类。

默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API 中对 Collection、List、Comparator等接口提供了丰富的默认方法。

代码演示:

public class SuperClass {

    public void method3(){
        System.out.println("SuperClass类method3 的方法");
    }
}

/**
 * 除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
 */
public interface CompareA {

    //静态方法
    public static void method1(){//public 可以省略
        System.out.println("CompareA接口method1 的静态方法");
    }

    //默认方法
    public default void method2(){//public 可以省略
        System.out.println("CompareA接口method2 的默认方法");
    }

    default void method3(){
        System.out.println("CompareA接口method3 的默认方法");
    }

    default void method4(){
        System.out.println("CompareA接口method4 的默认方法");
    }
}

public interface CompareB {

    default void method4(){
        System.out.println("CompareB接口method4 的默认方法");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
public class SubClassTest {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
       // subClass.method1(); // 接口实现类的静态方法不能使用
        //知识1:接口定义静态方法,只能通过接口来调用
        CompareA.method1();
        //知识2:通过实现类的对象,可以调用接口中的默认方法
        //如果实现类重写接口的默认方法,调用时,仍然调用的是重写的方法
        subClass.method2();
        //知识3:如果子类(或者实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
        // 那么子类在没有重写此方法的情况下,默认调用的是父类的同名同参数的方法。 ---> 类优先原则
        subClass.method3();
        //知识4:如果实现类实现了多个接口,而这个接口定义了同名同参的默认方法,
        // 那么在实现类没有重写此方法的情况下,报错(因为实现类不知道找哪个默认方法) ---> 接口冲突
        // 这就需要我们必须在实现类中重写此方法,或者 父类声明了同名同参数的方法,
        subClass.method4();

    }
}
class SubClass extends SuperClass implements CompareA,CompareB  {
    public void method2(){
        System.out.println("SubClass类method2 的重写方法");
    }
    public void method4(){
        System.out.println("SubClass类method4 的重写方法");
    }

    //知识5:然后在子类(实现类)的方法中调用父类、接口中被重写的方法
    public void myMethod(){
        method3();//调用自己定义的重写方法
        super.method3();//调用父类中声明的方法
        //调用接口中的默认方法
        CompareA.super.method3();
        CompareB.super.method4();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

执行效果:
在这里插入图片描述



可重复注解、类型注解

可重复注解
1、在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
2、MyAnnotation的注解 和 MyAnnotations上的注解必须都有

代码演示:

@Inherited
@Repeatable(MyAnnotations.class)//可重复注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
public @interface MyAnnotation {
    String value() default "hello";
}

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
public @interface MyAnnotations {

    MyAnnotation[] value();
}

//jdk8之前的写方法:
//@MyAnnotations({@MyAnnotation(value = "hi"),@MyAnnotation(value = "abc")})
@MyAnnotation(value = "hi")
@MyAnnotation(value = "abc")
public class Test {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

类型注解

JDK1.8之后,关于元注解 @Target的参数类型 ElementType枚举值多了两个:
TYPE_PARAMETERTYPE_USE
在这里插入图片描述

在Java 8 之前,注解只是在声明的地方所使用,Java8开始,注解可以应用在任何地方

  • ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)
  • ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中
@Target({ElementType.TYPE_PARAMETER,ElementType.TYPE_USE})
public @interface MyAnnotation {
    String value() default "hello";
}

class Generic<@MyAnnotation T>{

    public void show() throws @MyAnnotation RuntimeException{
        ArrayList<@MyAnnotation String> list = new ArrayList<>();
        int num = (@MyAnnotation int)10L;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12



Lambda表达式

为什么使用 Lambda 表达式

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

代码举例:

/**
 * Lambda 表达式的使用举例
 */
public class LambdaTest {

    @Test
    public void test1(){
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("我爱中国");
            }
        };
        r1.run();
        System.out.println("======= Lambda 表达式 =============");

        Runnable r2 = () -> System.out.println("我爱祖国");

        r2.run();

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

        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };
        int compare1 = com1.compare(12, 21);
        System.out.println(compare1);
        System.out.println("======= Lambda 表达式 =============");
        //Lambda表达式的写法
        Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2);
        int compare2 = com2.compare(32, 21);
        System.out.println(compare2);
        //方法引用
        Comparator<Integer> com3 = Integer :: compare;
        int compare3 = com3.compare(32, 32);
        System.out.println(compare3);
        
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

执行效果:

在这里插入图片描述


Lambda具体介绍举例:

/**
 * Lambda表达式的使用
 *
 * 1. 举例:(o1,o2) -> Integer.compare(o1,o2)
 * 2. 格式:
 *      -> : Lambda操作符 或 箭头操作符
 *      -> 左边:lambda形参列表 (其实就是接口中的抽象方法形参列表)
 *      -> 右边:lambda体 (其实就是重写抽象方法的方法体)
 *
 * 3. Lambda表达式的使用:(分为6种情况介绍)
 *
 *      总结:
 *      -> 左边:lambda形参列表的参数类型可以省略(类型推断):如果lambda形参列表只有一个参数,其一对()也可以省略
 *      -> 右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是 return语句),可以省略一对{}和 return关键字
 *
 * 4. Lambda表达式的本质:作为函数式接口的实例
 *
 * 5. 如果一个接口中,只声明一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface注解,
 *    这样可以检查它是否是一个函数式接口
 * 
 * 6. 所以以前用匿名实现类表示的现在都可以使用Lambda表达式来写
 */
public class LambdaTest1 {

    //语法一:无参,无返回值
    @Test
    public void test1(){
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("我爱中国");
            }
        };
        r1.run();
        System.out.println("======= Lambda 表达式 =============");
        Runnable r2 = () -> {
            System.out.println("我爱祖国");
        };
        r2.run();
    }

    //语法格式二:Lambda 需要一个参数,当没有返回值
    @Test
    public void test2(){
        Consumer<String> con = new Consumer<String>(){
            @Override
            public void accept(String s){
                System.out.println(s);
            }
        };
        con.accept("小明和小花");
        System.out.println("======= Lambda 表达式 =============");
        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("小喵和小狗");
    }

    //语法格式三:数据类型可以省略,因为可由编译器推断得出,称为 “类型推断”
    @Test
    public void test3(){
        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("小喵和小狗");
        System.out.println("===========");
        Consumer<String> con2 = (s) -> {
            System.out.println(s);
        };
        con2.accept("小喵和小狗");
        ArrayList<String> list = new ArrayList<>();//类型推断
        int[] arr = {1,2,3,4};//类型推断
    }

    //语法格式四:Lamda 若只需要一个参数时,参数的小括号可以省略
    @Test
    public void test4(){
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("小喵和小狗");
        System.out.println("*****************");
        Consumer<String> con2 = s -> {
            System.out.println(s);
        };
        con2.accept("小喵和小狗");
    }

    //语法格式五:Lambda 需要两个以上的参数,多条件执行语句,并且可以有返回值
    @Test
    public void test5(){
        Comparator<Integer> comp1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(o2);
            }
        };
        System.out.println(comp1.compare(12,21));

        System.out.println("*****************");
        Comparator<Integer> comp2 = (o1,o2) ->  {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
        System.out.println(comp2.compare(23,13));
    }

    //语法格式六:当 Lambda 体只有一条语句时,return 与大括号如有,都可以省略
    @Test
    public void test6(){
        Comparator<Integer> comp1 = (o1,o2) ->  {
            return o1.compareTo(o2);
        };
        System.out.println(comp1.compare(23,13));

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

        Comparator<Integer> comp2 = (o1,o2) -> o1.compareTo(o2);
        System.out.println(comp2.compare(36,48));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124



函数式(Functional)接口

自定义函数式接口:

/**
 * 自定义函数式接口
 */
@FunctionalInterface//函数式接口注解
public interface MyInterface {
    void method1();

    //void method2();//报错
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

什么是函数式(Functional)接口

  • 只包含一个抽象方法的接口,称为 函数式接口
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上声明)。
  • 我们可以在一个接口上使用 @FunctionalInterface注解,这样可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • 在 java.util.funtion 包下定义了 Java 8 的丰富函数式接口

如何理解函数式接口

  • Java 从诞生起就是一直倡导 “一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得做出调整以便支持更加广泛的技术要求,也即Java不但可以支持OOP还可以支持OOF(面向函数编程)
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型 — 函数式接口
  • 简单来说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只有一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
  • 所以以前用匿名实现类表示的现在都可以使用Lambda表达式来写。

Java 内置四大核心函数式接口

函数式接口参数类型返回类型用途
Consumer<T>消费型接口Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier<T>供给型接口T返回类型为T的对象,包含方法:T get()
Function<T,R>函数型接口TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate<T>断定型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t)
/**
 * java内置4大核心函数式接口
 *
 *  消费类接口 Consumer<T>   void accept(T t)
 *  供给型接口 Supplier<T>   T get()
 *  函数型接口 Function<T,R> R apply(T t)
 *  断定型接口 Predicate<T>  boolean test(T t)
 */
public class LambdaTest2 {

    @Test
    public void test1(){
        happyTime(500, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("学习太累了,去消费火锅:" + aDouble);
            }
        });
        System.out.println("=============================");
        happyTime(400, moeny -> System.out.println("学习太累了,去消费烤鱼:" + moeny));
    }

    public void happyTime(double money, Consumer<Double> con){
        con.accept(money);
    }

    @Test
    public void test2(){
        List<String> list = Arrays.asList("北京","南京","天津","东京","西京","普京");
        List<String> filterString = filterString(list, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.contains("京");
            }
        });
        System.out.println(filterString);

        System.out.println("=================");
        List<String> filterStr = filterString(list, str -> str.contains("京"));
        System.out.println(filterStr);
    }

    //根据给定规则,过滤集合中的字符串。此规则有Predicate的方法决定
    public List<String> filterString(List<String> list, Predicate<String> pre){
        ArrayList<String> filterList = new ArrayList<>();
        for(String str : list){
            if(pre.test(str)){
                filterList.add(str);

            }
        }
        return filterList;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

执行效果:

在这里插入图片描述


其他接口

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



方法引用与构造器引用

方法引用(Method References)

  • 当要传递给 Lambda体的操作,已经有实现的方法了,可以使用方法引用
  • 方法引用可以看做是 Lambda 表达式深层次的表达。换句来说,方法引用就是 Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现接口的抽象方法的参数列表和返回返回值类型,必须与方法引用的方法参数列表和返回值类型保持一致
  • 格式:使用操作符 “::” 将类(或对象)与方法名分割开来
  • 如下三种主要使用情况:
    • 对象 :: 实例方法名
    • 类 :: 静态方法名
    • 类 :: 实例方法名
/**
 * 方法引用的使用
 *
 * 1. 使用情境:当要传递给 Lambda体的操作,已经有实现的方法了,可以使用方法引用
 *
 * 2. 方法引用,本质上就是Lambda表达式,而lambda表达式作为函数式接口的实例。所以
 *    方法引用,也是函数式接口的实例
 *
 * 3. 使用格式: 类(对象) :: 方法名
 *
 * 4. 具体分为如下的三种情况:
 *    情况1   对象 :: 非静态方法
 *    情况2    类 :: 静态方法
 *
 *    情况3    类 :: 非静态方法
 *
 * 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
 *    形参列表和返回值类型相同!
 */
@SuppressWarnings({"all"})
public class MethodRefTest {

    // 情况一:对象 :: 实例化
    // Consumer中的 void accept(T t)
    // PrintStream中的 void println(T t)
    @Test
    public void test1(){
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("中国你好");
        System.out.println("=================");
        PrintStream ps = System.out;
        Consumer<String> con2 = ps ::println;
        con2.accept("中国,hello");
    }

    // Supplier中的 T get()
    // Employee中的 String getName()
    @Test
    public void test2(){
        Employee emp = new Employee(1001,"Tom",23,5000);
        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());
        System.out.println("=================");
        Supplier<String> sup2 = emp :: getName;
        System.out.println(sup2.get());
    }

    // 情况二:类 :: 静态方法
    // Comparator中的 int compare(T t1,T t2)
    // Integer中的 int compare(T t1,T t2)
    @Test
    public void test3(){
        Comparator<Integer> com1 = (o1,o2) -> Integer.compare(o1,o2);
        System.out.println(com1.compare(12,23));
        System.out.println("=================");
        Comparator<Integer> com2 = Integer ::compare;
        System.out.println(com1.compare(25,13));
    }

    // Function中的 R apply(T t)
    // Math中的 Long round(Double d)
    @Test
    public void test4(){
        //原写法
        Function<Double,Long> fun = new Function<Double, Long>() {
            @Override
            public Long apply(Double aDouble) {
                return Math.round(aDouble);
            }
        };
        //lambda写法
        Function<Double,Long> fun1 = d -> Math.round(d);
        System.out.println(fun1.apply(12.3));
        System.out.println("=================");
        //方法引用
        Function<Double,Long> fun2 = Math ::round;
        System.out.println(fun2.apply(12.6));
    }

    // 情况三:类 :: 实例方法 (有难度)
    // Comparator中的 int compare(T t1,T t2)
    // String中的 int t1.compareTo(t2)
    @Test
    public void test5(){
        Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc","abd"));
        System.out.println("=================");
        Comparator<String> com2 = String :: compareTo;
        System.out.println(com2.compare("abd","abm"));

    }

    // BiPredicate中的 boolean test(T t1,T t2)
    // String中的 boolean t1.equals(t2)
    @Test
    public void test6(){
        BiPredicate<String,String> pre1 = (s1, s2) -> s1.equals(s2);
        System.out.println(pre1.test("abc","abc"));
        System.out.println("=================");
        BiPredicate<String,String> pre2 = String :: equals;
        System.out.println(pre2.test("abc","abd"));
    }

    // Function中的 R apply(T t)
    // Employee中的 String getName()
    @Test
    public void test7(){
        Employee emp = new Employee(1002,"jack",23,6000);
        Function<Employee,String> fun1 = e -> e.getName();
        System.out.println(fun1.apply(emp));
        System.out.println("=================");
        Function<Employee,String> fun2 = Employee :: getName;
        System.out.println(fun2.apply(emp));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

构造器引用

加数组引用

/**
 * 一、构造器应用
 *      和方法引用类似,函数式接口的抽象方法的形参和构造器的形参列表一致。
 *      抽象方法的返回值类型即为构造器所属的类的类型
 *
 * 二、数组引用
 *      大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
 */
@SuppressWarnings({"all"})
public class ConstructorRefTest {

    //构造器引用
    //Supplier中的 T get()
    //Employee的空参构造器:Employee()
    @Test
    public void test1(){
        Supplier<Employee> sup = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println(sup.get());

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

        Supplier<Employee> sup1 = () -> new Employee();
        System.out.println(sup1.get());

        System.out.println("*********构造器引用*************");

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

    //Function中的 R apply(T t)
    @Test
    public void test2(){
        Function<Integer,Employee> fun1 = id -> new Employee(id);
        Employee employee = fun1.apply(1001);
        System.out.println(employee);

        System.out.println("*********构造器引用*************");

        Function<Integer,Employee> fun2 = Employee :: new;
        Employee employee1 = fun1.apply(1002);
        System.out.println(employee1);

    }

    //BiFunction中的 R apply(T t,U u)
    @Test
    public void test3(){
        BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
        System.out.println(func1.apply(1001,"tom"));

        System.out.println("*********构造器引用*************");

        BiFunction<Integer,String,Employee> func2 = Employee :: new;
        System.out.println(func2.apply(1002,"jack"));
    }

    //数组引用
    //Function中的 R apply(T t)
    @Test
    public void test4(){
        Function<Integer,String[]> fun1 = length -> new String[length];
        String[] arr1 = fun1.apply(5);
        System.out.println(Arrays.toString(arr1));

        System.out.println("*********数组引用引用*************");

        Function<Integer,String[]> fun2 = String[] :: new;
        String[] arr2 = fun1.apply(10);
        System.out.println(Arrays.toString(arr2));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

执行结果:

在这里插入图片描述


Stream API

Stream API 说明

  • Java8 中有两大最为重要的改变。第一个是 Lambda 表达式;另一个是 Stream API

  • Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为 Stream API 可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码

  • Stream 是 Java8 中处理集合的关系抽象的概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 Stream API 对集合数据进行操作,就类似与使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效率且易于使用的处理数据的方法。


为什么要使用 Stream API

  • 实际开发中,项目中多数据源都来自于 Mysql,Oracle等。但现在数据源可以更多,有 MongDB,Radis等,而这些NoSQL的数据库就需要Java层面去处理

  • StreamCollection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。


什么是 Stream

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据,Stream讲的是计算!”

注意:

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


Stream 的操作三个步骤

  • 1、创建 Stream
    一个数据源(如:集合、数组),获取一个流

  • 2、中间操作
    一个中间操作链,对数据源的数据进行处理

  • 3、终止操作(终端操作)
    一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

在这里插入图片描述


创建 Stream方式一:通过集合

Java8 中 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream<E> stream(): 返回一个顺序流
  • default Stream<E> parallelStream():返回一个并行流

创建 Stream方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static <T> Stream<T> stream(T[] array):返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static InStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

创建 Stream方式三:通过Stream的 of()

可以调用 Stream类静态方法 of(),通过显示值创建一个流。它可以接收任意数量的参数

  • public static<T> Stream<T> of(T... values):返回一个流

创建 Stream方式四:通过无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流

  • 迭代
    public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)

  • 生成
    public static<T> Stream<T> generate(Supplier<T> s)

/**
 * 1. Stream 关注的是对数据的运算,与CPU打交道
 *     集合关注的是数据存储,与内存打交道
 *
 * 2.
 *  ①Stream 自己不会存储元素。
 *  ②Stream 不会改变源对象。相反,它们会返回一个持有结果的新Stream
 *  ③Stream 操作是延迟执行的。这意味着它们会等到需要结果的时候才执行
 *
 * 3. Stream 执行流程:
 *   ①Stream的实例化
 *   ②一系列的中间操作(过滤、映射、..)
 *   ③终止操作
 *
 * 4. 说明:
 *   4.1 一个中间操作链,对数据源的数据进行处理
 *   4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
 *
 *   测试Stream的实例化
 *
 */
public class StreamAPITest {

    //创建 Stream方式一:通过集合
    @Test
    public void test1(){
        List<Employee> employees = EmployeeDate.getEmployees();
        // default Stream<E> stream(): 返回一个顺序流
        Stream<Employee> stream = employees.stream();

        // default Stream<E> parallelStream():返回一个并行流
        Stream<Employee> employeeStream = employees.parallelStream();
    }

    //创建 Stream方式二:通过数组
    @Test
    public void test2(){
        int[] arr = new int[]{1,2,3,4,5};
        //调用Arrays类的 static <T> Stream<T> stream(T[] array):返回一个流
        IntStream stream = Arrays.stream(arr);

        Employee emp1 = new Employee(1001,"tom");
        Employee emp2 = new Employee(1002,"mary");
        Employee[] arr1 = new Employee[]{emp1,emp2};
        Stream<Employee> stream1 = Arrays.stream(arr1);
    }

    //创建 Stream方式三:通过Stream的 of()
    @Test
    public void test3(){
        //public static<T> Stream<T> of(T... values):返回一个流
        Stream<Integer> of = Stream.of(1,2,3,4,5);
    }

    //创建 Stream方式四:通过无限流
    @Test
    public void test4(){
        //迭代
        //public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)
        //遍历前10个偶数
        Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out :: println);
        //生成
        //public static<T> Stream<T> generate(Supplier<T> s)
        Stream.generate(Math::random).limit(10).forEach(System.out :: println);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

执行效果:

在这里插入图片描述


Stream 的中间操作

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

1 - 筛选与切面

方法描述
filter<Predicate p>接收 Lambda,从流中排除某些元素
distinct()筛选,通过流所产生元素的 hashCode() 和 equals() 去除重复的元素
limit<long maxSize>截断流,使其元素不超过给定的数量
skip<long n>跳过元素,返回一个扔掉了前n个元素的流。若流中的元素不足n个,则返回一个空流。与 limit(n) 互补

2 - 映 射

方法描述
map<Function f>接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble<ToDoubleFunction f>接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt<ToIntFunction f>接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong<ToLongFunction f>接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap<Function f>接收一个函数作为参数,该流中的每个值换成另外一个流,然后把所有流连接成一个流。

3 - 排 序

方法描述
sorted()产生一个新流,其中按自然排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序
/**
 * 测试stream的中间操作
 */
@SuppressWarnings({"all"})
public class StreamAPITest1 {

    //1 - 筛选与切面
    @Test
    public void test1() {
        List<Employee> list = EmployeeDate.getEmployees();
        //filter<Predicate p>	接收 Lambda,从流中排除某些元素
        Stream<Employee> stream = list.stream();
        //练习:查询员工中薪资大于 7000的员工信息
        stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);

        System.out.println();
        //limit<long maxSize>	截断流,使其元素不超过给定的数量
        list.stream().limit(3).forEach(System.out::println);

        System.out.println();
        //skip<long n>	跳过元素,返回一个扔掉了前n个元素的流。若流中的元素不足n个,则返回一个空流。与 limit(n) 互补
        list.stream().skip(3).forEach(System.out::println);

        System.out.println();
        //distinct()	筛选,通过流所产生元素的 hashCode() 和 equals() 去除重复的元素
        list.stream().distinct().forEach(System.out::println);
    }

    //映射
    @Test
    public void test2() {

        //map<Function f>	接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
        list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);

        //练习1:获取员工姓名长度大于3的员工的姓名
        List<Employee> employees = EmployeeDate.getEmployees();
        Stream<String> namesStream = employees.stream().map(Employee::getName);
        namesStream.filter(name -> name.length() > 3).forEach(System.out::println);

        System.out.println();
        //练习2:
        Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
        streamStream.forEach(s -> {
            s.forEach(System.out::println);
        });

        System.out.println();
        //flatMap<Function f>	接收一个函数作为参数,该流中的每个值换成另外一个流,然后把所有流连接成一个流。
        Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
        characterStream.forEach(System.out::println);
    }

    //将字符串中的多个字符构造成的集合转为为对应的Stream的实例
    public static Stream<Character> fromStringToStream(String str) {//aa
        ArrayList<Character> list = new ArrayList<>();
        for (Character character : str.toCharArray())
        {
            list.add(character);
        }
        return list.stream();
    }

    //3 - 排 序
    @Test
    public void test3() {
        //sorted()	产生一个新流,其中按自然排序
        List<Integer> list = Arrays.asList(12,43,65,87,0,-98,7);
        list.stream().sorted().forEach(System.out::println);

        System.out.println();
        //List<Employee> employees = EmployeeDate.getEmployees();
        // 抛异常,原因:Employee没有实现Comparable接口
        // employees.stream().sorted().forEach(System.out::println);

        //sorted(Comparator com)	产生一个新流,其中按比较器顺序排序
        List<Employee> employees = EmployeeDate.getEmployees();
//        employees.stream().sorted((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()))
//                .forEach(System.out::println);
        //多条件
        employees.stream().sorted((e1,e2) -> {
            int compare = Integer.compare(e1.getAge(), e2.getAge());
            if(compare != 0){
                return compare;
            }else {
                return -Double.compare(e1.getSalary(), e2.getSalary());//薪资 大到小
            }

        }).forEach(System.out::println);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

Stream 终止操作

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

  • 流进行了终止操作后,不能再次使用

1 - 匹配与查找

方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFist()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代 — 它帮你把迭代做了)
/**
 * 测试Stream 的终止操作
 */
public class StreamTest2 {

    //1 - 匹配与查找
    @Test
    public void test1(){
        List<Employee> employees = EmployeeDate.getEmployees();

        //allMatch(Predicate p)	检查是否匹配所有元素
        //练习:是否所有员工的年龄都大于18岁
        boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
        System.out.println(allMatch);

        //anyMatch(Predicate p)	检查是否至少匹配一个元素
        //练习:是否存在员工的工资大于 10000
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        System.out.println(anyMatch);

        //noneMatch(Predicate p)	检查是否没有匹配所有元素
        //练习:是否存在员工姓 “雷”
        boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
        System.out.println(noneMatch);

        //findFist()	返回第一个元素
        Optional<Employee> optional = employees.stream().sorted((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()))
                .findFirst();
        System.out.println(optional);

        //findAny()	返回当前流中的任意元素
        Optional<Employee> any = employees.parallelStream().findAny();
        System.out.println(any);
    }

    @Test
    public void test2(){
        List<Employee> employees = EmployeeDate.getEmployees();
        //count()	返回流中元素总数
        long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
        System.out.println(count);

        //max(Comparator c)	返回流中最大值
        //练习:返回最高的工资
        Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
        Optional<Double> max = salaryStream.max(Double::compare);
        System.out.println(max);

        //min(Comparator c)	返回流中最小值
        //练习:返回最低工资的员工
        Optional<Employee> min = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println(min);

        //forEach(Consumer c)	内部迭代
        employees.stream().forEach(System.out::println);
        //使用集合的遍历操作
       //employees.forEach(System.out::println);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

2 - 归约

方法描述
reduce(T iden,BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional<T>

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

//2 - 归约
@Test
public void test3(){
    //reduce(T iden,BinaryOperator b)	可以将流中元素反复结合起来,得到一个值。返回 T
    //练习:计算1-10的自然数
    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    Integer sum = list.stream().reduce(0, Integer::sum);
    System.out.println(sum);

    //reduce(BinaryOperator b)	可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
    //练习:计算公司所有员工工资的总和
    List<Employee> employees = EmployeeDate.getEmployees();
    Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
    //Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
    Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
    System.out.println(sumMoney);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3 - 收集

方法描述
collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给 Stream中元素做汇总

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

方法返回类型作用示例
toListList<T>把流中元素收集到ListList<Employee> emps = list.stream().collect(Collectors.toList());
toSetSet<T>把流中元素收集到SetSet<Employee> emps = list.stream().collect(Collectors.toSet());
toCollectionCollection<T>把流中元素收集到创建的集合Collection<Employee> emps = list.stream().collect(Collectors.toCollection(ArrayList::new));
countingLong计算流中的个数long count = list.stream().collect(Collectors.counting());
summingIntInteger对流中元素的整数属性求和int total = list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingIntDouble计算流中元素Integer属性平均值double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingIntIntSummaryStatistics收集流中Integer属性的统计值。如:平均值int summaryStatisticsiss = list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joiningString连接流中每个字符串String str = list.stream().map(Employee::getName).collect(Collectors.joining());
maxByOptional<T>根据比较器选择最大值Optional<Emp> max = list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minByOptional<T>根据比较器选择最小值Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing归约产生的类型从一个作为累加器的初始值开始,利用 BinaryOperator与流中元素逐个结合,从而归约成单个值int total = list.stream().collect(Collectors.reducing(0,Employee::getSalary,Integer::sum));
collectingAndThen转换函数返回的类型包裹另一个收集器。对其结果转换函数int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(),List::size));
groupingByMap<K,List<T>>根据某属性值对流分组,属性为K,结果为VMap<Emp.Status,List<Emp>> map = list.stream().collect(Collectors.groupingBy(Employee::getManage));
partitioningByMap<Boolean,List<T>>根据true或者false进行分区Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));
//3 - 收集
@Test
public void test4(){
    List<Employee> employees = EmployeeDate.getEmployees();
    //collect(Collector c)	将流转换为其他形式。接收一个 Collector接口的实现,用于给 Stream中元素做汇总
    //练习: 查找工资大于6000的员工,结果返回一个list或set
    //List<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
    Set<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
    collect.forEach(System.out::println);
}

//5 - joining("分隔符")(集合拼接指定字符串)
@Test
public void test4(){
	List list = new ArrayList();
    list.add("a");
    list.add("b");
    list.add("c");

   String str = (String) list.stream().collect(Collectors.joining(","));
   System.out.println(str);//a,b,c
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Optional类

  • 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经称为Java 8类库的一部分。

  • Optional<T>类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。

  • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent() 方法会返回 true,调用 get() 方法会返回该对象。

  • Optional提供很多有用的方法,这样我们就不用显式进行空指针检测。

  • 创建Optional类对象的方法:

    • Optional.of(T t):创建一个 Optional 实例,t 必须非空
    • Optional.empty():创建一个空的 Optional 实例
    • Optional.ofNullable(T t):t 可以为 null
  • 判断Optional容器中是否包含对象:

    • boolean isPresent():判断是否包含对象
    • void isPresent(Cousumer<? super T> consumer):如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它
  • 获取Optional容器的对象:

    • T get():如果调用对象包含值,返回该值,否则抛出异常
    • T orElse(T other):如果有值则将其返回,否则返回指定的other对象
    • T orElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回有Supplier接口实现通过的对象。
    • T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出有Supplier接口通过的异常。
/**
 * Optional类:为了在程序中避免出现空指针异常而创建
 * 常用方法:ofNullable(T t)
 *         orElse(T t)
 */
public class OptionalTest {

    /**
     * Optional.of(T t):创建一个 Optional 实例,t 必须非空
     * Optional.empty():创建一个空的 Optional 实例
     * Optional.ofNullable(T t):t 可以为 null
     */
    @Test
    public void test1(){
        Girl girl = new Girl();
        girl = null;
        //of(T t):保证t是非空的
        //Optional<Girl> optional1 = Optional.of(girl);
        //ofNullable(T t):t 可以为 null
        Optional<Girl> optionalGirl = Optional.ofNullable(girl);
        System.out.println(optionalGirl);//Optional.empty
        //orElse(T t1):如果当前的Optional内部封装的t是非空的,则返回内部的t
        //            如果内部t是空的,则返回orElse()方法中的参数t1
        Girl girl1 = optionalGirl.orElse(new Girl("小芳"));
        System.out.println(girl1);//Girl{name='小芳'}
    }

    //可能会报空指针异常
    public String getGirlName(Boy boy){
        return boy.getGirl().getName();
    }

    @Test
    public void test2(){
        Boy boy = new Boy();
        String girlName = getGirlName(boy);
        System.out.println(girlName);
    }

    //优化以后的getGirlName
    public String getGirlName1(Boy boy){
        if(boy != null){
            Girl girl = boy.getGirl();
            if(girl != null){
                return girl.getName();
            }
        }
        return null;
    }
    @Test
    public void test3(){
        Boy boy = new Boy();
        String girlName = getGirlName1(boy);
        System.out.println(girlName);
    }

    //使用Optional类的getGirlName
    public String getGirlName2(Boy boy){
        Optional<Boy> boyOptional = Optional.ofNullable(boy);
        //此时的boy1一定非空
        Boy boy1 = boyOptional.orElse(new Boy(new Girl("小翠")));
        Girl girl = boy1.getGirl();
        Optional<Girl> girlOptional = Optional.ofNullable(girl);
        //此时的girl1一定非空
        Girl girl1 = girlOptional.orElse(new Girl("小花"));
        return girl1.getName();
    }
    @Test
    public void test4(){
        Boy boy = null;//小翠
        boy = new Boy();//小花
        boy = new Boy(new Girl("小倩"));//小倩
        String girlName = getGirlName2(boy);
        System.out.println(girlName);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76



Java9 新特性

自从2017年9月21日 Java 9 正式发布之时,Oracle 就宣布今后会按照每个六月一次的节奏进行更新,在过去的几个月中,我们见证了其兑现了诺言,但万万没想到,苦了大批迎头而上的开发者们。


Java9 发布

  • 经过4次跳票,历经曲折的Java 9 终于在2017年9月21日发布。
  • 从 Java 9 这个版本开始,Java 的计划发布周期是 6个月,下一个 Java 的主版本将在 2018年3月发布,命为 Java 18.3,紧接着再过六个月将发布 Java 18.9。
  • 这意味着Java的更新从传统的以特性启动的发布周期,转变为以时间驱动的(六个月为周期)发布模式,并逐步的将 Oracle JDK 源商业特性进行开源。
  • 针对企业客户的需求,Oracle 将以 三年为周期发布长期支持版本(long term support)。
  • Java 9 通过了超过150项新功能特性,包括备受期待的模块化系统、可交互的 REPL工具:jshell,JDK编译工具,Java 公共 API 和私有代码,以及安全增加、扩展提升、性能管理改善等。可以说Java 9 是一个庞大的系统工程,完全做了一个整体改变。

oracle 理念 与 小步快跑,快速迭代

下载地址



一、JDK 和 jre 目录结构的改变

在这里插入图片描述

在这里插入图片描述


二、模块化系统:Jigsaw -> Modularity

  • 谈到 Java 9 大家往往第一个想到的就是 Jigsaw 项目。众所周知,Java 已经发展超过 20 年(95年最初发布),Java 和相关生态在不断丰富的同时也越来越暴露出一些问题:
    • Java 运行环境的膨胀和臃肿。每次JVM启动的时候,至少会有30~60M的内存加载,主要原因是JVM需要加载rt.jar,不管其中的类是否被 classloader加载,第一步这个jar都会被JVM加载到内存当中去(而模块化可以根据模块的需要加载程序运行需要的class)
    • 当代码库越来越大,创建复杂,盘根错节的“意大利面条式代码”的几率呈现指数级的增长。不同版本的类库交叉依赖导致让人头疼的问题,这些都阻碍了 Java 开发和运行效率的提升。
    • 很难真正地对代码进行封装,而系统并没有对不同部分(也就是JAR文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它公共类所访问到,这样就导致无意中使用了并不想被公开访问的API
  • 本质上讲也就是说,用模块来管理各个package,通过声明某个package暴露,模块(module)的概念,其实就是package外再裹一层,不声明默认就是隐藏。因此,模块化使得代码组织上更安全,因为它可以指定哪些部分可以暴露,哪些部分隐藏。
  • 实现目标:
    • 模块化的主要目的在于减少内存的开销
    • 只须必要模块,而非全部jdk模块,可简化各种类库和大型应用的开发和维护
    • 改进 Java SE 平台,使其可以适应不同大小的计算设备
    • 改进其安全性,可维护性,提高性能

模块将由通常的类和新的模块声明文件(module-info.java)组成。该文件是位于java代码块结构的顶层,该模块描述符明确地定义了我们的模块需要什么依赖关系,以及哪些模块被外部使用。在exports子句中未提供及的所有包默认情况下将封装在模块中,不能在外部使用。

在这里插入图片描述

在模块1服务,创建一个类,然后生成一个 module-info.java

package com.zzpedu.bean;

public class Person {
    private String name;
    private int age;

 	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

module-info.java对外暴露出去

module chapter {
    exports com.zzpedu.bean;
}
  • 1
  • 2
  • 3

在模块2服务,创建测试类,然后生成一个 module-info.java

module-info.java 引用 模块1服务

module java9test {
    requires chapter;
}
  • 1
  • 2
  • 3

测试类:

package com.zzpedu;

import com.zzpedu.bean.Person;

public class ModuleTest {
    public static void main(String[] args) {
        Person person = new Person("tom",18);
        System.out.println("person=" + person);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

执行效果:

在这里插入图片描述


三、Java的REPL工具:jShell命令

  • 产生背景
    Python 和 Scala 之类的语言早就有交互编程环境 REPL(read - evaluate - print - loop)了,以交互式的方式对语句和表达式进行求值。开发者只需要输入一些代码,就可以在编译前获得对程序的反馈。而之前的Java版本要想执行代码,必须创建文件、声明类、提供测试方法方可实现

  • 设计理念
    即写即得、快速运行

  • 实现目标

  • Java 9 中终于拥有了 REPL工具:jShell。让Java可以像脚本语言一样运行,从控制台启动jShell,利用jShell在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行Java的代码,而无需创建Java文件,无需跟人解释 “public static void main(String[] args){}

  • jShell也可以从文件中加载语句或者将语句保存到文件中

  • jShell也可以是tab键进行自动补全和自动添加分号。

在这里插入图片描述

获取相关命令的列表:

/help intro
在这里插入图片描述
在这里插入图片描述

导入指定的包

import java.util.*;
在这里插入图片描述

默认已经导入如下的所有包:(包含java.lang包)

/imports
在这里插入图片描述

Tips: 在 jShell 环境下,语句末尾的 “;” 是可以可选的。但推荐还是最后加上。提高代码可读性。

一些命令演示:

在这里插入图片描述

可以覆盖以前的变量或者方法等
在这里插入图片描述

只需要按下 Tab 键,就能自动补全代码

在这里插入图片描述

列出当前session 里所有有效的代码片段

在这里插入图片描述

使用 /open命令调用执行 .java文件

/open D:\xx\xx.java

没有受检异常(编译异常)

在这里插入图片描述
说明:本来应该强迫我们捕获一个 IOException,但没有出现。因为jShell在后台为我们隐藏了。
在这里插入图片描述

退出jShell

/exit
在这里插入图片描述


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

Java 8中规定接口中的方法除了抽象方法之外,还可以定义静态方法和默认的方法。一定程读度上,扩展了接口的功能,此时的接口更像是一个抽象类。

在Java 9中,接口更加的灵活和强大,连方法的访问权限修饰符都可以声明为 private 的了,此时方法将不会成为你对为暴露的API的一部分。

public interface MyInterface {
    //如下的三个方法的权限修饰符都是 public
    void methodAbstract();
    static void methodStatic(){
        System.out.println("我是接口中的静态方法");
    }
    default void methodDefault(){
        System.out.println("我是接口中的默认方法");
        methodPrivate();
    }
    // jdk9 中允许接口中定义私有方法
    private void methodPrivate(){
        System.out.println("我是接口中的私有方法");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
public class MyInterfaceImpl implements MyInterface{
    @Override
    public void methodAbstract() {

    }

    @Override
    public void methodDefault() {
        MyInterface.super.methodDefault();
        System.out.println("实现类重写接口中的默认方法methodDefault");
    }

    public static void main(String[] args) {
        //接口中的静态方法只能由接口自己调用
        MyInterface.methodStatic();
        //接口的实现类不能调用接口的静态方法
       // MyInterfaceImpl.methodStatic();

        MyInterfaceImpl impl = new MyInterfaceImpl();
        impl.methodDefault();
        //不能调用接口中私有方法
        //impl.methodPrivate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

执行效果:

在这里插入图片描述


五、语法改进:钻石操作符使用升级

我们将能够于匿名实现类共同使用钻石操作符(diamond operator)
Java 8 中如下操作是会报错的:

Comparator<Object> com = new Comparator<>() {
     @Override
     public int compare(Object o1, Object o2) {
         return 0;
     }
 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

编译报错信息:Cannot use “<>” with anonymous inner classes.

Java 9中如下操作可以正常执行通过:

// anonymous classes can now use type inference
Comparator<Object> com = new Comparator<>() {
    @Override
    public int compare(Object o1, Object o2) {
        return 0;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

代码示例:

//java9特性五:钻石操作符的升级
@Test
public void test1(){
    //钻石操作符与匿名内部类在jdk 8中不能共存(正确写法:new Comparator<>(Object))。在java 9可以
    Comparator<Object> com = new Comparator<>() {
        @Override
        public int compare(Object o1, Object o2) {
            return 0;
        }
    };

    //jdk7中的新特性:类型推断
    ArrayList<String> list = new ArrayList<>();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

六、语法改进:try语句

Java 8中,可以实现资源的自动关闭,但是要求执行后必须关闭所有资源必须在try子句中初始化,否则编译不通过。如下例所示:

try(InputStreamReader reader = new InputStreamReader(System.in)){
	//读取数据细节操作
}catch (IOException e) {
	e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5

Java 9中,用资源语句编写try将更容易,我们可以在try子句中使用已经初始化过的资源,此时的资源是final的:

InputStreamReader reader = new InputStreamReader(System.in)
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try(reader; writer ){
	//reader是final的,不可再被赋值
	//reader = null;
	//具体读写操作...
}catch (IOException e) {
	e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

代码示例:

public static void main(String[] args) {
   //java 8之前的资源关闭的操作
//        InputStreamReader reader = null;
//        try {
//            //读取控制台的输入,然后再输出
//            reader = new InputStreamReader(System.in);
//            char[] chars = new char[20];
//            int len;
//            if((len = reader.read(chars)) != -1){
//                String str = new String(chars,0,len);
//                System.out.println(str);
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }finally {
//            if(reader != null){
//                try {
//                    reader.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//        }

   //java 8中资源关闭操作:Java 8中,可以实现资源的自动关闭
   //要求自动关闭的资源的实例化必须放在try的一对小括号中
//        try (InputStreamReader reader = new InputStreamReader(System.in)){
//            char[] chars = new char[20];
//            int len;
//            if((len = reader.read(chars)) != -1){
//                String str = new String(chars,0,len);
//                System.out.println(str);
//            }
//        }catch (IOException e) {
//            e.printStackTrace();
//        }

   //java 9中资源关闭操作:需要自动关闭的资源的实例化可以放在try的一对小括号外。
   //此时的资源属性是常量,声明为final的,不可修改
   InputStreamReader reader = new InputStreamReader(System.in);
   try (reader){
       char[] chars = new char[20];
       int len;
       if((len = reader.read(chars)) != -1){
           String str = new String(chars,0,len);
           System.out.println(str);
       }
       //reader = null;//错误 不能修改
   } catch (IOException e) {
       e.printStackTrace();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

七、String存储结构变化


Motivation
The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.
诱因:String类的当前实现将字符存储在字符数组中,每个字符使用两个字节(16位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且大多数字符串对象只包含拉丁字符。这样的字符只需要一个字节的存储空间,因此这样的字符串对象的内部字符数组中有一半的空间没有使用。

Description
We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.
描述:我们建议将字符串类的内部表示形式从UTF-16字符数组更改为字节数组加上编码标志字段。新的String类将根据字符串的内容存储编码为ISO-8859-1/Latin-1(每个字符一个字节)或UTF-16(每个字符两个字节)的字符。编码标志将指示使用哪种编码。

java9之前的String存储结构,是使用private final char value[];char数组类型

在这里插入图片描述

Java 9的String存储结构,是使用private final byte[] value;byte数组类型:

在这里插入图片描述

结论:String 再也不用 char[] 来存储,改成 byte[] 加上编码标记,节约了一下空间。

那么StringBuffer 和 StringBuilder 是否仍然不动呢?

String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM’s intrinsic(固有的、内置的) string operations.
与字符串相关的类,如AbstractStringBuilder、StringBuilder和StringBuffer,将被更新为使用相同的表示形式,HotSpot VM的固有字符串操作也是如此。


八、集合工厂方法:快速创建只读集合

要创建一个只读、不可改变的集合,必须构造和分配它,然后添加元素,最后包装成一个不可修改的集合。

List<String> list = new ArrayList<>();
list.add("jack");
list.add("tom");
list.add("milan");

list = Collections.unmodifiableList(list);
System.out.println(list);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

缺点:我们一下子写了五行。即:它不能表达为单个表达式。

List<String> list = Collections.unmodifiableList(Arrays.asList("a","b","c"));
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a","b","c")));
//如下操作不适用于jdk 8 及之前版本,适用于 jdk 9
Map<String,Integer> map = Collections.unmodifiableMap(new HashMap<>(){
    {
        put("a",1);
        put("b",2);
        put("c",3);
    }
});
map.forEach((k,v) -> System.out.println(k + ":" + v));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Java 9因此引入了方便的方法,这使得类似的事情更容易表达

在这里插入图片描述

List<Integer> list = List.of(1, 2, 3, 4, 5);
调用集合中静态方法of(),可以将不同数量的参数传输到此工厂方法中。此功能可用于 Set和List,也可以用于Map的类似形式。此时得到的集合,是不可变的:在创建后,继续添加元素到这些集合会导致UnsupportedOperationException
由于Java 8中接口方法的实现,可以直接在List,Set和Map的接口内定义这些方法,便于调用。

代码示例:

public class Java9Test1 {

    //java8中的写法:
    @Test
    public void test1(){
        List<String> list = new ArrayList<>();
        list.add("jack");
        list.add("tom");
        list.add("milan");

        list = Collections.unmodifiableList(list);
        //返回的list是一个只读的集合
        //list.add("king");//运行报错 UnsupportedOperationException
        System.out.println(list);
    }

    @Test
    public void test2(){
        List<String> list = Collections.unmodifiableList(Arrays.asList("a","b","c"));
        Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a","b","c")));
        //如下操作不适用于jdk 8 及之前版本,适用于 jdk 9
        Map<String,Integer> map = Collections.unmodifiableMap(new HashMap<>(){
            {
                put("a",1);
                put("b",2);
                put("c",3);
            }
        });
        map.forEach((k,v) -> System.out.println(k + ":" + v));
    }

    @Test
    public void test3(){
        //此时得到的集合list也是一个只读集合
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        //源码:
        /* AbstractList类
         public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
         */
       // list.add(6);//抛出异常 UnsupportedOperationException 说明 Arrays.asList 是不可修改
    }

    //java9新特性:集合工厂方法:快速创建只读集合
    @Test
    public void test4(){
        List<Integer> list1 = List.of(1, 2, 3, 4, 5);
        //不能添加
        //list1.add(6);//抛出异常 UnsupportedOperationException
        System.out.println(list1);

        Set<Integer> set1 = Set.of(12, 23, 34);
        //不能添加
        //set1.add(1);
        System.out.println(set1);

        Map<String, Integer> map1 = Map.of("tom", 23, "jack", 18);
        //不能添加
        //map1.put("king",50);
        System.out.println(map1);

        Map<String, Integer> map2 = Map.ofEntries(Map.entry("tom", 12), Map.entry("jack", 21));
        //不能添加
        //map2.put("milan",24);
        System.out.println(map2);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

九、InputStream 加强

InputStream 终于有了一个非常有用的方法:transferTo(),可以用来将数据直接传输到 OutputStream,这是在处理原数据时非常常见的一种用法,如下示例:

//java9新特性:InputStream的新方法:transferTo()
@Test
public void test5(){
    ClassLoader c1 = this.getClass().getClassLoader();
    try(InputStream is = c1.getResourceAsStream("hello.txt");
      OutputStream os = new FileOutputStream("src\\hello1.txt")){

        is.transferTo(os);//把输入流中的所有数据直接自动复制到输出流中
    }catch (IOException e){
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

十、增强的 Stream API

  • Java 的Stream API 是java标准库最好改进之一,让开发者能够快速运算,从而能够有效的利用数据并进行计算。Java 8 提供的 stream 能够利用多核架构实现声明式的数据处理。

  • 在 Java 9 中,Stream API 变得更好,Stream 接口中添加了 4个新的方法:takeWhile, dropWhile, offNullable,还有个 iterate 方法的重载方法,可以让你提供一个 Predicate(判断条件)来指定什么时候结束迭代。

  • 除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 stream() 将一个 Optional 对象转换为一个(可能是空的)Stream 对象。

takeWhile() 的使用

用于从 Stream 中获取一部分数据,接收一个 Predicate 来进行选择。在有序的 Stream 中,返回从开头开始的按照指定规则尽量多的元素。

List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 90, 73, 67, 88);
list.stream().takeWhile(x -> x < 50).forEach(System.out :: println);

System.out.println();

list = Arrays.asList(1,2,3,4,5,6,7,8);
list.stream().takeWhile(x -> x < 5).forEach(System.out :: println);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

dropWhile() 的使用

dropWhile 的行为与 takeWhile 相反,返回剩余的元素

ofNullable() 的使用

Java 8 中 Stream 不能完全为null,否则会报空指针异常。而 Java 9 中的 ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。

// 报NullPointerException
// Stream<Object> stream1 = Stream.of(null);
// System.out.println(stream1.count());
// 不报异常,允许通过
Stream<String> strStream = Stream.of("AA","BB",null);
System.out.println(strStream.count());// 3
// 不报异常,允许通过
List<String> list= new ArrayList<>();
list.add("AA");
list.add(null);
System.out.println(list.stream().count());// 2
// ofNullable():允许值为null
Stream<Object> stream1 = Stream.ofNullable(null);
System.out.println(stream1.count());// 0
Stream<String> stream = Stream.ofNullable("hello world");
System.out.println(stream.count());// 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

iterate() 的使用

这个 iterate 方法的新重载方法,可以让你提供一个 Predicate(判断条件)来指定什么时候结束迭代

// 原来的控制终止方法
Stream.iterate(1, i -> i + 1).limit(10).forEach(System.out :: println);
// 现在的终止方法
Stream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out :: println);
  • 1
  • 2
  • 3
  • 4
//java9新特性十:Stream API的加强
@Test
public void test1(){
    List<Integer> list = Arrays.asList(45, 43, 76, 87, 42, 77, 90, 73, 67, 88);
    //takeWhile 返回从开头开始的按照指定规则尽量多的元素。
    //list.stream().takeWhile(x -> x < 50).forEach(System.out :: println);
    //与 takeWhile 相反,返回剩余的元素
    list.stream().dropWhile(x -> x < 60).forEach(System.out :: println);
    System.out.println();

    list = Arrays.asList(1,2,3,4,5,6,7,8);
    list.stream().takeWhile(x -> x < 5).forEach(System.out :: println);
}

@Test
public void test2(){
    //of()参数中的多个元素,可以包含多个null值
    Stream<Integer> stream1 = Stream.of(1,2,3,null);
    stream1.forEach(System.out::println);
    //of()参数不能存储单个null值,否则 抛出异常 NullPointerException
//        Stream<Object> stream2 = Stream.of(null);
//        stream2.forEach(System.out::println);

    Integer i = 10;
    i = null;
    //ofNullable():形参变量可以为null值的单个元素
    Stream<Integer> stream2 = Stream.ofNullable(i);
    long count = stream2.count();
    System.out.println(count);
}

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

    //java9中新增的重载的方法
    Stream.iterate(0,x -> x < 100,x -> x + 1).forEach(System.out::println);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

十一、Optional获取Stream的方法

Optional类中stream() 的使用

List<String> list = new ArrayList<>();
list.add("tom");
list.add("jerry");
list.add("king");

Optional<List<String>> optional = Optional.ofNullable(list);
Stream<List<String>> stream = optional.stream();
stream.flatMap(x -> x.stream()).forEach(System.out :: println);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
//java9新特性十一:Optional提供了新的方法stream()
@Test
public void test4(){
    List<String> list = new ArrayList<>();
    list.add("tom");
    list.add("jerry");
    list.add("king");

    Optional<List<String>> optional = Optional.ofNullable(list);
    Stream<List<String>> stream = optional.stream();
//        long count = stream.count();
//        System.out.println(count);//1
    stream.flatMap(x-> x.stream()).forEach(System.out :: println);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

十二、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 10 新特性

  • 2018年3月12份,Oracle官方宣布Java10正式发布
  • 需要注意的是 Java 9 和 Java 10 都不是 LTS(Long- Term-Support) 版本。和过去的 Java 大版本升级不同,这两个只有半年左右的开发和维护期。而Java 11,也就是 18.9 LTS,才是 Java 8 之后第一个 LTS 版本。
  • JDK10 一共定义了109个新特性,其中包含12个JEP(对于程序员来讲,真正新特性其实就一个),还有一些新特性API和JVM规范以及JAVA语音规范上的改动。
  • JDK10的12个JEP(JDK Enhancement Proposal特性加强提议)参阅官方文档:http://openjdk.java.net/projects/jdk/10/

JDK10的12个JEP

286: Local-Variable Type Inference :局部变量类型推断
296: Consolidate the JDK Forest into a Single Repository :将JDK林整合到单个存储库中
304: Garbage-Collector Interface :统一的垃圾收集器接口
307: Parallel Full GC for G1 :为G1提供的并行的Full GC
310: Application Class-Data Sharing : 应用程序级数据(AppCDS)共享
312: Thread-Local Handshakes :线程本地握手
313: Remove the Native-Header Generation Tool (javah) : 移除JDK中附带的javah工具
314: Additional Unicode Language-Tag Extensions :使用附加的Unicode语言标记扩展
316: Heap Allocation on Alternative Memory Devices :能将堆内存占用分配给用户指定的备用内存设备
317: Experimental Java-Based JIT Compiler :使用基于Java的JIT编译器
319: Root Certificates :根证书
322: Time-Based Release Versioning :基于时间的发布版本


一、局部变量类型推断

  • 产生背景
    开发者经常抱怨Java中引用代码的程度。局部变量的显示类型声明,常常被认为是是不必须的,给一个好听的名字经常可以很清楚的表达出下面应该怎么样继续。

  • 好处
    减少了啰嗦形式的代码,避免了信息冗余,而且对齐了变量名,更容易阅读

  • 举例如下

    • 场景一:类实例化
      作为 Java 开发者,在声明一个变量时,我们总习惯了敲打两次变量类型,第一次用于声明变量类型,第二次用于构造器。

    LinkedHashSet<Integer> set = new LinkedHashSet<>();

    • 场景二:返回值类型含复杂泛型结构
      变量的声明类型书写复杂且较长,尤其是加上泛型的使用

    Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();

    • 场景三:
      我们也经常声明一种变量,它只会被使用一次,而且是用在下一行代码中,
      比如:
    URL url = new  URL("http://www.baidu.com");
    URLConnection connection = url.openConnection();
    Reader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    
    • 1
    • 2
    • 3

    尽量IDE可以帮我们自动完成这些代码,但当变量总是跳来跳去的时候,可读性还是会受到影响,因为变量类型的名称有各种不同长度的字符组成。而且,有时候开发人员会尽力避免声明中间变量,因为太多的类型声明只会分散注意力,不会带来额外的好处。

使用于以下情况

//1. 局部变量额初始化
var list = new ArrayList<Integer>();
list.add(123);
//2. 增强for循环中索引
for(var v : list){
	System.out.println(v);
}
//3. 传统for循环中
for (var i = 0; i < 100; i++) {
    System.out.println(i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在局部变量中使用时,如下情况不适用:

初始值为 null

var a = null;
错误:无法推断本地变量 a 类型
<变量初始化程序为 'null'>
  • 1
  • 2
  • 3

方法引用

var r = System.out::println;
错误:无法推断本地变量 r 类型
<方法引用需要显式目标类型>
  • 1
  • 2
  • 3

Lambda表达式

var r = () -> Math.randon();
错误:无法推断本地变量 r 类型
<Lambda 表达式需要显式目标类型>
  • 1
  • 2
  • 3

为数组静态初始化

var arr = {"a","b","c"};
错误:无法推断本地变量 r 类型
<数组初始化程序需要显式目标类型>
  • 1
  • 2
  • 3
/**
 * java10的新特性一:局部变量的类型推断
 */
@Test
public void test1(){
    //1. 声明变量时,根据所附的值,推断变量的类型
    var num = 10;

    var list = new ArrayList<Integer>();
    list.add(123);

    //2. 遍历操作
    for(var i : list){
        System.out.println(i);
        System.out.println(i.getClass());
    }

    //3. 普通的遍历操作
    for (var i = 0; i < 100; i++) {
        System.out.println(i);
    }
}

@Test
public void test2(){
    //1.局部变量不赋值,就不能实现类型推断
    //var num; //错误

    //2.lambda表达式中, 左边的函数式接口不能声明为var
    Supplier<Double> sup = () -> Math.random();
    //var sup = () -> Math.random();//错误

    //3.方法引用中, 左边的函数式接口不能声明为var
    Consumer<String> con = System.out::println;
    //var con = System.out::println;//错误

    //4.数组的静态初始化中,注意如下情况也不可以
    int[] arr ={1,2,3};
    //var arr ={1,2,3};//错误
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

不适用以下的结构中:

  • 情况1:没有初始化的局部变量声明
  • 情况2:方法的返回类型
  • 情况3:方法的参数类型
  • 情况4:构造器的参数类型
  • 情况5:属性
  • 情况6:catch块

工作原理
在处理 var时,编译器先是查看表达式右边部分,并根据右边变量值的类型进行推断,作为左边变量的类型,然后将该类型写入字节码当中。

注意

  • var不是一个关键字
    你不需要担心变量名或方法名会与 var发生冲突,因为var实际上并不是一个关键字,而是一个类型名,只有在编译器需要知道类型的地方才需要用到它。除此之外,它就是一个普通合法的标识符。也就是说,除了不能用它作为类名,其他的都可以,但极少人会用它作为类名。

  • 这不是JavaScript
    首先说明的是,var并不是改变Java是一门静态类型语言的事实。编译器负责推断出类型,并把结果写入字节码文件,就好像是开发人员自己敲入类型一样。下面是使用 InteliJ(实际上是 Fernflower的反编译器)反编译器反编译出代码:

    var url = new URL("http://www.baidu.com");
    var connection = url.openConnection();
    var reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    
    • 1
    • 2
    • 3

    反编译后

    URL url = new URL("http://www.baidu.com");
    URLConnection connection = url.openConnection();
    BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    
    • 1
    • 2
    • 3

    从代码来看,就好像之前已经声明了这些类型一样。事实上,这一特性只发生在编译阶段,与运行时无关,所以对运行时的性能不会产生任何影响。所以请放心,这不是 JavaScript。

@Test
public void test3(){
    //情况1:没有初始化的局部变量声明
    var r = null;//错误

    //情况6:catch块
    try {

    }catch (var e){//错误
        e.printStackTrace();
    }
}
//情况2:方法的返回类型
public var method1(){//错误
    return 1;
}
//情况3:方法的参数类型
public void method2(var num){//错误
    return 0;
}
//情况4:构造器的参数类型
public Java10Test(var i){//错误
}
//情况5:属性
var num;//错误


@Test
public void test4(){
    try {
        var url = new URL("http://www.baidu.com");
        var connection = url.openConnection();
        var reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

二、集合新增创建不可变集合的方法

自Java 9 开始,jdk 里面为集合(List/Set/Map)都添加了 of(jdk9新增)copyOf(jdk10新增)方法,它们两个都用来创建不可变的集合,来看下它们的使用和区别

//实例1
var list1 = List.of("Java","Python","C");
var copy1 = List.copyOf(list1);
System.out.println(list1 == copy1);//true

//示例2
var list2 = new ArrayList<String>();
var copy2 = List.copyOf(list2);
System.out.println(list2 == copy2);//false

//实例1和实例2代码基本一致,为什么一个true,一个为false?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
//java10的新特性二:集合中新增的copyOf(),用于创建一个只读的集合
@Test
public void test5(){
    //实例1
    var list1 = List.of("Java","Python","C");
    var copy1 = List.copyOf(list1);
    System.out.println(list1 == copy1);//true

    //示例2
    var list2 = new ArrayList<String>();
    list2.add("aaa");
    var copy2 = List.copyOf(list2);
    System.out.println(list2 == copy2);//false

    //实例1和实例2代码基本一致,为什么一个true,一个为false?
    //结论:copyOf(Xxx coll):如果参数coll本身就是一个只读集合,则copyOf()返回值即为当前的coll
    //  如果参数coll不是一个只读集合,则copyOf()返回一个新的集合,这个集合是只读的。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

从源码分析,可以看出 copyOf() 方法会先判断来源集合是不是 AbstractImmutableList 类型,如果是,就直接返回,如果不是,则调用 of() 创建一个新的集合。

实例2因为用的 new 创建的集合,不属于不可变 AbstractImmutableList 类的子类,所以 copyOf() 方法又创建了一个新的实例,所以为 false

注意:使用 of 和 copyOf 创建的集合为不可变集合,不能进行添加、删除、替换、排序等操作,不然会报 java.lang.UnsupportedOperationException异常。

上面演示了 List的 of 和 copyOf 方法,Set 和 Map 接口都有。



java11 新特性

北京时间 2018年9月26日,Oracle官方宣布 Java 11 正式发布。这是 Java 大版本周期变化后的第一个长期支持版本,非常值得关注。从官网即可下载,最新发布的 Java11 将带来 ZGC Http Cilent 等重要特性,一共包括 17 个 JEP(JDK Enhancement Proposals,JDK 增强提案)。其实,总共更新不止17个,只是我们更关注如下17个JEP更新。

参阅官方文档:http://openjdk.java.net/projects/jdk/11/

官网公开的17个 JEP (JDK Enhancement Proposals,JDK 特性增强提议)

181: Nest-Based Access Control :基于嵌套的访问控制
309: Dynamic Class-File Constants :动态的类文件常量
315: Improve Aarch64 Intrinsics :改进 Aarch64 Intrinsics
318: Epsilon: A No-Op Garbage Collector :Epsilon垃圾回收器
320: Remove the Java EE and CORBA Modules :移除JavaEE和CORBA模块
321: HTTP Client (Standard) :HTTP客户端(标准)
323: Local-Variable Syntax for Lambda Parameters :Lambda参数的局部变量语法
324: Key Agreement with Curve25519 and Curve448 :与Curve25519和Curve448的关键协议
327: Unicode 10 :Unicode 10
328: Flight Recorder :飞行记录器
329: ChaCha20 and Poly1305 Cryptographic Algorithms :实现ChaCha20 和 Poly1305加密算法
330: Launch Single-File Source-Code Programs :启动单个Java源代码的程序
331: Low-Overhead Heap Profiling :低开销的堆分配采样方法
332: Transport Layer Security (TLS) 1.3 : 对 TLS 1.3 的支持
333: ZGC: A Scalable Low-Latency Garbage Collector
   (Experimental) :ZGC:可伸缩的低延迟垃圾收回收器,处于实验阶段)
335: Deprecate the Nashorn JavaScript Engine : 弃用Nashorn JavaScript引擎
336: Deprecate the Pack200 Tools and API : 弃用使用Pack200工具和API

JDK 11 将是一个 企业不可忽视的版本。 从时间节点来看, JDK 11 的发布正处于在 JDK 8 免费更新到期的前夕,同时 JDK 9、10 也陆续称为 “历史版本”,其他java版本发现详情 https://www.oracle.com/java/technologies/java-se-support-roadmap.html

JDK 11 是一个长期支持版本(LTS,Long-Term-Support)

  • 对于企业来说,选择 11 将意味着长期的、可靠的、可预算的技术路线图。其中免费的OpenJDK11 确定将得到 OpenJDK 社区的长期支持,LTS 版本将是可以放心选择的版本。
  • 从JVM GC 的角度,JDK11 引人了两种新的 GC,其中包括也许是划时代意义的 ZGC,虽然其目前还是实验特性,但是从能力上来看,这是JDK的一个巨大突破,为特性生产环境的苛刻需求提供了一个可能的选择。例如,对部分企业核心存储等产品,如果能够保证不超过 10ms 的 GC 暂停,可靠性会上一个大的台阶,这是过去我们进行 GC 调优几乎做不到的,是能与不能的问题。

按照官方的说法,新的发布周期会严格遵循时间点,将于每年的3月份和9月份发布。所以 Java 11 的版本号是 18.9(LTS)。

不过与 Java 9 和 Java 10 这两个被称为“功能性的版本”不同(两者均只提供半年的技术支持),Java 11 不仅提供了长期支持服务,还将作为 Java 平台的参考实现。Oracle 直到2023年9月都会 Java 11 提供技术支持,而补丁和安全警告等扩展支持将持续到2026年。

在这里插入图片描述

在这里插入图片描述


一、新增了一系列字符串处理方法

描述举例
判断字符串是否为空白" ".isBlank();//true
去除首尾空白" Javastack ".strip();//"Javastack"
去除尾部空白" Javastack ".stripTrailing();//" Javastack"
去除首部空白" Javastack ".stripLeading();//"Javastack "
复制字符串"Java".repeat(3);//"JavaJavaJava"
行数统计"A\nB\nC".lines().count();//3
//java 11新增特性一:String中新增的方法
@Test
public void test1(){
    //isBlank():判断字符串是否为空白
    System.out.println(" \t \t \n ".isBlank());//true
    //strip():去除首尾空白
    System.out.println("----" + "  \t abd \t \n ".strip() + "---");//----abd---
    System.out.println("----" + "  \t \t abc \n ".trim() + "---");//----abc---
    //stripTrailing():去除尾部空白
    System.out.println("----" + "  \t \t abc \n ".stripTrailing() + "---");//----  	 	 abc---
    //stripLeading():去除首部空白
    System.out.println("----" + "  \t \t abc \n ".stripLeading() + "---");//----abc
                                                                            //---
    String str = "abc";
    //repeat():复制字符串
    System.out.println(str.repeat(3));//abcabcabc
    //lines().count():行数统计
    String str2 = "abc\nde\nfg";
    System.out.println(str2.lines().count());//3
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

执行效果;

在这里插入图片描述


二、Optional 加强

Optional 也增加了几个非常酷的方法,现在可以很方便的将一个 Optional 转换成一个 Stream,或这当一个空 Optional 时给它一个替代的。

新增方法描述新增版本
boolean isEmpty()判断是否为空JDK 11
ifPresentOrElse(Consumer<? super T> action,Runnable emptyAction)value非空,执行参数1功能;如果value为空,执行参数2功能JDK 9
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)value非空,返回对应的Optional;value为空,返回形参封装的OptionalJDK 9
Stream<T> stream()value非空,返回仅包含此value的Stream;否则,返回一个空的StreamJDK 9
T orElseThrow()value非空,返回value;否则抛出异常NosuchElementExceptionJDK 10
//java 11新增特性二:Optional新增的方法
@Test
public void test2(){
    var op = Optional.empty();
    System.out.println(op.isPresent());//java8 判断内部value是否存在 false
    System.out.println(op.isEmpty());//判断内部的value是否为空 true

    op = Optional.of("abc");
    //orElseThrow():如果为空的 抛出异常java.util.NoSuchElementException: No value present
    var obj = op.orElseThrow();
    System.out.println(obj);//abc

    //or():value非空,返回对应的Optional;value为空,返回形参封装的Optional
    Optional<String> op1 = Optional.of("hello");
    //op = Optional.empty();
    Optional<Object> op2 = op.or(() -> op1);
    System.out.println(op2);//Optional[abc]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

执行效果:

在这里插入图片描述


三、局部变量类型推断升级

在 var 上添加注解的语法格式,在jdk10中是不能实现的。在jdk中加入了这样的语法。

//错误的形式:必须要有类型,可以加上var
//Consumer<String> con1 = (@Deprecated t) -> System.out.println(t.toUpperCase());
//正确的形式:
//使用var的好处是在使用lambda表达式时给参数加上注解
Consumer<String> con2 = (@Deprecated var t) -> System.out.println(t.toUpperCase());
  • 1
  • 2
  • 3
  • 4
  • 5
//java 11新增特性三:局部变量类型推断的升级
@Test
public void test3(){
    //错误的形式:必须要有类型,可以加上var
    //Consumer<String> con1 = (@Deprecated t) -> System.out.println(t.toUpperCase());
    //正确的形式:
    //使用var的好处是在使用lambda表达式时给参数加上注解
    Consumer<String> con2 = (@Deprecated var t) -> System.out.println(t.toUpperCase());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

四、全新的HTTP 客户端API

  • HTTP,用于传输网页的协议,早在1997年就被采用在目前的1.1版本中。直到 2015年,HTTP2才成为标准

    在这里插入图片描述

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

  • 这是 Java 9 开始引入的一个处理 HTTP 请求的 HTTP Client API,该 API 支持同步和异步,而在 Java 11 中已经为正式可用状态,你可以在 java.net 包中找到这个 API

  • 它将替代仅使用于 blocking 模式的 HttpURLConnection(HttpURLConnection是在 HTTP 1.0的时代创建的,并使用了协议无关的方法),并提供对 WebSocket 和 HTTP/2的支持。

//java 11新增特性四:httpClint替换原有的HttpURLConnection
@Test
public void test4() throws IOException, InterruptedException {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();
    HttpResponse.BodyHandler<String> responseBodyHandler = HttpResponse.BodyHandlers.ofString();
    HttpResponse<String> response = client.send(request,responseBodyHandler);
    String body = response.body();
    System.out.println(body);
}
@Test
public void test5() throws ExecutionException, InterruptedException {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();
    HttpResponse.BodyHandler<String> responseBodyHandler = HttpResponse.BodyHandlers.ofString();
    CompletableFuture<HttpResponse<String>> sendAsync = client.sendAsync(request,responseBodyHandler);
    sendAsync.thenApply(t -> t.body()).thenAccept(System.out::println);
//        HttpResponse<String> response = sendAsync.get();
//        String body = response.body();
//        System.out.println(body);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

执行效果:

在这里插入图片描述


五、更简化的编译运行程序

看下面代码

//编译
javac Javastack.java
//运行
java Javastack

在我们的认知里面,要运行一个 Java 源码必须先编译,在运行,两步执行动作。而在未来的 Java 11 版本中,通过一个 Java 命令就直接搞定了,如下所示:

java Javastack.java

一个命令编译运行源代码的注意点:

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

六、废弃Nashorn引擎

废弃Nashorn Javascript引擎,在之后续版本准备移除掉,有需要的可以考虑使用 GraaIVM


七、ZGC

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

  • ZGC, A Scalable Low-Latency Garbage Collector(Experimental) ZGC,这应该是 JDK11最高瞩目的特性,没有之一。但是后面带了Experimental,说明这还不建议用到生产环境。

  • ZGC是一个开发,基于region,压缩型垃圾收集器,只有root扫描阶段会STW(stop the world),因此GC停顿时间不会随着堆的增长和存活对象的增长而变长。

  • 优势:

    • GC 暂停时间不会超过10ms
    • 即能处理几百兆的小堆,也能处理几个T的大堆(OMG)
    • 和G1相比,应用吞吐量和利用colord指针以及Load barriers优化奠定基础
    • 初始只支持64位系统
  • ZGC 的设计目标是:支持TB级内存容量,暂时时间低(<10ms),对整个程序吞吐量的影响小于15%。将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。


八、其他新特性

  • Unicode 10
  • Deprecate the Pack200 Tools and API
  • 新的Epsilon垃圾收集器
  • 完全支持Linux容器(包括Docker)
  • 支持G1上并行完全垃圾回收集
  • 最新的HTTPS安全协议TLS 1.3
  • Java Flight Recorder



在当前JDK中看不到什么?

一个标准化和轻量级的 JSON API

一个标准化和轻量级的JSON API被许多Java开发人员所青睐。但是由于资金问题无法在Java当前版本中见到,但并不会消减掉。Java平台首席架构师Mark Reinhold在JDK 9邮件列中说:“这个JEP将是平台上的一个有用的补充,但是在计划中,它并不像Oracle资助的其他功能那么重要,可能会重新考虑JDK 10或更高版本中实现。”


新的货币 API

  • 对许多应用而言货币价值都是一个关键的特性,但JDK对此却几乎没有任何支持。严格来讲,现有的 java.util.Currency类只是代表了当前 ISO 4217货币的一个数据结构,但并没有关联的值或者自定义货币。JDK对货币的运算及转换也没有内建的支持,更别说一个能够代表货币值的标准类型了。

  • 此前,Oracle 公布的 JSR 354定义了一套新的Java货币API:JavaMoney,计划会在Java中正式引入。但是目前没有出现在JDK新特性中。

  • 不过,如果你用的是Maven的话,可以做如下的添加,即可使用相关的API处理货币:

    <dependency>
    	<groupId>org.javamoney</groupId>
    	<artifactId>moneta</artifactId>
    	<version>0.9</version>
    </dependency>	
    
    • 1
    • 2
    • 3
    • 4
    • 5



展望

  • 随着云计算和 AI 等技术浪潮,当前的计算模式和场景正在发送翻天覆地的变化,不仅对 Java 的发展速度提出了更高要求,也深刻影响者 Java 技术的发展方向。传统的大型企业或互联网应用,正在被云端、容器化应用、模块化的微服务甚至是函数(FaaS, Function-as-a-Service)所替代。

  • Java虽然标榜面向对象编程,却毫无顾虑的加入面向接口编程思想,又扯出匿名对象之概念,每增加一个新的东西,对Java的根本所在的面向对象思想的一次冲击。反观Python,抓住面向对象的本质,又能在函数编程思想方面游刃有余。Java对标C/C++,以抛掉内存管理为卖点,却又陷入JVM优化的噩梦。选择比努力更重要,选择Java的人更加需要对它有更清晰的认识。

  • Java 需要在新的计算创建下,改进开发效率。这话说的有点笼统,我谈一些自己的体会,Java 代码虽然进行 一些类型推断的改进,更易用的集合API等,但仍然给开发者留下了过于刻版、形式主义的印象,这是一个长期的改进方向。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号