一、闲话
Java8已经出来很久了,今天闲来无事,将它整理一下,方便后面工作中查阅
Java8有着很多优点
- 它的速度更快
- 因为lambda表达式的出现,使得代码更少
- 便于并行
- 强大的Stream API
- Optional类可以最大程度减少空指针
二、Lambda表达式
1、组成部分
主要组成部分:
Lambda形参列表(其实就是接口中抽象方法的形参列表)
->操作符
Lambda方法体(其实就是重写的抽象方法的方法体)
2、Lambda表达式的语法
- 无参数,无返回值
- 有一个参数,无返回值
- Lambda表达式的参数列表的数据类型可以省略,因为JVM编译器可以推断初数据类型,即类型判断
- 只有一个参数时,左侧参数的括号可省略
- 有两个以上的参数,有返回值,并且Lambda函数体中有多条语句
- 如果方法体中只有一条执行语句,那么右侧的大括号和return都可以省略
package com.decade.java8.lambda;
import org.junit.Test;
import java.util.Comparator;
import java.util.function.Consumer;
public class Lambda1 {
@Test
public void test1() {
// 语法一:无参无返回值,可以写成
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("execute r1");
}
};
r1.run();
System.out.println("==============================");
Runnable r2 = () -> {
System.out.println("execute r2");
};
r2.run();
}
@Test
public void test2() {
// 语法二:有一个参数,无返回值,可以写成
Consumer<String> c1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
c1.accept("execute c1");
System.out.println("===============================");
Consumer<String> c2 = (String input) -> {
System.out.println(input);
};
c2.accept("execute c2");
}
@Test
public void test3() {
// 语法三:lambda表达式的参数列表的数据类型可以省略,因为JVM编译器可以推断出数据类型,即类型判断
Consumer<String> c3 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
c3.accept("execute c3");
System.out.println("===============================");
Consumer<String> c4 = (input) -> {
System.out.println(input);
};
c4.accept("execute c4");
}
@Test
public void test4() {
// 语法四:只有一个参数时,左侧参数的括号可省略,可以写成
Consumer<String> c5 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
c5.accept("execute c5");
System.out.println("===============================");
Consumer<String> c6 = input -> {
System.out.println(input);
};
c6.accept("execute c6");
}
@Test
public void test5() {
// 有两个以上的参数,有返回值,并且lambda函数体中有多条语句时,可以写成
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator.compare(8, 10));
System.out.println("================================");
Comparator<Integer> comparator2 = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(comparator2.compare(8, 10));
}
@Test
public void test6() {
// 语法六:如果方法体中只有一条执行语句,那么右侧的大括号和return都可以省略
Comparator<Integer> comparator3 = (o1, o2) -> {
return o1.compareTo(o2);
};
System.out.println(comparator3.compare(10, 8));
System.out.println("===================================");
Comparator<Integer> comparator4 = (o1, o2) -> o1.compareTo(o2);
System.out.println(comparator4.compare(10, 8));
}
}
3、Lambda表达式的本质
作为函数式接口的实例,以前用匿名实现类表示的现在都可以使用Lambda表达式来写
4、函数式接口
1)定义
使用@FunctionalInterface
修饰, 有且仅有一个 抽象方法的接口就称为函数式接口
可以使用@FunctionalInterface
检查是否是函数式接口
假设接口中有不止一个抽象方法,那么该注解就会爆红
// 函数式接口
@FunctionalInterface
public interface Intfunction{
T apply(int a);
}
// 调用函数式接口的类:
public class Test{
public static Object test(int a, IntFunction intFunction){
return intFunction.apply(i);
}
public static void main(String args[]){
Test.test(1, (int 1)->{
return 1;
});
}
}
2)Java内置四大核心函数式接口
Consumer(消费型接口)
:接受一个参数,无返回结果Supplier(供给型接口)
:无参数,返回一个结果Function(函数型接口)
:传一个参数,返回一个结果Pridicate(断定型接口)
:接受一个参数,返回一个boolean值
package com.decade.java8.lambda;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Java内置四大函数式接口
*
* 消费型接口 Consumer<T> void accept(T t):传入一个类型为T的参数,不返回结果
* 供给型接口 Supplier<T> T get():不传入参数,返回一个类型为T的结果
* 函数型接口 Function<T,R> R apply(T t):传入一个类型为T的参数,返回一个类型为R的结果
* 断定型接口 Predicate<T> boolean test(T t):传入一个类型为T的参数,判断其是否满足某个约束,返回boolean值
*/
public class Lambda2 {
@Test
public void test1() {
buyGoods(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("我在超市买了点东西,花了" + aDouble);
}
});
System.out.println("=====================================");
// 使用lambda表达式
buyGoods(400, r -> System.out.println("我在超市买了点东西,花了" + r));
}
public void buyGoods(double money, Consumer<Double> consumer) {
consumer.accept(money);
}
@Test
public void test2() {
List<String> stringList = Arrays.asList("北京","南京","纽约","巴黎");
final List<String> result = filterString(stringList, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(result);
System.out.println("=====================================");
// 使用lambda表达式
List<String> result2 = filterString(stringList, s -> s.contains("京"));
System.out.println(result2);
}
// 根据给定约束过滤字符串,此规则由Predicate的test方法决定
public List<String> filterString(List<String> strings, Predicate<String> predicate) {
List<String> list = new ArrayList<>();
// 如果满足predicate中的test方法,那么就将当前元素加入结果集
for(String s : strings) {
if (predicate.test(s)) {
list.add(s);
}
}
return list;
}
}
3)方法引用与构造器引用
- 方法引用
- 使用场景:当我们要写在Lambda方法体中的操作,已经有方法去实现了,我们就可以直接使用方法引用
- 方法引用本质上就是lambda表达式,它的使用格式为
类(或者实例) :: 方法名
,具体分为三种情况对象::非静态方法
类::静态方法
类::非静态方法
- 使用要求:接口中的抽象方法的形参列表与返回值类型==方法引用的形参列表与返回值类型(针对情况1和情况2)
package com.decade.java8.lambda;
import org.junit.Test;
import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class Lambda3 {
// 情况一:对象::实例方法
@Test
public void test1() {
// Consumer中的 void accept(T t)
// PrintStream中的 void println(T t)
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("test1北京");
System.out.println("===================");
final PrintStream printStream = System.out;
final Consumer<String> consumer1 = printStream::println;
consumer1.accept("test1上海");
System.out.println("*********************");
// Employee中的String getName()
// Supplier中的T get()
final Employee employee = new Employee("1", "test");
Supplier<String> supplier = () -> employee.getName();
System.out.println("test1" + supplier.get());
System.out.println("==============");
Supplier<String> supplier1 = employee::getName;
System.out.println("test1" + supplier1.get());
}
class Employee {
private String id;
private String name;
public Employee(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 情况二:类::静态方法
@Test
public void test2() {
// Comparator中的 Integer compare(T t1, T t2)
// Integer中的 Integer compare(T t1, T t2)
Comparator<Integer> comparator = (t1, t2) -> Integer.compare(t1, t2);
System.out.println(comparator.compare(12, 23));
System.out.println("==================");
Comparator<Integer> comparator1 = Integer::compareTo;
System.out.println(comparator1.compare(23, 12));
System.out.println("*********************");
// Math中的取整方法 Long round(Double d)
// Function中的 R apply(T t)
Function<Double, Long> function = new Function<Double, Long>() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
System.out.println("==================");
Function<Double, Long> function1 = d -> Math.round(d);
System.out.println(function1.apply(12.3));
System.out.println("==================");
Function<Double, Long> function2 = Math::round;
System.out.println(function2.apply(12.6));
}
// 情况三:类::非静态方法
@Test
public void test3() {
// Comparator中的int compare(T t1, T t2)
// String中的 int t1.compareTo(t2)
Comparator<String> comparator = (t1, t2) -> t1.compareTo(t2);
System.out.println(comparator.compare("abc", "abd"));
System.out.println("==================");
Comparator<String> comparator2 = String::compareTo;
System.out.println(comparator2.compare("abf", "aba"));
System.out.println("*********************");
final Employee employee = new Employee("002", "test2");
Function<Employee, String> function = e -> e.getName();
System.out.println(function.apply(employee));
System.out.println("==================");
Function<Employee, String> function2 = Employee::getName;
System.out.println(function2.apply(employee));
}
}
- 构造器引用、数组引用
- 构造器引用
- 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致
- 抽象方法的返回值的类型即为构造方法所属的类
- 数组引用:可以把数组看做一个特殊的类,写法与构造器引用一致
- 构造器引用
package com.decade.java8.lambda;
import org.junit.Test;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class Lambda4 {
@Test
public void test1() {
Supplier<Student> supplier = new Supplier<Student>() {
@Override
public Student get() {
return new Student();
}
};
System.out.println("====================");
Supplier<Student> supplier1 = () -> new Student();
supplier1.get();
System.out.println("====================");
Supplier<Student> supplier2 = Student::new;
supplier2.get();
System.out.println("****************************");
// Function中的 R apply(T t)
Function<String, Student> function = id -> new Student(id);
final Student apply = function.apply("001");
System.out.println(apply);
System.out.println("====================");
Function<String, Student> function1 = Student::new;
final Student apply1 = function1.apply("002");
System.out.println(apply1);
System.out.println("****************************");
// BiFunction中的 R apply(T t,U u)
BiFunction<String, String, Student> biFunction = (id, name) -> new Student(id, name);
final Student student = biFunction.apply("001", "test1");
System.out.println(student);
System.out.println("====================");
BiFunction<String, String, Student> biFunction1 = Student::new;
final Student student1 = biFunction1.apply("002", "test2");
System.out.println(student1);
}
class Student {
private String id;
private String name;
public Student() {
System.out.println("无参构造");
}
public Student(String id) {
this.id = id;
System.out.println("id相关的构造器方法");
}
public Student(String id, String name) {
this.id = id;
this.name = name;
System.out.println("包含所有字段的有参构造");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
@Test
public void test2() {
Function<Integer, String[]> function = length -> new String[length];
final String[] strings = function.apply(5);
System.out.println(strings.length);
System.out.println("=======================");
Function<Integer, String[]> function1 = String[]::new;
final String[] strings1 = function1.apply(15);
System.out.println(strings1.length);
}
}
三、Stream API
1、什么是Stream
它是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
集合关注的是存储数据,Stream关注的是对数据进行计算
2、注意点
- Stream不会存储元素
- Stream不会改变源对象,它会返回一个持有结果的新Stream对象
- Stream的操作是延迟执行的,它会等到需要结果的时候才执行
3、Stream的三个操作步骤
- 创建Stream:一个数据源(如集合、数组),获取一个Stream流
- 中间操作:一个中间操作链,对数据进行处理,如过滤、映射等
- 终止操作:一旦执行终止操作,就执行中间操作对数据进行处理,结果产生后,就不能再回头重新执行中间操作了,不可再被使用
4、创建Stream流
package com.decade.java8.stream;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Stream1 {
// 创建Stream方式一:通过集合获取
@Test
public void testCreate1() {
final List<String> stringList = getStringList();
// default Stream<E> stream(),返回一个顺序流,数据的处理按照顺序进行
final Stream<String> stream = stringList.stream();
// default Stream<E> parallelStream():返回一个并行流,数据的处理是并行的,不一定按照顺序进行
final Stream<String> stringStream = stringList.parallelStream();
}
// 创建Stream方式二:通过数组获取
@Test
public void testCreate2() {
// 调用Arrays类的 static <T>Stream stream(T[] array)
final IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4, 5});
}
// 创建Stream方式三:通过Stream.of()
@Test
public void testCreate3() {
final Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
}
// 创建Stream方式四:创建无限流
@Test
public void testCreate4() {
// 迭代操作,返回由函数f迭代应用于初始元素seed产生的无限有序流,产生由seed, f(seed), f(f(seed))等组成的流
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 返回一个无限顺序无序流,其中每个元素都由提供的provider生成。这适用于生成恒定流、随机元素流等
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
public static List<String> getStringList() {
List<String> resList = new ArrayList<>();
resList.add("111");
resList.add("222");
resList.add("333");
resList.add("444");
return resList;
}
}
5、Stream的中间操作
- 筛选与切片
- filter(Predicate p) :过滤给定规则的元素
- limit(Integer n) :截取给定数量的元素
- skip(Integer n) :顺序流,跳过前n个元素,若不足n个,则返回一个空流,与limit(n)互补
- distinct() :根据元素的hashcode和equals方法来去重
package com.decade.java8.stream;
import java.util.ArrayList;
import java.util.List;
public class Employee {
private String id;
private String name;
private int age;
private Double salary;
public Employee(String id, String name, int age, Double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
public static List<Employee> getEmployeeList() {
List<Employee> employeeList = new ArrayList<>();
final Employee test1 = new Employee("001", "test1", 20, 5000.32);
final Employee test2 = new Employee("002", "test2", 25, 9000.32);
final Employee test3 = new Employee("003", "test3", 32, 19800.00);
final Employee test4 = new Employee("004", "test4", 45, 33000.00);
employeeList.add(test1);
employeeList.add(test2);
employeeList.add(test3);
employeeList.add(test4);
return employeeList;
}
}
package com.decade.java8.stream;
import org.junit.Test;
import java.util.List;
import java.util.stream.Stream;
public class Stream2 {
@Test
public void testFilter() {
final List<Employee> employeeList = Employee.getEmployeeList();
final Stream<Employee> stream = employeeList.stream();
// 查询员工中年龄大于30岁的,filter(Predicate p) 过滤给定规则的元素
stream.filter(e -> e.getAge() > 30).forEach(System.out::println);
}
@Test
public void testLimit() {
final List<Employee> employeeList = Employee.getEmployeeList();
final Stream<Employee> stream = employeeList.stream();
// 截取数据源中前三条数据,limit(Integer n) ===> 截取给定数量的元素
stream.limit(3).forEach(System.out::println);
}
@Test
public void testSkip() {
final List<Employee> employeeList = Employee.getEmployeeList();
final Stream<Employee> stream = employeeList.stream();
// 跳过数据源中前三条数据,skip(Integer n) ===> 顺序流,跳过前n个元素,若不足n个,则返回一个空流,与limit(n)互补
stream.skip(3).forEach(System.out::println);
}
@Test
public void testDistinct() {
final List<Employee> employeeList = Employee.getEmployeeList();
final Employee test5 = new Employee("005", "test5", 30, 17458.00);
employeeList.add(test5);
employeeList.add(test5);
final Stream<Employee> stream = employeeList.stream();
// 这里会打印2次005员工
employeeList.forEach(System.out::println);
System.out.println("===============");
// distinct() ===>根据元素的hashcode和equals方法来去重
stream.distinct().forEach(System.out::println);
}
}
- 映射
- map(Function f) :将流中的元素按照某种规则映射出来,一个元素类型为 T 的流转换成元素类型为 R 的流
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。map对比集合中的list.add(),flatMap对比集合中的list.addAll()去理解
package com.decade.java8.stream;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Stream3 {
@Test
public void testMap() {
// map(Function f) ===> 将流中的元素按照某种规则映射出来,一个元素类型为 T 的流转换成元素类型为 R 的流,此处就是将所有的值都大写
List<String> list = Arrays.asList("aa","bb","cc","dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
System.out.println("================");
// 获取年龄大于25岁的员工的年纪
final List<Employee> employeeList = Employee.getEmployeeList();
final Stream<Integer> ageStream = employeeList.stream().map(Employee::getAge);
ageStream.filter(r -> r > 25).forEach(System.out::println);
}
@Test
public void testFlatMap() {
// flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
List<String> list = Arrays.asList("aa","bb","cc","dd");
Stream<Character> characterStream = list.stream().flatMap(Stream3::fromStringToStream);
//输出a a b b c c d d
characterStream.forEach(System.out::println);
}
// 将字符串中的多个字符进行拆分并转换成Stream,假设入参是aa会返回a,a
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
@Test
public void testListAddAndAddAll() {
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
ArrayList list2 = new ArrayList();
list2.add(3);
list2.add(4);
ArrayList list3 = new ArrayList();
list3.add(5);
list3.add(6);
list2.add(list);
System.out.println(list2);
System.out.println("==========");
list3.addAll(list);
System.out.println(list3);
}
}
- 排序
- sorted():自然排序
- sorted(Comparator com):定制排序,按照自己制定的规则进行排序
package com.decade.java8.stream;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
public class Stream4 {
@Test
public void test() {
List<Employee> employeeList = Employee.getEmployeeList();
List<Integer> list = Arrays.asList(1,4,2,8,7);
//sorted()--->自然排序,输出1,2,3,7,8
list.stream().sorted().forEach(System.out::println);
//sorted(Comparator com)--->定制排序,此处会抛异常,因为Employee没有实现comparable接口
// employeeList.stream().sorted().forEach(System.out::println);
// 按照年龄进行排序
employeeList.stream().sorted((e1, e2) -> {
//按年龄从小到大输出,年龄一样按工资从低到高(想从大到小,在Double前面加一个-即可)
int ageValue = Integer.compare(e1.getAge(),e2.getAge());
if(ageValue != 0){
return ageValue;
}else{
return Double.compare(e1.getSalary(),e2.getSalary());
}
}).forEach(System.out::println);
}
}
6、Stream的终止操作
- 匹配与查找
package com.decade.java8.stream;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class Stream5 {
@Test
public void testMatch() {
List<Employee> employeeList = Employee.getEmployeeList();
//allMatch---检查是否匹配所有元素
boolean allMatch = employeeList.stream().allMatch(e -> e.getAge() > 18);//是否所有人年纪都大于18
System.out.println("是否所有人年纪都大于18:" + allMatch);
//anyMatch---检查是否至少匹配一个元素
boolean anyMatch = employeeList.stream().anyMatch(e -> e.getSalary() > 10000);//是否有一个人的薪资大于10000
System.out.println("是否有一个人的薪资大于10000:" + anyMatch);
//noneMatch---检查是否没有匹配元素
boolean noneMatch = employeeList.stream().noneMatch(e -> e.getName().startsWith("陈"));//检查是否没有人姓陈
System.out.println("检查是否没有人姓陈:" + noneMatch);
//findFirst--返回第一个元素
Optional<Employee> firstEmployee = employeeList.stream().findFirst();
System.out.println("返回第一个元素:" + firstEmployee);
// findAny--返回当前流中任意元素
// 此执行在一般情况下,并不会随机输出,而是按顺序输出,因为该流是有顺序的串行流,按照最优执行会按照顺序,但是换成并行流之后也是如此
Optional<Employee> anyEmployee = employeeList.stream().findAny();
System.out.println("返回任意元素:" + anyEmployee);
//count()----返回流中元素的总个数
long count = employeeList.stream().filter(e -> e.getSalary() > 5000).count();//返回流中工资大于5000的总数
System.out.println("返回流中工资大于5000的总数:" + count);
//max(Comparator c)-----返回流中最大值
Stream<Double> salaryStream = employeeList.stream().map(e -> e.getSalary());
Optional<Double> maxSalary = salaryStream.max(Double :: compare);//返回最高工资
System.out.println("返回最高工资:" + maxSalary);
//min(Comparator c)----返回流中最小值
Optional<Employee> minEmployee = employeeList.stream().min((e1,e2) -> Double.compare(e1.getSalary(),e2.getSalary()));//返回最低工资员工
System.out.println("返回最低工资员工:" + minEmployee);
//forEach(Comsumer c)---内部迭代
employeeList.stream().forEach(System.out::println);
}
}
- 规约:规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个汇总结果的过程。元素求和、求最大值或最小值、求出元素总个数、将所有元素转换成一个列表或集合,都属于规约操作
package com.decade.java8.stream;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class Stream6 {
@Test
public void test() {
//reduce(T identity,BinaryOperator)---可以将流中的元素反复结合起来与初始值做运算,得到一个值,返回T类型
//练习:计算1-5的和
List<Integer> list = Arrays.asList(1,2,3,4,5);
Integer sum = list.stream().reduce(0,Integer::sum);//初始值是0
System.out.println(sum);
//reduce(BinaryOperator)---可以将流中的元素反复结合起来,得到一个值,返回Optional<T>
//练习:计算所有员工工资的总和
List<Employee> employees = Employee.getEmployeeList();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
//也可以写成:Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
Optional<Double> sumMoney = salaryStream.reduce((d1, d2) -> d1 + d2);
System.out.println(sumMoney);
}
}
- 收集
- collect(Collector c):Collector接口中的方法实现决定了如何对流执行收集的操作(如收集到List、Set、Map)
package com.decade.java8.stream;
import org.junit.Test;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class Stream7 {
@Test
public void test() {
//练习1:查找工工资大于6000的员工,将结果返回一个List或Set
List<Employee> employees = Employee.getEmployeeList();
List<Employee> employeesList = employees.stream().filter(e->e.getSalary() > 6000).collect(Collectors.toList());//把流中元素收集到List
employeesList.forEach(System.out::println);
System.out.println("====================");
Set<Employee> employeesSet = employees.stream().filter(e->e.getSalary() > 6000).collect(Collectors.toSet());//把流中元素收集到Set
employeesSet.forEach(System.out::println);
}
}
四、Optional类(预防空指针)
1、概念
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测,很好的解决空指针异常
2、创建Optional类对象:of、ofNullable、empty
- Optional.of(T t)、Optional.ofNullable(T t)都是拿Optional对象的value
- Optional.of(T t)不能传空,会报空指针异常,Optional.ofNullable(T t)可以传空,它会返回一个空的Optional对象
- Optional.empty()会返回一个空的Optional对象
package com.decade.java8.optional;
import org.junit.Test;
import java.util.Optional;
public class Optional1 {
@Test
public void test() {
Optional<String> empOp = Optional.empty();
System.out.println(empOp.orElse("aaa"));//输出结果是aaa
Optional<String> ofOp = Optional.of("abc");
System.out.println(ofOp.orElse("bbb"));//输出结果abc
Optional<String> ofNullOpNull = Optional.ofNullable(null);
System.out.println(ofNullOpNull.orElse("ccc"));//输出结果是ccc
Optional<String> ofOpNull = Optional.of(null);
System.out.println(ofOpNull.orElse("ddd"));//输出结果java.lang.NullPointerException
}
}
运行结果如下
3、获取Optional容器的对象
- get(T t):获取Optional中的value值,尽量不使用,因为要防止它为null的情况下抛出异常
- orElse(T other):获取Optional中的value值,没有值,返回一个other
- orElseGet(Supplier<? extends T> other):获取Optional中的value值,没有值则执行other操作并返回
- orElseThrow(Supplier<? extends T> exceptionSupplier):获取Optional中的value值,没有值则执行exceptionSupplier操作并抛出异常
package com.decade.java8.optional;
import com.decade.java8.stream.Employee;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
public class Optional2 {
// 打印切割符
private static final String SPLIT_STR = "=================";
@Test
public void test() {
final List<Employee> employeeList = Employee.getEmployeeList();
final Optional<String> firstEmpName = employeeList.stream().findFirst().map(Employee::getName);
System.out.println(firstEmpName.get());
System.out.println(SPLIT_STR);
final Optional<Object> empty = Optional.empty();
//抛出异常,java.util.NoSuchElementException: No value present
System.out.println(empty.get());
}
@Test
public void test2() {
Optional<String> str = Optional.empty();
//输出aa
System.out.println(str.orElse("aa"));
System.out.println(SPLIT_STR);
String b = str.orElseGet(() -> {
System.out.println("bb");
return "123";
});
//输出bb和123
System.out.println(b);
System.out.println(SPLIT_STR);
//输出cc 与java.lang.RuntimeException: 抛出异常
String result = str.orElseThrow(()->{
System.out.println("cc");
return new RuntimeException("抛出异常");
});
System.out.println(result);
}
}
4、判断Optional容器中是否包含对象
ifPresent、ifpresentOrElse:不为空时执行,该方法和orElse对立,表示不为空时执行,没有返回值
- void ifPresent(Consumer<? super T> consumer):当Optional不为空时,将他的值传递给consumer
- Boolean isPresent(),当Optional不为空,返回true
package com.decade.java8.optional;
import com.decade.java8.stream.Employee;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
public class Optional3 {
// 打印切割符
private static final String SPLIT_STR = "=================";
@Test
public void test() {
Optional<String> str = Optional.empty();
// 无输出值
str.ifPresent(r->{
System.out.println("不为空");
});
System.out.println(SPLIT_STR);
final List<Employee> employeeList = Employee.getEmployeeList();
final Optional<Employee> firstEmployee = employeeList.stream().findFirst();
// 输出值:不为空-->Employee{id='001', name='test1', age=20, salary=5000.32}
firstEmployee.ifPresent(r->{
System.out.println("不为空-->" + r.toString());
});
// 输出:true
boolean result = firstEmployee.isPresent();
System.out.println(result);
}
}
5、映射
- map(映射)转换成另一个Optional,和Stream中使用类似
Optional.map(Function<? super T,?extend U> mapper):返回映射值的Optional,只要这个Optional不为空且结果不为null,否则产生一个空Optional
package com.decade.java8.optional;
import com.decade.java8.stream.Employee;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
public class Optional4 {
// 打印切割符
private static final String SPLIT_STR = "=================";
@Test
public void test() {
Optional<String> aaa = Optional.empty().map(r->{
return "aa";
});
// 输出结果:Optional对象为空
System.out.println(aaa.orElse("Optional对象为空"));
System.out.println(SPLIT_STR);
final List<Employee> list = Employee.getEmployeeList();
Optional<String> aaa1 = list.stream().findFirst().map(r->{
return r.getName();
});
//输出结果:test1
System.out.println(aaa1.orElse("对象为空"));
}
}
6、过滤
- filter:过滤Optional,和stream使用类似
Optional.filter(Predicate<? super T> predicate):按照predicate规定的规则进行过滤,不满足规则的返回empty
package com.decade.java8.optional;
import com.decade.java8.stream.Employee;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
public class Optional5 {
// 打印切割符
private static final String SPLIT_STR = "=================";
@Test
public void test() {
final List<Employee> list = Employee.getEmployeeList();
Optional<String> name = list.stream().map(Employee::getName).findFirst().filter(r->{
return true;
});
//输出结果:test1
System.out.println(name.orElse("对象为空"));
System.out.println(SPLIT_STR);
Optional<String> name1 = list.stream().map(Employee::getName).findFirst().filter(r->{
return false;
});
//输出结果:对象为空
System.out.println(name1.orElse("对象为空"));
}
}
7、flatMap
- flatMap:连接Optional,连续调用,如果想在调用oneOptional之后再调用twoOptional,就需要使用flatMap
Optional.flatMap(Function<? super T, Optional<U>>mapper)
:类似于map,但是map中返回的值自动被Optoinal包装,而flatMap返回值保持不变,但是入参必须是Optional类型
假设我们先现在有一个School类,有一个字段属性为Option封装的Tearch对象
Teacher下面又有一个Optional封装的Student对象
package com.decade.java8.optional;
import org.junit.Test;
import java.util.Optional;
public class School {
private String name;
// 属性为Option封装的Teacher对象
private Optional<Teacher> teacher;
public School(String name, Optional<Teacher> teacher) {
this.name = name;
this.teacher = teacher;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Optional<Teacher> getTeacher() {
return teacher;
}
public void setTeacher(Optional<Teacher> teacher) {
this.teacher = teacher;
}
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Teacher{
private String name;
private Optional<Student> student;
public Teacher(String name, Optional<Student> student) {
this.name = name;
this.student = student;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Optional<Student> getStudent() {
return student;
}
public void setStudent(Optional<Student> student) {
this.student = student;
}
}
}
如果我们现在想获取Student的name属性,使用连续map会报错
School::getTeacher
会返回School实例的teacher属性,但是使用map(School::getTearch)
时,会在结果外再加一层Optional,最终结果为Optional<Optional<Teacher>>
,而不是我们所想的Optional<Teacher>
,如图所示
而使用flatmap就不会报错
public static String getStudentNameTrue(School school){
return Optional.ofNullable(school)
.flatMap(School::getTeacher)
.flatMap(Teacher::getStudent)
.map(Student::getName)
.orElse("false");
}
由此可得,如果某个类的属性是Optional包装过的类型,那么对他进行操作时就要使用flatMap方法,如果不是Optional包装的,例如Student的name属性,就直接使用map就可以了
五、新时间和日期API
1、为什么要引入新的时间API
因为旧版的时间API存在很多问题,比如:
- 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一
- 设计很差:Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义
- 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题
2、具体使用代码样例及输出
package com.decade.java8.time;
import org.junit.Test;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Set;
public class TestLocalDateTime {
public static final String SPLIT_STR = "===============";
@Test
public void testLocalDateTime() {
// 获取当前时间
final LocalDate localDate = LocalDate.now();
final LocalTime localTime = LocalTime.now();
final LocalDateTime localDateTime = LocalDateTime.now();
// 2023-06-11
// 21:17:10.080
// 2023-06-11T21:17:10.080
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
System.out.println(SPLIT_STR);
// 获取指定时间
final LocalDateTime localDateTime1 = LocalDateTime.of(2023, 6, 11, 16, 48, 32);
final LocalDate localDate1 = LocalDate.of(2023, 6, 11);
final LocalTime localTime1 = LocalTime.of(16, 49, 33);
// 2023-06-11T16:48:32
// 2023-06-11
// 16:49:33
System.out.println(localDateTime1);
System.out.println(localDate1);
System.out.println(localTime1);
System.out.println(SPLIT_STR);
// 对时间进行操作,使用plus就是加操作,使用minus就是减操作
// LocalDateTime可以对年月日时分秒进行加减,LocalDate只能加减年月日,LocalTime只能加减时分秒
// 对时间进行操作后会返回一个新的实例,避免了线程不安全的问题
// 2023-06-13T16:48:32
// 2023-06-12
// 13:49:33
System.out.println(localDateTime1.plusDays(2));
System.out.println(localDate1.plusDays(1));
System.out.println(localTime1.minusHours(3));
System.out.println(SPLIT_STR);
// 单独获取年月日等信息,使用get可以查看详细方法
// 2023
// 11
// 16
System.out.println(localDate.getYear());
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localTime1.getHour());
}
@Test
public void testInstant() {
// Instant:时间戳,表示从Unix元年1070年1月1日00:00:00到某个时刻为止的毫秒值,取的是UTC时区,北京是UTC+8时区
final Instant instant = Instant.now();
// 2023-06-11T13:16:39.227Z
System.out.println(instant);
// 带偏移量的时间戳
final OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
// 2023-06-11T21:16:39.227+08:00
System.out.println(offsetDateTime);
// 打印时间戳, 1686489399227
System.out.println(instant.toEpochMilli());
// 对Unix元年时间戳进行运算,此处就是加上60秒
final Instant instant1 = Instant.ofEpochSecond(60);
// 1970-01-01T00:01:00Z
System.out.println(instant1);
}
@Test
public void testDurationAndPeriod() {
final Instant instant = Instant.ofEpochSecond(60);
final Instant instant1 = Instant.ofEpochSecond(89);
// Duration:计算两个时间之间的间隔
final Duration duration = Duration.between(instant, instant1);
// 29
System.out.println(duration.getSeconds());
System.out.println(SPLIT_STR);
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime plusTime = now.plusHours(3);
final Duration duration1 = Duration.between(now, plusTime);
// 180
System.out.println(duration1.toMinutes());
System.out.println(SPLIT_STR);
// Period:计算两个日期之间的间隔
final LocalDate localDate = LocalDate.now();
final LocalDate localDate1 = LocalDate.of(2015, 7, 1);
final Period period = Period.between(localDate1, localDate);
// 7年
// 11个月
// 10天
System.out.println(period.getYears() + "年");
System.out.println(period.getMonths() + "个月");
System.out.println(period.getDays() + "天");
}
@Test
public void testTemporalAdjuster() {
final LocalDateTime localDateTime = LocalDateTime.now();
// 2023-06-11T21:15:23.973
System.out.println(localDateTime);
// 将当前日期替换成本月的第x天
final LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(10);
// 2023-06-10T21:15:23.973
System.out.println(localDateTime1);
// TemporalAdjuster:时间校正器,它是函数接口,在TemporalAdjusters 类中有很多预定义的实现
// TemporalAdjuster 可以执行复杂的日期操作,例如,可以获得下一个星期日对于日期、当月的最后一天、下一年的第一天
// TemporalAdjusters类有很多预定义的static方法返回TemporalAdjuster对象,此处为获取下个周六的日期
final LocalDateTime dateTime = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
// 2023-06-17T21:15:23.973
System.out.println(dateTime);
// 自定义校正器,假设我们想要获取下一个工作日是什么时候
final LocalDateTime workDay = localDateTime.with(l -> {
LocalDateTime localDateTime2 = (LocalDateTime) l;
final DayOfWeek dayOfWeek = localDateTime2.getDayOfWeek();
// 如果今天是周五,那下一个工作日就是周一,需要加三天
if (DayOfWeek.FRIDAY.equals(dayOfWeek)) {
return localDateTime2.plusDays(3);
} else if (DayOfWeek.SATURDAY.equals(dayOfWeek)) {
// 如果今天是周六,那下一个工作日就是周一,需要加两天
return localDateTime2.plusDays(2);
} else {
// 其他时候工作日都只需要加一天
return localDateTime2.plusDays(1);
}
});
// 下一个工作日是2023-06-12T21:15:23.973
System.out.println("下一个工作日是" + workDay);
}
@Test
public void testDateTimeFormatter() {
// DateTimeFormatter:格式化时间/日期
// iso的日期时间格式化程序,它使用可用的偏移量和区域来格式化或解析日期时间,例如'2011-12-03T10:15:30'
final DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.now();
final String formatRes = localDateTime.format(formatter);
// 2023-06-11T21:23:21.956
System.out.println(formatRes);
// ISO日期格式化程序,在可用的情况下使用偏移量格式化或解析日期,例如'2011-12-03',只打印日期
final DateTimeFormatter formatter1 = DateTimeFormatter.ISO_DATE;
// 2023-06-11
System.out.println(localDateTime.format(formatter1));
// 自定义的日期格式
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
final String format = localDateTime.format(dateTimeFormatter);
// 2023年06月11日 21:27:27
System.out.println(format);
// 转换回localTimeDate
final LocalDateTime parseRes = LocalDateTime.parse(format, dateTimeFormatter);
// 2023-06-11T21:30:25
System.out.println(parseRes);
}
@Test
public void testZone() {
final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
// availableZoneIds.forEach(System.out::println);
System.out.println(SPLIT_STR);
// 将当前之间转换成指定时区的时间
final LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Europe/Guernsey"));
// 2023-06-11T14:35:06.913
System.out.println(localDateTime);
final LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("Europe/Guernsey"));
// 在时间后面拼接上时区信息
final ZonedDateTime zonedDateTime = localDateTime1.atZone(ZoneId.of("Europe/Guernsey"));
// 2023-06-11T14:38:19.518+01:00[Europe/Guernsey]
System.out.println(zonedDateTime);
}
}
如有错误,欢迎指正!!!