当前位置:   article > 正文

JDK8新特性 (Lambda表达式和Stream流式编程)_jdk8流式编程

jdk8流式编程

目录

一:JDK8新特性

1. Java SE的发展历史

2. 了解Open JDK 和 Oracle JDK

3. JDK 8新特性

3.1 Lambda表达式(重点)

3.2 接口的增强

3.3 函数式接口

3.4 方法引用

3.5 集合之Stream流式操作(重点)

3.6 新的时间和日期 API


一:JDK8新特性

1. Java SE的发展历史

        Sun公司在1991年成立了一个称为绿色计划( Green Project )的项目,由James Gosling(高斯林)博土领导,绿色计划 的目的是开发一种能够在各种消费性电子产品(机顶盒、冰箱、收音机等)上运行的程序架构。这个项目的产品就是Java语言的前身: Oak(橡树)。Oak当时在消费品市场上并不算成功,但随着1995年互联网潮流的兴起,Oak迅速找到 了最适合自己发展的市场定位。

现在我们就介绍一下JDK的更新版本和命名:

JDK Beta - 1995 JDK 1.0 - 1996年1月 (真正第一个稳定的版本JDK 1.0.2,被称作 Java 1 ) JDK 1.1 - 1997年2月

J2SE 1.2 - 1998年12月

J2ME(Java 2 Micro Edition,Java 2平台的微型版),应用于移动、无线及有限资源的环境

J2SE(Java 2 Standard Edition,Java 2平台的标准版),应用于桌面环境。

J2EE(Java 2 Enterprise Edition,Java 2平台的企业版),应用于基于Java的应用服务器。

J2SE 1.3 - 2000年5月

J2SE 1.4 - 2002年2月

J2SE 5.0 - 2004年9月

Java SE 6 - 2006年12月

Java SE 7 - 2011年7月

Java SE 8(LTS) - 2014年3月

Java SE 9 - 2017年9月

Java SE 10(18.3) - 2018年3月

Java SE 11(18.9 LTS) - 2018年9月

Java SE 12(19.3) - 2019年3月

Java SE 13(19.9) - 2019年9月

........................................................

2. 了解Open JDK 和 Oracle JDK

(1)Open JDK来源

        Java 由 Sun 公司发明,Open JDK是Sun在2006年末把Java开源而形成的项目。也就是说Open JDK是Java SE平台版 的开源和免费实现,它由 SUN 和 Java 社区提供支持,2009年 Oracle 收购了 Sun 公司,自此 Java 的维护方之一的SUN 也变成了 Oracle。

(2)Open JDK 和 Oracle JDK的关系

        大多数 JDK 都是在 Open JDK 的基础上进一步编写实现的,比如 IBM J9, Oracle JDK 和 Azul Zulu, Azul Zing。Oracle JDK完全由 Oracle 公司开发,Oracle JDK是基于Open JDK源代码的商业版本;此外,它包含闭源组件。Oracle JDK根据二进制代码许可协议获得许可,在没有商业许可的情况下,在2019年1月之后发布的Oracle Java SE 8的公开更新将无法用于商业或生产用途。但是 Open JDK是完全开源的,可以自由使用。

 (3)Open JDK 官网介绍

Open JDK 官网:https://openjdk.org/

JDK Enhancement Proposals(JDK增强建议);通俗的讲JEP就是JDK的新特性! 

3. JDK 8新特性

3.1 Lambda表达式(重点)

(1)匿名内部类存在的问题

我们先写一个线程的实现,通过匿名内部类的方式;由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。

  1. public class Test {
  2. public static void main(String[] args) {
  3. // 匿名内部类
  4. new Thread(new Runnable() {
  5. public void run() {
  6. System.out.println("Hello");
  7. }
  8. }).start();
  9. }
  10. }

代码分析:对于 Runnable 的匿名内部类用法,可以分析出几点内容:

①Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类。

②为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部类 。

③必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;而实际上似乎只有方法体才是关键所在。

(2)Lambda表达式初体验

Lambda是一个匿名函数,可以理解为一段可以传递的代码;借助Java8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果!

  1. public class Test {
  2. public static void main(String[] args) {
  3. // Lambda表达式
  4. new Thread(() ->
  5. System.out.println("Hello")).start();
  6. }
  7. }

代码分析:这段代码和刚才的执行效果是完全一样的,可以在JDK8或更高的编译级别下通过。

①从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定;我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象

②Lambda的优点:简化匿名内部类的使用,语法更加简单;实际上Lambda是匿名内部类的简写。

(3)Lambda的标准格式

Lambda表达式是一个匿名函数,而函数相当于Java中的方法;Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:

  1. (参数类型 参数名称) -> {
  2. 代码体;
  3. }

格式说明:

①(参数类型 参数名称):参数列表;

②{代码体;}:方法体;

③-> :箭头,分隔参数列表和方法体,起到连接的作用;

Lambda与方法的实现对比

例如:public static void main(String[] args),Lambda就是这种形式(String[] args);可以省略修饰符列表、返回值、方法名:

匿名内部类

  1. public void run() {
  2. System.out.println("aa");
  3. }

Lambda表达式

() -> System.out.println("bb")

(4)无参数、无返回值的Lambda

定义一个接口,无参数方法返回void:

  1. package com.zl;
  2. public interface Swimmable {
  3. // 抽象方法,省略了public static
  4. void swimming();
  5. }

测试类:

  1. package com.zl;
  2. public class SwimmableTest {
  3. public static void main(String[] args) {
  4. // 使用匿名内部类
  5. play(new Swimmable() {
  6. @Override
  7. public void swimming() {
  8. System.out.println("匿名内部类游泳!");
  9. }
  10. });
  11. // 使用Lambda表达式:对匿名内部类的简写
  12. play(()-> System.out.println("Lambda的游泳!"));
  13. }
  14. // 调用方法
  15. public static void play(Swimmable s){
  16. s.swimming();
  17. }
  18. }

(5)有参数、有返回值的Lambda

定义一个接口,有参数方法返回int类型:

  1. package com.zl;
  2. public interface Love {
  3. int myLove(String name);
  4. }

测试类

  1. package com.zl;
  2. public class LoveTest {
  3. public static void main(String[] args) {
  4. // 匿名内部类
  5. lovers(new Love() {
  6. @Override
  7. public int myLove(String name) {
  8. System.out.println("匿名内部类的方式");
  9. return 1;
  10. }
  11. });
  12. // Lambda表达式,多行代码需要一个大括号{}
  13. lovers((String name) ->{
  14. System.out.println("Lambda表达式的方式");
  15. return 1;
  16. });
  17. }
  18. // 调用方法
  19. public static void lovers(Love love){
  20. int count = love.myLove("小红");
  21. System.out.println("返回值是"+count);
  22. }
  23. }

小总结:

①以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,Lambda表达式相当于是对接口中抽象方法的重写。

② 匿名内部类和Lambda表达式结果的对比:

匿名内部类:在编译后会形成一个新的类,叫做:类名$.class,可以直接打开。

Lambda表达式:在编译后不会生成新的类(运行的时候才会形成类)就是原来的类,但是此时原来的类打不开,并且反编译工具无法反编译;我们使用JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令:javap -c -p 文件名.class ,发现会在这个类当中生成一个私有的静态方法;实际上Lambda表达式中的代码就会放到这个新增的静态方法当中。

总结:匿名内部类在编译的时候会一个class文件;Lambda在程序运行的时候形成一个类:在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码、还会形成一个匿名内部类,实现接口,重写抽象方法、在接口的重写方法中会调用新生成的方法。

(6)Lambda省略格式

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

①小括号内参数的类型可以省略;

②如果小括号内有且仅有一个参数,则小括号可以省略;

③如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号;

对于一个Lambda表达式:

  1. (int a) -> {
  2. return new Person();
  3. }

第一步省略:省略类型

  1. (a) -> {
  2. return new Person();
  3. }

 第二步省略:只有一个参数时,小括号也可以省略

  1. a -> {
  2. return new Person();
  3. }

第三步省略:发现括号内只有一条语句,省略大括号、return关键字、结束分号(必须同时省略)

a -> new Person()

例:在调用Collectons.sort()方法排序时,里面的参数只能是一个 List集合;对于自定义的类型进行排序还需要传一个比较器(比较器中编写比较的逻辑)

Person类:

  1. package com.zl.mapper;
  2. public class Person {
  3. private String name;
  4. private int age;
  5. public Person() {
  6. }
  7. public Person(String name, int age) {
  8. this.name = name;
  9. this.age = age;
  10. }
  11. @Override
  12. public String toString() {
  13. return "Person{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. '}';
  17. }
  18. public String getName() {
  19. return name;
  20. }
  21. public void setName(String name) {
  22. this.name = name;
  23. }
  24. public int getAge() {
  25. return age;
  26. }
  27. public void setAge(int age) {
  28. this.age = age;
  29. }
  30. }

PersonSort类:把上面的Person放到List集合当中,然后进行排序

  1. package com.zl.mapper;
  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.Comparator;
  5. import java.util.List;
  6. public class PersonSort {
  7. public static void main(String[] args) {
  8. // 创建一个ArrayList集合
  9. List<Person> pList = new ArrayList<>();
  10. // 准备数据
  11. Person p1 = new Person("张三", 20);
  12. Person p2 = new Person("李四", 19);
  13. Person p3 = new Person("王五", 22);
  14. // 把数据添加到集合当中
  15. pList.add(p1);
  16. pList.add(p2);
  17. pList.add(p3);
  18. // 调用Collections工具类的sort方法进行排序
  19. // 第一种方法:采用匿名内部类的方式
  20. Collections.sort(pList, new Comparator<Person>() {
  21. @Override
  22. public int compare(Person o1, Person o2) {
  23. return o1.getAge()-o2.getAge();
  24. }
  25. });
  26. // 第二种方式:采用Lambda表达式
  27. Collections.sort(pList,(Person o1,Person o2)->{
  28. return o1.getAge()-o2.getAge();
  29. });
  30. // 第三种方式:使用Lambda表达式的省略模式
  31. Collections.sort(pList,((o1, o2) -> o1.getAge()-o2.getAge()));
  32. // 打印
  33. for (Person person : pList) {
  34. System.out.println(person);
  35. }
  36. // 打印的第二种方式
  37. pList.forEach(person -> System.out.println(person));
  38. }
  39. }

(7)Lambda的前提条件

Lambda的语法非常简洁,但是Lambda表达式使用时有几个条件要特别注意:

①方法的参数或局部变量类型必须为接口才能使用Lambda表达式;

②接口中有且仅有一个抽象方法;

怎么判定在接口中只有一个抽象方法呢?使用函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口。

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

③使用FunctionalInterface注解,这个注解与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上

  1. @FunctionalInterface
  2. public interface Operator {
  3. void myMethod();
  4. }

④一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样!

(8)Lambda和匿名内部类对比

了解Lambda和匿名内部类在使用上的区别?

①所需的类型不一样

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

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

②抽象方法的数量不一样

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

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

③实现原理不同:

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

Lambda表达式:在程序运行的时候动态生成class。

总结:一方面Lambda表达式作为接口的实现类的对象,另一方面Lambda表达式是一个匿名函数;当接口中只有一个抽象方法时,建议使用Lambda表达式;其他其他情况还是需要使用匿名内部类!

3.2 接口的增强

(1)JDK8中接口的新增

在JDK8中针对接口有做增强,在JDK8之前,接口中只能有常量和抽象方法

  1. interface 接口名{
  2.    静态常量;
  3.    抽象方法;
  4. }

JDK8之后对接口做了增加,接口中可以有默认方法静态方法

  1. interface 接口名{
  2.    静态常量;
  3.    抽象方法;
  4.    默认方法;
  5.    静态方法;
  6. }

(2)默认方法

我们先创建一个接口,然后再创建一个类实现这个接口,并且必须重写接口中的方法!

Anmail接口

  1. package com.zl.anmails;
  2. public interface Anmails {
  3. // 抽象方法
  4. void fly();
  5. }

Bird类实现接口,并重写里面的方法

  1. package com.zl.anmails;
  2. public class Bird implements Anmails{
  3. @Override
  4. public void fly() {
  5. System.out.println("小鸟起飞!");
  6. }
  7. }

如果此时我们在接口中又增加了其它方法呢?那么实现类都必须要重写这个抽象方法,这样就不利于接口的扩展;所以我们就可以定义成默认方法,语法格式如下:

  1. interface 接口名{
  2.    修饰符 default 返回值类型 方法名{
  3.        方法体;
  4.   }
  5. }

在接口中定义默认方法:

这个默认方法,实现类会默认继承过去,继承过去的是和父类一模一样的方法!

当然也可以进行重写,在方法体中编写自己的业务逻辑。

  1. package com.zl.anmails;
  2. public interface Anmails {
  3. // 抽象方法
  4. void fly();
  5. // 默认方法
  6. public default void eat(){
  7. System.out.println("小鸟爱吃虫子");
  8. }
  9. }

(3)静态方法

JDK8中为接口新增了静态方法,作用也是为了接口的扩展,语法规则:

  1. interface 接口名{
  2.    修饰符 static 返回值类型 方法名{
  3.        方法体;
  4.   }
  5. }

接口中增加静态方法:

接口中的静态方法在实现类中是不能被重写的,换言之默认也没有被继承过去。

②调用的话只能通过接口类型来调用: 接口名.静态方法名();使用多态的形式创建对象,使用对象. 的方式进行访问不行,因为实现类中根本没有这个静态方法。

  1. package com.zl.anmails;
  2. public interface Anmails {
  3. // 抽象方法
  4. void fly();
  5. // 默认方法
  6. public default void eat() {
  7. System.out.println("小鸟爱吃虫子");
  8. }
  9. // 静态方法
  10. public static void tryCatch(){
  11. System.out.println("小鸟尝试抓虫子");
  12. }
  13. }

默认方法和静态方法两者的区别:

①默认方法通过实例调用,静态方法通过接口名调用。

②默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法;静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用。

3.3 函数式接口

①如果接口只声明有一个抽象方法,则此接口就称为函数式接口。简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例;这就是Lambda表达式和函数式接口的关系。

②我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名, 抽象方法名;只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda表达式更加的方便,在JDK中提供了大量常用的函数式接口。

自定义函数式接口

  1. package com.zl.anmails;
  2. public class Computer {
  3. public static void main(String[] args) {
  4. // 调用fun方法
  5. fun((arr) -> {
  6. int sum = 0;
  7. for (int i : arr) {
  8. sum += i;
  9. }
  10. return sum;
  11. });
  12. }
  13. // 调用求和的方法
  14. public static void fun(Operator operator){
  15. int[] arr = {1,2,3,4};
  16. int sum = operator.getSum(arr);
  17. System.out.println("sum = "+sum);
  18. }
  19. }
  20. /**
  21. * 函数式接口
  22. */
  23. // 用来求和的接口
  24. @FunctionalInterface
  25. interface Operator{
  26. int getSum(int[] arr);
  27. }

在JDK中帮我们提供的有函数式接口,主要是在 java.util.function 包中!

四大核心函数式接口

注:以下的接口是已经提供好的,和我们上面自定义的函数接口Operate作用是类似的!

函数式接口称谓参数类型用途
Consumer<T>消费型接口T对类型为T的对象应用操作,包含方法: void accept(T t)
Supplier<T>供给型接口返回类型为T的对象,包含方法:T get()
Function<T, R>函数型接口T对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate<T>判断型接口T确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t)

(1)Supplier

Supplier是一个无参、有返回值的接口,对于的Lambda表达式需要提供一个返回数据的类型。

  1. @FunctionalInterface
  2. public interface Supplier<T> {
  3.    T get();
  4. }

例:求一组数据中的最大值,不自己定义接口和抽象方法了,使用Supplier函数式接口作为参数

  1. package com.zl.fun;
  2. import java.util.Arrays;
  3. import java.util.function.Supplier;
  4. public class SupplierFun {
  5. public static void main(String[] args) {
  6. // 使用Lambda表达式,相当于重写get()方法的逻辑
  7. fun(()->{
  8. int arr[] = {1,2,7,8,4,5,6};
  9. // 排序
  10. Arrays.sort(arr);
  11. // 找到最后一个元素就是最大值
  12. return arr[arr.length-1];
  13. });
  14. }
  15. // 调用接口中的方法
  16. public static void fun(Supplier<Integer> supplier){
  17. Integer max = supplier.get();
  18. System.out.println("max = "+max);
  19. }
  20. }

(2)Consumer

Consumer是一个有参、无返回值得接口,前面介绍的Supplier接口是用来生产数据的,而Consumer接口是用来消费数据的,使用的时候需要指定一个泛型来定义参数类型

  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3.    void accept(T t);
  4. }

例:将输入的数据统一转换为小写输出

  1. package com.zl.fun;
  2. import java.util.function.Consumer;
  3. public class ConsumerFun {
  4. public static void main(String[] args) {
  5. fun((msg)->{
  6. // 小写转大写
  7. String s = msg.toLowerCase();
  8. System.out.println(msg+"对应的小写"+s); // Hello AAA对应的小写hello aaa
  9. });
  10. }
  11. // 调用接口中的方法
  12. public static void fun(Consumer<String> consumer){
  13. consumer.accept("Hello AAA");
  14. }
  15. }

默认方法:andThen

如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时候, 首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default方法 andThen方法

  1. default Consumer<T> andThen(Consumer<? super T> after) {
  2.    Objects.requireNonNull(after);
  3.   return (T t) -> { accept(t); after.accept(t); };
  4. }

具体的操作:

  1. package com.zl.fun;
  2. import java.util.Locale;
  3. import java.util.function.Consumer;
  4. public class ConsumerAndFun {
  5. public static void main(String[] args) {
  6. fun(msg1->{
  7. System.out.println(msg1 + "-> 转换为小写:" + msg1.toLowerCase());
  8. },msg2->{
  9. System.out.println(msg2 + "-> 转换为大写:" + msg2.toUpperCase(Locale.ROOT));
  10. });
  11. }
  12. // 调用接口中的方法
  13. public static void fun(Consumer<String> c1,Consumer<String> c2){
  14. String str = "HELLO world";
  15. c1.accept(str);
  16. c2.accept(str);
  17. // 上面就等价于
  18. c1.andThen(c2).accept(str);
  19. }
  20. }

(3)Function

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

  1. @FunctionalInterface
  2. public interface Function<T, R> {
  3.    R apply(T t);
  4. }

例:传递进入一个字符串返回一个数字

  1. package com.zl.fun;
  2. import javax.print.DocFlavor;
  3. import java.util.function.Function;
  4. public class FuncationFun {
  5. public static void main(String[] args) {
  6. fun((num)->{
  7. Integer number = Integer.parseInt(num);
  8. return number;
  9. });
  10. }
  11. // 调用接口中的方法
  12. public static void fun(Function<String, Integer> function){
  13. Integer apply = function.apply("666");
  14. System.out.println("applay = "+apply);
  15. }
  16. }

默认方法:andThen,也是用来进行组合操作

  1. default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
  2.        Objects.requireNonNull(after);
  3.        return (T t) -> after.apply(apply(t));
  4.   }

具体的操作:

  1. package com.zl.fun;
  2. import java.util.function.Function;
  3. public class FuncationAndthenFun {
  4. public static void main(String[] args) {
  5. fun(num1->{
  6. return Integer.parseInt(num1);
  7. },num2->{
  8. return num2*10+6;
  9. });
  10. }
  11. // 调用接口中的方法
  12. public static void fun(Function<String, Integer> f1,Function<Integer,Integer> f2){
  13. String str = "666";
  14. Integer i1 = f1.apply(str);
  15. Integer i2 = f2.apply(i1);
  16. System.out.println("i2 = "+i2);
  17. }
  18. }

注:默认的compose方法的作用顺序和andThen方法刚好相反 !而静态方法identity则是,输入什么参数就返回什么参数 !

(4)Predicate

Predicate是一个有参、且返回值为Boolean的接口。

  1. @FunctionalInterface
  2. public interface Predicate<T> {
  3.    boolean test(T t);
  4. }

例:求一个字符串的长度是否大于5

  1. package com.zl.fun;
  2. import java.util.function.Predicate;
  3. public class PredicateFun {
  4. public static void main(String[] args) {
  5. fun(msg->{
  6. return msg.length() > 5;
  7. },"Hello World");
  8. }
  9. // 调用接口中的方法
  10. public static void fun(Predicate<String> p,String str){
  11. boolean b = p.test(str);
  12. System.out.println("b = "+b);
  13. }
  14. }

在Predicate中的默认方法提供了逻辑关系操作 and、or、negate、isEquals方法

  1. package com.zl.fun;
  2. import java.util.function.Predicate;
  3. public class PredicateTest {
  4. public static void main(String[] args) {
  5. fun(msg1->{
  6. return msg1.contains("H");
  7. },msg2->{
  8. return msg2.contains("W");
  9. });
  10. }
  11. // 调用接口中的方法
  12. public static void fun(Predicate<String> p1, Predicate<String> p2){
  13. // 1. 判断p1包含H,同时P2包含W
  14. boolean b1 = p1.and(p2).test("Hello World");
  15. System.out.println(b1);
  16. // 2. 判断p1包含H,或者P2包含W
  17. boolean b2 = p1.or(p2).test("Hello World");
  18. System.out.println(b2);
  19. // 3.结果取反,表示p1不包含H
  20. boolean b3 = p1.negate().test("HelloWorld");
  21. System.out.println(b3);
  22. // 4. 判断两个结果是否相等
  23. boolean bb1 = p1.test("Hello");
  24. boolean bb2 = p2.test("World");
  25. }
  26. }

3.4 方法引用

①Lambda表达式是可以简化函数式接口的变量或形参赋值的语法;而方法引用和构造器引用是为了简化Lambda表达式的!

②当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

方法引用的本质:方法引用作为了函数式接口的实例!

(1)为什么使用方法引用?

在使用Lambda表达式的时候,也会出现代码冗余的情况,如:用Lambda表达式求一个数组的和

  1. package com.zl.method;
  2. import java.util.function.Consumer;
  3. public class Test {
  4. public static void main(String[] args) {
  5. printSum(arr->{
  6. int sum = 0;
  7. for (int i : arr) {
  8. sum += i;
  9. }
  10. System.out.println("数组之和:" + sum);
  11. });
  12. }
  13. /**
  14. * 求数组的和,和上面功能代码相同
  15. * @param arr
  16. */
  17. public static void getSum(int arr[]){
  18. int sum = 0;
  19. for (int i : arr) {
  20. sum += i;
  21. }
  22. System.out.println("数组之和:" + sum);
  23. }
  24. public static void printSum(Consumer<int[]> consumer){
  25. int[] arr = {30,10,26,18,45,9};
  26. consumer.accept(arr);
  27. }
  28. }

以上两个方法的逻辑业务明显相同,代码冗余,这样就可以使用方法的引用

  1. package com.zl.method;
  2. import java.util.function.Consumer;
  3. public class Test {
  4. public static void main(String[] args) {
  5. // 引用下面相同功能的代码
  6. printSum(Test::getSum);
  7. }
  8. /**
  9. * 求数组的和,和上面功能代码相同
  10. * @param arr
  11. */
  12. public static void getSum(int arr[]){
  13. int sum = 0;
  14. for (int i : arr) {
  15. sum += i;
  16. }
  17. System.out.println("数组之和:" + sum);
  18. }
  19. public static void printSum(Consumer<int[]> consumer){
  20. int[] arr = {30,10,26,18,45,9};
  21. consumer.accept(arr);
  22. }
  23. }

(2)方法引用的格式

符号表示: ::

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

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

方法引用在JDK8中使用是相当灵活的,常见的引用方式有以下几种形式:

  1. ①instanceName::methodName 对象::方法名
  2. ②ClassName::staticMethodName 类名::静态方法
  3. ③ClassName::methodName 类名::普通方法

(3)对象名::方法名

最常见的一种用法;如果一个类中的已经存在了一个成员方法,则可以通过对象名引用成员方法

  1. package com.zl.method;
  2. import java.util.Date;
  3. import java.util.function.Supplier;
  4. public class MethodTest01 {
  5. public static void main(String[] args) {
  6. // 练习1:
  7. // 第一种方式:匿名内部类
  8. Consumer<String> c1 = new Consumer<String>() {
  9. @Override
  10. public void accept(String s) {
  11. System.out.println(s);
  12. }
  13. };
  14. c1.accept("张三");
  15. // 方法二:Lambda表达式
  16. Consumer<String> c2 = (msg)->{
  17. System.out.println(msg);
  18. };
  19. c2.accept("李四");
  20. // 方法三:方法引用
  21. Consumer<String> c3 = System.out::println;
  22. c3.accept("王五");
  23. // 练习2:
  24. Date date = new Date();
  25. Supplier<Long> supplier = ()->{ return date.getTime();};
  26. System.out.println(supplier.get());
  27. // 通过 方法引用 的方式处理
  28. Supplier<Long> supplier = date::getTime;
  29. System.out.println(supplier.get());
  30. }
  31. }

总结:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的形参列表、返回值类型都相同(或一致:比如多态,自动拆箱装箱等),则我们可以使用方法b实现对方法a的重写、替换。 此方法b是非静态的方法,需要对象来调用!

(4)类名::静态方法名

也是一种比较常用的方式,通过类名调用静态方法!

原来的方式:

  1. package com.zl.method;
  2. import java.util.Date;
  3. import java.util.function.Supplier;
  4. public class MethodTest01 {
  5. public static void main(String[] args) {
  6. fun(()->{
  7. return System.currentTimeMillis();
  8. });
  9. // 采用方法引用的方式
  10. Supplier<Long> s = System::currentTimeMillis;
  11. System.out.println(s.get());
  12. }
  13. public static void fun(Supplier<Long> supplier){
  14. Long time = supplier.get();
  15. System.out.println(time);
  16. // 上面就等价于
  17. System.out.println(supplier.get());
  18. }
  19. }

精简的方式:

  1. public class FunctionRefTest04 {
  2.    public static void main(String[] args) {
  3.        Supplier<Long> supplier1 = ()->{
  4.            return System.currentTimeMillis();
  5.       };
  6.        System.out.println(supplier1.get());
  7.        // 通过 方法引用 来实现
  8.        Supplier<Long> supplier2 = System::currentTimeMillis;
  9.        System.out.println(supplier2.get());
  10.   }
  11. }

总结:函数式接口中的抽象方法a与其内部实现时调用的的某个方法b的形参列表、返回值类型都相同(或一致:比如多态,自动拆箱装箱等),则我们可以使用方法b实现对方法a的重写、替换。 此方法b是静态的方法,需要类来调用!

(5)类名::引用实例方法(难)

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

抽象方法有两个参数(n),内部实现的方法有一个参数(n-1),n个参数可以分为1和n-1,且第1个参数是方法的调用者!

例题1:

  1. package com.zl.methods;
  2. import java.util.Comparator;
  3. public class Test03 {
  4. public static void main(String[] args) {
  5. // 第一种:匿名内部类
  6. Comparator<String> com1 = new Comparator<String>() {
  7. @Override
  8. public int compare(String o1, String o2) { // 两个参数
  9. return o1.compareTo(o2); // 一个参数
  10. }
  11. };
  12. System.out.println(com1.compare("abc", "abd"));
  13. // 第二种:Lambda
  14. Comparator<String> com2 = (s1,s2)->{
  15. return s1.compareTo(s2);
  16. };
  17. System.out.println(com2.compare("abc", "abd"));
  18. // 第三种:类::实例方法
  19. Comparator<String> com3 = String::compareTo;
  20. System.out.println(com3.compare("abc","abb"));
  21. }
  22. }

 例题2:

  1. package com.zl.method;
  2. import java.util.function.Function;
  3. public class MethodTest02 {
  4. public static void main(String[] args) {
  5. Function<String,Integer> function = (s)->{
  6. return s.length();
  7. };
  8. System.out.println(function.apply("hello"));
  9. // 使用方法引用
  10. Function<String,Integer> function2 = String::length;
  11. System.out.println(function2.apply("hello"));
  12. }
  13. }

总结:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的返回值类型都相同。同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第一个参数作为方法b的调用者,且抽象方法a的后n-1个参数与方法b的n-1个参数的类型相同(或一致:比如多态,自动拆箱装箱等)。则我们可以使用方法b实现对方法a的重写、替换。 此方法b是非静态的方法,需要对象来调用,但是形式上是写成对象a所属的类!

(6)类名::构造器

由于构造器的名称和类名完全一致,所以构造器引用使用 ::new 的格式使用!

ClassName::new 类名::new

 Person类:提供有参和无参构造方法

  1. package com.zl.method;
  2. public class Person {
  3. private String name;
  4. private int age;
  5. public Person() {
  6. }
  7. public Person(String name, int age) {
  8. this.name = name;
  9. this.age = age;
  10. }
  11. @Override
  12. public String toString() {
  13. return "Person{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. '}';
  17. }
  18. }

测试:

注:如果使用一个参数的构造方法,可以定义为Function;定义为两个参数的构造方法,使用BiFunction;最终返回的都是一个Person类型!

  1. package com.zl.method;
  2. import java.util.function.BiFunction;
  3. import java.util.function.Supplier;
  4. public class MythodTest03 {
  5. public static void main(String[] args) {
  6. Supplier<Person> supplier = ()->{
  7. return new Person();
  8. };
  9. System.out.println(supplier.get());
  10. // 通过 方法引用来实现
  11. Supplier<Person> supplier1 = Person::new;
  12. System.out.println(supplier1.get());
  13. // 也可以给属性赋值
  14. BiFunction<String ,Integer,Person> function = Person::new;
  15. System.out.println(function.apply("张三",22)); // 底层调用有参数构造方法
  16. }
  17. }

总结:调用了类名对应的类中的某一个确定的构造器,具体调用的是类中的哪一个构造器?取决于函数式接口的抽象方法的形参列表!

(7)数组::构造器

当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用。

格式:数组类型名::new

TypeName[]::new 数组名[]::new 

 例1:传一个数字,返回一个对应长度的数组

  1. public static void main(String[] args) {
  2.        Function<Integer,String[]> fun1 = (len)->{
  3.            return new String[len];
  4.       };
  5.        String[] a1 = fun1.apply(3);
  6.        System.out.println("数组的长度是:" + a1.length);
  7.        // 方法引用 的方式来调用数组的构造器
  8.        Function<Integer,String[]> fun2 = String[]::new;
  9.        String[] a2 = fun2.apply(5);
  10.        System.out.println("数组的长度是:" + a2.length);
  11.   }

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

3.5 集合之Stream流式操作(重点)

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

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

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

(1)什么是 Stream?

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

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

③Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构, 讲的是数据,而 Stream 是有关计算的,讲的是数据的计算(排序、查找、过滤、映射、遍历等)前者是主要面向内存, 存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

注意:

①Stream 自己不会存储元素。

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

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行;即 一旦执行终止操作,就执行中间操作链,并产生结果。

④ Stream 一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。

(2)Stream的操作三个步骤?

1- 创建 Stream

Stream的实例化:一个数据源(如:集合、数组),获取一个流 !

2- 中间操作

每次处理都会返回一个持有结果的新Stream,即中间操作的方法返回值仍然是Stream类型的对象。因此中间操作可以是个操作链,可对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

3- 终止操作(终端操作)

终止操作的方法返回值类型就不再是Stream了,因此一旦执行终止操作,就结束整个Stream操作了。一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

(3) 创建Stream实例

方式一:通过集合

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

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

例:

  1. package com.zl.methods;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. import java.util.stream.Stream;
  5. public class Test04 {
  6. public static void main(String[] args) {
  7. // 创建一个数组
  8. int[] arr = {1,2,3,4,5,6};
  9. // 把一个数组转换成一个集合
  10. List<int[]> list = Arrays.asList(arr);
  11. // 得到Stream实例(返回一个顺序流)
  12. Stream<int[]> stream = list.stream();
  13. // 得到Stream实例(返回一个并行流)
  14. Stream<int[]> stream1 = list.parallelStream();
  15. }
  16. }

方式二:通过数组

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

  1. static <T> Stream<T> stream(T[] array): 返回一个流
  2. public static IntStream stream(int[] array)
  3. public static LongStream stream(long[] array)
  4. public static DoubleStream stream(double[] array)

例:

  1. package com.zl.methods;
  2. import java.util.Arrays;
  3. import java.util.stream.IntStream;
  4. import java.util.stream.Stream;
  5. public class Test04 {
  6. public static void main(String[] args) {
  7. // 创建一个数组
  8. int[] arr = {1,2,3,4,5,6};
  9. // 得到Stream实例(返回一个顺序流)
  10. IntStream stream = Arrays.stream(arr);
  11. // 再例如
  12. Integer[] integers = {1,2,3,4,5};
  13. Stream<Integer> stream1 = Arrays.stream(integers);
  14. }
  15. }

方式三:通过Stream的of()

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

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

主要用在多个数据没有容器(数组、集合)进行存储

  1. package com.zl.methods;
  2. import java.util.stream.Stream;
  3. public class Test04 {
  4. public static void main(String[] args) {
  5. // 通过静态方法进行调用
  6. Stream<Integer> stream = Stream.of(1, 2, 3, 4);
  7. }
  8. }

(4)一系列中间操作

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

准备Employee类

  1. package com.zl.data;
  2. import java.util.Objects;
  3. public class Employee {
  4. private int id;
  5. private String name;
  6. private int age;
  7. private double salary;
  8. public int getId() {
  9. return id;
  10. }
  11. public void setId(int id) {
  12. this.id = id;
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public int getAge() {
  21. return age;
  22. }
  23. public void setAge(int age) {
  24. this.age = age;
  25. }
  26. public double getSalary() {
  27. return salary;
  28. }
  29. public void setSalary(double salary) {
  30. this.salary = salary;
  31. }
  32. public Employee() {
  33. System.out.println("Employee().....");
  34. }
  35. public Employee(int id) {
  36. this.id = id;
  37. }
  38. public Employee(int id, String name) {
  39. this.id = id;
  40. this.name = name;
  41. }
  42. public Employee(int id, String name, int age, double salary) {
  43. this.id = id;
  44. this.name = name;
  45. this.age = age;
  46. this.salary = salary;
  47. }
  48. @Override
  49. public String toString() {
  50. return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
  51. }
  52. @Override
  53. public boolean equals(Object o) {
  54. if (this == o) return true;
  55. if (o == null || getClass() != o.getClass()) return false;
  56. Employee employee = (Employee) o;
  57. return id == employee.id && age == employee.age && Double.compare(employee.salary, salary) == 0 && Objects.equals(name, employee.name);
  58. }
  59. @Override
  60. public int hashCode() {
  61. return Objects.hash(id, name, age, salary);
  62. }
  63. }

准备数据

  1. package com.zl.data;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class EmployeeData {
  5. public static List<Employee> getEmployees(){
  6. List<Employee> list = new ArrayList<>();
  7. list.add(new Employee(1001, "马化腾", 34, 6000.38));
  8. list.add(new Employee(1002, "马云", 2, 19876.12));
  9. list.add(new Employee(1003, "刘强东", 33, 3000.82));
  10. list.add(new Employee(1004, "雷军", 26, 7657.37));
  11. list.add(new Employee(1005, "李彦宏", 65, 5555.32));
  12. list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
  13. list.add(new Employee(1007, "任正非", 26, 4333.32));
  14. list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
  15. return list;
  16. }
  17. }

1-筛选与切片

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

需求:查询员工表中薪资大于7000的员工

  1. package com.zl.data;
  2. import java.util.List;
  3. import java.util.stream.Stream;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Employee> list = EmployeeData.getEmployees();
  7. // 实例Stream
  8. Stream<Employee> stream = list.stream();
  9. // 进行筛选(参数是Predicate函数式接口)
  10. // stream.filter(emp -> emp.getSalary() > 7000);
  11. // 进行打印,实际上也就是终止条件
  12. // stream.filter(emp -> emp.getSalary() > 7000).forEach(emp-> System.out.println(emp));
  13. // 后面也可以使用方法引用
  14. stream.filter(emp -> emp.getSalary() > 7000).forEach(System.out::println);
  15. }
  16. }

需求:截断流,使元素不超过给定数量

  1. package com.zl.data;
  2. import java.util.List;
  3. import java.util.stream.Stream;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Employee> list = EmployeeData.getEmployees();
  7. // 实例Stream
  8. Stream<Employee> stream = list.stream();
  9. // 截断流
  10. stream.limit(2).forEach(System.out::println);
  11. }
  12. }

需求:跳过前2个,只打印后面的;实际上和limit(只取前几个)是一个互补的关系

  1. package com.zl.data;
  2. import java.util.List;
  3. import java.util.stream.Stream;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Employee> list = EmployeeData.getEmployees();
  7. // 实例Stream
  8. Stream<Employee> stream = list.stream();
  9. // 跳过前2个
  10. stream.skip(2).forEach(System.out::println);
  11. }
  12. }

需求:去重,根据equals方法和hashCode方法进行去重

  1. package com.zl.data;
  2. import java.util.List;
  3. import java.util.stream.Stream;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Employee> list = EmployeeData.getEmployees();
  7. // 添加几个相同的元素
  8. list.add(new Employee(1008, "特朗布", 35, 2500.32));
  9. list.add(new Employee(1008, "特朗布", 35, 2500.32));
  10. list.add(new Employee(1008, "特朗布", 35, 2500.32));
  11. // 实例Stream
  12. Stream<Employee> stream = list.stream();
  13. // 进行去重
  14. stream.distinct().forEach(System.out::println);
  15. }
  16. }

2-映 射

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

需求:大写转小写

  1. package com.zl.data;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. import java.util.stream.Stream;
  5. public class Test01 {
  6. public static void main(String[] args) {
  7. // 把一个数组转化成集合
  8. List<String> list = Arrays.asList("aa", "bb", "cc");
  9. // 实例Stream
  10. Stream<String> stream = list.stream();
  11. // 大写转小写
  12. // stream.map(str->str.toUpperCase()).forEach(System.out::println);
  13. // 使用方法引用也可以
  14. stream.map(String::toUpperCase).forEach(System.out::println);
  15. }
  16. }

需求:获取员工姓名长度大于3的员工姓名

  1. package com.zl.data;
  2. import java.util.List;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. List<Employee> list = EmployeeData.getEmployees();
  6. // 获取员工姓名长度大于3的员工姓名
  7. // 方法一:先过滤到名字长度低于3的,在映射名字进行打印
  8. list.stream().filter(emp->emp.getName().length()>3).map(emp->emp.getName()).forEach(System.out::println);
  9. // 方式二:先映射名字
  10. list.stream().map(emp->emp.getName()).filter(name->name.length()>3).forEach(System.out::println);
  11. // 方式三:使用方法引用
  12. list.stream().map(Employee::getName).filter(name->name.length()>3).forEach(System.out::println);
  13. }
  14. }

3-排序

方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序

需求:进行自然排序

  1. package com.zl.data;
  2. import java.util.Arrays;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. // 定义一个数组
  6. int[] arr = new int[]{1,2,9,76,3,4,5};
  7. Arrays.stream(arr).sorted().forEach(System.out::println);
  8. }
  9. }

需求:借用比较器进行排序

  1. package com.zl.data;
  2. import java.util.List;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. List<Employee> list = EmployeeData.getEmployees();
  6. // 借用比较器进行排序,按照年级进行比较
  7. list.stream().sorted((o1,o2)-> o1.getAge() - o2.getAge()).forEach(System.out::println);
  8. }
  9. }

(5)终止操作

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

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

1-匹配与查找

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

需求:是否所有的员工的年龄都大于18

  1. package com.zl.data;
  2. import java.util.List;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. List<Employee> list = EmployeeData.getEmployees();
  6. boolean b = list.stream().allMatch(emp -> emp.getAge() > 18);
  7. System.out.println(b); // false
  8. }
  9. }

需求:是否存在员工的工资大于10000(至少有一个)

  1. package com.zl.data;
  2. import java.util.List;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. List<Employee> list = EmployeeData.getEmployees();
  6. boolean b = list.stream().anyMatch(emp -> emp.getSalary() > 10000);
  7. System.out.println(b); // true
  8. }
  9. }

 需求:返回第一个元素

  1. package com.zl.data;
  2. import java.util.List;
  3. import java.util.Optional;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Employee> list = EmployeeData.getEmployees();
  7. // 返回的是一个Option
  8. Optional<Employee> optionalEmployee = list.stream().findFirst();
  9. System.out.println(optionalEmployee); // Optional[Employee{id=1001, name='马化腾', age=34, salary=6000.38}]
  10. // 在调用get方法就能拿到第一个值
  11. System.out.println(optionalEmployee.get()); // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
  12. }
  13. }

需求:返回流中薪资大于7000元素的总个数

  1. package com.zl.data;
  2. import java.util.List;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. List<Employee> list = EmployeeData.getEmployees();
  6. long count = list.stream().filter(emp -> emp.getSalary() > 7000).count();
  7. System.out.println(count); // 3
  8. }
  9. }

需求:返回最高的薪资

  1. package com.zl.data;
  2. import java.util.List;
  3. import java.util.Optional;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Employee> list = EmployeeData.getEmployees();
  7. // 先获取返回最高工资的员工
  8. // 得到的是一个Option
  9. Optional<Employee> optionalEmployee = list.stream().max((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
  10. System.out.println(optionalEmployee); // Optional[Employee{id=1002, name='马云', age=2, salary=19876.12}]
  11. // 方法1(根据上面在调用get方法,获取员工,在调用getSalary获取到薪资)
  12. System.out.println(list.stream().max((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary())).get().getSalary());
  13. // 方法2(映射)
  14. System.out.println(list.stream().map(emp -> emp.getSalary()).max((salgrade1, salgrade2) -> Double.compare(salgrade1, salgrade2)).get());
  15. // 使用方法引用
  16. System.out.println(list.stream().map(emp -> emp.getSalary()).max(Double::compare).get());
  17. }
  18. }

注:在JDK8中增加了一个遍历集合的方法

  1. package com.zl.data;
  2. import java.util.List;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. List<Employee> list = EmployeeData.getEmployees();
  6. // 进行遍历---使用lambda表达式
  7. list.forEach((emp)-> System.out.println(emp));
  8. // 使用方法的引用
  9. list.forEach(System.out::println);
  10. }
  11. }

2-归约

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

需求:计算1-10的自然数的和

第一个参数是一个随机数的种子:相当于指定一个初始值,后续的累加的最终结果,都需要加上这个数值。

第二个参数BinaryOperator:实际上是继承了BiFunction类,两个参数返回一个值。

  1. package com.zl.data;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  7. // 进行求和
  8. // 第一种方法
  9. System.out.println(list.stream().reduce(0, (x1, x2) -> x1 + x2)); // 55
  10. // 第二种方法(调用Integer的sum方法进行累加)
  11. System.out.println(list.stream().reduce(0, (x1, x2) -> Integer.sum(x1,x2))); // 55
  12. // 第三种方法:在二的基础上就可以使用方法的引用
  13. System.out.println(list.stream().reduce(0, Integer::sum)); // 55
  14. }
  15. }

需求:计算公司所员工的总和(先映射在规约)

  1. package com.zl.data;
  2. import java.util.List;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. List<Employee> list = EmployeeData.getEmployees();
  6. // 先映射在规约(得到的是Option)
  7. // 在调用get方法得到值
  8. System.out.println(list.stream().map(emp -> emp.getSalary()).reduce((s1,s2)->Double.sum(s1,s2))); // Optional[58424.08]
  9. System.out.println(list.stream().map(emp -> emp.getSalary()).reduce(Double::sum).get()); // 58424.08
  10. }
  11. }

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

3-收集

调用的sort方法进行排序,但是并不会影响到原来的数据,所以我们可以把排序过后的数据存储到一个集合当中;Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。

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

另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用
toListCollector<T, ?, List<T>>把流中元素收集到List
List<Employee> emps= list.stream().collect(Collectors.toList());
方法返回类型作用
toSetCollector<T, ?, Set<T>>把流中元素收集到Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
方法返回类型作用
toCollectionCollector<T, ?, C>把流中元素收集到创建的集合
Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new));

需求:查找工资大于6000的员工,结果返回到一个List

  1. package com.zl.data;
  2. import java.util.List;
  3. import java.util.stream.Collectors;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. List<Employee> list = EmployeeData.getEmployees();
  7. // 进行过滤(并把数据存储到List集合)
  8. List<Employee> list1 = list.stream().filter(emp -> emp.getSalary() > 6000).collect(Collectors.toList());
  9. // 进行打印
  10. list1.forEach(System.out::println);
  11. }
  12. }

3.6 新的时间和日期 API

JDK8之前:日期时间API

(1)java.lang.System类的currentTimeMills方法

①System类提供的public static long currentTimeMillis():用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。

②此方法适于计算时间差。

  1. package com.zl.data;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. // 获取当前时间的毫秒数
  7. long timeMillis = System.currentTimeMillis();
  8. System.out.println(timeMillis);
  9. // 获取昨天此时的时间
  10. Date date = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
  11. System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
  12. }
  13. }

(2)java.util.Date

①构造器

  1. Date():使用无参构造器创建的对象可以获取本地当前时间
  2. Date(long 毫秒数):把该毫秒值换算成日期时间对象

②常用方法

  1. getTime(): 返回自 19701100:00:00 GMT 以来此 Date 对象表示的毫秒数
  2. toString(): 把此 Date 对象转换为以下形式的 String

例:

  1. package com.zl.data;
  2. import java.util.Date;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. // 无参构造器
  6. Date date = new Date();
  7. System.out.println(date.toString()); // Mon Apr 10 19:46:50 CST 2023
  8. // 有参构造
  9. Date date1 = new Date(100L);
  10. System.out.println(date1.toString()); // Thu Jan 01 08:00:00 CST 1970
  11. // 调用getTime方法获取当前的毫秒数(和System.currentTimeMillis相同的效果)
  12. System.out.println(date.getTime());
  13. System.out.println(System.currentTimeMillis());
  14. }
  15. }

③其子类java.sql.Date,对应着数据库中的Date类型,只有有参构造器

  1. package com.zl.data;
  2. import java.sql.Date;
  3. import java.text.ParseException;
  4. public class Test01 {
  5. public static void main(String[] args) throws ParseException {
  6. // java.sql.Date
  7. // 获取时间戳
  8. long timeMillis = System.currentTimeMillis(); // 1681181172832
  9. System.out.println(timeMillis);
  10. // 只有一个有参构造器
  11. java.sql.Date date = new Date(timeMillis);
  12. // 只打印年月日,但是其实也包含时分秒,只是没有显示,从下面调用getTime方法的结果与前面的时间戳相等也能体现
  13. System.out.println(date); // 2023-04-11
  14. System.out.println(date.getTime()); // 1681181172832
  15. }
  16. }

(3)java.text.SimpleDateFormat

java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类

①可以进行格式化:日期 --> 文本

②可以进行解析:文本 --> 日期

构造器:

  1. SimpleDateFormat() :默认的模式和语言环境创建对象
  2. public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象

格式化:

public String format(Date date):方法格式化时间对象date

解析:

public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期

例:

  1. package com.zl.data;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. public class Test01 {
  6. public static void main(String[] args) throws ParseException {
  7. // 无参构造器
  8. Date date = new Date();
  9. System.out.println(date); // Mon Apr 10 20:02:33 CST 2023
  10. // 指定格式化的格式
  11. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  12. // 把日期格式化为字符串
  13. System.out.println(sdf.format(date)); // 2023-04-10 20:02:33
  14. // 把时间的字符串解析为日期
  15. System.out.println(sdf.parse("2022-4-10 6:10:10")); // Sun Apr 10 06:10:10 CST 2022
  16. }
  17. }

(4)java.util.Calendar(日历)

①Date类的API大部分被废弃了,替换为Calendar(抽象类)。

②Calendar 类是一个抽象类,主用用于完成日期字段之间相互操作的功能。

获取Calendar实例

第一种方法:创建子类GregorianCalendar

第二种方法:调用Calendar的静态方法getInstance

  1. package com.zl.data;
  2. import java.util.Calendar;
  3. import java.util.GregorianCalendar;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. // 第一种方法
  7. GregorianCalendar gregorianCalendar = new GregorianCalendar();
  8. System.out.println(gregorianCalendar);
  9. // 第二种方法(常用)实际上也是子类GregorianCalendar
  10. Calendar calendar = Calendar.getInstance();
  11. System.out.println(calendar.getClass()); // class java.util.GregorianCalendar
  12. }
  13. }

常用的方法

  1. public int get(int field):返回给定日历字段的值
  2. public void set(int field,int value) :将给定的日历字段设置为指定的值
  3. public void add(int field,int amount):根据日历的规则,为给定的日历字段添加或者减去指定的时间量
  4. public final Date getTime():将Calendar转成Date对象
  5. public final void setTime(Date date):使用指定的Date对象重置Calendar的时间

常用的field字段

  1. package com.zl.data;
  2. import java.util.Calendar;
  3. import java.util.Date;
  4. public class Test01 {
  5. public static void main(String[] args) {
  6. Calendar calendar = Calendar.getInstance();
  7. // get方法
  8. System.out.println(calendar.get(Calendar.DAY_OF_WEEK)); // 3,今天是这个周的第几天
  9. System.out.println(calendar.get(Calendar.DAY_OF_YEAR)); // 101,今天是这个年的第几天
  10. // set方法
  11. calendar.set(Calendar.DAY_OF_WEEK,5); // 把今天是这个周的第几天设置为另一个值
  12. System.out.println(calendar.get(Calendar.DAY_OF_WEEK)); // 5,把今天设置为这个周的第5天
  13. // add方法
  14. calendar.add(Calendar.DAY_OF_WEEK,1);
  15. System.out.println(calendar.get(Calendar.DAY_OF_WEEK)); // 6,在原来的基础上增加一天
  16. calendar.add(Calendar.DAY_OF_WEEK,-2);
  17. System.out.println(calendar.get(Calendar.DAY_OF_WEEK)); // 4,在原来的基础上减少两天
  18. // getTime方法,从Calendar转换为Date
  19. System.out.println(calendar.getTime()); // Wed Apr 12 11:20:29 CST 2023
  20. // setTime方法,使用指定的Date重置Calendar
  21. Date date = new Date();
  22. calendar.setTime(date);
  23. // 其实再次获取今天是这周的第几天
  24. System.out.println(calendar.get(Calendar.DAY_OF_WEEK)); // 3,重置过后还是原来的第三天
  25. }
  26. }

例题:

例:将一个java.util.Date的实例转换为java.sql.Date的实例

  1. package com.zl.data;
  2. import java.util.Date;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. // 很容易想到的思路,进行强转,会出现ClassCastException异常
  6. // 原因我们创建的就是父类java.util.Date,不能往下转为java.sql.Date(可以王当前的父类转,但是不能往当前的子类进行转)
  7. /*Date date = new Date();
  8. java.sql.Date date2 = (java.sql.Date) date; // err*/
  9. // 正确的做法---根据时间戳进行转换
  10. Date date = new Date();
  11. java.sql.Date date1 = new java.sql.Date(date.getTime());
  12. }
  13. }

JDK8:新的日期时间API

如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是: 

①可变性:像日期和时间这样的类应该是不可变的。

②偏移性:Date中的年份是从1900开始的,而月份都从0开始。

③格式化:格式化只对Date有用,Calendar则不行。

④此外,它们也不是线程安全的;不能处理闰秒等。

Java 8 以一个新的开始为 Java 创建优秀的 API。新的日期时间API包含:

  1. java.time – 包含值对象的基础包
  2. java.time.chrono – 提供对不同的日历系统的访问。
  3. java.time.format – 格式化和解析时间和日期
  4. java.time.temporal – 包括底层框架和扩展特性
  5. java.time.zone – 包含时区支持的类

(1)本地日期时间:LocalDate、LocalTime、LocalDateTime---类似于Calendar

获取实例对象

方法描述
now()/ now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of(xx,xx,xx,xx,xx,xxx)静态方法,根据指定日期/时间创建对象
  1. package com.zl.data;
  2. import java.time.LocalDate;
  3. import java.time.LocalDateTime;
  4. import java.time.LocalTime;
  5. public class Test01 {
  6. public static void main(String[] args) {
  7. // now():获取当前日期和时间对应的实例
  8. LocalDate localDate = LocalDate.now(); // 年月日
  9. LocalTime localTime = LocalTime.now(); // 时分秒
  10. LocalDateTime localDateTime = LocalDateTime.now(); // 年月日和时分秒
  11. System.out.println(localDate); // 2023-04-11
  12. System.out.println(localTime); // 14:41:45.596
  13. System.out.println(localDateTime); // 2023-04-11T14:41:45.596
  14. // of():获取指定的日期,时间对应的实例
  15. LocalDate localDate1 = LocalDate.of(2023, 4, 11);
  16. LocalDateTime localDateTime1 = LocalDateTime.of(2023, 4, 11, 14, 45, 30);
  17. System.out.println(localDate1); // 2023-04-11
  18. System.out.println(localDateTime1); // 2023-04-11T14:45:30
  19. }
  20. }

常用方法:获取(get)、修改(with)、增加(plus)、减少(min)

方法描述
getDayOfMonth()/getDayOfYear()获得月份天数(1-31) /获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
getMonth()获得月份, 返回一个 Month 枚举值
getMonthValue() / getYear()获得月份(1-12) /获得年份
getHours()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
with(TemporalAdjuster t)将当前日期时间设置为校对器指定的日期时间
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths() / minusWeeks()/minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时
plus(TemporalAmount t)/minus(TemporalAmount t)添加或减少一个 Duration 或 Period
isBefore()/isAfter()比较两个 LocalDate
isLeapYear()判断是否是闰年(在LocalDate类中声明)
format(DateTimeFormatter t)格式化本地日期、时间,返回一个字符串
parse(Charsequence text)将指定格式的字符串解析为日期、时间

  1. package com.zl.data;
  2. import java.time.LocalDateTime;
  3. public class Test01 {
  4. public static void main(String[] args) {
  5. LocalDateTime localDateTime = LocalDateTime.now();
  6. // getXxx()获取
  7. int dayOfMonth = localDateTime.getDayOfMonth();
  8. System.out.println(dayOfMonth); // 11,获取今天是这个月的第几天
  9. // withXxx()修改
  10. LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(15); // 把今天修改为这个月的第15天
  11. System.out.println(localDateTime); // 2023-04-11T14:57:05.485,原来的并没有更改
  12. System.out.println(localDateTime1); // 2023-04-15T14:57:05.485,返回新的LocalDateTime对象才被更改,体现了不可变性
  13. // plusXxx()增加
  14. LocalDateTime localDateTime2 = localDateTime1.plusDays(5); // 又会生成一个新的LocalDateTime对象
  15. System.out.println(localDateTime2); // 2023-04-20T15:00:25.650,在原来的基础上增加了5天
  16. // minXxx()减少
  17. LocalDateTime localDateTime3 = localDateTime2.minusDays(4);
  18. System.out.println(localDateTime3); // 2023-04-16T15:02:58.050,在原来的基础上增加了4天
  19. }
  20. }

(2)瞬时:Instant---类似于Date

①Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

②java.time.Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。

方法描述
now()静态方法,返回默认UTC时区的Instant类的对象
ofEpochMilli(long epochMilli)静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象
atOffset(ZoneOffset offset)结合即时的偏移来创建一个 OffsetDateTime
toEpochMilli()返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳
  1. package com.zl.data;
  2. import java.time.Instant;
  3. import java.time.OffsetDateTime;
  4. import java.time.ZoneOffset;
  5. public class Test01 {
  6. public static void main(String[] args) {
  7. // 类似于Date的无参构造器
  8. Instant instant = Instant.now();
  9. System.out.println(instant); //2023-04-11T07:20:51.614Z与现在的时间差了8个小时
  10. OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8)); // 设置偏移的8个小时
  11. System.out.println(offsetDateTime); // 2023-04-11T15:20:51.614+08:00
  12. // 类似于Date的有参构造器
  13. Instant instant1 = Instant.ofEpochMilli(System.currentTimeMillis());
  14. System.out.println(instant1); // 2023-04-11T07:20:51.695Z
  15. // 获取当前的毫秒数
  16. long toEpochMilli = instant.toEpochMilli();
  17. System.out.println(toEpochMilli); // 1681197782827
  18. }
  19. }

(3)日期时间格式化:DateTimeFormatter---类似于SimpleDateFormat

DateTimeFormatter用户格式化和解析:LocalDate、LocalTime、LocalDateTime!

自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

ofPattern(String pattern)静态方法,返回一个指定字符串格式的DateTimeFormatter
format(TemporalAccessor t)格式化一个日期、时间,返回字符串
parse(CharSequence text)将指定格式的字符序列解析为一个日期、时间
  1. package com.zl.data;
  2. import java.time.LocalDateTime;
  3. import java.time.format.DateTimeFormatter;
  4. import java.time.temporal.TemporalAccessor;
  5. public class Test01 {
  6. public static void main(String[] args) {
  7. LocalDateTime localDateTime = LocalDateTime.now();
  8. // 自定义格式
  9. DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  10. // 格式化
  11. String strDateTime = dtf.format(localDateTime);
  12. System.out.println(strDateTime); // 2023-04-11 15:44:14
  13. // 解析(得到的是一个TemporalAccessor接口,LocalDateTime实现了这个接口)
  14. TemporalAccessor temporalAccessor = dtf.parse("2023-10-10 15:30:30");
  15. // 在调用LocalDateTime.from方法转换为LocalDateTime
  16. LocalDateTime localDateTime1 = LocalDateTime.from(temporalAccessor);
  17. System.out.println(localDateTime1); // 2023-10-10T15:30:30
  18. }
  19. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/53097
推荐阅读
相关标签
  

闽ICP备14008679号