Bootstrap

Java8新特性(自用笔记)

目录

一、Lambda表达式

1、概述  

2、Lambda表达式的格式

3、表达式格式解析

4、Lambda表达式的使用条件

5、Lambda表达式的省略格式

6、表达式的表现形式        

7、几种语法格式

8、函数式接口

9、案例

二、函数式接口

1、消费型接口

2、提供型接口

3、函数型接口

4、断言型接口

5、其他接口

三、引用

1、概念

2、方法引用 

① 对象的引用 :: 实例方法名

② 类名 :: 静态方法名

③ 类名 :: 实例方法名

3、构造器引用

4、数组引用

5、Lambda 表达式中的闭包问题

四、Stream API

1、什么是 Stream 流

2、操作 Stream 流

中间操作

1.筛选与切片

2.映射

3.排序

终止操作

1.查找与匹配

2.归约

3.收集

3、Stream API 练习

五、并行流与串行流

1、串行 Stream 流

1.获取串行流的两种方式:

2.Stream注意事项(重要)

2、并行 parallelStream 流

1.获取并行流的两种方式:

2.并行流的效率是要比串行流要高,底层使用Fork/Join框架进行处理,具体自行百度测试效率

3.解决并行流的线程安全问题:

4.注意事项

3.了解 Fork/Join 框架

六、Optional类

七、接口中的默认方法与静态方法

八、重复注解与类型注解

重复注解是什么?

如何定义重复注解?

 类型注解

九、新时间日期API

示例1:LocalDateTime转Long型值

示例2:时间戳转日期类型的字符串 

示例3 :获取当前时间

示例4:创建指定的本地化日期时间 

示例5:使用时区的日期时间API:ZoneId 、ZonedDateTime

示例6:时间格式化

示例7:时间比较 

示例8:时间加减 

示例9:其它方法 

 新旧日期转换:(了解会用就行,不要求掌握)

java.util.Date 转 java.time.LocalDateTime

java.util.Date 转 java.time.LocalDate

java.util.Date 转 java.time.LocalTime

java.time.LocalDateTime 转 java.util.Date

java.time.LocalDate转 java.util.Date

java.time.LocalTime转 java.util.Date

获取时间戳


一、Lambda表达式

1、概述  

Lambda表达式是函数式编程(体现了函数编程思想),只需要将要执行的代码放到函数中即可(可以理解为函数就是类中的方法)

----Lambda表达式是简化匿名内部类的简写

深入理解Java Lambda表达式 - 知乎 (zhihu.com)
        面向对象编程思想:
                强调的是【对象】,必须通过对象的形式来做一些事情,一般情况下会比较复杂。

                例如:多线程执行任务,需要创建对象,对象需要实现接口Runnable,我们想自己完成,需要将run方法中的代码传递给线程对象,这么麻烦?直接执行不久好了吗?

        函数编程思想:
                函数需要得有输入量、输出量,使用输入量计算得到输出量,【拿什么东西做什么事】

                就是为了尽量忽略对象的复杂用法---强调做什么,而不是以什么实行做,

                同样执行线程任务,使用函数编程思想,可以直接通过传递一段代码给线程对象执行,不需要创建任务对象。

                总之就一句话,函数编程思想可以通过一段代码完成面向对象想要做的代码量

image-20210813103450671

2、Lambda表达式的格式

1.标准格式:
                (参数列表) -> {代码}

        2.格式说明:
                - 小括内的语法与传统方法参数列表一致,没有参数就留空,有多个参数就用逗号分隔

                -  【->】  是新引入的语法格式,代表指向动作

                - 大括号内的语法与传统方法体要求一致

        3.案例说明
                第一个线程案例

Thread thread1 = new Thread(new Runnable() {
@Override
public void run () {
    System.out.println("线程需要执行的任务代码1");
}
});
thread1.start();
 
// Lambda表达式
Thread t2 = new Thread(()->{
    System.out.println("线程需要执行的任务代码2");
});
t2.start();


                第二个比较器案例

        List<Integer> list = new ArrayList<>();
        Collections.addAll(list,11,22,33,44,55);
        System.out.println("排序之前的集合:" + list);
        
        // 比较器的正常书写格式
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare (Integer o1, Integer o2) {
                return o2-o1;
            }
        });
 
        // Lambda表达式
        Collections.sort(list,(Integer o1, Integer o2)->{return o2-o1;});
        System.out.println("排序之后的集合:" + list);

3、表达式格式解析

Lambda表达式格式:()->{}

                1.小括号中书写的内容和接口中的抽象方法的参数列表一致

                2.大括号中书写的内容和实现接口中的抽象方法体一致

                3.箭头是固定的

4、Lambda表达式的使用条件

首先,都是接口;    其次,接口中有且只有一个接口,才可以使用lambda表达式

        1.接口中只有一个抽象方法的接口,叫做函数式接口

        2.如果是函数式接口,那么就可以用@FunctionalInterface注解标识

   上面这个是Thread线程的底层,下面的是Collections的底层

        思考(扩展):
         Collections接口中有且只有一个抽象方法compareTo(),为什么Java底层没有给它@FunctionalInterface注解标识?(底层代码较多,截图不便,放心,我替你们看过了)
        因为:lambda表达式属于  锦上添花  的功能,每个类都有各自的功能或作用,而Comparable接口目前学习到的主要是为了给自定义类实现比较器方法的,@FunctionalInterface注解标识的作用主要是为了给编译器看,给编译器提供信息的,不管有没有这个注解,只要满足函数式的要求,那么它就是一个函数式接口

5、Lambda表达式的省略格式

Lambda是挺简便的,往下看

 Lambda表达式的省略格式:
                1.小括号中的形参类型可以省略

                2.如果小括号中只有一个参数的话,那么小括号可以省略

                3.如果大括号中只有一条语句,那么大括号、分号、return可以一起 省略

        代码案例:
                1.第一个还是线程案例

      new Thread(()-> System.out.println("省略")).start();

                就这一行,就创建好了一个线程,并且可以将它顺利启动。

                如果觉得不够明了的话,看第二个案例

                2.第二个比较器的案例

    public static void main (String[] args) {
      
        // 比较器案例
        ArrayList<Integer> list = new ArrayList<>();
            list.add(11);
            list.add(22);
            list.add(33);
            list.add(44);
            System.out.println("排序前:" + list);
 
            // 比较器常规写法
            Collections.sort(list, new Comparator<Integer>() {
                @Override
                public int compare (Integer o1, Integer o2) {
                    return o2 - o1;
                }
            });
 
            // Lambda表达式
            Collections.sort(list,(o1,o2)->o2-o1);
            System.out.println("排序后:" + list);
        }

6、表达式的表现形式        

1.变量的形式:变量的类型为函数式接口,就么可以复制一个Lambda表达式【不常用】

        // 变量的形式
        Runnable r = ()->{
            System.out.println("任务代码");
        };
        // 函数式接口类型的变量
        Thread t = new Thread(r);

  2.参数的形式:方法的形参类型为函数式接口,就可以传入一个Lambda表达式【常用】

        // 变量的形式-比较器
        Comparator<Integer> comparable = (o1, o2)->{return o2 - o1;};
        // 创建集合
        ArrayList<Integer> list = new ArrayList<>();
        // 存入数据
        Collections.addAll(list,11,22,33,44,55);
 
        // 将函数式接口类型  的 形参类型,传给Collections
        Collections.sort(list,comparable);

  3.返回值的形式:方法的返回值类型为函数式接口,就可以返回一个Lambda表达式【常用】

        // 定义一个方法
        public static Comparator<Integer> getComparator(){
            return (Integer o1,Integer o2)->{return  o2-o1;};
        }
        
        public static void main (String[] args) {
            // 返回值形式
            Collections.sort(list,getComparator());
        }

7、几种语法格式

1、无参数,无返回值

语法格式:

无参数,无返回值:() -> System.out.println(“Hello Lambda!”);

例如 Runnable接口:

 public class Test02 {
     int num = 10; //jdk 1.7以前 必须final修饰
     
     @Test
     public void test01(){
         //匿名内部类
         new Runnable() {
             @Override
             public void run() {
                 //在局部类中引用同级局部变量
                 //只读
                 System.out.println("Hello World" + num);
             }
         };
     }
 
     @Test
     public void test02(){
         //语法糖
         Runnable runnable = () -> {
             System.out.println("Hello Lambda");
         };
     }
 }

2、有一个参数,并且无返回值

(x) -> System.out.println(x)

若只有一个参数,小括号可以省略不写 x -> System.out.println(x)

 @Test
 public void test03(){
     Consumer<String> consumer = (a) -> System.out.println(a);
     consumer.accept("我觉得还行!");
 }

3、有两个及以上的参数,有返回值,并且 Lambda 体中有多条语句

 @Test
 public void test04(){
     Comparator<Integer> comparator = (a, b) -> {
         System.out.println("比较接口");
         return Integer.compare(a, b);
     };
 }

4、有两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写)

 @Test
 public void test04(){
     Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
 }

5、若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写

 Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

6、Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”

 (Integer x, Integer y) -> Integer.compare(x, y);

8、函数式接口

接口中只有一个抽象方法的接口 @FunctionalIterface

定义一个函数式接口:

@FunctionalInterface
public interface MyFun {
    Integer count(Integer a, Integer b);
}

用一下:

@Test
public void test05(){
    MyFun myFun1 = (a, b) -> a + b;
    MyFun myFun2 = (a, b) -> a - b;
    MyFun myFun3 = (a, b) -> a * b;
    MyFun myFun4 = (a, b) -> a / b;
}

再用一下:

public Integer operation(Integer a, Integer b, MyFun myFun){
    return myFun.count(a, b);
}

@Test
public void test06(){
    Integer result = operation(1, 2, (x, y) -> x + y);
    System.out.println(result);
}

9、案例

案例一:调用 Collections.sort() 方法,通过定制排序 比较两个 Employee (先按照年龄比,年龄相同按照姓名比),使用 Lambda 表达式作为参数传递

定义实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    
    private Integer id;
    private String name;
    private Integer age;
    private Double salary;
}

定义 List 传入数据

List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test

@Test
public void test01(){
    Collections.sort(employeesList,(e1,e2)->{
        if (e1.getAge().equals(e2.getAge())) {
            return e1.getName().compareTo(e2.getName());
        }else {
            return Integer.compare(e1.getAge(),e2.getAge());
        }
    });

    for (Employee employee : employeesList) {
        System.out.println(employee);
    }
}

案例二:声明函数式接口,接口中声明抽象方法,String getValue(String str); 声明类 TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值;再将一个字符串的第二个和第四个索引位置进行截取字串

@FunctionalInterface
public interface MyFun02<T> {
    /**
     * 测试案例2
     * @param t
     * @return
     */
    T getValue(T t);
}
/**
 * 案例二:声明函数式接口,接口中声明抽象方法,String getValue(String str);
 * 声明类 TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值;
 * 再将一个字符串的第二个和第四个索引位置进行截取字串
 */
@Test
public void test02() {
    String str = "helloworld";
    
    // s -> s.toUpperCase() 可以写成 String::toUpperCase
    String toUpperString = toUpperString(str, s -> s.toUpperCase());
    System.out.println("toUpperString = " + toUpperString);
}

public String toUpperString(String string, MyFun02<String> myFun02) {
    return myFun02.getValue(string);
}

二、函数式接口

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

函数式接口参数类型返回类型用途
Consumer 消费型接口Tvoid对类型为T的对象应用操作:void accept(T t)
Supplier 提供型接口T返回类型为T的对象:T get()
Function<T, R> 函数型接口TR对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t)
Predicate 断言型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t)

1、消费型接口

/**
 * Consumer<T> 消费型接口 :
 */
@Test
public void consumerTest() {
    happy(2999.99, m -> System.out.println("此次消费了:" + m + "元"));
}

public void happy(Double money, Consumer<Double> consumer) {
    consumer.accept(money);
}

2、提供型接口

/**
 * Supplier<T> 供给型接口 :
 */
@Test
public void supplierTest() {
    Random random = new Random();
    List<Integer> numList = getNumList(10, () -> random.nextInt(100));
    numList.forEach(System.out::println);
}

/**
 * 需求:产生指定个数的整数,并放入集合中
 *
 * @param num
 * @param sup
 * @return
 */
public List<Integer> getNumList(int num, Supplier<Integer> sup) {
    List<Integer> list = new ArrayList<>();

    for (int i = 0; i < num; i++) {
        Integer n = sup.get();
        list.add(n);
    }

    return list;
}

3、函数型接口

/**
 * Function<T, R> 函数型接口
 */
@Test
public void functionTest() {
    // s -> s.trim() 可替换成 String::trim
    String newStr = strHandler("\t\t\t 威武   ", s -> s.trim());
    System.out.println(newStr);

    String subStr = strHandler("  威武呀", s -> s.substring(2, 5));
    System.out.println(subStr);
}

/**
 * 需求:用于处理字符串
 *
 * @param str
 * @param fun
 * @return
 */
public String strHandler(String str, Function<String, String> fun) {
    return fun.apply(str);
}

4、断言型接口

/**
 * Predicate<T> 断言型接口:
 */
@Test
public void predicateTest(){
    List<String> list = Arrays.asList("Hello", "yxj", "Lambda", "www", "ok");
    List<String> strings = filterStr(list, p -> p.length() > 3);
    strings.forEach(System.out::println);
}

/**
 * 需求:将满足条件的字符串,放入集合中
 *
 * @param list
 * @param pre
 * @return
 */
public List<String> filterStr(List<String> list, Predicate<String> pre) {
    List<String> strList = new ArrayList<>();

    for (String str : list) {
        if (pre.test(str)) {
            strList.add(str);
        }
    }

    return strList;
}


5、其他接口

20201207110028521

三、引用

1、概念

image-20210814174433781

image-20210813105218249

2、方法引用 

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

语法格式:

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

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


① 对象的引用 :: 实例方法名

/**
 * 对象::实例方法
 */
@Test
public void test01() {
    PrintStream printStream = System.out;
    Consumer<String> consumer = s -> printStream.println(s);
    consumer.accept("aaa");

    Consumer<String> consumer2 = printStream::println;
    consumer2.accept("bbb");
}

@Test
public void test02(){
    Employee emp = new Employee(101, "张三", 18, 9999.99);
    Supplier<String> supplier = ()->emp.getName();
    System.out.println(supplier.get());

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

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

    BiFunction<Double, Double, Double> fun2 = Math::max;
    Double apply1 = fun2.apply(88.88, 188.88);
    System.out.println(apply1);
}

**注意:**Lambda 表达实体中调用方法的参数列表、返回类型必须和函数式接口中抽象方法保持一致

② 类名 :: 静态方法名

/**
 * 类名 :: 静态方法名
 */
@Test
public void test02() {
    Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
    System.out.println(comparator.compare(1, 2));

    Comparator<Integer> comparator2 = Integer::compare;
    System.out.println(comparator2.compare(10, 20));
}

③ 类名 :: 实例方法名

/**
 * 类名 :: 实例方法名
 */
@Test
public void test05() {
    BiPredicate<String, String> bp = (x, y) -> x.equals(y);
    boolean test = bp.test("hello word", "hello future");

    BiPredicate<String, String> bp2 = String::equals;
    boolean test2 = bp2.test("hello word", "hello future");

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

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

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

**条件:**Lambda 参数列表中的第一个参数是方法的调用者,第二个参数是方法的参数时,才能使用 ClassName :: Method

3、构造器引用

/**
 * 构造器引用
 */
@Test
public void test06() {
    Supplier<Employee> supplier = () -> new Employee();
    Employee employee = supplier.get();
    System.out.println(employee);

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

@Test
public void test07(){
    Supplier<List> supplier = ()->new ArrayList<Integer>();
    List list = supplier.get();
    list.add(1);
    list.add(2);
    System.out.println(list);

    Supplier<List> supplier1 = ArrayList::new;
    List list1 = supplier1.get();
    System.out.println(list1);
}

**注意:**需要调用的构造器的参数列表要与函数时接口中抽象方法的参数列表保持一致

4、数组引用

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

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

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

apply.length = 10
--------------------------
apply1.length = 20

Process finished with exit code 0

5、Lambda 表达式中的闭包问题

        这个问题我们在匿名内部类中也会存在,如果我们把注释放开会报错,告诉我 num 值是 final 不能被改变。这里我们虽然没有标识 num 类型为 final,但是在编译期间虚拟机会帮我们加上 final 修饰关键字。

import java.util.function.Consumer;
public class Main {
    public static void main(String[] args) {

        int num = 10;

        Consumer<String> consumer = ele -> {
            System.out.println(num);
        };

        //num = num + 2;
        consumer.accept("hello");
    }
}

四、Stream API

1、什么是 Stream 流

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

注意:

Stream 自己不会存储元素

Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream

Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

2、操作 Stream 流

Stream 的操作三个步骤

创建 Stream
        一个数据源(如:集合、数组),获取一个流
中间操作
        一个中间操作链,对数据源的数据进行处理
终止操作(终端操作)
        一个终止操作,执行中间操作链,并产生结果

中间操作

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

1.筛选与切片

这里写图片描述

    List<Person> employees= Arrays.asList(
            new Person("张三",18,9999),
            new Person("李四",58,5555),
            new Person("王五",26,3333),
            new Person("赵六",36,6666),
            new Person("田七",12,8888),
            new Person("田七",12,8888)
    );

    /*  筛选与切片
     *  filter--接收Lambda,从流中排除某些元素。
     *  limit--截断流,使其元素不超过给定数量。
     *  skip(n)--跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n) 互补
     *  distinct--筛选,通过流所生成元素的 hashCode() 和 equals() 去掉重复元素
     */

    //内部迭代:迭代操作由 Stream API 完成
    @Test
    public void test1(){
        //中间操作:不会执行任何操作
        Stream<Person> stream=employees.stream()
                .filter((e) -> e.getAge()>35 );
        //终止操作:一次性执行全部内容,即 惰性求值
        stream.forEach(System.out::println);
    }

    //外部迭代
    @Test
    public void test2(){
        Iterator<Person> it=employees.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }

    @Test
    public void test3(){//发现“短路”只输出了两次,说明只要找到 2 个 符合条件的就不再继续迭代
        employees.stream()
                .filter((e)->{
                    System.out.println("短路!");
                    return e.getSalary()>5000;
                })
                .limit(2)
                .forEach(System.out::println);
    }

    @Test
    public void test4(){
        employees.stream()
                .filter((e)->e.getSalary()>5000)
                .skip(2)//跳过前两个
                .distinct()//去重,注意:需要Employee重写hashCode 和 equals 方法
                .forEach(System.out::println);
    }

 结果:

        test1:

Person(name=李四, age=58, salary=5555)
Person(name=赵六, age=36, salary=6666)

        test2:

Person(name=张三, age=18, salary=9999)
Person(name=李四, age=58, salary=5555)
Person(name=王五, age=26, salary=3333)
Person(name=赵六, age=36, salary=6666)
Person(name=田七, age=12, salary=8888)
Person(name=田七, age=12, salary=8888)

        test3:

短路!
Person(name=张三, age=18, salary=9999)
短路!
Person(name=李四, age=58, salary=5555)

        test4:

Person(name=赵六, age=36, salary=6666)
Person(name=田七, age=12, salary=8888)

2.映射

这里写图片描述

@Test
public void test01() {
    List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
    strList.stream().map(String::toUpperCase).forEach(System.out::println);

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

    Stream<Stream<Character>> streamStream = strList.stream().map(StreamTest02::filterCharacter);
    streamStream.forEach(sm->sm.forEach(System.out::println));
}

/**
 * 把 String 类型字符串转换成 Character 类型的流
 * @param str
 * @return
 */
public static Stream<Character> filterCharacter(String str) {
    List<Character> list = new ArrayList<>();

    for (char c : str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}

结果:

AAA
BBB
CCC
DDD
EEE
-----------------
a
a
a
b
b
b
c
c
c
d
d
d
e
e
e

Process finished with exit code 0

3.排序

这里写图片描述

Comparable:自然排序

@Test
public void test04(){
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    list.stream()
        .sorted() //comparaTo()
        .forEach(System.out::println);
}

Comparator:定制排序

@Test
public void test05(){
    emps.stream()
        .sorted((e1, e2) -> { //compara()
            if (e1.getAge().equals(e2.getAge())){
                return e1.getName().compareTo(e2.getName());
            } else {
                return e1.getAge().compareTo(e2.getAge());
            }
        })
        .forEach(System.out::println);
}

终止操作

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

1.查找与匹配

这里写图片描述

这里写图片描述

	List<Employee> employees=Arrays.asList(
			new Employee("张三",18,9999.99,Status.FREE),
			new Employee("李四",58,5555.55,Status.BUSY),
			new Employee("王五",26,3333.33,Status.VOCATION),
			new Employee("赵六",36,6666.66,Status.FREE),
			new Employee("田七",12,8888.88,Status.BUSY)
			);
	/*
	 * 查找与匹配
	 * 
	 */
	
	@Test
	public void test1(){
		boolean b1=employees.stream()//allMatch-检查是否匹配所有元素
							.allMatch((e)->e.getStatus().equals(Status.BUSY));
		System.out.println(b1);//false
		
		boolean b2=employees.stream()//anyMatch-检查是否至少匹配一个元素
							.anyMatch((e)->e.getStatus().equals(Status.BUSY));
		System.out.println(b2);//true
		
		boolean b3=employees.stream()//noneMatch-检查是否没有匹配所有元素
		         			.noneMatch((e)->e.getStatus().equals(Status.BUSY));
		System.out.println(b3);//false
		
		Optional<Employee> op=employees.stream()//findFirst-返回第一个元素//Optional是Java8中避免空指针异常的容器类
				 .sorted((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()))
				 .findFirst();
		System.out.println(op.get());//Employee [name=王五, age=26, salary=3333.33, Status=VOCATION]
		
		Optional<Employee> op2=employees.parallelStream()//findAny-返回当前流中的任意元素
										.filter((e)->e.getStatus().equals(Status.FREE))
										.findAny();
		System.out.println(op2.get());//Employee [name=赵六, age=36, salary=6666.66, Status=FREE]
		
		Long count=employees.stream()//count-返回流中元素的总个数
				            .count();
		System.out.println(count);//5
		
		Optional<Employee> op3=employees.stream()//max-返回流中最大值
				                        .max((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()));
		System.out.println(op3.get());//Employee [name=张三, age=18, salary=9999.99, Status=FREE]
		
		Optional<Double> op4=employees.stream()//min-返回流中最小值
				                      .map(Employee::getSalary)
				                      .min(Double::compare);
		System.out.println(op4.get());//3333.33
	}

2.归约

这里写图片描述

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

@Test
public void test07(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
    System.out.println(reduce);

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

    Optional<Double> optional = emps.stream().map(Employee::getSalary).reduce(Double::sum);
    System.out.println(optional.get());
}

结果:

55
-----------
33333.355

Process finished with exit code 0

3.收集

这里写图片描述

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

这里写图片描述

这里写图片描述

toList 

/**
 * 把员工的名字收集到指定的集合中
 */
@Test
public void test08(){
    List<String> list = emps.stream().map(Employee::getName).collect(Collectors.toList());
    list.forEach(System.out::println);

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

    Set<String> set = emps.stream().map(Employee::getName).collect(Collectors.toSet());
    set.forEach(System.out::println);

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

    Map<Long, String> collect = emps.stream().collect(Collectors.toMap(Employee::getId, Employee::getName));
    System.out.println(collect);
}

结果:

李四
张三
王五
赵六
田七
--------------
李四
张三
王五
赵六
田七
--------------
{101=张三, 102=李四, 103=王五, 104=赵六, 105=田七}

Process finished with exit code 0

总数、平均值

@Test
public void test09() {
    /// 总数
    // 等价于 Long count = emps.stream().count()
    // 还等价于 Long count = (long) emps.size(); 
    Long count = emps.stream().collect(Collectors.counting());
    System.out.println(count);

    //平均值
    Double avg = emps.stream()
            .collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println(avg);

    /// 总和
    // 等价于 Double sum = emps.stream().mapToDouble(Employee::getSalary).sum();
    Double sum = emps.stream().collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println(sum);

    //最大值
    Optional<Employee> max = emps.stream()
            .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
    System.out.println(max.get());

    //最小值
    Optional<Double> min = emps.stream()
            .map(Employee::getSalary)
            .collect(Collectors.minBy(Double::compare));
    System.out.println(min.get());
}

分组:

/**
 * 分组
 */
@Test
public void test10() {
    Map<Employee.status, List<Employee>> collect = emps.stream().collect(Collectors.groupingBy(Employee::getStatus));
    collect.forEach((key, value) -> System.out.println(key + ":" + value));
}

结果:

VOCATION:[Employee{id=103, name='王五', age=28, salary=3333.33, status=VOCATION}]
BUSY:[Employee{id=101, name='张三', age=18, salary=9999.99, status=BUSY}, Employee{id=105, name='田七', age=38, salary=5555.55, status=BUSY}]
FREE:[Employee{id=102, name='李四', age=59, salary=6666.66, status=FREE}, Employee{id=104, name='赵六', age=8, salary=7777.77, status=FREE}]

Process finished with exit code 0

多级分组:

/**
 * 多级分组
 */
@Test
public void test11() {
    Map<Employee.status, Map<Object, List<Employee>>> collect = emps.stream()
            .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
        if (e.getAge() < 35) {
            return "青年";
        } else if (e.getAge() < 50) {
            return "中年";
        }else {
            return "老年";
        }
    })));
    collect.forEach((key, value) -> System.out.println(key + ":" + value));
}

结果:

BUSY:{青年=[Employee{id=101, name='张三', age=18, salary=9999.99, status=BUSY}], 中年=[Employee{id=105, name='田七', age=38, salary=5555.55, status=BUSY}]}
VOCATION:{青年=[Employee{id=103, name='王五', age=28, salary=3333.33, status=VOCATION}]}
FREE:{青年=[Employee{id=104, name='赵六', age=8, salary=7777.77, status=FREE}], 老年=[Employee{id=102, name='李四', age=59, salary=6666.66, status=FREE}]}

Process finished with exit code 0

分区:

/**
 * 分区
 */
@Test
public void test12(){
    Map<Boolean, List<Employee>> collect = emps.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));
    collect.forEach((key, value) -> System.out.println(key + ":" + value));
}

结果:

false:[Employee{id=102, name='李四', age=59, salary=6666.66, status=FREE}, Employee{id=103, name='王五', age=28, salary=3333.33, status=VOCATION}, Employee{id=104, name='赵六', age=8, salary=7777.77, status=FREE}, Employee{id=105, name='田七', age=38, salary=5555.55, status=BUSY}]
true:[Employee{id=101, name='张三', age=18, salary=9999.99, status=BUSY}]

Process finished with exit code 0

summing:

/**
 * summing
 */
@Test
public void test13(){
    DoubleSummaryStatistics collect = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
    double max = collect.getMax();
    double min = collect.getMin();
    long count = collect.getCount();
    double average = collect.getAverage();
    double sum = collect.getSum();
}

joining:

/**
 * joining
 */
@Test
public void test14(){
    String collect = emps.stream().map(Employee::getName).collect(Collectors.joining(",","start ==","== end"));
    System.out.println(collect);
}

结果:

start ==李四,张三,王五,赵六,田七== end

Process finished with exit code 0

3、Stream API 练习

案例一:给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?

如:给定【1,2,3,4,5】,返回【1,4,9,16,25】

@Test
public void test01(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> stream = list.stream().map(e -> e * e);
    stream.forEach(System.out::println);

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

    Integer[] integer = new Integer[]{1, 2, 3, 4, 5};
    Arrays.stream(integer).map(e->e*e).forEach(System.out::println);
}

案例二:怎样使用 map 和 reduce 数一数流中有多少个 Employee 呢?

@Test
public void test02(){
    Optional<Integer> reduce = emps.stream().map(e -> 1).reduce(Integer::sum);
    System.out.println(reduce.get());
}

案例三:

Trader 交易员类:

public class Trader {
    private String name;
    private String city;
}

Transaction 交易类:

public class Transaction {
    private Trader trader;
    private int year;
    private int value;
}

TransactionTest 测试类:

public class TransactionTest {
    List<Transaction> transactions = null;

    @Before
    public void before() {
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");

        transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
    }
}

1.找出2011年发生的所有交易, 并按交易额排序(从低到高)

@Test
public void test01(){
    int targetYear = 2011;
    /// 可替换成 
    // Stream<Transaction> sorted = transactions.stream().filter(e -> e.getYear() == targetYear).sorted(Comparator.comparingInt(Transaction::getValue));
    Stream<Transaction> sorted = transactions.stream().filter(e -> e.getYear() == targetYear).sorted((e1, e2) -> {
        return Integer.compare(e1.getValue(), e2.getValue());
    });
    sorted.forEach(System.out::println);
}

结果:

Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300}
Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2011, value=400}

Process finished with exit code 0

2.交易员都在哪些不同的城市工作过?

@Test
public void test02(){
    transactions.stream().map(e->e.getTrader().getCity()).distinct().forEach(System.out::println);
}

结果:

Cambridge
Milan

Process finished with exit code 0

3.查找所有来自剑桥的交易员,并按姓名排序

@Test
public void test03(){
    String targetCity = "Cambridge";
    transactions.stream()
            .filter(e->e.getTrader().getCity().equals(targetCity))
            .map(Transaction::getTrader)
            // 可替换成 .sorted(Comparator.comparing(Trader::getName))
            .sorted((t1,t2)->t1.getName().compareTo(t2.getName()))
            .distinct()
            .forEach(System.out::println);
}

结果:

Trader{name='Alan', city='Cambridge'}
Trader{name='Brian', city='Cambridge'}
Trader{name='Raoul', city='Cambridge'}

Process finished with exit code 0

4.返回所有交易员的姓名字符串,按字母顺序排序

@Test
public void test04(){
    System.out.println("=====按姓名字母顺序排=====");
    transactions.stream().map(e->e.getTrader().getName()).sorted().distinct().forEach(System.out::println);

    System.out.println("=====先排序后拼接=====");
    String reduce = transactions.stream().map(e -> e.getTrader().getName()).sorted().distinct().reduce("", String::concat);
    System.out.println(reduce);

    System.out.println("=====把名字拆成一个个字母然后按字母排序(忽略大小写)=====");
    transactions.stream()
            .map(e -> e.getTrader().getName())
            .flatMap(TransactionTest::filterCharacter)
            /// 可替换成 .sorted(String::compareToIgnoreCase)
            .sorted((e1,e2)->e1.compareToIgnoreCase(e2))
            .forEach(System.out::print);
}

/**
 * 把 String 字符串中的字符一个个提取出来转成一个个字符串,然后再转换成 Stream 流
 * @param str
 * @return
 */
public static Stream<String> filterCharacter(String str){
    List<String> list = new ArrayList<>();

    for (Character c : str.toCharArray()) {
        list.add(c.toString());
    }
    return list.stream();
}

结果:

=====按姓名字母顺序排=====
Alan
Brian
Mario
Raoul
=====先排序后拼接=====
AlanBrianMarioRaoul
=====把名字拆成一个个字母然后按字母排序(忽略大小写)=====
aaaaaAaBiiilllMMnnoooorRRrruu
    
Process finished with exit code 0

5.有没有交易员是在米兰工作的?

@Test
public void test05(){
    String targetCity = "Milan";
    boolean b = transactions.stream().anyMatch(e -> e.getTrader().getCity().equals(targetCity));
    System.out.println(b);
}

结果:

true

Process finished with exit code 0

6.打印生活在剑桥的交易员的所有交易额

@Test
public void test06(){
    String targetCity = "Cambridge";
    Optional<Integer> sum = transactions.stream()
            .filter(e -> e.getTrader().getCity().equals(targetCity))
            .map(Transaction::getValue)
            .reduce(Integer::sum);
    System.out.println(sum.get());
}

结果:

2650

Process finished with exit code 0

7.所有交易中,最高的交易额是多少

@Test
public void test07(){
    Optional<Integer> max = transactions.stream().map(Transaction::getValue).max(Integer::compare);
    System.out.println(max.get());

    System.out.println("=====注意审题:要的是最高交易额,而不是最高交易额的交易=====");
    System.out.println("=====以下的返回的是最高交易额的交易=====");
    Optional<Transaction> transaction = transactions.stream()
        .max(Comparator.comparingInt(Transaction::getValue));
    System.out.println(transaction.get());
}

结果:

1000
=====注意审题:要的是最高交易额,而不是最高交易额的交易=====
=====以下的返回的是最高交易额的交易=====
Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2012, value=1000}

Process finished with exit code 0

8.找到交易额最小的交易

@Test
public void test08(){
    Optional<Transaction> min = transactions.stream().min(Comparator.comparingInt(Transaction::getValue));
    System.out.println(min.get());
}

结果:

Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300}

Process finished with exit code 0

五、并行流与串行流

1、串行 Stream 流

1.获取串行流的两种方式:

所有的 Collection 集合都可以通过 stream 默认方法获取流:list.stream();
Stream 接口的静态方法 of 可以获取数组对应的流:Stream.of(6,1,5,4,3);

// 集合获取流
// Collection接口中的方法: default Stream<E> stream() 获取流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();

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

Vector<String> vector = new Vector<>();
Stream<String> stream3 = vector.stream();

// Map获取流
Map<String, String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

2.Stream注意事项(重要)

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

2、并行 parallelStream 流

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

1.获取并行流的两种方式:

直接获取并行的流:
将串行流转成并行流:

// 直接获取并行的流
ArrayList<Integer> list = new ArrayList<>();
Stream<Integer> stream = list.parallelStream();
// 将串行流转成并行流
ArrayList<Integer> list2 = new ArrayList<>();
Stream<Integer> stream = list2.stream().parallel();

2.并行流的效率是要比串行流要高,底层使用Fork/Join框架进行处理,具体自行百度
测试效率

public class Demo {
    private static long times = 50000000000L;
    private long start;
    
    @Before
    public void init() {
        start = System.currentTimeMillis();
    } 

    @After
    public void destory() {
        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end - start));
    }
    
    // 测试效率,parallelStream 122
    @Test
    public void parallelStream() {
        System.out.println("serialStream");
        LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
    } 
    
    // 测试效率,普通Stream 354
    @Test
    public void serialStream() {
        System.out.println("serialStream");
        LongStream.rangeClosed(0, times).reduce(0, Long::sum);
    }
}

3.解决并行流的线程安全问题:

多线程下,使用并行流会有线程安全的问题
根据需要进行不同的处理:

-使用同步代码块 synchronized (比如使用forEach循环处理时)
-使用线程安全的集合 Vector、Collections.synchronizedList(list)
-调用Stream流的 collect/toArray 方法


4.注意事项

1.parallelStream是线程不安全的
2.parallelStream适用的场景是CPU密集型的,只是做到别浪费CPU,假如本身电脑CPU的负载很大,那还到处用并行流,那并不能起到作用
3.I/O密集型 磁盘I/O、网络I/O都属于I/O操作,这部分操作是较少消耗CPU资源,一般并行流中不适用于I/O密集型的操作,就比如使用并流行进行大批量的消息推送,涉及到了大量I/O,使用并行流反而慢了很多
4.在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证其中的顺序
 

3.了解 Fork/Join 框架

       Fork/Join 框架:就是在必要的情况下,将一个大任务,进形拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运行的结果进行join汇总。

这里写图片描述

Fork/Join 框架与传统线程池的区别:

采用“工作窃取”模式(work-stealing):
当执行新的任务时,它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
 

六、Optional类

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

注意:Optional 不能被序列化

常用方法:

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

	@Test
	public void test5(){
		Man man=new Man();
		String name=getGodnessName(man);
		System.out.println(name);
	}
	//需求:获取一个男人心中女神的名字
	public String getGodnessName(Man man){
		if(man!=null){
			Godness g=man.getGod();
			if(g!=null){
				return g.getName();
			}
		}
		return "赵丽颖";
	}
	
	//运用Optional的实体类
	@Test
	public void test6(){
		Optional<Godness> godness=Optional.ofNullable(new Godness("林志玲"));
		Optional<NewMan> op=Optional.ofNullable(new NewMan(godness));
		String name=getGodnessName2(op);
		System.out.println(name);
	}
	
	public String getGodnessName2(Optional<NewMan> man){
		return man.orElse(new NewMan())
				  .getGodness()
				  .orElse(new Godness("赵丽颖"))
				  .getName();
	}

七、接口中的默认方法与静态方法

以前接口类中只允许有全局静态常量和抽象方法

1、Java8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。

接口默认方法的“类优先”原则:

若一个接口中定义了一个默认方法,而另一个父类或接口中又定义了一个同名的方法时

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

interface MyFunc{
    default String getName(){
        return "hahaha";
    }
}

interface Named{
    default String getName(){
        return "hehehe";
    }
}

class MyClass implements MyFunc,Named{
    public String getName(){
        return Named.super.getName();
    }
}

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

八、重复注解与类型注解

Java8 对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

        注解(Annotation)是Java 5推出来的,推出来后使用极其广泛,一些流行的框架基本都会用到注解实现一些功能,代替以前的配置方式,最典型的就是Spring。在继续往下讲之前,我先声明你得知道注解的知识,我假设你已经掌握注解知识并能够自定义注解了。

重复注解是什么?

       重复注解表示可以在同一处位置(方法、类以及类变量等等)多次使用同一个注解,效果如下所示。

public class TestAnnotation {

    @MyAnnotation("Hello")
    @MyAnnotation("World")
    public void show() {
        System.out.println("美美侠");
    }

}

如何定义重复注解?

       在Java 8之前,同一个注解是不能在一个位置上重复使用的,例如我定义了如下这样一个@MyAnnotation注解。

package com.meimeixia.java8;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	
	String value() default "美美侠";

}

       然后再定义了一个TestAnnotation类,在该类的方法上重复使用@MyAnnotation注解,那么编译时就会报错。

在这里插入图片描述

如果一定要在这个类的方法上多次使用@MyAnnotation注解,按照以前的做法,我们需要定义一个注解容器,顾名思义是存放注解的,例如我定义如下这样一个注解容器(@MyAnnotations),其中,它的value属性是一个MyAnnotation数组。 

package com.meimeixia.java8;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

	MyAnnotation[] value();
	
}

那么在TestAnnotation类的show()方法上我们就可以通过注解容器让show()方法多次使用@MyAnnotation注解了。

public class TestAnnotation {
	
	@MyAnnotations({@MyAnnotation("Hello"), @MyAnnotation("World")})
	public void show() {
		
	}
}

其实严格意义上来说,以上TestAnnotation类的show()方法并不是重复使用了@MyAnnotation注解,而是使用了@MyAnnotations注解容器。

现在,在Java 8中提供了一个@Repeatable注解,对用户来说,它可以隐藏掉注解容器,所以,定义重复注解只需要在原有的注解基础上添加@Repeatable这个元注解标签。
 

package com.meimeixia.java8;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Repeatable(MyAnnotations.class) //用@Repeatable注解指定了那个注解容器类
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	
	String value() default "美美侠";

}

需要注意的是,@Repeatable注解是需要一个注解容器类的。也就是说,定义重复注解需要两个类,一个是注解类,另外一个是注解的容器类。 现在在TestAnnotation类的show()方法上我们就可以直接重复使用@MyAnnotation注解了。

package com.meimeixia.java8;

import java.lang.reflect.Method;

import org.junit.Test;

/**
 * 重复注解与类型注解
 * 
 * 要想定义重复注解,你必须得给它搞一个注解容器类
 * @author liayun
 *
 */
public class TestAnnotation {
	
	//通常,以后我们会配合反射来使用
	@Test
	public void test1() throws Exception {
		Class<TestAnnotation> clazz = TestAnnotation.class;
		Method m1 = clazz.getMethod("show");
		MyAnnotation[] mas = m1.getAnnotationsByType(MyAnnotation.class);
		for (MyAnnotation myAnnotation : mas) {
			System.out.println(myAnnotation.value());
		}
	}
	
	@MyAnnotation("Hello")
	@MyAnnotation("World")                
	public void show() {
		
	}
	
}

运行以上test1()测试方法,你便能在Eclipse控制台中看到如下打印内容了。

在这里插入图片描述

 类型注解

        在注解类中的@Target元注解中添加变量"TYPE_PARAMETER",可以使得注解能够定义在类型上。

在这里插入图片描述

使用效果如下所示:

@MyAnnotation("Hello")
@MyAnnotation("World")                
public void show(@MyAnnotation("abc") String str) {
    
}

 温馨提示:类型注解可以注解任意类型,包括泛型类型、数组类型等等。

不知你有没有想过,在Java 8中可不可以像下面这样使用类型注解?

在这里插入图片描述

很明显,不可以。因为在Java8中还没有内置像@NonNull这样的注解,需要我们去配合其他的框架(例如checker framework)使用。比如说,要是使用了checker framework,并且用@NonNull注解了以上obj这个变量,那么这个变量的值就不能为null了,如果为null,那么就会报编译时检查错误,也即编译不通过!
 

九、新时间日期API

以前的时间API是线程不安全的,是可变的

多线程对日期进行处理要加锁

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

这里写图片描述

示例1:LocalDateTime转Long型值

public static void main(String[] args) throws Exception {
    String str = "20201231121212";
    String pattern = "yyyyMMddHHmmss";

    long res1 = new SimpleDateFormat(pattern).parse(str).getTime();
    System.out.println(res1);

    LocalDateTime ldt = LocalDateTime.parse(str, DateTimeFormatter.ofPattern(pattern));
    Long newSecond = ldt.toEpochSecond(ZoneOffset.of("+8")) * 1000;
    System.out.println(newSecond);
}

结果:

在这里插入图片描述

示例2:时间戳转日期类型的字符串 

public static String long2str(long dateLong, String pattern) {
    LocalDateTime ldt =LocalDateTime.ofInstant(Instant.ofEpochMilli(dateLong), ZoneId.systemDefault());
    String res = ldt.format(DateTimeFormatter.ofPattern(pattern));
    return res;
}
public static void main(String[] args) throws Exception {
    String str = "20201231121212";
    String pattern = "yyyyMMddHHmmss";

    long longTime = new SimpleDateFormat(pattern).parse(str).getTime();
    System.out.println(longTime);
    String res = long2str(longTime, pattern);
    System.out.println(res);
}

示例3 :获取当前时间

@Test
public void testNowTime() {
    // 获取当前日期
    LocalDate localDate = LocalDate.now();
    System.out.println("当前日期:" + localDate);
    System.out.println("当前日期:" + localDate.getYear() + "-" + localDate.getMonthValue() + "-" + localDate.getDayOfMonth());

    // 获取当天时间
    LocalTime localTime = LocalTime.now();
    System.out.println("当天时间:" + localTime);
    System.out.println("当天时间:" + localTime.getHour() + ":" + localTime.getMinute() + ":" + localTime.getSecond());

    // 当前精确时间
    LocalDateTime now = LocalDateTime.now();
    System.out.println("当前精确时间:" + now);
    System.out.println("当前精确时间:" + now.getYear() + "-" + now.getMonthValue() + "-" + now.getDayOfMonth() + " " + now.getHour() + "-" + now.getMinute() + "-" + now.getSecond());

    // 有时区的当前精确时间
    ZonedDateTime nowZone = LocalDateTime.now().atZone(ZoneId.systemDefault());
    System.out.println("当前精确时间(有时区):" + nowZone);
    System.out.println("当前精确时间(有时区):" + nowZone.getYear() + "-" + nowZone.getMonthValue() + "-" + nowZone.getDayOfMonth() + " " + nowZone.getHour() + "-" + nowZone.getMinute() + "-" + nowZone.getSecond());
}

结果:

在这里插入图片描述

示例4:创建指定的本地化日期时间 

 @Test
    public void dateAPI() {
        LocalDate localDate = LocalDate.of(1999,9,21);
        System.out.println(localDate);
        System.out.println(localDate.getYear());
        System.out.println(localDate.getMonth());
        System.out.println(localDate.getDayOfMonth());
        LocalDate localDateParse = LocalDate.parse("1840-01-01");
        System.out.println(localDateParse.getYear());

        LocalTime localTime = LocalTime.of(3, 20);
        System.out.println(localTime);
        System.out.println(localTime.getHour());
        System.out.println(localTime.getMinute());
        LocalTime localTimeParse = LocalTime.parse("11:35");
        System.out.println(localTimeParse.getHour());

        LocalDateTime localDateTime1 = LocalDateTime.of(1999, 9, 21, 12, 12, 12);
        System.out.println(localDateTime1);
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(localDateTime);
        LocalDate localDate1 = localDateTime.toLocalDate();
        System.out.println(localDate1);
        LocalTime localTime1 = localDateTime.toLocalTime();
        System.out.println(localTime1);
        LocalDateTime localDateTimeParse = LocalDateTime.parse("2999-09-21T11:44:11");
        System.out.println(localDateTimeParse.getMinute());
    }

示例5:使用时区的日期时间API:ZoneId 、ZonedDateTime

@Test
public void zonedDateTime(){
    ZoneId currentZone = ZoneId.systemDefault();
    System.out.println("当期时区: " + currentZone);

    // 获取当前时间日期
    ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
    System.out.println("date1: " + date1);

    LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
    System.out.println(localDateTime);
    LocalDateTime localDateTime11 = LocalDateTime.now(ZoneId.of("Europe/Paris"));
    System.out.println(localDateTime11);
}

示例6:时间格式化

@Test
public void testFormat() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println("当前时间:" + now);
    System.out.println("格式化后:" + now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    System.out.println("格式化后:" + now.format(DateTimeFormatter.ISO_LOCAL_DATE));
    System.out.println("格式化后:" + now.format(DateTimeFormatter.ISO_LOCAL_TIME));
    System.out.println("格式化后:" + now.format(DateTimeFormatter.ofPattern("YYYY-MM-dd hh:mm:ss")));
}

结果:

在这里插入图片描述

示例7:时间比较 

@Test
public void testComp() {
    LocalDateTime now = LocalDateTime.now();
    LocalDateTime after = LocalDateTime.of(1999,9,21,12,12,12);

	System.out.println(now.equals(after));
	// isEqual:如果比较的日期为空,则会抛出异常
	System.out.println(now.isEqual(after));
	
    System.out.println(now + " 在 " + after + " 之后吗? " + now.isAfter(after));
    System.out.println(now + " 在 " + after + " 之前吗? " + now.isBefore(after));

    // 时间差
    long day = after.until(now, ChronoUnit.DAYS);
    System.out.println("相差天数: " + day);
    long month = after.until(now, ChronoUnit.MONTHS);
    System.out.println("相差月份: " + month);
    long hours = after.until(now, ChronoUnit.HOURS);
    System.out.println("相差小时: " + hours);
    long minutes = after.until(now, ChronoUnit.MINUTES);
    System.out.println("相差分钟: " + minutes);

    // 距离JDK 14 发布还有多少天?
    LocalDate jdk14 = LocalDate.of(2020, 11, 17);
    LocalDate nowDate = LocalDate.now();
    System.out.println("距离JDK 14 发布还有:" + nowDate.until(jdk14, ChronoUnit.DAYS) + "天");
}

结果:

在这里插入图片描述

示例8:时间加减 

@Test
public void testCalculate() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println("当前时间是:"+now);
    LocalDateTime plusTime = now.plusMonths(1).plusDays(2).plusHours(12).plusMinutes(12).plusSeconds(12);
    System.out.println("增加1月2天12小时12分钟12秒时间后:" + plusTime);
    LocalDateTime minusTime = now.minusMonths(2);
    System.out.println("减少2个月时间后:" + minusTime);
}

结果:

在这里插入图片描述

示例9:其它方法 

@Test
public void timeFunctionTest() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println("当前时间:" + now);
    // 第十天
    LocalDateTime firstDay = now.withDayOfMonth(10);
    System.out.println("本月第一天:" + firstDay);
    // 最后一天
    LocalDateTime lastDay = now.with(TemporalAdjusters.lastDayOfMonth());
    System.out.println("本月最后一天:" + lastDay);
    // 是否闰年
    System.out.println("今年是否闰年:" + Year.isLeap(now.getYear()));
}

结果:

在这里插入图片描述

 新旧日期转换:(了解会用就行,不要求掌握)

java.util.Date 转 java.time.LocalDateTime

public LocalDateTime date2LocalDateTime(Date date) {
    Instant instant = date.toInstant();
    ZoneId zone = ZoneId.systemDefault();
    LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
    return  localDateTime;
}

java.util.Date 转 java.time.LocalDate

public LocalDate date2LocalDate(Date date) {
    Instant instant = date.toInstant();
    ZoneId zone = ZoneId.systemDefault();
    LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
    LocalDate localDate = localDateTime.toLocalDate();
    return localDate;
}

java.util.Date 转 java.time.LocalTime

public LocalTime date2LocalTime(Date date) {
    Instant instant = date.toInstant();
    ZoneId zone = ZoneId.systemDefault();
    LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
    LocalTime localTime = localDateTime.toLocalTime();
    return localTime;
}

java.time.LocalDateTime 转 java.util.Date

public Date LocalDateTime2Date(LocalDateTime localDateTime) {
    ZoneId zone = ZoneId.systemDefault();
    Instant instant = localDateTime.atZone(zone).toInstant();
    Date date = Date.from(instant);
    return date;
}

java.time.LocalDate转 java.util.Date

public Date LocalDate2Date(LocalDate localDate) {
    ZoneId zone = ZoneId.systemDefault();
    Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
    Date date = Date.from(instant);
    return date;
}

java.time.LocalTime转 java.util.Date

public Date LocalTime2Date(LocalTime localTime) {
    LocalDate localDate = LocalDate.now();
    LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
    ZoneId zone = ZoneId.systemDefault();
    Instant instant = localDateTime.atZone(zone).toInstant();
    Date date = Date.from(instant);
    return date;
}

获取时间戳

public static void main(String[] args) {
    //获取当前时间戳
    final long epochMilli = Instant.now().toEpochMilli();
    System.out.println(epochMilli);

    // 通过时间戳生成当前时间(东八区)
    final LocalDateTime localDateTime = Instant.ofEpochMilli(epochMilli)
            .atZone(ZoneOffset.ofHours(8))
            .toLocalDateTime();
    System.out.println(localDateTime);
}

结果:

在这里插入图片描述


 

;