JDK8新特性-个人笔记

it2024-06-24  39

视频地址:https://www.bilibili.com/video/BV1iJ411U7xU?p=1

目录

一、Lambda表达式

二、Lambda需要“函数式接口”的支持

三、Java8  内置的核心函数式接口

四、方法引用与构造器引用

五、Stream API

六、并行流与顺序流

七、Optional类

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

九、新时间日期API

十、注解相关


一、Lambda表达式

 Lambda 表达式也可称为闭包,是推动 Java 8 发布的最重要新特性。lambda表达式本质上是一个匿名方法。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)或者把代码看成数据。使用 Lambda 表达式可以使代码变的更加简洁紧凑。在最简单的形式中,一个lambda可以由:用逗号分隔的参数列表、–>符号、函数体三部分表示,在某些情况下lambda的函数体会更加复杂,这时可以把函数体放到在一对花括号中,就像在Java中定义普通函数一样。Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。如果lambda的函数体只有一行的话,那么没有必要显式使用return语句。

    如何使现有的函数友好地支持lambda。最终采取的方法是:增加函数式接口的概念。函数式接口就是接口里面必须有且只有一个抽象方法的普通接口,像这样的接口可以被隐式转换为lambda表达式成为函数式接口。 在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。 任何函数式接口都可以使用lambda表达式替换,例如:ActionListener、Comparator、Runnable。

    函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface,但是默认方法与静态方法并不影响函数式接口的契约,可以任意使用。

    使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。通过() -> {}代码块替代了整个匿名类。

package com.wts.mybatisplus.jdk8.Lambda; import org.junit.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.*; /** * @author WangTianShun * @date 2020/10/21 14:11 */ @SpringBootTest public class TestLambda { //原来的匿名内部类 @Test public void Test(){ Comparator<Integer> comparator = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1,o2); } }; TreeSet<Integer> ts = new TreeSet<>(comparator); } //Lambda表达式 @Test public void test2(){ Comparator<Integer> comparator =(x,y) -> Integer.compare(x,y); TreeSet<Integer> ts = new TreeSet<>(comparator); } /*============================================================*/ List<Employee> employees = Arrays.asList( new Employee("wts1",21,22222.2), new Employee("wts2",22,32222.2), new Employee("wts3",23,42222.2), new Employee("wts4",24,52222.2) ); //需求:获取当前公司中员工年龄大于35的员工信息 @Test public void test3(){ List<Employee> employees = filterEmployee1(this.employees); for (Employee emp : employees) { System.out.println(emp); } } public List<Employee> filterEmployee1(List<Employee> list){ List<Employee> emps = new ArrayList<>(); for (Employee emp : emps){ if (emp.getAge() >= 22){ emps.add(emp); } } return emps; } //需求:获取当前公司中员工年工资大于5000 public List<Employee> filterEmployee2(List<Employee> list){ List<Employee> emps = new ArrayList<>(); for (Employee emp : emps){ if (emp.getSalary()<5000){ emps.add(emp); } } return emps; } //优化方式一:策略设计模式 @Test public void test4(){ List<Employee> employees = filterEmployee3(this.employees, new FilterEmployeeByAge()); for (Employee employee : employees) { System.out.println(employee); } List<Employee> employees1 = filterEmployee3(this.employees, new FilterEmployeeBySalary()); for (Employee employee1:employees1) { System.out.println(employee1); } } public List<Employee> filterEmployee3(List<Employee> employees, MyPredicate<Employee> mp){ List<Employee> emps = new ArrayList<>(); for (Employee employee : employees) { if (mp.test(employee)){ emps.add(employee); } } return emps; } //优化方式二:匿名内部类 @Test public void test5(){ List<Employee> employees = filterEmployee3(this.employees, new MyPredicate<Employee>() { @Override public boolean test(Employee employee) { return employee.getSalary() > 3000; } }); for (Employee employee: employees) { System.out.println(employee); } } //优化方式三:Lambda表达式 @Test public void test6(){ List<Employee> list = filterEmployee3(employees,(e) -> e.getSalary() > 3000); list.forEach(System.out::println); } //优化方式四:Stream API @Test public void test7(){ employees.stream().filter((e) -> e.getSalary() >3000) .limit(2) .forEach(System.out::println); System.out.println("-----------------------------------------"); employees.stream().map(Employee::getName) .forEach(System.out::println); } } package com.wts.mybatisplus.jdk8.Lambda; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import org.junit.Test; /** * @author WangTianShun * @date 2020/10/21 14:22 */ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class Employee { private String name; private Integer age; private double salary; } package com.wts.mybatisplus.jdk8.Lambda; /** * @author WangTianShun * @date 2020/10/21 14:34 */ public interface MyPredicate<T> { public boolean test(T t); } package com.wts.mybatisplus.jdk8.Lambda; /** * @author WangTianShun * @date 2020/10/21 14:35 */ public class FilterEmployeeByAge implements MyPredicate<Employee> { @Override public boolean test(Employee employee) { return employee.getAge() > 35; } } package com.wts.mybatisplus.jdk8.Lambda; /** * @author WangTianShun * @date 2020/10/21 14:43 */ public class FilterEmployeeBySalary implements MyPredicate<Employee> { @Override public boolean test(Employee employee) { return employee.getSalary() >3000; } }

Lambda 表达式的基础语法:

        Java8中引入了一个新的操作符 "->" 该操作符称为箭头操作符或Lambda 操作符          箭头操作符将Lambda表达式拆分成两部分          左侧:Lambda表达式的参数列表          右侧:Lambda表达式中所需要执行的功能  即Lambda体

         语法格式一:无参数,无返回值                   () -> System.out.println("hello");

         语法格式二:有一个参数,并且无返回值                   (x) -> System.out.println(x);

         语法格式三:若只有一个参数,小括号可以省略不写                   x -> System.out.println(x);

         语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句                   Comparator<Integer> com = (x, y) ->{                      System.out.println("函数式接口");                      return Integer.compare(x,y);                  };

          语法格式五:若Lambda体中只有一条语句,return 和大括号就可以省略不写                   Comparator<Integer> com = (x, y) -> Integer.compare(x,y);

          语法格式六:Lambda 表达式的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即"类型推断"                   Comparator<Integer> com = (Integer x,Integer y) -> Integer.compare(x,y);           左右遇一括号省           左侧推断类型省

 

二、Lambda需要“函数式接口”的支持

函数式接口:有且只有一个抽象方法的接口,称之为函数式接口。当然接口中可以包含其他的方法(默认、静态,私有)

                      使用@FunctionalInterface注解修饰

                      作用:可以检测接口是否是一个函数式接口 是:编译成功 否:编译失败(接口中没有抽象方法,接口中的抽象方法个数多余1个)

@SpringBootTest public class TestLambda2 { @Test public void run(){ int num = 0; //jdk1.7前,必须是 final Runnable r = new Runnable(){ @Override public void run() { System.out.println("hello word"); } }; r.run(); System.out.println("-------------------------"); Runnable r1 = () -> System.out.println("Hello Word"); r1.run(); } @Test public void test2(){ Consumer<String> con = x -> System.out.println(x); con.accept("hello"); } @Test public void test3(){ Comparator<Integer> com = (x, y) ->{ System.out.println("函数式接口"); return Integer.compare(x,y); }; } @Test public void test4(){ Comparator<Integer> com = ( x, y) -> Integer.compare(x,y); } //需求:对一个数进行运算 @Test public void test5(){ Integer num = operation(100,(x) -> x*x); System.out.println(num); System.out.println(operation(200, (y) -> y + 200)); } public Integer operation(Integer num, MyFunction mf){ return mf.getValue(num); } } package com.wts.mybatisplus.jdk8.Lambda; import org.junit.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * @author WangTianShun * @date 2020/10/21 16:18 */ @SpringBootTest public class TestLambda3 { List<Employee> employees = Arrays.asList( new Employee("wts2",22,22222.2), new Employee("wts1",22,32222.2), new Employee("wts3",23,42222.2), new Employee("wts4",24,52222.2) ); @Test public void test1(){ Collections.sort(employees, (e1,e2) ->{ if (e1.getAge() == e2.getAge()){ return e1.getName().compareTo(e2.getName()); }else { return Integer.compare(e1.getAge(), e2.getAge()); } }); for (Employee employee : employees){ System.out.println(employee); } } @Test public void test02(){ String trimStr = strHandler("\t\t\t 我大尚硅谷威武 ", (str)-> str.trim()); //str.trim()去掉字符串首尾的空格 System.out.println(trimStr); String upperCaseStr = strHandler("abcdef",(str) -> str.toUpperCase()); System.out.println(upperCaseStr); } //需求:用于处理字符串的方法 public String strHandler(String str, MyFunction01 mf){ return mf.getValue(str); } @Test public void test3(){ op(100L,200L, (x,y) -> x+y); } //需求:对于两个Long 型数据进行处理 public void op(Long l1, Long l2, MyFunction02<Long,Long> mf){ System.out.println(mf.getValue(l1,l2)); } } package com.wts.mybatisplus.jdk8.Lambda; /** * @author WangTianShun * @date 2020/10/21 20:28 */ public interface MyFunction01 { public String getValue(String str); } package com.wts.mybatisplus.jdk8.Lambda; /** * @author WangTianShun * @date 2020/10/21 20:40 */ public interface MyFunction02<T,R> { public R getValue(T t1, T t2); }

三、Java8  内置的核心函数式接口  

@SpringBootTest public class TestLambda4 { //Predicate<T> : 断言型接口 @Test public void test4(){ List<String> list = Arrays.asList("hello","atguigu","Lambda","www","ok0"); List<String> strings = filterStr(list, (s) -> s.length() > 3); for (String str : strings) { System.out.println(str); } } //需求:将满足条件的字符串放到集合当中 public List<String> filterStr(List<String> list, Predicate<String> pre){ List<String> strlist = new ArrayList<>(); for (String str: strlist) { if (pre.test(str)){ strlist.add(str); } } } @Test public void test3(){ String newStr = strHandler("\t\t\t 我是哈哈哈 ", (str) -> str.trim()); System.out.println(newStr); } //Function<T,R> :函数型接口 //需求:用于处理字符串 public String strHandler(String str, Function<String, String> fun){ return fun.apply(str); } //Supplier<T>:供给型接口 @Test public void test02(){ getNumList(10,() -> (int)(Math.random() * 100)); } //需求:产生指定个数的整数,并放入集合中 public List<Integer> getNumList(int num, Supplier<Integer> sup){ List<Integer> list = new ArrayList<>(); for (int i = 0; i < num; i++) { Integer e = sup.get(); list.add(e); } return list; } //Consumer<T> 消费型接口 @Test public void test01(){ happy(10000, (m) -> System.out.println("大哥们每次消费:"+ m +"元")); } public void happy(double money, Consumer<Double> con){ con.accept(money); } }

四、方法引用与构造器引用

方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 

* 一、方法引用:若Lambda 体中的内容有方法已经实现了,我们可以使用"方法引用" *      (可以理解为方法引用是Lambda 表达式的另一种表现形式) * * 主要有三种语法格式 * *  对象::实例方法名 * *  类::静态方法名 * *  类::实例方法名 *  Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口 *  Lambda 参数列表中的第一个参数是 实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method * *  二、构造器引用: * *  格式: *  ClassName::new * *  注意:需要调研的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致 * *  三、数组数组引用 * *  type::new;  

定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。

public static class Car {

    public static Car create( final Supplier< Car > supplier ) {

        return supplier.get();

    }                       

    public static void collide( final Car car ) {

        System.out.println( "Collided " + car.toString() );

    }         

    public void follow( final Car another ) {

        System.out.println( "Following the " + another.toString() );

    }         

    public void repair() {  

        System.out.println( "Repaired " + this.toString() );

    }

}

第一种方法引用是构造器引用,它的语法是Class::new,或者更一般的Class< T >::new。请注意构造器没有参数。

    final Car car = Car.create( Car::new );

    final List< Car > cars = Arrays.asList( car ); 

第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数

    cars.forEach( Car::collide );

第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。

    cars.forEach( Car::repair );

第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数

    final Car police = Car.create( Car::new );

    cars.forEach( police::follow );

 

public class TestMethodRef { //1、对象::实例方法名 @Test public void test01(){ Consumer<String> con = (x) -> System.out.println(x); PrintStream ps = System.out; Consumer<String> con1 = ps::println; Consumer<String> con2 = System.out::println; con2.accept("abcdef"); } @Test public void test02(){ Employee employee =new Employee(); Supplier<String> sup = () -> employee.getName(); String str = sup.get(); System.out.println(str); Supplier<Integer> sup2 = employee::getAge; Integer num = sup2.get(); System.out.println(num); } //2、类::静态方法名 @Test public void test03(){ Comparator<Integer> com = (x,y) -> Integer.compare(x,y); Comparator<Integer> com1 = Integer::compare; } //类:静态方法名 public void test04(){ BiPredicate<String,String> bp = (x,y) -> x.equals(y); } @Test public void test05(){ Supplier<Employee> sup = () -> new Employee(); sup.get(); //构造器引用方式 Supplier<Employee> sup2 = Employee::new; Employee employee = sup2.get(); System.out.println(employee); } @Test public void test06(){ Function<Integer,Employee> fun2 = Employee::new; Employee employee = fun2.apply(23); System.out.println(employee); } //数组引用 public void test07(){ Function<Integer,String[]> fun = (x) -> new String[x]; String[] strs = fun.apply(10); System.out.println(strs.length); Function<Integer,String[]> fun2 = String[]::new; String[] strs2 = fun2.apply(20); System.out.println(strs2.length); } }

五、Stream API

Java 8 API添加了一个新的抽象称为流Stream把真正的函数式编程风格引入到Java中,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API极大简化了集合框架的处理,这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

Stream流有一些特性:

    1.Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。

    2.这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。

    3.Stream不保存数据,故每个Stream流只能使用一次。

    所以这边有两个概念:流、管道。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。这里有2个操作:中间操作、最终操作。

    中间操作:返回结果都是Stream,故可以多个中间操作叠加。

    终止操作:用于返回我们最终需要的数据,只能有一个终止操作。

    使用Stream流,可以清楚地知道我们要对一个数据集做何种操作,可读性强。而且可以很轻松地获取并行化Stream流,不用自己编写多线程代码,可以更加专注于业务逻辑。默认情况下,从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。无限流的存在,侧面说明了流是惰性的,即每当用到一个元素时,才会在这个元素上执行这一系列操作。

Stream的三个操作步骤

    1.创建Stream

    2.转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换)

    3.对Stream进行聚合操作,获取想要的结果

一、 流的生成方法

    1.Collection接口的stream()或parallelStream()方法

    2.静态的Stream.of()、Stream.empty()方法

    3.Arrays.stream(array, from, to)

    4.静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数

    5.静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数

    6.Pattern接口的splitAsStream(input)方法

    7.静态的Files.lines(path)、Files.lines(path, charSet)方法

    8.静态的Stream.concat()方法将两个流连接起来

二、 流的Intermediate方法(中间操作)

    1.filter(Predicate) :将结果为false的元素过滤掉

    2.map(fun) :转换元素的值,可以用方法引元或者lambda表达式

    3.flatMap(fun) :若元素是流,将流摊平为正常元素,再进行元素转换

    4.limit(n) :保留前n个元素

    5.skip(n) :跳过前n个元素

    6.distinct() :剔除重复元素

    7.sorted() :将Comparable元素的流排序

    8.sorted(Comparator) :将流元素按Comparator排序

    9.peek(fun) :流不变,但会把每个元素传入fun执行,可以用作调试

三、 流的Terminal方法(终结操作)

(1)约简操作

    1.reduce(fun) :从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数

    2.reduce(a, fun) :a为幺元值,作为累积器的起点

    3.reduce(a, fun1, fun2) :与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并

 

(2)收集操作

    1.iterator():

    2.forEach(fun):

    3.forEachOrdered(fun) :可以应用在并行流上以保持元素顺序

    4.toArray():

    5.toArray(T[] :: new) :返回正确的元素类型

    6.collect(Collector):

    7.collect(fun1, fun2, fun3) :fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来

 

(3)查找与收集操作

    1.max(Comparator):返回流中最大值

    2.min(Comparator):返回流中最小值

    3.count():返回流中元素个数

    4.findFirst() :返回第一个元素

    5.findAny() :返回任意元素

    6.anyMatch(Predicate) :任意元素匹配时返回true

    7.allMatch(Predicate) :所有元素匹配时返回true

    8.noneMatch(Predicate) :没有元素匹配时返回true

 

1、创建Stream

public class TestStream01 { //创建stream @Test public void test01(){ //1、可以通过Collection 系列集合提供的stream() 或parallelStream() List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream(); //2、通过Arrays中的静态方法stream() 获取数组流 Employee[] emps = new Employee[10]; Stream<Employee> stream2 = Arrays.stream(emps); //3、通过Stream 类中的静态方法的of() Stream<String> stream3 = Stream.of("aa", "bb", "cc"); //4、创建无线流 //迭代 Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2); stream4.limit(10).forEach(System.out::println); //生成 Stream.generate(() -> Math.random()) .forEach(System.out::println); } }

2、中间操作

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

     筛选与切片

     filter---接收Lambda,从流中排除某些元素

     limit---截断流,使其元素不超过给定数量

     skip(n)---跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补

    distinct---筛选,通过流所生成元素的hasCode() 和 equals()去除重复元素

映射

map--接收Lambda, 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

flatmap--接收一个函数作为参数,将流中的每个值换成另一个流,然后把所有的流连接成一个流。 

@Test public void test06(){ List<String> list = Arrays.asList("aaa","bbb","cccc","dddd"); list.stream() .map((str) -> str.toUpperCase()) .forEach(System.out::println); System.out.println("-----------------------------------------"); employees.stream() .map(Employee::getAge) .forEach(System.out::println); System.out.println("-----------------------------------------"); Stream<Stream<Character>> stream = list.stream() .map(TestStreamAPI02::filterCharacter);//{{a,a,a},{b,b,b}} stream.forEach((sm) ->{ sm.forEach(System.out::println); }); System.out.println("-----------------------------------------"); Stream<Character> characterStream = list.stream() .flatMap(TestStreamAPI02::filterCharacter);//{a,a,a,b,b,b} characterStream.forEach(System.out::println); } public static Stream<Character> filterCharacter(String str){ List<Character> list = new ArrayList<>(); for (Character ch : str.toCharArray()) { list.add(ch); } return list.stream(); }

排序

sorted()-自然排序

sorted(Comparator com) --定制排序

 

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

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

查找与匹配

allMatch--检查是都匹配所有元素

anyMatch--检查是否至少匹配一个元素

noneMatch--检查是否没有匹配所有元素

findFrist--返回第一个元素

findAny--返回当前流中的任意元素

count--返回流中元素的总个数

max--返回流中最大值

min--返回流中最小值

List<Employee> employees = Arrays.asList( new Employee("wts1",21,22222.2, Employee.Status.BUSY), new Employee("wts2",22,32222.2, Employee.Status.FREE), new Employee("wts3",23,42222.2, Employee.Status.VOCATION), new Employee("wts4",24,52222.2,Employee.Status.VOCATION) ); @Test public void test01(){ boolean b = employees.stream() .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(b); boolean b1 = employees.stream() .anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(b1); boolean b2 = employees.stream() .noneMatch((e) -> e.getStatus().equals(Employee.Status.FREE)); System.out.println(b2); boolean b3 = employees.stream() .noneMatch((e) -> e.getStatus().equals(Employee.Status.VOCATION)); System.out.println(b3); Optional<Employee> first = employees.stream() .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())) .findFirst(); System.out.println(first.get()); Optional<Employee> any = employees.stream() .filter((e) -> e.getStatus().equals(Employee.Status.FREE)) .findAny(); System.out.println(any); } @Test public void test02(){ long count = employees.stream().count(); System.out.println(count); Optional<Employee> max = employees.stream() .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())); System.out.println(max.get()); Optional<Double> min = employees.stream() .map(Employee::getSalary) .min(Double::compare); System.out.println(min.get()); }

归约

reduce(T identity , BinaryOperator)/reduce(BinaryOperator)  ---可以将流中元素反复结合起来,得到一个值。

@Test public void test03(){ List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9); Integer sum = list.stream() .reduce(0, (x, y) -> x + y); System.out.println(sum); System.out.println("----------------------------------------"); Optional<Double> op = employees.stream() .map(Employee::getSalary) .reduce(Double::sum); System.out.println(op.get()); }

收集

collect---将流转化为其他形式。接收一个Collector接口的实现,用于给Stream中元素汇总的方法。

@Test public void test04(){ List<String> collect = employees.stream() .map(Employee::getName) .collect(Collectors.toList()); collect.forEach(System.out::println); System.out.println("----------------------------------"); Set<String> collect1 = employees.stream() .map(Employee::getName) .collect(Collectors.toSet()); collect1.forEach(System.out::println); System.out.println("---------------------------------"); HashSet<String> collect2 = employees.stream() .map(Employee::getName) .collect(Collectors.toCollection(HashSet::new)); collect2.forEach(System.out::println); } @Test public void test05(){ //总数 Long count = employees.stream() .collect(Collectors.counting()); System.out.println(count); System.out.println("------------------------------------"); //平均值 Double avg = employees.stream() .collect(Collectors.averagingDouble(Employee::getSalary)); System.out.println(avg); //总和 DoubleSummaryStatistics sum = employees.stream() .collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.println(sum); //最大值 Optional<Employee> max = employees.stream() .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))); System.out.println(max.get()); //最小值 Optional<Double> collect = employees.stream() .map(Employee::getSalary) .collect(Collectors.minBy(Double::compare)); System.out.println(collect.get()); }

分组

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

分区

@Test public void test08(){ Map<Boolean, List<Employee>> map = employees.stream() .collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000)); System.out.println(map); } @Test public void test09(){ DoubleSummaryStatistics collect = employees.stream() .collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.println(collect.getMax()); System.out.println(collect.getAverage()); System.out.println(collect.getMin()); System.out.println(collect.getSum()); } @Test public void test10(){ String collect = employees.stream() .map(Employee::getName) .collect(Collectors.joining(",","==","==")); System.out.println(collect); }

六、并行流与顺序流

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

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

package com.wts.mybatisplus.jdk8.forkjoin; import java.util.concurrent.RecursiveTask; /** * @author WangTianShun * @date 2020/10/23 12:58 */ public class ForkJoinCalculate extends RecursiveTask<Long> { private long start; private long end; public ForkJoinCalculate(long start, long end) { this.start = start; this.end = end; } private static final long THRESHOLD = 10000; @Override protected Long compute() { long length = end - start; if (length <= THRESHOLD){ long sum = 0; for (long i = start; i<= end; i++){ sum += i; } return sum; }else { long middle = (start + end)/2; ForkJoinCalculate left = new ForkJoinCalculate(start,middle); left.fork();//拆分子任务,同时压入线程队列 ForkJoinCalculate right = new ForkJoinCalculate(middle+1,end); right.fork(); return left.join() + right.join(); } } } package com.wts.mybatisplus.jdk8.forkjoin; import org.junit.Test; import java.time.Duration; import java.time.Instant; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.stream.LongStream; /** * @author WangTianShun * @date 2020/10/23 13:37 */ public class TestForkJoin { public static void main(String[] args) { Instant start = Instant.now(); ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinCalculate(0,100000000L); Long invoke = pool.invoke(task); System.out.println(invoke); Instant end = Instant.now(); System.out.println("耗费时间:"+ Duration.between(start,end).toMillis()); } //普通 @Test public void test02(){ Instant start = Instant.now(); long sum = 0L; for (long i = 0; i <= 100000000L; i++){ sum += i; } System.out.println(sum); Instant end = Instant.now(); System.out.println("耗费时间:"+ Duration.between(start,end).toMillis()); } //java8并行流 @Test public void test3(){ Instant start = Instant.now(); LongStream.rangeClosed(0,100000000L) .parallel() .reduce(0,Long::sum); Instant end = Instant.now(); System.out.println("耗费时间:"+ Duration.between(start,end).toMillis()); } }

七、Optional类

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

常用方法: 

Optional.of(T t): 创建一个Optional实例 * Optional.empty(): 创建一个空的Optional实例

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

isPresent(): 判断是否包含值 * orElse(T t):如果调用对象包含值,返回该值,否则返回t

orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s的获取值

map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()

flatMap(Function mapper):与map 类似,要求返回值必须是Optional

 

1.创建optional对象,一般用ofNullable()而不用of():

    (1)empty() :用于创建一个没有值的Optional对象:Optional<String> emptyOpt = Optional.empty();

    (2)of() :使用一个非空的值创建Optional对象:Optional<String> notNullOpt = Optional.of(str);

    (3)ofNullable() :接收一个可以为null的值:Optional<String> nullableOpt = Optional.ofNullable(str);

    2.判断Null:

    (1)isPresent():如果创建的对象实例为非空值的话,isPresent()返回true,调用get()方法会返回该对象,如果没有值,调用isPresent()方法会返回false,调用get()方法抛出NullPointerException异常。

    3.获取对象:

    (1)get()

    4.使用map提取对象的值,如果我们要获取User对象中的roleId属性值,常见的方式是先判断是否为null然后直接获取,但使用Optional中提供的map()方法可以以更简单的方式实现

    5.使用orElse方法设置默认值,Optional类还包含其他方法用于获取值,这些方法分别为:

    (1)orElse():如果有值就返回,否则返回一个给定的值作为默认值;

    (2)orElseGet():与orElse()方法作用类似,区别在于生成默认值的方式不同。该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;

    (3)orElseThrow():与前面介绍的get()方法类似,当值为null时调用这两个方法都会抛出NullPointerException异常,区别在于该方法可以指定抛出的异常类型。

    6.使用filter()方法过滤,filter()方法可用于判断Optional对象是否满足给定条件,一般用于条件过滤,在代码中,如果filter()方法中的Lambda表达式成立,filter()方法会返回当前Optional对象值,否则,返回一个值为空的Optional对象。:

 

@SpringBootTest public class TestOptional { @Test public void test1(){ Optional<Employee> op = Optional.of(new Employee()); System.out.println(op.get()); } @Test public void test2(){ Optional<Employee> op = Optional.empty(); System.out.println(op.get()); } @Test public void test3(){ Optional<Employee> op = Optional.ofNullable(new Employee()); /*if (op.isPresent()){ System.out.println(op.get()); }*/ Employee emp = op.orElse( new Employee("wts1",21,22222.2)); System.out.println(emp); System.out.println("--------------------------"); Employee emp1 = op.orElseGet(() -> new Employee()); System.out.println(emp1); } @Test public void test04(){ Optional<Employee> op = Optional.ofNullable(new Employee("wts1",21,22222.2)); Optional<String> s = op.map((e) -> e.getName()); System.out.println(s.get()); System.out.println("---------------------------"); Optional<String> s1 = op.flatMap((e) -> Optional.of(e.getName())); System.out.println(s1.get()); } }

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

Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求,就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个default关键字即可实现默认方法。为什么要有这个特性?以前当需要修改接口的时候,需要修改全部实现该接口的类。而引进的默认方法的目的是为了解决接口的修改与现有的实现不兼容的问题。

public interface MyFun { default String getName(){ return "哈哈哈"; } public static void show(){ System.out.println("接口中的静态方法"); } }

当出现这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,这种情况的解决方法:

    1.是创建自己的默认方法,来覆盖重写接口的默认方法

    2.可以使用 super 来调用指定接口的默认方法

 

    Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。

 

九、新时间日期API

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,比如:

        1.非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。

        2.设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。

        3.时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

   Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

       1.Local(本地) − 简化了日期时间的处理,没有时区的问题。

       2.Zoned(时区) − 通过制定的时区处理日期时间。

    新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

package com.wts.mybatisplus.jdk8.date; import org.junit.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.xml.bind.SchemaOutputResolver; import java.time.*; import java.util.function.DoubleToIntFunction; /** * @author WangTianShun * @date 2020/10/23 22:16 */ @SpringBootTest public class TestLocalDateTime { //1、LocalDate 2、LocalTime 3、LocalDateTime @Test public void test(){ LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); LocalDateTime ldt2 = LocalDateTime.of(2019,10,19,13,22,33); System.out.println(ldt2); LocalDateTime ldt3 = ldt.plusYears(2); System.out.println(ldt3); LocalDateTime ldt4 = ldt.minusMonths(2); System.out.println(ldt4); System.out.println(ldt.getYear()); System.out.println(ldt.getMonth()); System.out.println(ldt.getDayOfMonth()); System.out.println(ldt.getHour()); System.out.println(ldt.getMinute()); System.out.println(ldt.getSecond()); } //2、Instant:时间戳(以Unix元年 1970年1月1日00:00:00到某个时间之间的毫秒值) @Test public void test2(){ Instant now = Instant.now();//默认获取UTC时区 System.out.println(now); OffsetDateTime time = now.atOffset(ZoneOffset.ofHours(8)); System.out.println(time); } //3、Duration : 计算两个“时间”之间的间隔 //Period: 计算两个日期之间的间隔 @Test public void test3(){ Instant ins1 = Instant.now(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Instant ins2 = Instant.now(); Duration duration = Duration.between(ins1, ins2); System.out.println(duration.toMillis()); System.out.println("------------------------"); LocalTime lt1 = LocalTime.now(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } LocalTime lt2 = LocalTime.now(); Duration duration1 = Duration.between(ins1, ins2); System.out.println(duration1.toMillis()); } }

十、注解相关

(1)可以进行重复注解

自从Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解可以在同一地方声明多次。

重复注解机制本身必须用@Repeatable注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。

(2)扩展注解的支持

Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。

十一、Java8 中 List 转 Map(Collectors.toMap)

在实际项目中我们经常会用到 List 转 Map 操作,在过去我们可能使用的是 for 循环遍历的方式。举个例子:

先定义类:

// 简单对象 @Accessors(chain = true) // 链式方法 @lombok.Data class User { private String id; private String name; }

然后有这样一个 List:

List<User> userList = Lists.newArrayList( new User().setId("A").setName("张三"), new User().setId("B").setName("李四"), new User().setId("C").setName("王五") );

我们希望转成 Map 的格式为:

A-> 张三 B-> 李四 C-> 王五

过去的做法(循环):

Map<String, String> map = new HashMap<>(); for (User user : userList) { map.put(user.getId(), user.getName()); }

使用 Java8 特性

Java8 中新增了 Stream 特性,使得我们在处理集合操作时更方便了。

以上述例子为例,我们可以一句话搞定:

userList.stream().collect(Collectors.toMap(User::getId, User::getName));

当然,如果希望得到 Map 的 value 为对象本身时,可以这样写:

userList.stream().collect(Collectors.toMap(User::getId, t -> t)); 或: userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));

关于 Collectors.toMap 方法

Collectors.toMap 有三个重载方法:

toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper); toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction); toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier);

参数含义分别是:

keyMapper:Key 的映射函数

valueMapper:Value 的映射函数

mergeFunction:当 Key 冲突时,调用的合并方法

mapSupplier:Map 构造器,在需要返回特定的 Map 时使用

还是用上面的例子,如果 List 中 userId 有相同的,使用上面的写法会抛异常:

List<User> userList = Lists.newArrayList( new User().setId("A").setName("张三"), new User().setId("A").setName("李四"), // Key 相同 new User().setId("C").setName("王五") ); userList.stream().collect(Collectors.toMap(User::getId, User::getName)); // 异常: java.lang.IllegalStateException: Duplicate key 张三 at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133) at java.util.HashMap.merge(HashMap.java:1245) at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320) at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) at Test.toMap(Test.java:17) ...

这时就需要调用第二个重载方法,传入合并函数,如:

userList.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1 + n2)); // 输出结果: A-> 张三李四 C-> 王五

第四个参数(mapSupplier)用于自定义返回 Map 类型,比如我们希望返回的 Map 是根据 Key 排序的,可以使用如下写法:

List<User> userList = Lists.newArrayList( new User().setId("B").setName("张三"), new User().setId("A").setName("李四"), new User().setId("C").setName("王五") ); userList.stream().collect( Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, TreeMap::new) ); // 输出结果: A-> 李四 B-> 张三 C-> 王五

 

 

 

 

 

 

最新回复(0)