Java8新特性 - Lambda表达式 - Stream API - 时间日期 API
1. Lambda表达式基础语法
1.1 定义函数式接口
package com.yq.demo;
/**
* Lambda 表达式需要“函数式接口”的支持
* 函数式接口: 接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解@FunctionalInterface修饰
* 可以检查接口是否是函数式接口
* **/
@FunctionalInterface
public interface MyFun {
public Integer getValue(Integer num);
}
1.2 Lambda表达式使用说明
package com.yq.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Comparator;
import java.util.function.Consumer;
/**
* Lambda 表达式的基本语法: Java8中引入的一个新的操作符 "->" 该操作符称为箭头操作符或者Lambda操作符
* 箭头操作符将Lambda表达式拆分成两部分:
*
* 左侧:Lambda 表达式的参数列表
* 右侧:Lambda 表达式中所需执行的功能,即Lambda体
*
* 语法格式一: 无参数,无返回值
* () -> System.out.println("Hello Lambda");
*
* 语法格式二: 有一个参数,无返回值, 一个参数左侧小扩宽可以省略不写
* (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);
* **/
@SpringBootTest
class DemoApplicationTests {
@Test
void test1() {
Runnable r =new Runnable() {
@Override
public void run() {
System.out.println("Hello Lambda");
}
};
r.run();
System.out.println("--------------- Lambda ---------------");
// 相当于创建匿名内部类,并实现其接口
Runnable r1 = () -> System.out.println("Hello Lambda");
r1.run();
}
@Test
void test2() {
Consumer<String> con1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
System.out.println("--------------- Lambda ---------------");
Consumer<String> con2 = (x) -> System.out.println(x);
}
@Test
void test3() {
Comparator<Integer> com1 =new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
System.out.println("--------------- Lambda ---------------");
Comparator<Integer> com2 = (Integer x,Integer y) -> Integer.compare(x,y);
}
@Test
void test4() {
MyFun myFun= x -> x*x;
Integer num1=myFun.getValue(10);
Integer num2=operation(20, myFun);
Integer num3=operation(30, x -> x*x);
System.out.println("num1: " + num1);
System.out.println("num2: " + num2);
System.out.println("num3: " + num3);
}
private Integer operation(Integer num,MyFun my){
return my.getValue(num);
}
}
1.3 Java8 内置的四大核心函数式接口
/**
* Consumer<T> : 消费型接口
* void accept(T t);
*
* Supplier<T> : 供给型接口
* T get();
*
* Function<T, R> : 函数型接口
* R apply(T t);
*
* Predicate<T> : 断言型接口
* boolean test(T t);
**/
1.4 Lambda另外一种使用方式 ::
package com.yq.demo;
import com.yq.demo.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 方法引用:若 Lambda 体中的内容有方法已经实现了,我们可以使用"方法引用
* (可以理解为方法引用是 Lambda 表达式的另外一种表现形式)
*
* 注意:
* Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
*
* 主要有三种语法格式:
*
* 对象::实例方法名
*
* 类::静态方法名
*
* 类::实例方法名
*
* 构造器引用:
* **/
@SpringBootTest
class DemoApplicationTests {
@Test
void test1() {
// 对象::实例方法名
// Lambda体内实现的函数具有与函数式接口中抽象方法相同类型的参数和返回值
Consumer<String> con1 = System.out::println;
Consumer<String> con2 = x -> System.out.println(x);
con1.accept("abcd");
con2.accept("1234");
// User实体类中定义了get,set方法
User user=new User();
user.setUserName("Jack");
Supplier<String> sup = user::getUserName;
Supplier<String> sup2 = () -> user.getUserName();
Supplier<String> sup3 = () -> {return user.getUserName();};
System.out.println("sup: " + sup.get());
System.out.println("sup2: " + sup2.get());
System.out.println("sup3: " + sup3.get());
//类::静态方法名
Comparator<Integer> com = Integer::compareTo;
Comparator<Integer> com2 = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com3 = (x,y) -> {return Integer.compare(x,y);};
//类::实例方法名
//Lambda 参数列表中的第一个参数是实列方法的调用者,而第二个参数是实例方法的参数时,可以使用类::实例方法名
BiPredicate<String,String> bp = String::equals;
BiPredicate<String,String> bp2 = (x,y) -> x.equals(y);
//构造器引用
//构造函数与lambda函数式接口具有相同的参数列表和返回值
Supplier<User> sup_1 = () -> new User();
Supplier<User> sup_2 = User::new;
//构造函数与lambda函数式接口具有相同的参数列表和返回值
Function<Integer,User> fun = (x) -> new User(x);
Function<Integer,User> fun2 = User::new;
}
}
2 Stream API
2.1 Stream简要说明
-
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
-
Stream本身不会存储元素
-
Stream不会改变源对象,相反,他会返回一个持有结果的新Stream
-
Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行
创建过程:
-
创建Stream
使用一个数据源(集合、数组等)获取一个流 -
中间操作
一个中间操作链(过滤、排序、查找、映射等),对数据进行处理 -
终止操作
一个终止操作,执行中间操作,并产生结果
注:中间操作不会执行任何处理,只有终止操作时一次性执行全部处理
2.2 Stream基本用法
package com.yq.demo;
import com.yq.demo.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@SpringBootTest
class DemoApplicationTests {
List<User> userList=Arrays.asList(
new User(1,"张三",20,"18612345678","男"),
new User(2,"李四",36,"18612345679","女"),
new User(3,"王五",28,"18612345677","男"),
new User(4,"马六",31,"18612345676","女"),
new User(5,"田七",42,"18612345675","男")
);
@Test
void test1() {
// 创建 Stream
// 1. 通过Collection系列集合提供的stream() 或 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
// 2. 通过Arrays中的静态方法stream()获取数组流
User[] users=new User[10];
Stream<User> stream2 = Arrays.stream(users);
// 3. 通过Stream类中的静态方法 of()
Stream<String> stream3 = Stream.of("11", "22", "33");
// 4. 创建无限流
// 迭代 - 相当于循环执行Function<T, R> , seed是初始值参数,之后用返回值作为参数
Stream<Integer> stream4 = Stream.iterate(0,x->x+2);
stream4.limit(10).forEach(System.out::println);
//生成 - Stream<T> generate(Supplier<T> s)
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
//
// 注:中间操作不会执行任何处理,只有终止操作时一次性执行全部处理
/**
* 中间操作: 筛选与切片
* filter--接收 Lambda ,从流中排除某些元素。
* limit-截断流,使其元素不超过给定数量。
* skip(n)一 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与limit(n)互补distinct-筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
* */
//Stream<T> Stream.filter(Predicate<? super T> predicate);
Stream<User> streamU1 = userList.stream().filter(x->{
System.out.println("获取年龄大于22的人员");
return x.getAge()>22;
})
.limit(2)
.skip(1)
.distinct();
// 终止操作 一次性执行全部处理
//void Stream.forEach(Consumer<? super T> action);
streamU1.forEach(x -> {System.out.println(x.getUserName());});
streamU1.forEach(System.out::println);
/**
* 中间操作: 映射
* map: 接收 Lambda ,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,
* 并将其映射成一个新的元素。
* flatMap: 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
* */
//<R> Stream<R> Stream.map(Function<? super T, ? extends R> mapper);
// 打印用户名称 - map相当于遍历集合/数组的每一个元素,每一个元素作为参数做处理并返回一个值
userList.stream().map(User::getUserName).forEach(System.out::println);
userList.stream().map(x -> {
return x.getUserName();
}).forEach(System.out::println);
//<R> Stream<R> Stream.flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
// 打印用户名称 - flatMap相当于遍历集合/数组的每一个元素,每一个元素作为参数做处理并返回一个Stream
userList.stream().flatMap(x -> Arrays.stream(x.getUserName().split(""))).forEach(System.out::println);
userList.stream().flatMap(x -> {
String[] strs=x.getUserName().split("");
Stream<String> stream=Arrays.stream(strs);
return stream;
}).forEach(System.out::println);
/**
* 中间操作: 排序
* sorted()-一自然排序
* sorted(Comparator com)-定制排序
* */
userList.stream().sorted().forEach(System.out::println);
// 按照年龄升序
userList.stream().sorted((e1,e2) -> {return e1.getAge() - e2.getAge();}).forEach(System.out::println);
// 按照电话号码升序
userList.stream().sorted((e1,e2) -> {return e1.getTel().compareTo(e2.getTel());}).forEach(System.out::println);
// 按照电话号码降序
userList.stream().sorted((e1,e2) -> {return -(e1.getTel().compareTo(e2.getTel()));}).forEach(System.out::println);
/**
* 终止操作
* al1Match-检查是否匹配所有元素
* anyMatch-检查是否至少匹配一个元素
* noneMatch一检查是否没有匹配所有元素
* findFirst-返回第一个元素
* findAny一返回当前流中的任意元素
* count-返回流中元素的总个数
* max-返回流中最大值
* min一返回流中最小值
* forEach-内部迭代
* */
boolean b1 = userList.stream().allMatch(e -> e.getAge() > 18);
boolean b2 = userList.stream().anyMatch(e -> e.getAge() > 18);
boolean b3 = userList.stream().noneMatch(e -> e.getAge() > 18);
Optional<User> op = userList.stream().filter(e -> e.getAge()>30).findFirst();
User u = op.get();
Optional<User> op2 = userList.stream().filter(e -> e.getAge()>30).findAny();
User u2 = op.get();
long l=userList.stream().count();
//Optional<T> max(Comparator<? super T> comparator);
//获取年龄最大的
Optional<User> op3 = userList.stream().max((e1,e2) -> {return e1.getAge() - e2.getAge();});
//获取年龄最小的
Optional<User> op4 = userList.stream().max((e1,e2) -> e1.getAge() - e2.getAge());
/**
* 归约
* reduce(T identity,BinaryOperator) / reduce(BinaryOperator) 一可以将流中元素反复结合起来,得到一个值。
* */
List<Integer> ilist = Arrays.asList(1,2,3,4,5);
//identity初始值
//interface BiFunction<T, U, R> { R apply(T t, U u); }
//获取数组元素总和
Integer sum = ilist.stream().reduce(0,(e1,e2) -> e1 + e2);
//求年龄总和
Optional<Integer> ageSum = userList.stream().map(User::getAge).reduce((e1,e2) -> e1+e2);
/**
* 收集
* collect一将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
* */
// 将名称收集到list数组
List<String> userNames = userList.stream().map(User::getUserName).collect(Collectors.toList());
// 将名称收集到Set
Set<String> sets = userList.stream().map(User::getUserName).collect(Collectors.toSet());
// 获取年龄大于30的用户
List<User> u1 = userList.stream().filter(e->e.getAge()>30).collect(Collectors.toList());
//返回总数
Long count = userList.stream().collect(Collectors.counting());
//获取平均年龄
Double avg = userList.stream().collect(Collectors.averagingInt(User::getAge));
//获取年龄最大的用户
Optional<User> op5 = userList.stream().collect(Collectors.maxBy((e1,e2) -> e1.getAge() - e2.getAge()));
//获取最大年龄
Optional<Integer> opMax = userList.stream().map(User::getAge).collect(Collectors.maxBy(Integer::compare));
//分组
//按照性别分组
Map<String,List<User>> map = userList.stream().collect(Collectors.groupingBy(User::getGender));
//多级分组
Map<String,Map<String,List<User>>> map2 = userList.stream().collect(Collectors.groupingBy(User::getGender,Collectors.groupingBy(
e -> {
if(((User)e).getAge()<30){
return "青年";
}else{
return "中年";
}
}
)));
//{女={中年=[2 李四 36 18612345679, 4 马六 31 18612345676]}, 男={青年=[1 张三 20 18612345678, 3 王五 28 18612345677], 中年=[5 田七 42 18612345675]}}
//分区
Map<Boolean,List<User>> map3 = userList.stream().collect(Collectors.partitioningBy(e -> e.getAge()>30));
//{false=[1 张三 20 18612345678, 3 王五 28 18612345677], true=[2 李四 36 18612345679, 4 马六 31 18612345676, 5 田七 42 18612345675]}
//将姓名拼接成字符串
String str = userList.stream().map(User::getUserName).collect(Collectors.joining(","));
}
}
3. 并行流
-
Stream API 可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换
-
parallel的底层是Fork/Join
-
Fork/Join 就是讲一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个小任务运算的结果进行join汇总
-
parallel做大量运算时适用效率高,如果运算量小则会比sequential效率低
Instant start = Instant.now();
//并行流
Long sum= LongStream.rangeClosed(0,100000000000L)
//.sequential()//顺序流,默认
.parallel()//并行流
.reduce(0,Long::sum);
Instant end = Instant.now();
System.out.println("耗时: "+ Duration.between(start,end).toMillis());
// 耗时:6436ms
// 顺序流耗时:36926ms
// for循环耗时:25571ms
4. 时间日期API
@Test
public void test3(){
/**
* 日期时间类
* LocalDate、LocalTime、LocalDateTime - 分别表示使用ISO-8601日历系统的日期、时间、日期和时间。
* 三个日期时间使用方式相同
* */
// 系统当前日期时间
LocalDateTime ldt = LocalDateTime.now();
// 自定义日期时间
LocalDateTime ldt2 = LocalDateTime.of(2023,2,1,12,30,0);
// 增加2小时,生成新的日期时间实例
LocalDateTime ldt3 = ldt2.plusHours(2);
// 将日期指定为5号
LocalDateTime ldt4 = ldt2.withDayOfMonth(5);
// 将年指定为2022年
LocalDateTime ldt5 = ldt2.withYear(2022);
// 时间日期格式化
String date = ldt2.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String datetime = ldt2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 字符串转日期
LocalDate ldt6 = LocalDate.parse(date,DateTimeFormatter.ofPattern("yyyyMMdd"));
System.out.println("ldt6: " + ldt6); //2023-02-01
// 获取月
Month m= ldt2.getMonth();
// 打印 ldt2.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
System.out.println("date: " + date);//20230201
// 打印 ldt2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("datetime: " + datetime);//2023-02-01 12:30:00
// 打印 LocalDateTime
System.out.println("ldt: " + ldt);//2023-02-03T13:43:51.030
// 打印 Month
System.out.println("m: " + m);//FEBRUARY
// 打印Month.getValue
System.out.println("m.getValue: " + m.getValue());//2
System.out.println("ldt4: " + ldt4);//2023-02-05T12:30
System.out.println("ldt5: " + ldt5);//2022-02-01T12:30
/**
* 时间戳
* Instant (以 Unix 元年: 1970年1月1日 00:00:00 到某个时间之间的毫秒值)
* */
//获取UTC时间
Instant ins1 = Instant.now();
//获取时间戳毫秒
long timestamp=ins1.toEpochMilli();
// 以Unix元年增加60秒
Instant ins2 = Instant.ofEpochSecond(60);
System.out.println("ins2: " + ins2);//1970-01-01T00:01:00Z
/**
* 计算日期时间间隔
* Duration : 计算两个“时间”之间的间隔
* Period : 计算两个“日期”之间的间隔
* */
Instant ins3 = Instant.now();
try{
Thread.sleep(2000);
}catch (Exception e){}
Instant ins4 = Instant.now();
// Duration 计算两个时间戳间隔 毫秒
long l1 = Duration.between(ins3,ins4).toMillis();
System.out.println("l1: " + l1);//2002
LocalDateTime dt1 = LocalDateTime.now();
try{
Thread.sleep(2000);
}catch (Exception e){}
LocalDateTime dt2 = LocalDateTime.now();
// Duration 计算两个日期时间的间隔
long l2 = Duration.between(dt1,dt2).toMillis();
System.out.println("l2: " + l2);//2004
// Period 参数只能是LocalDate
LocalDate ld1 = LocalDate.of(2022,5,6);
LocalDate ld2 = LocalDate.now();
Period period = Period.between(ld1,ld2);
System.out.println("period.getYears: " + period.getYears());//相差的年数 0
System.out.println("period.getMonths: " + period.getMonths());//相差的月数 8
System.out.println("period.getDays: " + period.getDays());//相差的天数 28
System.out.println("period.get(ChronoUnit.DAYS): " + period.get(ChronoUnit.DAYS));//28
/**
* 日期时间矫正器
* TemporalAdjuster
* */
LocalDateTime now = LocalDateTime.now();
// 返回下个周日日期时间
LocalDateTime ll1 = now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
//自定义:下一个工作日 Temporal adjustInto(Temporal temporal);
LocalDateTime newWorkDate = now.with(l -> {
LocalDateTime ll2 = (LocalDateTime)l;
DayOfWeek dow = ldt4.getDayOfWeek();
if(dow.equals(DayOfWeek.FRIDAY)){
return ll2.plusDays(3);
}
if(dow.equals(DayOfWeek.SATURDAY)){
return ll2.plusDays(2);
}
return ll2.plusDays(1);
});
/**
* 时区
* ZonedDate、ZonedTime、ZonedDateTime
*
* */
//获取所有时区
Set<String> set = ZoneId.getAvailableZoneIds();
set.forEach(System.out::println);
//指定时区创建时间
LocalDateTime lt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
//将时间转换成指定时区的时间
ZonedDateTime zdt = ldt.atZone(ZoneId.of("Asia/Shanghai"));
LocalDateTime lt2 = zdt.toLocalDateTime();
}