目录
JDK8
Lambda
概述
特点
语法
几种基本的写法
方法的引用
几种常见方式
常见的函数式接口
Supplier 生产数据
Consumer 消费数据
Function
Predicate
LocalDate&LocalTime&LocalDateTime
jdk7日期时间存在的问题
设计不合理
时间格式化和解析操作线程不安全
无法直接处理时区,针对时区的操作比较麻烦
jdk8提供了一套全新的时间日期API
LocalDate
LocalTime
LocalDateTime
DateTimeFormatter
ZonedDateTime
Instant
Duration
Period
时间校正器
接口增强
Optional
概述
获取Optional
常用方法
不常用方法
Stream流
概述
特点
方法
Stream流的获取
中间方法
filter,过滤
map,转换
distinct,去重
limit,截取
skip,跳过
sorted,排序
peek,执行操作
concat,合并
终结方法
forEach,遍历
count,计数
match,条件判断
find,查找
reduce,聚合
min
max
流中数据接收
用集合接收Stream流中数据
转为map的一些处理场景
用数组接收Stream流中数据
另一种聚合方式
mapToInt,转换
流中数据的分组
流中数据分区
流中数据拼接
问题
Stream串行流和并行流
获取并行流
并行流的线程安全问题
并行流的原理
核心思想
JDK8是java第一个LTS长期支持版本,2014年发布,是最成功,应用最广泛的版本,JDK8目前计划更新到2030年,虽然JDK已经有了新的LTS版本JDK11,JDK17,JDK21,使用JDK8的项目仍然广泛存在
jdk8引入了一个新的操作符 lambda操作符 ->
操作符左侧:lambda参数列表,右侧:lambda函数体
可以理解为可传递的匿名函数,即传递函数体并生成对象,可以简化匿名内部类的使用
需要函数式接口的支持
函数式接口:接口中只有一个抽象方法
可以用@FunctionalInterface注解给接口做检查,如果接口有多个抽象方法,会语法报错
函数没有名称,有参数列表,函数体,返回类型,可抛出的异常列表
参数列表不需要声明类型,lambda可以根据上下文推断类型
有无返回值:单行情况下,函数体的整体就是返回值;多行情况下,return返回值
函数体为多行时用{}括起来
无参,无返回值
Runnable r1 = () -> System.out.println("lambda");
r1.run();
有一个参数,无返回值
x -> System.out.println(x);
有两个参数,有返回值
(x, y) -> x + y
如果是多行函数体,加上{}
Runnable r1 = () -> {
System.out.println("lambda111");
System.out.println("lambda222");
System.out.println("lambda333");
};
r1.run();
SumFunction sumObj= (x, y) -> {
int a = x;
int b = y;
return a + b;
};
System.out.println(sumObj.sum(1, 2));
作用:解决使用lambda时重复定义类似方法的问题,减少冗余
语法格式:方法引用运算符,::(双冒号),接口方法的返回值和参数列表要和被传递方法保持一致
类::静态方法名
Comparator bb = Integer::compare;
将Integer的compare方法传递给Comparator的唯一抽象方法
类::实例方法名
StringFunction obj = String::equals;
将String的equals方法传递给StringFunction的唯一抽象方法
对象::实例方法名
Runnable r1 = student::study
将Student的study方法传递给Runnable的唯一抽象方法
通过 类名::new 构造器的引用,创建一个引用
可以是无参,有参构造,自动上下文推断
接口的方法要和构造方法的返回值,参数列表保持一致
StudentFunction stuObj = Student::new
将Student的构造方法传递给StudentFunction的唯一抽象方法
xxx[]::new 数组构造器,需要给定数组长度
@PostMapping("/jdk8TestMethods")
@ApiOperation(value = "方法引用测试")
public ResponseResult jdk8TestMethods() {
//类名::普通方法
Function function = String::length;
//类名::静态方法
Function function1 = String::valueOf;
//类名::new
BiFunction function2 = StudentVO::new;
//[]::new
Function function3 = String[]::new;
System.out.println(function.apply("function"));
System.out.println(function1.apply(211).substring(0, 1));
System.out.println(function2.apply("张三", 25));
System.out.println(function3.apply(3).length);
return new ResponseResult().resultFlag(true);
}
jdk提供这些函数式接口,方便开发者使用lambda表达式
无参有返回值,泛型为<返回值类型>
@PostMapping("/jdk8TestSupplier")
@ApiOperation(value = "jdk8TestSupplier")
public ResponseResult jdk8TestSupplier() {
System.out.println(testSupplier(() -> 1 + 2));
return new ResponseResult().resultFlag(true);
}
private Integer testSupplier(Supplier supplier) {
return supplier.get();
}
有参无返回值,泛型为<入参类型>
andThen(),用于多个consumer先后执行
@PostMapping("/jdk8TestConsumer")
@ApiOperation(value = "jdk8TestConsumer")
public ResponseResult jdk8TestConsumer() {
testConsumer(message -> System.out.println(message.toUpperCase()), "Hello World!");
testConsumer2(message -> System.out.println(message.toUpperCase()), message -> System.out.println(message.toLowerCase()), "Hello World!");
return new ResponseResult().resultFlag(true);
}
private void testConsumer(Consumer consumer, String str) {
consumer.accept(str);
}
private void testConsumer2(Consumer consumer1, Consumer consumer2, String str) {
consumer1.andThen(consumer2).accept(str);
}
有参有返回值,根据一个数据,得到另一个数据
泛型为<入参类型,返回值类型>
apply()
andThen()
@PostMapping("jdk8TestFunction")
@ApiOperation(value = "jdk8TestFunction")
public ResponseResult jdk8TestFunction() {
Integer integer = testFunction((msg) -> msg.length(), (intValue -> intValue * 2), "function");
System.out.println(integer);
return new ResponseResult().resultFlag(true);
}
private Integer testFunction(Function function1, Function function2, String str) {
return function1.andThen(function2).apply(str);
}
有参有返回值,返回值为Boolean,泛型为<入参类型>
@PostMapping("/jdk8TestPredicate")
@ApiOperation(value = "jdk8TestPredicate")
public ResponseResult jdk8TestPredicate() {
testPredicate((msg) -> msg.startsWith("pre"),
(msg) -> msg.endsWith("hh"),
"predicate");
return new ResponseResult().resultFlag(true);
}
private void testPredicate(Predicate predicate1, Predicate predicate2, String str) {
//and
Boolean boolean1 = predicate1.and(predicate2).test(str);
//or
Boolean boolean2 = predicate1.or(predicate2).test(str);
//negate
Boolean boolean3 = predicate1.negate().test(str);
System.out.println(boolean1);
System.out.println(boolean2);
System.out.println(boolean3);
}
Date存在1900的偏移量,不能直接获取某个时刻
util和sql包都有Date类,util包的Date类包含日期时间,sql包的Date类只包含日期,负责格式化和解析日期时间的类在text包下
//1900偏移量
Date date = new Date(2021, 06, 25);
System.out.println(date);
//Mon Jul 25 00:00:00 CST 3921
java.time包下
线程安全,时间日期对象不可变,修改的操作是会生成一个新的值
可以直接处理时区
可以通过预定义处理日期时间
创建
//指定日期2021-06-25
LocalDate localDate = LocalDate.of(2021, 6, 25);
//当前日期2021-06-25
LocalDate localDate1 = LocalDate.now();
获取
//获取年月日
int year = localDate.getYear();
//月份是枚举类型
int month1 = localDate.getMonth().getValue();
int month2 = localDate.getMonthValue();
//周几是枚举类型,周几就对应几,如周一,则value为1,周日value为7
int dayOfWeek1 = localDate.getDayOfWeek().getValue();
System.out.println(dayOfWeek1);
//几日是int类型
int dayOfMonth1 = localDate.getDayOfMonth();
int dayOfYear1 = localDate.getDayOfYear();
创建
//指定11:39:31.111111111
LocalTime localTime = LocalTime.of(11, 39, 31,111111111);
//当前11:59:33.754
LocalTime localTime1 = LocalTime.now();
获取
localTime.getHour();
localTime.getMinute();
localTime.getMinute();
localTime.getNano();
创建
LocalDateTime localDateTime = LocalDateTime.of(2021,6,25,13,48,1,11111);
LocalDateTime localDateTime1 = LocalDateTime.now();
获取
localDateTime.getYear();
修改日期时间:LocalDateTime提供的修改操作,实际上并没有修改原本的对象,而是进行了副本拷贝,这样就解决了线程安全的问题
直接修改为指定值
LocalDateTime localDateTime2 = localDateTime.withYear(2022);
localDateTime.withDayOfMonth(26);
localDateTime.withDayOfYear(200);
加减
LocalDateTime localDateTime3 = localDateTime.plusYears(1);
LocalDateTime localDateTime4 = localDateTime.minusYears(1);
比较日期时间
boolean after = localDateTime.isAfter(localDateTime1);
boolean before = localDateTime.isBefore(localDateTime1);
boolean equal = localDateTime.isEqual(localDateTime1);
格式化和解析
指定格式
//系统预定义格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//自定义格式
DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
时间日期转字符串
//2021-06-25T18:26:15.068
String format1 = localDateTime1.format(dateTimeFormatter);
//2021-06-25 18:26:15
String format2 = localDateTime1.format(dateTimeFormatter1);
字符串转日期时间
LocalDateTime parseLocalDateTime = LocalDateTime.parse("2021-6-25 18:34:00", dateTimeFormatter1);
时区操作
//获取所有时区ID
ZoneId.getAvailableZoneIds();
//获取当前时间,东八区,比格林尼治时间早8h
LocalDateTime localDateTime = LocalDateTime.now();
//标准时间
ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
//系统默认时区
ZonedDateTime zonedDateTime1 = ZonedDateTime.now();
//指定时区
ZonedDateTime zonedDateTime2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));
时间戳类,从1970.1.1起始,精确到纳秒
Instant instant = Instant.now();
instant.getNano();
计算时间差LocalTime
LocalTime localTime1 = LocalTime.now();
LocalTime localTime2 = LocalTime.of(11, 0, 0);
Duration duration = Duration.between(localTime1, localTime2);
// localTime2-localTime1
System.out.println(duration.toHours());
System.out.println(duration.toMinutes());
System.out.println(duration.toMillis());
System.out.println(duration.toNanos());
计算日期差LocalDate
LocalDate localDate1 = LocalDate.now();
LocalDate localDate2 = LocalDate.of(2020, 1, 1);
Period period = Period.between(localDate2, localDate1);
period.getYears();
period.getMonths();
period.getDays();
调整时间
自定义校正规则/使用系统预定义校正规则
TemporalAdjuster adjuster = (temporal) -> {
LocalDateTime localDateTime1 = (LocalDateTime) temporal;
LocalDateTime localDateTime2 = localDateTime1.plusMonths(1).withDayOfMonth(1);
return localDateTime2;
};
LocalDateTime localDateTime = LocalDateTime.now();
//自定义
LocalDateTime localDateTime1 = localDateTime.with(adjuster);
//系统预定义
LocalDateTime localDateTime2 = localDateTime.with(TemporalAdjusters.firstDayOfNextMonth());
JDK8给接口提供了增强
接口可以给定默认实现,使用default关键字
修饰符 default 返回值类型 xxx() {...}
default soutXxx() {sout("xxx");}
对于静态方法,可以通过接口名直接调用
修饰符 static 返回值类型 xxx() {"xxx"}
static void soutXxx() {System.out.println("xxx");}
Optional是工具类,没有任何子类
Optional是一个可以为null的容器,保存null时表示值不存在
作用:避免显式的进行null检查,防止NullPointException
Optional.of(T t) 不支持null,即t必须非空
Optional.ofNullable(T t) 支持null,即t可以为空
Optional.empty() 直接获取一个空的Optional,可以指定其中的数据类型
Optional optional1 = Optional.of("name");
Optional optional2 = Optional.ofNullable(null);
Optional optional3 = Optional.empty();
isPresent() 判断是否包含值,包含返回true,不包含返回false
ifPresent() 判断是否包含值,包含值,就做相应操作
get() 获取容器中的值,没值则抛出NoSuchElementException
orElse(T t) 容器有值则返回该值,否则返回t,T类型只能是Optional中元素类型
orElseGet(Supplier other) 即orElseGet(()->{...}),容器有值则返回该值,否则返回lambda表达式的值
orElseThrow(Supplier exceptionSupplier) 容器有值则返回该值,否则返回异常
System.out.println(optional1.isPresent() ? optional1.get() : "000");
optional1.ifPresent(System.out::println);
System.out.println(optional1.orElse("111"));
System.out.println(optional1.orElseGet(() -> {
return "222";
}));
System.out.println(optional1.orElseThrow(Exception::new));
Optional filter(Predicate predicate)
Optional map(Function mapper)
Optional flatMap(Function> mapper)
java API的新成员,遍历数据的高级迭代器
作用:给数组/集合的操作提供了更好的解决方式
支持声明式处理集合,天然支持并行
和IO流没有任何关系,不是数据结构,不保存数据
虽然不保存数据,但Stream流对于引用型变量的操作是实际的改变
集合:collection.stream()
注意:Map没有实现Collection接口,无法直接获取Stream流,操作Map要通过其key,value集合
数组:Stream.of(arr)
注意:
这种方式的本质上是Arrays工具类的Arrays.stream
Stream流无法操作基本数据类型的数组
@PostMapping("/testStreamCreate")
@ApiOperation(value = "获取Stream流")
public ResponseResult testStreamCreate() {
ArrayList arrayList = new ArrayList<>();
Stream streamArr = arrayList.stream();
Map map = new HashMap<>();
Stream stream1 = map.keySet().stream();
Stream stream2 = map.values().stream();
Stream> stream3 = map.entrySet().stream();
String[] arr = new String[10];
Stream streamArr = Stream.of(arr);
Stream streamArr2 = Arrays.stream(arr);
return new ResponseResult().resultFlag(true);
}
返回Stream本身
根据条件过滤数据
list.stream().filter(str -> str.length() > 2).forEach(System.out::println);
将流中的元素进行统一的处理,多行语句处理时,需要通过return语句将单个结果返回
list.stream().map(String::toUpperCase).forEach(System.out::println);
//多行处理语句
List maped = employees.stream()
.map(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
return e;
}).collect(Collectors.toList());
去重的依据是hashCode()和equals()
list.stream().distinct().forEach(System.out::println);
截取前n个数据,n>=stream.length才操作
list.stream().limit(3).forEach(System.out::println);
跳过前n个数据,截取后面的流
list.stream().skip(3).forEach(System.out::println);
默认根据字典顺序排序,默认升序,可以使用reversed()降序
组合使用sorted+limit+skip可以在内存中实现sql一样的分页效果
//单个属性排序
list.stream().sorted(Comparator.comparing(实体::get属性))
.collect(Collectors.toList());
list.stream().sorted(Comparator.comparing(实体::get属性).reversed())
.collect(Collectors.toList());
//多个属性排序
list.stream().sorted(Comparator.comparing(实体::get属性)
.thenComparing(实体::get属性))
.collect(Collectors.toList());
//可以自定义比较方式
stream.map(Integer::parseInt)
.sorted((inte1, inte2) -> inte2 - inte1)
.forEach(System.out::println);
Comparator comparator = Collator.getInstance(Locale.CHINA);
List resList = stream.sorted((v1, v2) ->
comparator.compare(v1.getRuleName(), v2.getRuleName())
).collect(Collectors.toList());
List
没有特殊处理,直接执行lambda方法
通常不会单独使用,因为如果只是遍历并执行操作,不如使用终结方法forEach
list.stream().peek(
u -> {
...
sout(u.getName());
}
);
两个流合并成一个流
Stream newStream = Stream.concat(stream1, stream2);
不会返回Stream本身
注意:
Stream不调用终结方法,那么中间操作不会执行
Stream只能使用一次终结方法
遍历流中元素,执行方法
list.stream().forEach(System.out::println);
获取流中元素数量
配合distinct可以判断集合中元素是否重复(去重前后的数量做对比)
long count = confList.stream().map(conf -> conf.getPropName()).distinct().count();
判断数据是否符合指定条件
Boolean b1 = stream.allMatch(str -> str.length() == 1);
Boolean b2 = stream.anyMatch(str -> str.length() == 1);
Boolean b3 = stream.noneMatch(str -> str.length() == 1);
查询流中指定元素
findFirst() 拿到流中的第一个元素
findAny() 拿到流中的任意一个元素
在串行的流中,findAny()和findFirst()返回的都是第一个对象;
在并行流(parallelStream())中,findAny返回的是最快处理完的那个线程的数据,所以并行流findAny()会比findFirst()要快
Optional optional1 = stream.findFirst();
Optional optional2 = stream.findAny();
将流中数据聚合成一个数据
参数有两个,参数一为初始值,参数二为运算逻辑函数
实际开发中,通常会和map一起使用
Integer integer = list.stream().map(Integer::parseInt).reduce(0, Integer::sum);
Integer integer = list.stream().map(Integer::parseInt).reduce(0, Integer::max);
Integer integer = studentSteam.map(StudentVO::getAge).reduce(0, Integer::max);
获取流中的最小值
Optional optional1 = stream.map(Integer::parseInt).min(Comparator.comparingInt(i -> i));
获取流中的最大值
Optional optional2 = stream.map(Integer::parseInt).max(Comparator.comparingInt(i -> i));
List list = stream.collect(Collectors.toList());
Set set = stream.collect(Collectors.toSet());
ArrayList list1 = stream.collect(Collectors.toCollection(ArrayList::new));
HashSet set1 = stream.collect(Collectors.toCollection(HashSet::new));
Map map1 = stream.collect(Collectors.toMap(Person::getName,Person::getAddress,(value1, value2) -> value1));
注意:当stream流中的数据存在重复,作为map的key,此时转map会失败,即并不会覆盖,而是直接报错,因此需要考虑对key的部分去重,或者重复数据处理策略
指定key-value,为对象中的某个属性值
Map userMap1 = userList.stream().collect(Collectors.toMap(User::getId,User::getName));
指定key-value,为对象本身,User->User是一个返回本身的lambda表达式,Function.identity()也会返回对象本身
Map userMap2 = userList.stream().collect(Collectors.toMap(User::getId,User->User));
Map userMap3 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
指定key-value,为对象本身,key 冲突的解决办法,key2覆盖key1
Map userMap4 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity(), (key1,key2)->key2));
拼接key->Map
Map userMap5 = userList.stream().collect(Collectors.toMap(user -> user.getName() + user.getHobbies(), u -> u));
String[] strArr = stream.toArray(String[]::new);
针对集合操作:minBy,maxBy,summingInt
Optional maxAgeStudent = studentSteam.collect(Collectors.maxBy(Comparator.comparingInt(StudentVO::getAge)));
Integer sumAge = studentSteam.collect(Collectors.summingInt(StudentVO::getAge));
针对数组操作,可以直接操作
Integer[] integers = {1, 2, 3, 4};
Integer sumVal = Stream.of(integers).max(Comparator.comparingInt(i -> i)).get();
将Stream中的Integer转为int
IntStream intStream = stream.map(Integer::parseInt).mapToInt(Integer::intValue);
可以多个字段分组
//先根据age,再根据name分组
Map>> studentMap = studentSteam.collect(
Collectors.groupingBy(StudentVO::getAge,
Collectors.groupingBy(StudentVO::getName)
));
//如果是List,根据某个key的值分组
Map> map = list.stream().collect(
Collectors.groupingBy((Map m) -> (String)m.get("name"));
);
studentMap.forEach((k, v) -> {
System.out.println(k);
v.forEach((k1, v1) -> {
System.out.println("\t" + k1);
System.out.println("\t\t" + v1);
});
});
Map> studentMap = studentSteam.collect(Collectors.partitioningBy((student) -> student.getAge() > 22));
studentMap.forEach((k, v) -> {
System.out.print(k + ":");
System.out.println(v);
});
可以指定分隔符,可以给拼接后的字符串增加前缀后缀
String res = stream.collect(Collectors.joining());
System.out.println(res);
String res1 = stream.collect(Collectors.joining(","));
System.out.println(res1);
String res2 = stream.collect(Collectors.joining(",", "前缀", "后缀"));
System.out.println(res2);
stream流中如果要使用流外定义的数据,那么这个数据必须是final修饰,但有时候我们又必须操作一个流外数据,将这个数据放入数组中
//排序,赋值序号
Integer[] rankNum = {1};
List list = list.stream().sorted(Comparator.comparing(RankDto::getSumScore).reversed())
.peek(RankDto -> RankDto.setRankNum(rankNum[0]++))
.collect(Collectors.toList());
并行流可以通过默认的ForkJoinPool采用多线程提高流操作的速度
通过list.parallelStream()直接获取
通过已有串行流间接获取
Stream
并行操作数据,伴随着线程安全问题,并行流如何解决线程安全问题
同步代码块
操作数据类型替换为线程安全的类型
fork/join框架
包含三个模块
线程池 ForkJoinPool
任务对象 ForkJoinTask
任务线程 ForkJoinWorkerThread
分治法,工作窃取算法
大任务分为小任务,小任务的结果组合成大任务的结果