JDK8特性,Lambda,时间日期,接口增强,Optional,Stream流

目录

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

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 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表达式

Supplier 生产数据

无参有返回值,泛型为<返回值类型>

@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();
}
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 consumer, String str) {
    consumer.accept(str);
}

private void testConsumer2(Consumer consumer1, Consumer 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 function1, Function 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 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);
}

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 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)

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 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本身

filter,过滤

根据条件过滤数据

list.stream().filter(str -> str.length() > 2).forEach(System.out::println);
map,转换

将流中的元素进行统一的处理,多行语句处理时,需要通过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());
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 resList = stream.sorted((v1, v2) ->
        comparator.compare(v1.getRuleName(), v2.getRuleName())
).collect(Collectors.toList());

List> 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> pageInfo = afterSort.stream().skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
peek,执行操作

没有特殊处理,直接执行lambda方法

通常不会单独使用,因为如果只是遍历并执行操作,不如使用终结方法forEach

list.stream().peek(
    u -> {
        ...
        sout(u.getName());
    }
);
concat,合并

两个流合并成一个流

Stream 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 optional1 = stream.findFirst();
Optional 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 optional1 = stream.map(Integer::parseInt).min(Comparator.comparingInt(i -> i));
max

获取流中的最大值

Optional optional2 = stream.map(Integer::parseInt).max(Comparator.comparingInt(i -> i));
流中数据接收
用集合接收Stream流中数据
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));
转为map的一些处理场景

注意:当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));
用数组接收Stream流中数据

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();
mapToInt,转换

将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());

Stream串行流和并行流

并行流可以通过默认的ForkJoinPool采用多线程提高流操作的速度

获取并行流

通过list.parallelStream()直接获取

通过已有串行流间接获取

Stream streamParallel = studentList.parallelStream(); Stream streamArr = Stream.of(arr1).parallel();

并行流的线程安全问题

并行操作数据,伴随着线程安全问题,并行流如何解决线程安全问题

同步代码块

操作数据类型替换为线程安全的类型

并行流的原理

fork/join框架

包含三个模块

        线程池 ForkJoinPool

        任务对象 ForkJoinTask

        任务线程 ForkJoinWorkerThread

核心思想

分治法,工作窃取算法

大任务分为小任务,小任务的结果组合成大任务的结果

你可能感兴趣的:(jdk8,stream流,optional,lambda)