JDK8部分新特性

文章目录

    • @[toc] JDK8新特性
      • Lambda表达式
        • 1. Lambda表达式的标准格式
        • 2. 了解Lambda的实现原理
        • 3. Lambda的省略格式
        • 4. Lambda表达式的前提条件
        • 5. Lambda表达式与内明内部类比较
        • 6. jdk8中新增的两个方法
            • 介绍
            • 默认方法
            • 实例代码
            • 静态方法
            • 接口的静态方法和默认方法的区别
        • 7. 常用内置函数接口
            • 内置函数接口的由来
            • 常用的内置函数接口
        • 8.介绍方法引用
            • Lambda的冗余场景
            • 方法引用的格式
            • 常见的方法引用方式
        • 9. 对象::方法名
        • 10.类名::静态方法
        • 11. 类名::实例方法
        • 12.类名::new引用构造器
        • 13. 数组::new引用数组构造器
        • 14.方法引用小结
      • 集合之Stream流式操作
        • 1. Stream流介绍
          • 集合处理数据的弊端
          • Stream流的思想和作用
        • 2. 获取Stream的两种方法
          • 方式1:根据Collection获取流
          • 方式二:根据Stream的of方法获取流
        • 3. Stream流的常用方法和注意事项
          • 常用方法
          • 注意事项
        • 4. Stream流中的forEach方法
        • 5. Stream流中的count方法
        • 6. Stream流的Filter方法
        • 7. Stream流的limit方法
        • 8. Stream流的skip方法
        • 9.Stream流的map操作
        • 10.Stream流中的sorted方法
        • 11.Stream流中的distinct方法
        • 12.Stream流中的Math方法
        • 13.Stream流中的Find
        • 14.Stream流中的Max_Min
        • 15.Stream流中的reduce方法
        • 16. map方法和reduce方法结和使用
        • 17.Stream流的mapToInt
        • 18.Stream流的Concat
        • 19. Stream流综合操作
        • 20. 收集流中的结果
          • 1. Stream流中数据放入集合
          • 2. Stream流中数据放入数组
          • 3. Stream流中结果进行聚合计算
          • 4. Stream流中结果进行分组
          • 5. Stream流中结果进行多级分组
          • 6. Stream流中数据进行分区
          • 7. Stream流中数据进行拼接
          • 8. 小结
        • 21. 并行的Stream流
          • 1. 串行的Stream流
          • 2. 获取并行Stream流的两种方式
          • 3. 并行流串行流及for循环速度比较
          • 4. parallelStream线程问题
          • 5. parallelStream 背后的技术
            • Fork/Join框架介绍
            • Fork/Join原理-分治法
            • Fork/Join原理-工作窃取算法
            • Fork/Join 案例
      • Optional类
        • 以前对null的处理方式
        • Optional类介绍
        • Optional 的基本使用
        • Optional 类的高级使用:
      • JDK 8新的日期和时间 API
        • 1. 旧版日期时间 API 存在的问题
        • 2. 新日期时间 API介绍
        • 3. JDK 8 的日期和时间类
        • 4. JDK 8 的时间格式化与解析
        • 5. JDK 8 的 Instant 类
        • 6. JDK 8 的计算日期时间差类
        • 7. JDK 8 的时间校正器
        • 8. JDK 8 设置日期时间的时区
        • 9. 小结
      • 重复注解与类型注解
        • 1. 重复注解
        • 2. 类型注解
        • 3. 小结

JDK8新特性

Lambda表达式

1. Lambda表达式的标准格式

(参数类型 参数名称)->{

​ 方法体;

}

格式说明

  • (参数类型 参数名称):参数列表
  • {代码体}:方法体
  • ->:箭头,分隔参数列表和方法体

无参Lambda与匿名内部类

package com.lld;
import com.lld.Inte.Smoke;
import com.lld.Inte.Study;

/**
 * @ClassName Lambdademo1
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 17:51
 * @Version 1.0
 */
public class Lambdademo1 {
    /*
        Lambda的标准格式:
            (参数列表) -> {

            }
            (参数列表):参数列表
            {}:方法体
            ->:没有实际含义
     */

    public static void main(String[] args) {

        goSmokeing(() -> {
            System.out.println("Lambda在抽烟");
        });
        goSmokeing(new Smoke() {
            @Override
            public void smokeing() {
                System.out.println("匿名内部类在抽烟");
            }
        });

       
        }
    }


    //练习无参数的Lambda
    public static void goSmokeing(Smoke smoke){
        smoke.smokeing();
    }
}
package com.lld.Inte;

/**
 * @ClassName Smoke
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 18:00
 * @Version 1.0
 */
public interface Smoke {

    public abstract void smokeing();
}

有参Lambda与匿名内部类

package com.lld;

import com.lld.Inte.Smoke;
import com.lld.Inte.Study;


/**
 * @ClassName Lambdademo1
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 17:51
 * @Version 1.0
 */
public class Lambdademo1 {
    /*
        Lambda的标准格式:
            (参数列表) -> {

            }
            (参数列表):参数列表
            {}:方法体
            ->:没有实际含义
     */

    public static void main(String[] args) {

        goStudying(new Study() {
            @Override
            public int Studying(String str) {
                System.out.println(str);
                return 666;
            }
        },"曲秃在学习");

        goStudying((String str) -> {
            System.out.println(str);
            return 666;
        },"张渣在学习");
    }



    //练习有参数的Lambda
    public static void goStudying(Study study,String str){
        int i = study.Studying(str);
        System.out.println("返回值:" + i);
    }

}
package com.lld.Inte;

/**
 * @ClassName Study
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 18:39
 * @Version 1.0
 */
public interface Study {

    public int Studying(String str);

}

2. 了解Lambda的实现原理

匿名内部类在编译时候会生成一个class文件

Lambda在程序运行后会生成一个类

JDK8部分新特性_第1张图片

  1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
  2. 还会形成一个匿名内部类,实现接口,重写抽象方法
  3. 在接口的重写方法中会调用新生成的方法

3. Lambda的省略格式

Lambda标准格式上的基础上,使用省略写法的规则为:

  1. 小括号内的参数可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略return,大括号,分号

省略前

goStudying((String str) -> {
	System.out.println(str);
},"张渣在学习");
  • 运行结果

JDK8部分新特性_第2张图片

省略后

goStudying(str -> System.out.println(str),"张渣在学习");
  • 运行结果

JDK8部分新特性_第3张图片

4. Lambda表达式的前提条件

Lambda语法虽然简洁,但使用Lambda必须注意以下几点

  1. 方法的参数或者局部变量类型必须为接口才能使用Lambda
  2. 接口中只有一个抽象方法

示范代码

package com.lld;

/**
 * @ClassName demo02
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/6 22:11
 * @Version 1.0
 */
public class demo02 {
    /*
        1. 方法的参数或者局部变量类型必须为接口才能使用Lambda
        2. 接口中只有一个抽象方法
     */

    public static void main(String[] args) {
        //方法参数为接口时
        isFly(str -> {
            System.out.println(str);
            return true;
        });

        //局部变量类型为接口时
        Flyable flyable = str -> true;
    }

    public static void isFly(Flyable flyable){
        boolean b = flyable.isFly("是的");
        System.out.println(b);
    }
}
//只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda表达式
@FunctionalInterface//该注解用于检测该接口内是否只有一个方法,当该接口内有多个方法时,就会报错
interface Flyable{
    public boolean isFly(String str);

}

5. Lambda表达式与内明内部类比较

  1. 所需的类型不一样

    匿名内部类需要的类型可以是类,抽象类,接口

    Lambda表达式需要的必须是接口

  2. 抽象方法的数量不一样

    匿名内部类所需接口中抽象方法数量随意

    Lambda表达式所需接口只能有一个抽象方法

  3. 实现原理不同

    匿名内部类会在编译后形成class

    Lambda表达式在程序运行时动态形成class

6. jdk8中新增的两个方法

介绍

jdk8以前的接口

interface 接口名{
	静态常量;
	抽象方法
}

增强后的

interface 接口名{
	静态常量;
	抽象方法;
	默认方法;
	静态方法
}
默认方法

格式

interface 接口名{
	修饰符 default 返回值类型 方法名(){
		方法体;
	}
}
实例代码
package com.lld;

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 10:03
 * @Version 1.0
 */
public class demo03 {
    public static void main(String[] args) {
        BB bb = new BB();
        bb.test1();
        CC cc = new CC();
        cc.test1();
    }
}

interface AA{
    //默认方法是有方法体的
    public default void test1(){
        System.out.println("这是AA接口的默认方法");
    }
}

//默认方法使用方法一:实现类可以直接使用
class BB implements AA{

}

//默认方法使用方法二:实现类可以根据自己需要进行复写
class CC implements AA{
    @Override
    public void test1() {
        System.out.println("这是CC实现类复写的默认方法");
    }
}
静态方法

格式

interface 接口名{
	修饰符 static 返回值类型 方法名(){
        方法体;
    }
}

示例代码

package com.lld;

/**
 * @ClassName demo04
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 10:13
 * @Version 1.0
 */
public class demo04 {
    /*
        1. 接口的静态方法不会被实现类继承
        2. 接口的静态方法不会被实现类复写
        3. 接口的的静态方法使用:接口名.静态方法名进行调用
     */
    public static void main(String[] args) {
        AAA.test1();
    }
}


interface AAA{
    //接口的静态方法
    public static void test1(){
        System.out.println("这是接口AAA的静态方法");
    }
}


接口的静态方法和默认方法的区别
  1. 默认方法通过实例调用,静态方法通过接口名调用
  2. 默认方法可以被继承,实现类可以直接使用接口的默认方法
  3. 静态方法不能被继承,实现类不能重写接口的静态方法

小结

  • 如果方法要被实现类继承或者重写,使用默认方法,如果接口中的方法不需要被继承就使用静态

7. 常用内置函数接口

内置函数接口的由来

Lambda表达式的前提是函数式接口,而Lambda不需要关心接口名,抽象方法名,只关心参数列表及返回值类型

常用的内置函数接口

都在java.util.function;包下

Supplier接口

供给型接口,用于产生一个数据

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

  • 使用Supplier返回数组元素最大值
package com.lld;

import java.util.Arrays;
import java.util.function.Supplier;

/**
 * @ClassName demo05
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 15:08
 * @Version 1.0
 */
public class demo05 {
    /*
        使用Supplier接口的get方法,没有入参,有返回值
     */
    public static void main(String[] args) {
        text(() ->{
            //定义一个数组
            int array[] = {1,99,88,126,66};
            //对数组进行排序,默认升序排序
            Arrays.sort(array);
            //获取最大值
            int i = array[array.length - 1];
            return i;
        });
    }

    public static void text(Supplier<Integer> supplier){
        Integer integer = supplier.get();
        System.out.println("integer:" + integer);
    }
}

Consumer接口

消费型接口,用于消费一个数据,其参数类型由泛型决定

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
  • 使用Consumer将一个字符串先转成大写,在转成小写
package com.lld;

import java.util.function.Consumer;

/**
 * @ClassName demo06
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 15:39
 * @Version 1.0
 */
public class demo06 {

    /*
        使用Consumer将一个字符串转成小写,再转成大写
     */
    public static void main(String[] args) {
                   //转成小写再打印,s是入参                 转成大写再打印,s是入参
        printHello(s -> System.out.println(s.toLowerCase()),s -> System.out.println(s.toUpperCase()));
    }

    public static void printHello(Consumer<String> c1,Consumer<String> c2){
        String str = "Hello World";
        //表示执行完c1的accept再执行c2的accept
        c1.andThen(c2).accept(str);
        //c1.accept(str);
        // c2.accept(str);
    }
}

Function接口

java.util.function.Function接口,根据一个类型的数据得到另一个类型的数据,前者为前置条件,后者为后置条件。有参数有返回值

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
  • 示例代码
package com.lld;

import java.util.function.Function;

/**
 * @ClassName demo07
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 16:00
 * @Version 1.0
 */
public class demo07 {

    public static void main(String[] args) {
        number(s -> Integer.parseInt(s), integer -> integer * 5);
    }

    //将字符串转成整型,再将整型数据乘以五
    public static void number(Function<String,Integer> f1,Function<Integer,Integer> f2){
        String str = "10";
        /*Integer num1 = f1.apply(str);
        System.out.println(num1);
        Integer num2 = f2.apply(num1);
        System.out.println(num2);*/
        //将str转成Integer,再将Integer乘以5
        System.out.println(f1.andThen(f2).apply(str));
    }
}

Predicate接口

判断某个值,得到一个Boolean类型的数据,可以用java.util.function.Predicate接口

@FunctionalInterface
public interface Predicate {
    boolean test(T t);
}
  • 判断一个人的名字是否很长,大于三个字返回true,小于三个字返回false
package com.lld;

import java.util.function.Predicate;

/**
 * @ClassName demo08
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 16:40
 * @Version 1.0
 */
public class demo08 {

    public static void main(String[] args) {

        //如果名字长度大于三,返回true,否则返回false
        isLongName(s -> s.length()>3,"曲秃");
    }

    public static void isLongName(Predicate<String> predicate,String string){
        boolean b = predicate.test(string);
        System.out.println("名字很长吗?   "+b);
    }
}
  • 使用Lambda表达式判断一个字符串中既包含W也包含H
  • 使用Lambda表达式判断一个字符串中既包含W或者包含H
  • 使用Lambda表达式判断一个字符串中既不包含W
package com.lld;

import java.util.function.Predicate;

/**
 * @ClassName demo08
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 16:40
 * @Version 1.0
 */
public class demo08 {
    public static void main(String[] args) {

        // * 使用Lambda表达式判断一个字符串中既包含W也包含H
        // * 使用Lambda表达式判断一个字符串中既包含W或者包含H
        // * 使用Lambda表达式判断一个字符串中既不包含W

        test(s -> s.contains("W"),s -> s.contains("H"));
    }

    public static void test(Predicate<String> p1,Predicate<String> p2){

        // * 使用Lambda表达式判断一个字符串中既包含W也包含H
        String str = "Hello World";
        boolean b = p1.and(p2).test(str);
        if (b) {
            System.out.println("既包含W也包含H");
        }

        // * 使用Lambda表达式判断一个字符串中既包含W或者包含H
        boolean b1 = p1.or(p2).test(str);
        if (b1) {
            System.out.println("既包含W或者包含H");
        }

        // * 使用Lambda表达式判断一个字符串中不包含W
        boolean b2 = p1.negate().test("Hello");
        if (b2) {
            System.out.println("不包含W");
        }
    }
}

JDK8部分新特性_第4张图片

8.介绍方法引用

Lambda的冗余场景

使用方法引用前

package com.lld;

import java.util.function.Consumer;

/**
 * @ClassName demo09
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 17:10
 * @Version 1.0
 */
public class demo09 {

    //将数组所有元素相加输出
    public static void getSum(int [] arr){
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        System.out.println(sum);
    }

    public static void main(String[] args) {
        //不使用方法引用
        //int[] arr是入参
        //getSum(arr);是调用了上面的静态方法
        pringMax((int[] arr) -> {
            getSum(arr);
        });
       
    }

    //使用Consumer获得数组的总和
    public static void pringMax(Consumer<int []> consumer){
        int [] arr = {1,2,3,4,5,6,7,8,9,10};
        consumer.accept(arr);
    }

}

使用方法引用后

package com.lld;

import java.util.function.Consumer;

/**
 * @ClassName demo09
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 17:10
 * @Version 1.0
 */
public class demo09 {

    //将数组所有元素相加输出
    public static void getSum(int [] arr){
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        System.out.println(sum);
    }

    public static void main(String[] args) {
        /*//不使用方法引用
        //int[] arr是入参
        //getSum(arr);是调用了上面的静态方法
        pringMax((int[] arr) -> {
            getSum(arr);
        });*/
        // 使用方法引用
        pringMax(demo09::getSum);
    }

    //使用Consumer获得数组的总和
    public static void pringMax(Consumer<int []> consumer){
        int [] arr = {1,2,3,4,5,6,7,8,9,10};
        consumer.accept(arr);
    }

}

JDK8部分新特性_第5张图片

方法引用的格式

符号表示:::(一对冒号)

符号说明:双冒号为方法引用运算符,而他所在的表达式称为方法引用

应用场景:如果Lambda所要实现的方案,已经有其他方法存在相同方案,那么则可以使用方法引用

常见的方法引用方式
  1. 对象::方法名
  2. 类名::静态方法
  3. 类名::普通方法
  4. 类名::new 调用构造器
  5. String[]::new 调用数组构造器

9. 对象::方法名

  • 示例代码
//对象::方法名
@Test
public void test01(){
    //创建一个对象
    Date date = new Date();
    //不使用方法引用
    //Supplier supplier = () -> date.getTime();
    //使用方法引用
    Supplier<Long> supplier1 = date::getTime;
    System.out.println(supplier1.get());
    /*
            使用方法引用的两个注意事项
                1. 被引用的方法,参数要和接口中的抽象方法参数一致
                2. 当接口抽象方法有返回值时,被引用的方法必须有返回值
         */
}

注意事项

  1. 被引用的方法,参数要和接口中的抽象方法参数一致
  2. 当接口抽象方法有返回值时,被引用的方法必须有返回值

10.类名::静态方法

  • 实例代码
//类名::静态方法
@Test
public void test02(){

    //不使用方法引用
    //Supplier su = () -> System.currentTimeMillis();

    //使用方法引用
    Supplier<Long> su = System::currentTimeMillis;
    Long time = su.get();
    System.out.println("time = " + time);
}
  • System.currentTimeMillis()方法解释(因为之前不知道)
//获取系统时间:new Date().getTime()底层也是调用的System.currentTimeMillis();方法
@Test
public void test03(){
    //获得系统的时间,单位为毫秒,转换为妙
    long totalMilliSeconds = System.currentTimeMillis();
    long totalSeconds = totalMilliSeconds / 1000;

    //求出现在的秒
    long currentSecond = totalSeconds % 60;

    //求出现在的分
    long totalMinutes = totalSeconds / 60;
    long currentMinute = totalMinutes % 60;

    //求出现在的小时
    long totalHour = totalMinutes / 60;
    long currentHour = totalHour % 24;

    //显示时间
    System.out.println("总毫秒为: " + totalMilliSeconds);
    System.out.println(currentHour + ":" + currentMinute + ":" + currentSecond + " GMT");
}

11. 类名::实例方法

在Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者

  • 示例代码
//类名::实例方法
@Test
public void test04(){

    //根据字符串获取长度

   /*
    //不使用方法引用
    Function fn = (String s) -> {
        return s.length();
    };*/
    //使用方法引用
    Function<String,Integer> fn = String::length;

    Integer integer = fn.apply("Hello World");
    System.out.println("integer = " + integer);

    //截取字符串
    //使用方法引用:BiFunction泛型有三个参数,参数一:方法调用者,参数二:方法参数,参数三:返回值类型
    BiFunction<String,Integer,String> bfn = (s, integer1) -> s.substring(integer1);
    //上面方法其实就是
    BiFunction<String,Integer,String> bfn1 = (String s,Integer i) ->{
        return s.substring(i);
    };
    String s = bfn.apply("Hello world", 3);

    System.out.println("s = " + s);
}

JDK8部分新特性_第6张图片

12.类名::new引用构造器

由于构造器名称与类名完全一致。所以构造器引用使用类名称::new的格式表示。

  • person
package com.lld;

/**
 * @ClassName Person
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 19:27
 * @Version 1.0
 */
public class Person {

    private String name ;
    private Integer age;

    public Person() {
        System.out.println("调用了无参数的构造方法");
    }

    public Person(String name, Integer age) {
        System.out.println("调用了有参数的构造方法");
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 示例代码
//类名::new
@Test
public void test05() {

    //不使用方法引用
    /*Supplier s = () -> {
      return new Person();
    };*/

    //使用方法引用
    Supplier<Person> s = Person::new;
    Person person = s.get();
    System.out.println("person = " + person);

    //不使用方法引用
    /*BiFunction bfn = (s1, integer) -> {
        return new Person(s1, integer);
    };*/
    //使用方法引用
    BiFunction<String,Integer,Person> bfn = Person::new;
    Person p1 = bfn.apply("曲秃", 22);
    System.out.println("p1 = " + p1);
}

JDK8部分新特性_第7张图片

13. 数组::new引用数组构造器

//数组::new
@Test
public void test06() {
    //不使用方法引用
    /*Function function = (Integer integer) -> {
        return new int[integer];
    };*/
    //使用方法引用
    Function<Integer,int[]> function = int[]::new;
    int[] ints = function.apply(10);
    System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints));
}

14.方法引用小结

方法引用是对Lambda表达式符合特定情况的一种缩写,它使我们的Lambda表达式更加精简,也可以理解为Lambda表达式的缩写形式,不过注意的是方法引用只能“引用”已经存在的方法!

集合之Stream流式操作

1. Stream流介绍

集合处理数据的弊端
  • 每一个需求都要循环一次集合,并创建一个集合进行数据的存储
package com.lld;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @ClassName demo01
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 20:09
 * @Version 1.0
 */
public class demo01 {
    // 1.拿到集合中所有姓张的
    // 2.拿到名字为三个字的
    // 3.进行输出

    @Test
    public void test01(){

        //准备工作:将数据添加到list集合中
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

        //不使用Stream流操作
        // 1.拿到集合中所有姓张的
        List<String> zhangList = new ArrayList<>();
        for (String s : list) {
            //判断第一个字符是不是张
            if (s.startsWith("张")){
                zhangList.add(s);
            }
        }
        // 2.拿到名字为三个字的
        List<String> twoList = new ArrayList<String>();
        for (String s : zhangList) {
            if (s.length() == 3){
                twoList.add(s);
            }
        }
        // 3.进行输出
        System.out.println(twoList.toString());


    }

}

Stream流的思想和作用

注意:Stream流和IO流没有任何关系

Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理,Stream可以看做流水线上的一个工序,在流水线上通过多个工序让一个原材料加工成一个商品

package com.lld;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @ClassName demo01
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/7 20:09
 * @Version 1.0
 */
public class demo01 {
    // 1.拿到集合中所有姓张的
    // 2.拿到名字为三个字的
    // 3.进行输出

    @Test
    public void test01(){

        //准备工作:将数据添加到list集合中
        List<String> list = new ArrayList<>();
        Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

        //不使用Stream流操作
        // 1.拿到集合中所有姓张的
        List<String> zhangList = new ArrayList<>();
        for (String s : list) {
            //判断第一个字符是不是张
            if (s.startsWith("张")){
                zhangList.add(s);
            }
        }
        // 2.拿到名字为三个字的
        List<String> twoList = new ArrayList<String>();
        for (String s : zhangList) {
            if (s.length() == 3){
                twoList.add(s);
            }
        }
        // 3.进行输出
        System.out.println(twoList.toString());

        //分割线
        System.out.println("------------------------------------------");

        //使用Stream流操作
        // 1.拿到集合中所有姓张的
        // 2.拿到名字为三个字的
        // 3.进行输出
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
    }

}

JDK8部分新特性_第8张图片

2. 获取Stream的两种方法

  1. 所有的Collection集合都可以通过Stream默认方法获取流
  2. Stream接口的静态方法of可以获取数组对应的流
方式1:根据Collection获取流
@Test
//方法一:通过Collection的默认静态方法:default Stream stream()
public void test01(){
    //方法一:通过Collection的默认静态方法:default Stream stream()
    //list集合
    List<String> list = new ArrayList<>();
    Stream<String> stream1 = list.stream();

    //set集合
    Set<String> set = new HashSet<>();
    Stream<String> stream2 = set.stream();

    //Map集合:map集合和collection没有关系,那么他怎么获取Stream流呢
    Map<String,String> map = new HashMap<>();
    //他的keyset方法时返回一个set集合,里面有所有的key
    Set<String> keySet = map.keySet();
    Stream<String> stream3 = keySet.stream();
    //他的entrySet方法时返回一个set集合,里面有所有的key和value的关系
    Set<Map.Entry<String, String>> entrySet = map.entrySet();
    Stream<Map.Entry<String, String>> stream4 = entrySet.stream();
    //他的values方法时返回一个sCollection集合,里面有所有的value
    Collection<String> values = map.values();
    Stream<String> stream5 = values.stream();
}
方式二:根据Stream的of方法获取流
@Test
//通过Stream的静态方法of获取Stream流:public static Stream of(T... values)参数是可变长度的
public void test02() {
    Stream<String> stream1 = Stream.of("张渣", "段黑", "曲秃", "莽夫", "奥特曼");

    //String数组
    String[] str = {"张渣", "段黑", "曲秃", "莽夫", "奥特曼"};
    Stream<String> stream2 = Stream.of(str);

    //基本数据类型的数组是不行的,因为他会将数组当做一个整体处理
    int[] ints = {11,22,33,44,55,66};
    Stream<int[]> Sstream3 = Stream.of(ints);//返回的泛型类型是int数组,而不是int
}
  • 注意:基本数据类型的数组是不支持的

3. Stream流的常用方法和注意事项

常用方法
方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 逐一处理 void 终结
filter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接
  • 终结方法:返回值类型不再是Stream类型的方法,不支持链式调用
  • 非终结方法:返回值类型依然是Stream方法,支持链式调用(除终结方法外,其余均是非终结方法,也称为函数拼接)

其余更多方法:参考api文档

注意事项
  1. Stream只能操作一次
  2. Stream方法返回的是新的流
  3. Stream不调用终结方法,中间操作不会执行

4. Stream流中的forEach方法

遍历

forEach用来输出

void forEach(Consumer action);//接收一个Consumer接口

//使用forEach方法
@Test
public void test03() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

    //使用Lambda表达式
    list.stream().forEach(s -> {
        System.out.println(s);
    });

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

    //使用Lambda表达式简写
    list.stream().forEach(s -> System.out.println(s));

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

    //使用方法引用
    list.stream().forEach(System.out::println);
}

5. Stream流中的count方法

计算个数

long count();

//Stream流中Count计数
@Test
public void test04() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

    long count = list.stream().count();
    System.out.println(count);
}

6. Stream流的Filter方法

过滤

Stream filter(Predicate predicate);

//Stream流中Filter方法
@Test
public void test05() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
    //过滤出姓名长度为二的并输出
    list.stream().filter(s -> s.length() == 2).forEach(System.out::println);
}

7. Stream流的limit方法

选择前几个

Stream limit(long maxSize);

//Stream流的limit方法
@Test
public void test06() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");

    //取数组前三个进行输出
    list.stream().limit(3).forEach(System.out::println);
}

8. Stream流的skip方法

跳过前几个

Stream skip(long n);

//Stream流的skip方法
@Test
public void testSkip() {
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"张无忌","张三丰","周芷若","张渣","曲秃","段黑");
    //跳过集合前三个进行输出
    list.stream().skip(3).forEach(System.out::println);
}

9.Stream流的map操作

将流中一种类型的数据转换成另一种类型的数据

//Stream流的map方法:将一种类型的流转换成另一种类型的流
@Test
public void testMap() {

    Stream<String> stream = Stream.of("11", "22", "33", "44");

    //使用Lambda表达式
    /*stream.map((String s)->{
        return Integer.parseInt(s);
    }).forEach(System.out::println);*/

    //使用Lambda表达式简写
    /*stream.map(s -> Integer.parseInt(s)).forEach(System.out::println);*/
    //使用方法引用
    stream.map(Integer::parseInt).forEach(System.out::println);
}

10.Stream流中的sorted方法

如果要将数据进行排序,可以使用sorted方法

Stream<T> sorted();进行自然排序Stream<T> 
Stream<T> sorted(Comparator<? super T> comparator);使用传入的比较器进行排序
//Stream流中的sorted方法:进行排序
@Test
public void testSorted() {
    // Stream sorted();进行自然排序
    // Stream sorted(Comparator comparator);使用传入的比较器进行排序
    Stream<Integer> stream = Stream.of(11, 22, 33, 66, 55, 982);
    // 使用sorted的无参方法进行自然排序
    //stream.sorted().forEach(System.out::println);

    // 使用sorted的有参方法传入比较器进行降序排序
   /* stream.sorted((Integer o1, Integer o2) -> {
        return o2 - o1;
    }).forEach(System.out::println);*/

    // 使用sorted的有参方法传入比较器进行降序排序:使用简写
    stream.sorted((o1, o2) -> o2-o1).forEach(System.out::println);
}

11.Stream流中的distinct方法

去重

  • String和Integer类型数组去重
//Stream流中distinct方法:String和Integer等类型去重
@Test
public void testDistinct() {
    //字符串去重
    Stream<String> stream = Stream.of("aa", "bb", "cc", "aa", "cc");
    stream.distinct().forEach(System.out::println);

    //Integer去重
    Stream<Integer> stream1 = Stream.of(11, 22, 33, 44, 55, 66, 66, 55, 44);
    stream1.distinct().forEach(System.out::println);
}
  • 自定义类型去重(一定要重写自定义类型中的equals方法和hashCode方法)
//Stream流中distinct方法:自定义类型去重(自定义类中一定要复写)
@Test
public void testDistinct1() {
    Stream<Person> personStream = Stream.of(
            new Person("段黑", 22),
            new Person("张渣", 22),
            new Person("曲秃", 22),
            new Person("曲秃", 22),
            new Person("段黑", 22),
            new Person("张渣", 22)
    );
    personStream.distinct().forEach(System.out::println);
}

12.Stream流中的Math方法

  1. allMatch:当stream流中所有元素都满足条件时,返回true,否则返回false
  2. anyMatch:当stream流中任意元素满足条件时,返回true,否则返回false
  3. noneMatch:当stream流中所有元素都不满足条件时,返回true,否则返回false
@Test
public void testMatch() {
    Stream<Integer> stream = Stream.of(1, 2, 5, 9, 3);

    //boolean b = stream.allMatch(integer -> integer > 0);//allMatch:当stream流中所有元素都满足大于0这个条件时,返回true,否则返回false
    //boolean b = stream.anyMatch(integer -> integer > 7);//anyMatch:当stream流中任意元素满足大于7这个条件时,返回true,否则返回false
    boolean b = stream.noneMatch(integer -> integer > 10);//noneMatch:当stream流中所有元素都不满足大于10这个条件时,返回true,否则返回false

    System.out.println(b);
}

13.Stream流中的Find

找出Stream流中的第一个元素

Optional<T> findAny();
Optional<T> findFirst();
@Test
public void testFind() {
    Stream<Integer> integerStream = Stream.of(11, 22, 33, 44, 55);
    //查找Stream中的第一个元素
    //Optional integer = integerStream.findAny();
    //查找Stream中的第一个元素,与findAny效果一致
    Optional<Integer> first = integerStream.findFirst();
    System.out.println(first.get());
}

14.Stream流中的Max_Min

  • max获取最大值:需要传入一个比较器
  • min获取最小值:需要传入一个比较器
@Test
public void testMax_Min() {
    //获取最大值
    Optional<Integer> max = Stream.of(11, 22, 1, 5, 9).max((o1, o2) -> o1 - o2);
    System.out.println("最大值:" + max.get());

    //获取最小值
    Optional<Integer> min = Stream.of(11, 22, 1, 5, 9).min((o1, o2) -> o1 - o2);
    System.out.println("最小值:" + min.get());
}

15.Stream流中的reduce方法

将所有的数据归纳总结,得到一个

@Test
public void testReduce() {
    Stream<Integer> stream = Stream.of(4, 5, 9, 3);
    // T reduce(T identity, BinaryOperator accumulator);
    // 获得总和
    // reduce是怎么执行的呢?
    // 第一次,将默认值(参数一)赋给x,将Stream流第一个元素赋给y
    // 第二次,将上一次返回的值赋给x,将Stream流中的第二个元素赋给y
    // 第三次,将上一次返回的值赋给x,将Stream流中的第三个元素赋给y
    // 第四次,将上一次返回的值赋给x,将Stream流中的第四个元素赋给y
    //Integer reduce = stream.reduce(0, (x, y) -> x + y);

    //获得最大值
    Integer reduce = stream.reduce(0, (x, y) -> x > y ? x : y);
    System.out.println("reduce = " + reduce);
}

16. map方法和reduce方法结和使用

//map方法和reduce方法一同使用
@Test
public void testMap_Reduce() {
    // 1.求出年龄的和
    // 使用map方法处理,其返回的流是年龄
    // 使用reduce方法,设置默认值为0,使用integer的sum方法求和
    Integer reduce = Stream.of(
            new Person("段黑", 22),
            new Person("张渣", 50),
            new Person("曲秃", 28)).map(person -> person.getAge()).reduce(0, Integer::sum);
    System.out.println("年龄总数 = " + reduce);
    // 2.求出年龄最大的
    // 使用map方法处理,其返回的流是年龄
    // 使用reduce方法,设置默认值为0,使用integer的max方法获取最大值
    Integer reduce1 = Stream.of(
            new Person("段黑", 22),
            new Person("张渣", 50),
            new Person("曲秃", 28)).map(person -> person.getAge()).reduce(0, Integer::max);
    System.out.println("年龄最大的 = " + reduce1);
    // 3.计算a出现的次数
    // 使用map方法进行判断,如果是a,返回1,不是,返回0
    // 处理后的数据               1   0     1    0     1
    // 使用reduce方法,设置默认值为零,使用Integer的sum方法求出a的个数
    Integer reduce2 = Stream.of("a", "b", "a", "c", "a").map(s -> {
        if (s.equals("a")) {
            return 1;
        } else {
            return 0;
        }
    }).reduce(0, Integer::sum);
    System.out.println("a出现的次数 = " + reduce2);
}

17.Stream流的mapToInt

如果需要将Stream类型的数据转换为int

@Test
public void testmapToInt() {
    // Integer占用内存比int多,在Stream六中操作会进行自动装箱和拆箱
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
    // 把大于三的打印出类
    // stream.filter(integer -> integer > 3).forEach(System.out::println);

    // IntStream:内部操作的是int类型的数据,可以节省内存空间
    /*IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(value -> {
        return value.intValue();
    });
    intStream.forEach(System.out::println);*/
    // 简写
    IntStream intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(Integer::intValue);
    intStream.filter(value -> value > 3).forEach(System.out::println);
}

18.Stream流的Concat

将两个Stream流合并成一个流

  • 注意:流合并后只能操作新的流,不能操作之前的流
@Test
public void testConcat() {
    // 定义两个流
    Stream<Person> 曲秃 = Stream.of(new Person("曲秃", 22));
    Stream<Person> 张渣 = Stream.of(new Person("张渣", 21));

    // 将两个流合并成为一个流
    Stream<Person> newStream = Stream.concat(曲秃, 张渣);

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

19. Stream流综合操作

package com.lld;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/8 20:26
 * @Version 1.0
 */
public class demo03 {

    public static void main(String[] args) {
        // 第一个队伍
        List<String> one = new ArrayList<>();
        Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
        // 第二个队伍
        List<String> two = new ArrayList<>();
        Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");

        // 1.第一个队伍只要名字为三个字的成员名称
        // one.stream().filter(s -> s.length() == 3).forEach(System.out::println);

        // 2.第一个队伍筛选之后只要前三人
        Stream<String> stream1 = one.stream().filter(s -> s.length() == 3).limit(3);

        System.out.println("------------分割线---------------");

        // 3.第二个队伍只要姓张的成员名称
        //two.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
        // 4.第二个队伍筛选之后不要前两人
        Stream<String> stream2 = two.stream().filter(s -> s.startsWith("张")).skip(2);
        // 5.将两个队伍合并成为一个队伍
        Stream<String> newStream = Stream.concat(stream1,stream2);
        // 6.根据姓名创建Person对象
        Stream<Person> stream = newStream.map(Person::new);
        // 7.打印整个队伍的Person对象的信息
        stream.forEach(System.out::println);
        
    }
}

20. 收集流中的结果

1. Stream流中数据放入集合

1583722905470

@Test
public void testToCollection(){
    // 一个Stream流
    Stream<String> stream = Stream.of("ss", "xx", "cc");
    //转成List
    // List collect = stream.collect(Collectors.toList());
    // System.out.println("collect = " + collect);
    
    // 转成set
    // Set collect = stream.collect(Collectors.toSet());
    // System.out.println("collect = " + collect);

    // 转成ArrayList
    // ArrayList collect = stream.collect(Collectors.toCollection(ArrayList::new));
    // System.out.println("collect = " + collect);

    // 转成HashSet
    // HashSet collect = stream.collect(Collectors.toCollection(HashSet::new));
    // System.out.println("collect = " + collect);
}
2. Stream流中数据放入数组

1583722924845

@Test
public void testToArray() {
    // 一个Stream流
    Stream<String> stream = Stream.of("ss", "xx", "cc");

    // toArray无参构造方法,转成object
    // Object[] objects = stream.toArray();
    //  for (Object object : objects) {
    //    System.out.println("object = " + object);
    // }

    // toArray有参构造方法,参数:数组构造器
    String[] strings = stream.toArray(String[]::new);
    for (String string : strings) {
        System.out.println("string = " + string);
    }
}
3. Stream流中结果进行聚合计算

1583722947730

// 其他流中数据聚合的方式:相当于数据库中的聚合函数
@Test
public void testStreamToOther() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 90),
            new Student("曲秃", 22, 92),
            new Student("段黑", 21, 80),
            new Student("奥特曼", 28, 85),
            new Student("高莽夫", 24, 96)
    );

    // 1.获取成绩最大值
    /*Optional student = studentStream.collect(Collectors.maxBy((o1, o2) -> o1.getGrade() - o2.getGrade()));
    if (student.get() != null){
        System.out.println(student.get());
    }*/
    // 2.获取成绩最小值
    /*Optional student = studentStream.collect(Collectors.minBy((o1, o2) -> o1.getGrade() - o2.getGrade()));
    if (student.get() != null){
        System.out.println(student.get());
    }*/
    // 3.求总和
    /*Integer sum = studentStream.collect(Collectors.summingInt(value -> value.getGrade()));
    System.out.println("sum = " + sum);*/

    // 4.平均值
    /*Double avg = studentStream.collect(Collectors.averagingInt(value -> value.getGrade()));
    System.out.println("avg = " + avg);*/
    // 5.统计数量
    Long count = studentStream.collect(Collectors.counting());
    System.out.println("count = " + count);
}
4. Stream流中结果进行分组
@Test
public void testGroupBy() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 40),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 85),
            new Student("高莽夫", 22, 96)
    );
    // 1.根据年龄进行分组
    // Map> map = studentStream.collect(Collectors.groupingBy(Student::getAge));
    // map.forEach((integer, students) -> System.out.println(integer + "::" + students));

    // 2.根据是否及格进行分组
    Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy(o -> o.getGrade() > 60 ? "及格" : "不及格"));
    map.forEach((s, students) -> System.out.println(s + "::" + students));
}

结果

不及格::[Student{name='张渣', age=20, grade=30}, Student{name='曲秃', age=20, grade=40}, Student{name='段黑', age=22, grade=50}]
及格::[Student{name='奥特曼', age=22, grade=85}, Student{name='高莽夫', age=22, grade=96}]
5. Stream流中结果进行多级分组
@Test
public void testCustomGroupBy() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 80),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 30),
            new Student("高莽夫", 22, 96)
    );

    // 先按照年龄分组,在按照成绩分组
    Map<Integer, Map<String, List<Student>>> map = studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(o -> o.getGrade() > 60 ? "及格" : "不及格")));

    // 遍历map集合
    map.forEach((integer, stringListMap) -> {
        System.out.println(integer);
        // 遍历map中的map
        stringListMap.forEach((s, students) -> System.out.println("\t" + s + " == " + students));
    });

结果

20
	不及格 == [Student{name='张渣', age=20, grade=30}]
	及格 == [Student{name='曲秃', age=20, grade=80}]
22
	不及格 == [Student{name='段黑', age=22, grade=50}, Student{name='奥特曼', age=22, grade=30}]
	及格 == [Student{name='高莽夫', age=22, grade=96}]

6. Stream流中数据进行分区

Collectors.partitioningBy 会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。

@Test
public void testPartition() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 80),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 30),
            new Student("高莽夫", 22, 96)
    );
    Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(o -> o.getGrade() > 60));

    map.forEach((aBoolean, students) -> System.out.println(aBoolean + " :: " +students));
}

结果

false :: [Student{name='张渣', age=20, grade=30}, Student{name='段黑', age=22, grade=50}, Student{name='奥特曼', age=22, grade=30}]
true :: [Student{name='曲秃', age=20, grade=80}, Student{name='高莽夫', age=22, grade=96}]
7. Stream流中数据进行拼接

Collectors.joining 会根据指定的连接符,将所有元素连接成一个字符串。

@Test
public void testJoining() {
    Stream<Student> studentStream = Stream.of(
            new Student("张渣", 20, 30),
            new Student("曲秃", 20, 80),
            new Student("段黑", 22, 50),
            new Student("奥特曼", 22, 30),
            new Student("高莽夫", 22, 96)
    );
    // 根据一个拼接:张渣__曲秃__段黑__奥特曼__高莽夫
    // String s = studentStream.map(Student::getName).collect(Collectors.joining("__"));

    // 根据三个参数拼接:^___^张渣__曲秃__段黑__奥特曼__高莽夫V___V
    String s = studentStream.map(Student::getName).collect(Collectors.joining("__", "^___^", "V___V"));
    System.out.println("s = " + s);
}
8. 小结

收集Stream流中的结果
到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()
到数组中: toArray()/toArray(int[]::new)
聚合计算:
Collectors.maxBy/Collectors.minBy/Collectors.counting/Collectors.summingInt/Collectors.averagingInt
分组: Collectors.groupingBy
分区: Collectors.partitionBy
拼接: Collectors.joinging

21. 并行的Stream流

1. 串行的Stream流
@Test
public void test0Serial() {
    long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
            .filter(s -> {
                System.out.println(Thread.currentThread() + ", s = " + s);
                return true;
            })
            .count();
    System.out.println("count = " + count);
}

JDK8部分新特性_第9张图片

2. 获取并行Stream流的两种方式
  1. 直接获取并行流
  2. 将串行流转换为并行流
@Test
public void testgetParallelStream() {
    ArrayList<Integer> list = new ArrayList<>();
    // 直接获取并行的流
    // Stream stream = list.parallelStream();
    // 将串行流转成并行流
    Stream<Integer> stream = list.stream().parallel();
}

操作代码

@Test
public void test0Parallel() {
    long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
            .parallel() // 将流转成并发流,Stream处理的时候将才去
            .filter(s -> {
                System.out.println(Thread.currentThread() + ", s = " + s);
                return true;
            })
            .count();
    System.out.println("count = " + count);
}

JDK8部分新特性_第10张图片

3. 并行流串行流及for循环速度比较

private static Long times = 500000000L;
private Long start;

@Before
public void init(){
    start = System.currentTimeMillis();
}

@After
public void destory(){
    System.out.println(System.currentTimeMillis() - start);
}

//时间:2688毫秒
@Test
public void testFor() {
    Long sum = 0L;
    for (Long i = 0L; i < times; i++) {
        sum += sum;
    }
}

//时间:831毫秒
@Test
public void serialStream() {
    LongStream.rangeClosed(0,times).reduce(0,Long::sum);
}

//时间:110毫秒
@Test
public void parallelStream() {
    LongStream.rangeClosed(0,times).parallel().reduce(0,Long::sum);
}

我们可以看到parallelStream的效率是最高的。Stream并行处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作。

4. parallelStream线程问题
//parallelStream流的线程安全问题
@Test
public void parallelStreamNotice() {
    List<Integer> list = new ArrayList<>();
    // 测试线程是否安全:792个
    /*IntStream.rangeClosed(0,1000).parallel().forEach(value -> list.add(value));
    System.out.println(list.size());*/

    // 解决方法一:使用同步代码块
    /*Object obj = new Object();
    IntStream.rangeClosed(0,999).parallel().forEach(value -> {
        synchronized (obj){
            list.add(value);
        }
    });
    System.out.println(list.size());*/

    // 解决方法二:使用线程安全的集合
    // 获得一个线程安全的list集合
    /*List list1 = Collections.synchronizedList(list);
    IntStream.rangeClosed(0,999).parallel().forEach(value -> list1.add(value));
    System.out.println(list1.size());*/

    // 解决方法三:调用Stream的 toArray() / collect()
    /*List collect = IntStream.rangeClosed(0, 999).parallel().boxed().collect(Collectors.toList());
    System.out.println("collect = " + collect.size());*/
    Integer[] array = IntStream.rangeClosed(0, 999).boxed().toArray(Integer[]::new);
    System.out.println("array = " + array.length);
}
5. parallelStream 背后的技术
Fork/Join框架介绍

parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小任务来异步执行。 Fork/Join框架主要包含三个模块:

  1. 线程池:ForkJoinPool
  2. 任务对象:ForkJoinTask
  3. 执行任务的线程:ForkJoinWorkerThread

JDK8部分新特性_第11张图片

Fork/Join原理-分治法

ForkJoinPool 主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法,ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。

JDK8部分新特性_第12张图片

Fork/Join原理-工作窃取算法

Fork/Join最核心的地方就是利用了现代硬件设备多核,在一个操作时候会有空闲的cpu,那么如何利用好这个空闲的cpu就成了提高性能的关键,而这里我们要提到的工作窃取(work-stealing)算法就是整个Fork/Join框架的核心理念Fork/Join工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。

JDK8部分新特性_第13张图片

那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。上文中已经提到了在Java 8引入了自动并行化的概念。它能够让一部分Java代码自动地以并行的方式执行,也就是我们使用了ForkJoinPool的ParallelStream。
对于ForkJoinPool通用线程池的线程数量,通常使用默认值就可以了,即运行时计算机的处理器数量。可以通过设置系统属性:java.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量),来调整ForkJoinPool的线程数量,可以尝试调整成不同的参数来观察每次的输出结果。

Fork/Join 案例

需求:使用Fork/Join计算1-10000的和,当一个任务的计算数量大于3000时拆分任务,数量小于3000时计算。

JDK8部分新特性_第14张图片

package com.lld;

/**
 * @ClassName demo05ForkJoin
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/9 14:31
 * @Version 1.0
 */
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class demo05ForkJoin {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        ForkJoinPool pool = new ForkJoinPool();
        SumRecursiveTask task = new SumRecursiveTask(1, 10000L);
        Long result = pool.invoke(task);
        System.out.println("result = " + result);
        long end = System.currentTimeMillis();
        System.out.println("消耗的时间为: " + (end - start));
    }
}
class SumRecursiveTask extends RecursiveTask<Long> {
    private static final long THRESHOLD = 3000L;
    private final long start;
    private final long end;
    public SumRecursiveTask(long start, long end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        long length = end - start;
        if (length <= THRESHOLD) {
// 任务不用再拆分了.可以计算了
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            System.out.println("计算: " + start + " -> " + end + ",结果为: " + sum);
            return sum;
        } else {
// 数量大于预定的数量,任务还需要再拆分
            long middle = (start + end) / 2;
            System.out.println("拆分: 左边 " + start + " -> " + middle + ", 右边 " + (middle +
                    1) + " -> " + end);
            SumRecursiveTask left = new SumRecursiveTask(start, middle);
            left.fork();
            SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
            right.fork();
            return left.join() + right.join();
        }
    }
}

Optional类

以前对null的处理方式

// 以前对null的处理
@Test
public void test1(){

    String str = "曲秃";
    if (str == null){
        System.out.println("str为null");
    }else {
        System.out.println(str);
    }
}

Optional类介绍

Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。

JDK8部分新特性_第15张图片

Optional 的基本使用

Optional类的创建方式:

Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例

Optional 类的常用方法:

isPresent() : 判断是否包含值,包含值返回true,不包含值返回false
get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
// 现在对null的处理
@Test
public void test02() {
    // of:不可以传入null
    // ofNullable:可以传入值,也可以传入null
    // empty:默认null,不可传值

    // Optional userNameO = Optional.of("凤姐");
    // Optional userNameO = Optional.of(null);
    // Optional userNameO = Optional.ofNullable(null);
    Optional<String> userNameO = Optional.empty();
    // isPresent() : 判断是否包含值,包含值返回true,不包含值返回false。
    if (userNameO.isPresent()) {
        // get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException。
        String userName = userNameO.get();
        System.out.println("用户名为:" + userName);
    } else {
        System.out.println("用户名不存在");
    }
}

Optional 类的高级使用:

@Test
public void test3() {
    User hello_world = new User(null);
    String s = getUpperCase(hello_world);
    System.out.println(s);
    Optional<User> hello_world1 = Optional.of(hello_world);
    System.out.println(getUpperCase1(hello_world1));

}

// 现在的方法
public String getUpperCase1(Optional<User> uO){
    String s1 = uO.map(user -> user.getUsername()).map(s -> s.toUpperCase()).orElse("null");
    return s1;
}

// 以前的方法
public String getUpperCase(User user){
    if (user != null){
        if (user.getUsername() != null){
            return user.getUsername().toUpperCase();
        }else {
            return null;
        }
    }else {
        return null;
    }
}

@Test
public void test4() {
    Optional<String> userNameO = Optional.of("凤姐");
    // Optional userNameO = Optional.empty();
    // 存在做的什么
    // userNameO.ifPresent(s -> System.out.println("用户名为" + s));
    // 存在做的什么,不存在做点什么
    userNameO.ifPresentOrElse(s -> System.out.println("用户名为" + s)
            , () -> System.out.println("用户名不存在"));
}

@Test
public void test5() {
    // Optional userNameO = Optional.of("凤姐");
    Optional<String> userNameO = Optional.empty();
    // 如果调用对象包含值,返回该值,否则返回参数t
    System.out.println("用户名为" + userNameO.orElse("null"));
    // 如果调用对象包含值,返回该值,否则返回参数Supplier得到的值
    String s1 = userNameO.orElseGet(() -> {return "未知用户名";});
    System.out.println("s1 = " + s1);
}

Optional是一个可以为null的容器对象。orElse,ifPresent,ifPresentOrElse,map等方法避免对null的判断,写出更加优雅的代码

JDK 8新的日期和时间 API

1. 旧版日期时间 API 存在的问题

  1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包
    含日期。此外用于格式化和解析的类在java.text包中定义。
  2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
@Test
public void test01(){
    /*
        问题一:设计不规范
            1. java.util.Date:包含日期和时间
            2. java.sql.Date:包含日期
            3. java.text.SimpleDateFormat:用于格式化和解析
    */
    Date date = new Date(1999,1,12);
    System.out.println(date);

    // 问题二:线程不安全
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    for (int i = 0; i < 50; i++) {
        new Thread(() -> {
            try {
                Date parse = dateFormat.parse("1999-09-28");
                System.out.println("parse = " + parse);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

2. 新日期时间 API介绍

JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包
中,下面是一些关键类。
LocalDate :表示日期,包含年月日,格式为 2019-10-16
LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
DateTimeFormatter :日期时间格式化类。
Instant:时间戳,表示一个特定的时间瞬间。
Duration:用于计算2个时间(LocalTime,时分秒)的距离
Period:用于计算2个日期(LocalDate,年月日)的距离
ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。

此外Java 8还提供了4套其他历法,分别是:

  • ThaiBuddhistDate :泰国佛教历

  • MinguoDate :中华民国历

  • JapaneseDate :日本历

  • HijrahDate :伊斯兰历

3. JDK 8 的日期和时间类

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

@Test
public void testLocalDate() {
    // 获取当前时间
    LocalDate now = LocalDate.now();
    System.out.println("now = " + now);
    // 获取指定时间
    LocalDate date = LocalDate.of(1999, 9, 28);
    System.out.println("date = " + date);

    // 获得年
    System.out.println(now.getYear());
    // 获得月
    System.out.println(now.getMonthValue());
    // 获得日
    System.out.println(now.getDayOfMonth());
}

@Test
public void testDateTime() {
    // 获得当前的时:分:秒:纳秒
    LocalTime now = LocalTime.now();
    System.out.println("now = " + now);

    // 指定时:分:秒
    LocalTime of = LocalTime.of(9, 19, 19);
    System.out.println("of = " + of);

    // 获得时
    System.out.println(now.getHour());
    // 获得分
    System.out.println(now.getMinute());
    // 获得秒
    System.out.println(now.getSecond());
}

@Test
public void testLocalDateTime() {
    // 获取年-月-日  时:分:秒:纳秒
    LocalDateTime now = LocalDateTime.now();
    System.out.println("now = " + now);

    // 指定年-月-日  时:分:秒
    LocalDateTime time = LocalDateTime.of(1999, 9, 28, 19, 19, 19);
    System.out.println("time = " + time);

    // 获取年
    System.out.println(now.getYear());
    // 获取月
    System.out.println(now.getMonthValue());
    // 获取日
    System.out.println(now.getDayOfMonth());
    // 获取时
    System.out.println(now.getHour());
    // 获取分
    System.out.println(now.getMinute());
    // 获取秒
    System.out.println(now.getSecond());
}

对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。

// 对日期时间修改
@Test
public void test() {
    // 获取年-月-日  时:分:秒:纳秒
    LocalDateTime now = LocalDateTime.now();

    // 修改时间:with···
    LocalDateTime setYear = now.withYear(2078);
    System.out.println("修改年份: " + setYear);
    System.out.println("now == setYear: " + (now == setYear));
    System.out.println("修改月份: " + now.withMonth(6));
    System.out.println("修改小时: " + now.withHour(9));
    System.out.println("修改分钟: " + now.withMinute(11));

    // 再当前对象的基础上加上或减去指定的时间  plus···增加/minus···减少
    LocalDateTime localDateTime = now.plusDays(5);
    System.out.println("5天后: " + localDateTime);
    System.out.println("now == localDateTime: " + (now == localDateTime));
    System.out.println("10年后: " + now.plusYears(10));
    System.out.println("20月后: " + now.plusMonths(20));
    System.out.println("20年前: " + now.minusYears(20));
    System.out.println("5月前: " + now.minusMonths(5));
    System.out.println("100天前: " + now.minusDays(100));
}

@Test
public void testEquals() {
    // 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
    LocalDate now = LocalDate.now();
    LocalDate date = LocalDate.of(2018, 8, 8);
    // 判断两个日期谁在谁之前
    System.out.println(now.isBefore(date)); // false
    // 判断两个日期谁在谁之后
    System.out.println(now.isAfter(date)); // true
    // 判断两个日期是否相等
    System.out.println(now.equals(date));  //
}

4. JDK 8 的时间格式化与解析

通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。

// 日期格式化
@Test
public void test02() {

    // 获取当前日期
    LocalDateTime now = LocalDateTime.now();

    // JDK自带的日期格式化
    DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
    String format = now.format(dtf);
    System.out.println("format = " + format);

    // 自己定义格式化
    DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyy年MM月dd日 HH时mm分ss秒");
    String s = now.format(pattern);
    System.out.println("s = " + s);

    // 解析及测试多线程是否安全

    for (int i = 0; i < 50; i++) {
        new Thread(() -> {
            LocalDateTime time = LocalDateTime.parse("1999年03月09日 20时54分03秒", pattern);
            System.out.println("time = " + time);
        }).start();
    }
}

5. JDK 8 的 Instant 类

Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。

// 时间戳
@Test
public void test03() {
    // Instant:内部保存了秒和纳秒,不是给用户用的,是用于给程序做一些统计的
    Instant now = Instant.now();
    System.out.println("now = " + now);

    // 减少时间操作
    Instant time = now.minusSeconds(20);
    System.out.println("instant = " + time);
    // 增加时间操作
    Instant seconds = now.plusSeconds(20);
    System.out.println("time = " + seconds);

    // 得到秒
    long epochSecond = now.getEpochSecond();
    System.out.println("epochSecond = " + epochSecond);
    // 得到纳秒
    int nano = now.getNano();
    System.out.println("nano = " + nano);
}

6. JDK 8 的计算日期时间差类

Duration/Period类: 计算日期时间差。

  1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
  2. Period:用于计算2个日期(LocalDate,年月日)的距离
// Duration/Period 类: 计算时间/日期差
@Test
public void test04() {
    // 获取当前时间
    LocalTime nowTime = LocalTime.now();
    // 指定一个时间
    LocalTime time = LocalTime.of(10, 8, 27);
    // 使用Duration比较:后面时间减去前面时间
    Duration duration = Duration.between(time, nowTime);
    System.out.println("计算相差的天数 " + duration.toDays());
    System.out.println("计算相差的小时数 " + duration.toHours());
    System.out.println("计算相差的分钟数 " + duration.toMinutes());

    // 获取当前日期
    LocalDate nowDate = LocalDate.now();
    // 指定一个日期
    LocalDate date = LocalDate.of(1999, 9, 28);
    // 使用Period比较:后面的日期减去前面的日期
    Period period = Period.between(date, nowDate);
    System.out.println("计算相差年 " + period.getYears());
    System.out.println("计算相差月 " + period.getMonths());
    System.out.println("计算相差日 " + period.getDays());
}

7. JDK 8 的时间校正器

有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。

  • TemporalAdjuster : 时间校正器。

  • TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。

// TemporalAdjuster时间调整器
// TemporalAdjusters提供了大量TemporalAdjuster的实现类
@Test
public void test05() {
    LocalDate now = LocalDate.now();

    // 自定义一个时间调整器:将时间调到下个月第一天
    TemporalAdjuster firstDateOfNextMonth = temporal -> {
        // 将传进来的参数强转成时间(因为传进来的就是时间)
        LocalDate date = (LocalDate) temporal;
        return date.plusMonths(1).withDayOfMonth(1);
    };

    // 时间调用with方法,将时间调整器传入
    LocalDate newDate = now.with(firstDateOfNextMonth);
    System.out.println("newDate = " + newDate);

    // TemporalAdjusters提供了大量TemporalAdjuster的实现类
    LocalDate localDate = now.with(TemporalAdjusters.firstDayOfMonth());
    System.out.println("localDate = " + localDate);
}
  • TemporalAdjusters 的一些方法

JDK8部分新特性_第16张图片

8. JDK 8 设置日期时间的时区

Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别
为:ZonedDate、ZonedTime、ZonedDateTime。
其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息。

// 设置日期时间的时区
@Test
public void test10() {
    // 1.获取所有的时区ID
    // ZoneId.getAvailableZoneIds().forEach(System.out::println);
    // 不带时间,获取计算机的当前时间
    LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
    System.out.println("now = " + now);
    // 2.操作带时区的类
    // now(Clock.systemUTC()): 创建世界标准时间
    ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
    System.out.println("bz = " + bz);
    // now(): 使用计算机的默认的时区,创建日期时间
    ZonedDateTime now1 = ZonedDateTime.now();
    System.out.println("now1 = " + now1); // 2019-10-19T16:19:44.007153500+08:00[Asia/Shanghai]
    // 使用指定的时区创建日期时间
    ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
    System.out.println("now2 = " + now2); // 2019-10-19T01:21:44.248794200-07:00[America/Vancouver]

    // 修改时区
    // withZoneSameInstant: 即更改时区,也更改时间
    ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
    System.out.println("withZoneSameInstant = " + withZoneSameInstant); // 2019-10-19T16:53:41.225898600+08:00[Asia/Shanghai]

    // withZoneSameLocal: 只更改时区,不更改时间
    ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
    System.out.println("withZoneSameLocal = " + withZoneSameLocal); // 2019-10-19T01:54:52.058871300+08:00[Asia/Shanghai]
}

9. 小结

  • 详细学习了新的日期是时间相关类,LocalDate表示日期,包含年月日,LocalTime表示时间,包含时分秒,LocalDateTime = LocalDate + LocalTime,
  • 时间的格式化和解析,通过DateTimeFormatter类型进行.
  • 学习了Instant类,方便操作秒和纳秒,一般是给程序使用的.
  • 学习Duration/Period计算日期或时间的距离,还使用时间调
    整器方便的调整时间,学习了带时区的3个类ZoneDate/ZoneTime/ZoneDateTime

JDK 8新的日期和时间 API的优势:

  1. 新版的日期和时间API中,日期和时间对象是不可变的。操纵的日期不会影响老值,而是新生成一个实例。
  2. 新的API提供了两种不同的时间表示方式,有效地区分了人和机器的不同需求。
  3. TemporalAdjuster可以更精确的操纵日期,还可以自定义日期调整器。
  4. 是线程安全的

重复注解与类型注解

1. 重复注解

自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。
重复注解的使用步骤:

  1. 定义重复的注解容器注解
  2. 定义一个可以重复的注解
  3. 配置多个重复的注解
  4. 解析得到指定注解
package com.lld;

/**
 * @ClassName demo02
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/9 21:59
 * @Version 1.0
 */
import org.junit.Test;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// 3.配置重复注解
@MyTest("ta")
@MyTest("tb")
@MyTest("tc")
public class demo02 {
    @Test
    @MyTest("ma")
    @MyTest("mb")
    public void test()  {
    }

    public static void main(String[] args) throws NoSuchMethodException {
        // 4.解析重复注解
        // 获取类上的重复注解
        // getAnnotationsByType是新增的API用户获取重复的注解
        MyTest[] annotationsByType = demo02.class.getAnnotationsByType(MyTest.class);
        for (MyTest myTest : annotationsByType) {
            System.out.println(myTest);
        }
        System.out.println("----------");
        // 获取方法上的重复注解
        MyTest[] tests = demo02.class.getMethod("test").getAnnotationsByType(MyTest.class);
        for (MyTest test : tests) {
            System.out.println(test);
        }
    }
}

// 1.定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests { // 这是重复注解的容器
    MyTest[] value();
}

// 2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
    String value();
}

2. 类型注解

JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: 、
TYPE_USE :表示注解可以再任何用到类型的地方使用。
TYPE_PARAMETER的使用

package com.lld;

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/9 22:00
 * @Version 1.0
 */
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;

public class demo03 <@TypeParam T> {
    private @NotNull int a = 10;

    public void test(@NotNull String str, @NotNull int a) {
        @NotNull double d = 10.1;
    }

    public <@TypeParam E extends Integer> void test01() {
    }
}

@Target(ElementType.TYPE_USE)
@interface NotNull {
}

@Target(ElementType.TYPE_PARAMETER)
@interface TypeParam {
}

3. 小结

通过@Repeatable元注解可以定义可重复注解, TYPE_PARAMETER 可以让注解放在泛型上, TYPE_USE 可以让注解放在类型的前面

你可能感兴趣的:(JDK8部分新特性)