目录
LocalDate&LocalTime&LocalDateTime
JDK8
JDK8是java第一个LTS长期支持版本,2014年发布,是最成功,应用最广泛的版本,JDK8目前计划更新到2030年,虽然JDK已经有了新的LTS版本JDK11,JDK17,JDK21,使用JDK8的项目仍然广泛存在
Lambda
概述
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<Integer> 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<String, Integer> function = String::length;
//类名::静态方法
Function<Integer, String> function1 = String::valueOf;
//类名::new
BiFunction<String, Integer, StudentVO> function2 = StudentVO::new;
//[]::new
Function<Integer, String[]> 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表达式
Supplier 生产数据
无参有返回值,泛型为<返回值类型>
@PostMapping("/jdk8TestSupplier")
@ApiOperation(value = "jdk8TestSupplier")
public ResponseResult jdk8TestSupplier() {
System.out.println(testSupplier(() -> 1 + 2));
return new ResponseResult().resultFlag(true);
}
private Integer testSupplier(Supplier<Integer> supplier) {
return supplier.get();
}
Consumer 消费数据
有参无返回值,泛型为<入参类型>
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<String> consumer, String str) {
consumer.accept(str);
}
private void testConsumer2(Consumer<String> consumer1, Consumer<String> consumer2, String str) {
consumer1.andThen(consumer2).accept(str);
}
Function
有参有返回值,根据一个数据,得到另一个数据
泛型为<入参类型,返回值类型>
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<String, Integer> function1, Function<Integer, Integer> function2, String str) {
return function1.andThen(function2).apply(str);
}
Predicate
有参有返回值,返回值为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<String> predicate1, Predicate<String> 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);
}
LocalDate&LocalTime&LocalDateTime
jdk7日期时间存在的问题
设计不合理
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
jdk8提供了一套全新的时间日期API
java.time包下
线程安全,时间日期对象不可变,修改的操作是会生成一个新的值
可以直接处理时区
可以通过预定义处理日期时间
LocalDate
创建
//指定日期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();
LocalTime
创建
//指定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 = 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 = 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);
ZonedDateTime
时区操作
//获取所有时区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"));
Instant
时间戳类,从1970.1.1起始,精确到纳秒
Instant instant = Instant.now();
instant.getNano();
Duration
计算时间差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());
Period
计算日期差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是工具类,没有任何子类
Optional是一个可以为null的容器,保存null时表示值不存在
作用:避免显式的进行null检查,防止NullPointException
获取Optional
Optional.of(T t) 不支持null,即t必须非空
Optional.ofNullable(T t) 支持null,即t可以为空
Optional.empty() 直接获取一个空的Optional,可以指定其中的数据类型
Optional<String> optional1 = Optional.of("name");
Optional<String> optional2 = Optional.ofNullable(null);
Optional<String> 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)
Stream流
概述
java API的新成员,遍历数据的高级迭代器
作用:给数组/集合的操作提供了更好的解决方式
特点
支持声明式处理集合,天然支持并行
和IO流没有任何关系,不是数据结构,不保存数据
虽然不保存数据,但Stream流对于引用型变量的操作是实际的改变
方法
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<String> arrayList = new ArrayList<>();
Stream<String> streamArr = arrayList.stream();
Map<String, String> map = new HashMap<>();
Stream<String> stream1 = map.keySet().stream();
Stream<String> stream2 = map.values().stream();
Stream<Map.Entry<String, String>> stream3 = map.entrySet().stream();
String[] arr = new String[10];
Stream<String> streamArr = Stream.of(arr);
Stream<String> streamArr2 = Arrays.stream(arr);
return new ResponseResult().resultFlag(true);
}
中间方法
返回Stream本身
filter,过滤
根据条件过滤数据
list.stream().filter(str -> str.length() > 2).forEach(System.out::println);
map,转换
将流中的元素进行统一的处理,多行语句处理时,需要通过return语句将单个结果返回
list.stream().map(String::toUpperCase).forEach(System.out::println);
//多行处理语句
List<Employee> maped = employees.stream()
.map(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
return e;
}).collect(Collectors.toList());
distinct,去重
去重的依据是hashCode()和equals()
list.stream().distinct().forEach(System.out::println);
limit,截取
截取前n个数据,n>=stream.length才操作
list.stream().limit(3).forEach(System.out::println);
skip,跳过
跳过前n个数据,截取后面的流
list.stream().skip(3).forEach(System.out::println);
sorted,排序
默认根据字典顺序排序,默认升序,可以使用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<UpdateComponentVo> resList = stream.sorted((v1, v2) ->
comparator.compare(v1.getRuleName(), v2.getRuleName())
).collect(Collectors.toList());
List<Map<String, Object>> afterSort = allList.stream().sorted((o1, o2) -> {
if (o1.get("UPDATETIME").toString().compareTo(o2.get("UPDATETIME").toString()) > 0) {
return -1;
} else {
return 1;
}
}).collect(Collectors.toList());
//排序后进行分页
List<Map<String, Object>> pageInfo = afterSort.stream().skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
peek,执行操作
没有特殊处理,直接执行lambda方法
通常不会单独使用,因为如果只是遍历并执行操作,不如使用终结方法forEach
list.stream().peek(
u -> {
...
sout(u.getName());
}
);
concat,合并
两个流合并成一个流
Stream<String> newStream = Stream.concat(stream1, stream2);
终结方法
不会返回Stream本身
注意:
Stream不调用终结方法,那么中间操作不会执行
Stream只能使用一次终结方法
forEach,遍历
遍历流中元素,执行方法
list.stream().forEach(System.out::println);
count,计数
获取流中元素数量
配合distinct可以判断集合中元素是否重复(去重前后的数量做对比)
long count = confList.stream().map(conf -> conf.getPropName()).distinct().count();
match,条件判断
判断数据是否符合指定条件
Boolean b1 = stream.allMatch(str -> str.length() == 1);
Boolean b2 = stream.anyMatch(str -> str.length() == 1);
Boolean b3 = stream.noneMatch(str -> str.length() == 1);
find,查找
查询流中指定元素
findFirst() 拿到流中的第一个元素
findAny() 拿到流中的任意一个元素
在串行的流中,findAny()和findFirst()返回的都是第一个对象;
在并行流(parallelStream())中,findAny返回的是最快处理完的那个线程的数据,所以并行流findAny()会比findFirst()要快
Optional<String> optional1 = stream.findFirst();
Optional<String> optional2 = stream.findAny();
reduce,聚合
将流中数据聚合成一个数据
参数有两个,参数一为初始值,参数二为运算逻辑函数
实际开发中,通常会和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);
min
获取流中的最小值
Optional<Integer> optional1 = stream.map(Integer::parseInt).min(Comparator.comparingInt(i -> i));
max
获取流中的最大值
Optional<Integer> optional2 = stream.map(Integer::parseInt).max(Comparator.comparingInt(i -> i));
流中数据接收
用集合接收Stream流中数据
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
ArrayList<String> list1 = stream.collect(Collectors.toCollection(ArrayList::new));
HashSet<String> set1 = stream.collect(Collectors.toCollection(HashSet::new));
Map<String, String> map1 = stream.collect(Collectors.toMap(Person::getName,Person::getAddress,(value1, value2) -> value1));
转为map的一些处理场景
注意:当stream流中的数据存在重复,作为map的key,此时转map会失败,即并不会覆盖,而是直接报错,因此需要考虑对key的部分去重,或者重复数据处理策略
指定key-value,为对象中的某个属性值
Map<Integer, String> userMap1 = userList.stream().collect(Collectors.toMap(User::getId,User::getName));
指定key-value,为对象本身,User->User是一个返回本身的lambda表达式,Function.identity()也会返回对象本身
Map<Integer, User> userMap2 = userList.stream().collect(Collectors.toMap(User::getId,User->User));
Map<Integer, User> userMap3 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
指定key-value,为对象本身,key 冲突的解决办法,key2覆盖key1
Map<Integer, User> userMap4 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity(), (key1,key2)->key2));
拼接key->Map
Map<String, User> userMap5 = userList.stream().collect(Collectors.toMap(user -> user.getName() + user.getHobbies(), u -> u));
用数组接收Stream流中数据
String[] strArr = stream.toArray(String[]::new);
另一种聚合方式
针对集合操作:minBy,maxBy,summingInt
Optional<StudentVO> 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();
mapToInt,转换
将Stream中的Integer转为int
IntStream intStream = stream.map(Integer::parseInt).mapToInt(Integer::intValue);
流中数据的分组
可以多个字段分组
//先根据age,再根据name分组
Map<Integer, Map<String, List<StudentVO>>> studentMap = studentSteam.collect(
Collectors.groupingBy(StudentVO::getAge,
Collectors.groupingBy(StudentVO::getName)
));
//如果是List<Map>,根据某个key的值分组
Map<String, List<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<Boolean, List<StudentVO>> 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<RankDto> list = list.stream().sorted(Comparator.comparing(RankDto::getSumScore).reversed())
.peek(RankDto -> RankDto.setRankNum(rankNum[0]++))
.collect(Collectors.toList());
Stream串行流和并行流
并行流可以通过默认的ForkJoinPool采用多线程提高流操作的速度
获取并行流
通过list.parallelStream()直接获取
通过已有串行流间接获取
Stream<StudentVO> streamParallel = studentList.parallelStream(); Stream<String> streamArr = Stream.of(arr1).parallel();
并行流的线程安全问题
并行操作数据,伴随着线程安全问题,并行流如何解决线程安全问题
同步代码块
操作数据类型替换为线程安全的类型
并行流的原理
fork/join框架
包含三个模块
线程池 ForkJoinPool
任务对象 ForkJoinTask
任务线程 ForkJoinWorkerThread
核心思想
分治法,工作窃取算法
大任务分为小任务,小任务的结果组合成大任务的结果