文章目录
JDK8新特性
1. 函数式接口
概念: 只有一个抽象方法的接口称之为函数式接口。函数式接口可以使用注解@FunctionalInterface
进行修饰
2. Lambda表达式
概念及语法格式
λ表达式可以理解为将代码或者方法作为参数进行传递去执行。
λ表达式可以拆分为两部分
左侧:参数列表
右侧:所需要执行的功能,及λ体
语法格式1:无参数,无返回值
@Test
public void test(){
// () -> System.out.println("Hello");
Runnable a = new Runnable(){
@Override
public void run(){
System.out.println("Hello")
}
};
//等同于
Runnable a1 = () -> System.out.println("Hello");
a1.run();
}
语法格式2:有一个参数,无返回值(若只有一个参数 小括号可以省略不写)
@Test
public void test(){
//Consumer被注解@FunctionalInterface修饰的接口(函数式接口) 唯一抽象方法 void accept(T t);
//左侧参数 -> 右侧执行体
Consumer<String> con = (x) -> System.out.println(x);
// x -> System.out.println(x);
con.accept("hahah");
}
语法格式3:有两个以上的参数,并且lambda体中有多条语句 (若lambda体中只有一条语句,return 和 大括号都可以省略不写)
@Test
public void test(){
//Comparator被注解@FunctionalInterface的接口 举例抽象方法 int compare(T o1,T o2);
Comparator<Integer> com = (x,y) -> {
System.out.println("hhaha0");
return (x < y) ? -1 : ((x == y) ? 0 : 1);
};
com.compare(1,2);
}
注意:lambda表达式的参数类型可以省略不写,因为jvm编译器可以从上下文推断出数据类型。即“类型推断”如果要在参数里面写数据类型,都要写上。
实例1
class Employee {
private String name;
private int age;
private double salary;
//省略 get and set and constructor
}
interface MyPredicate<T> {
boolean test(T t);
}
public class Test{
static List<Employee> list = Arrays.asList(
new Employee("张三",10,1),
new Employee("里斯",20,1),
new Employee("王五",16,1),
new Employee("二三",30,1)
);
public static List<Employee> filterEmployee(List<Employee> list,MyPredicate<Employee> mp){
List<Employee> emps = new ArrayList<>();
for (Employee employee : list) {
if(mp.test(employee)){
emps.add(employee);
}
}
return emps;
}
@org.junit.Test
public void test1(){
//需要使用自定义的方法,过滤年龄等于等于15岁的员工
List<Employee> list2 = filterEmployee(list,(e) -> e.getAge() >= 15);
list2.stream().map(Employee::getName).forEach(System.out::println);
}
@org.junit.Test
public void test2(){
//可以使用stream进行list集合的过滤 不使用自定义接口
List<Employee> list2 = list.stream().filter((e) -> e.getAge() >= 15).collect(Collectors.toList());
list2.stream().map(Employee::getName).forEach(System.out::println);
}
}
实例2
创建一个MyFun接口使用@FunctionalInterface注解,并创建一个抽象方法Integer getValue(Integer num);在Test类对变量进行某种操作。
@FunctionalInterface
interface MyFun{
Integer getValue(Integer num);
}
public class Test{
@org.junit.Test
public void Test(){
operation(100,num -> ++num);
}
/**
* param1 num : 传入的整形数
* param2 mf : 实现某种方式对 整形数 进行操作。
**/
public Integer operation(Integer num,MyFun mf){
return mf.getValue(num);
}
}
四大核心函数式接口
Consumer : 消费性接口 void accept(T t);
Supplier : 供给性接口 T get();
Function<T,R> : 函数性接口 T代表参数,R代表返回值 R apply(T t);
Predicate :断言性接口 boolean test(T t);
// 举例:消费型接口Consumer<T>的使用
class Test{
@org.junit.Test
publilc void test(){
happy(10000,(money)->System.out.println("happy消费"+money+"元"));
}
public void happy(double money,Consumer<double> con){
con.accept(money);
}
}
lambda方法引用
主要有三种语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
class Test{
//对象::实例方法名
@org.junit.Test
public void test(){
Consumer<String> con = (x) -> System.out.println(x);
con.accept("haha");
Consumer<String> con2 = System.out::println;
con2.accept("haha");
}
//类::静态方法名
@org.junit.Test
public void test2(){
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com2 = Integer::compare;
com.compare(1,2);
com2.compare(1,2);
}
//类::实例方法名
@org.junit.Test(){
BiPredicate<String,String> bp = (x,y) -> x.equals(y);
bp.test("a","a");
BiPredicate<String,String> bp2 = String::equals;
}
}
lambda构造器引用
格式:
CalssName::new
class Test{
@org.junit.Test
public void test(){
Supplier<String> sup = () -> new String();
//这里的构造器引用取决于接口方法的参数的个数。 此处函数式接口 T get(); 为无参抽象方法所以String在实例化时 也是实例化无参的构造方法 其他类也适用
Supplier<String> sup2 = String::new;
String str = sup2.get();
}
}
3.Stream API
Java8中有两大最为重要的改变。第一个是 Lambda表达式;另外一个则是 Stream API((java.util. stream.*)。
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的査找、过滤和映射数据等操作。使用 Stream API对集合数据进行操作,就类似于使用SQL执行数据库查询。也可以使用 Stream API来并行执行操作。简而言之Stream API提供了一种高效且易于使用的处理数据的方式。
Stream流的特点
- Stream自己不会存储数据,主要做的是对数据的计算和处理
- Stream不会改变原对象(集合、数组)。相反它会返回一个持有结果的新的Stream
- Stream操作是延迟操作。这意味着它会等到需要结果(终止操作)的时候才执行中间操作
Stream操作的三个步骤
- 创建Stream。根据数据(集合、数组)源获取一个流
- 中间操作。中间操作链,对数据源的数据进行处理
- 终止操作(终端操作)。执行完中间操作进行终止操作产生结果
创建Stream流
方式1:通过Collection
集合的Stream()
方法或parallelStream()
获取流
@Test
public void test(){
ArrayList<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
}
方式2:通过Arrays
中的静态方法Stream()获取数组流
Stream<String> stream1 = Arrays.stream(new String[]{"abc","def","ghi"});
方式3:通过Stream
中的静态方法of(...)
Stream<String> stream2 = Stream.of("a", "b", "c");
方式4:使用Stream
的iterate()
方法创建无限流,使用其generate()
方法生成无限流
Stream<Integer> stream3 = Stream.iterate(0, x -> x + 2);
stream3.limit(10).forEach(System.out::println);// 0 2 4 6 8 10 12 ... 18
// 生成方式
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
Stream的中间操作
1.筛选与切片
- filter 过滤操作
- limit 分页操作
- skip(n) 跳过前n个元素,若流中元素不足n个则返回一个空流
- distinct 去重
示例
// 1.使用filter过滤年龄大雨35岁的员工并使用终止操作进行输出
List<com.company.Employee> employees = Arrays.asList(
new com.company.Employee("张三", 18, 9999.99),
new com.company.Employee("李四", 58, 5555.55),
new com.company.Employee("王五", 26, 3333.33),
new com.company.Employee("赵六", 35, 6666.66),
new com.company.Employee("田七", 12, 8888.88),
new com.company.Employee("田七", 12, 8888.88)
);
public void test11(){
employees.stream().filter(e -> {
System.out.println("调用了中间操作");
return e.getAge()>=35;
}).forEach(System.out::println);
}
// 输出,可见只有执行终止了操作的时候才会执行中间操作
调用了中间操作
调用了中间操作
Employee{name='李四', age=58, price=5555.55}
调用了中间操作
调用了中间操作
Employee{name='赵六', age=35, price=6666.66}
调用了中间操作
// 2. 使用limit获取工资大于5000的前两位员工
employees.stream().filter(x ->x.getSalary()>5000).limit(2).forEach(System.out::println);
// 3.获取工资大于5000的员工后,使用skip操作跳过前两个员工获取符合条件的员工
employees.stream().filter(e -> e.getSalary()>5000).skip(2).forEach(System.out::println);
// 4.使用distinct对employees集合去重,注意对于去重的对象需要重写hashCode和equals方法,否则无法去重
employees.stream().distinct().forEach(System.out::println);
2.映射
- map 接收lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
// 1.使用map获取employees集合中所有员工的名字并使用终止操作打印输出
employees.stream().map(e -> e.getName()).forEach(System.out::println);
3. 排序
- sorted() 自然排序(按照Comparable排序)
- sorted(Comparator com) 定制排序(按照Comparator排序)
示例
// 使用sorted定制排序,年龄一致按姓名排序,不一致按年龄排序
employees.stream().sorted( (e1, e2) -> {
if (e1.getAge() == e2.getAge()){
return e1.getName().compareTo(e2.getName());
}else {
return e1.getAge().compareTo(e2.getAge());
}
}).forEach(System.out::println);
Stream终止操作
1.查找与匹配
- allMatch,检查是否匹配所有元素
- anyMatch,检查是否至少匹配一个元素
- noneMatch,检查是否没有匹配所有元素
- findFirst,返回第一个元素
- findAny,返回流中的任意元素
- count,返回流中的元素总个数
- max,返回流中的最大值
- min,返回流中的最小值
示例
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99,Status.Bisy),
new Employee("李四", 58, 5555.55,Status.Free),
new Employee("王五", 26, 3333.33,Status.Vocation),
new Employee("赵六", 35, 6666.66,Status.Bisy),
new Employee("田七", 12, 8888.88,Status.Bisy),
new Employee("田七", 12, 8888.88,Status.Bisy)
);
// 1.使用allMatch操作匹配employees集合中的所有员工是否都处于Bisy状态
boolean allMatch = employees.stream().allMatch(e -> e.getStatus().equals(Status.Bisy));
System.out.println(allMatch);// false
// 2.employees集合按照工资倒序排序后获取第一个,返回一个可能为空的Optional对象,是为了避免出现空指针
Optional<Employee> optional = employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).findFirst();
System.out.println(optional.get());
// 3.使用max函数返回工资最大的员工
Optional<Employee> max = employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());
// 4.获取employees集合中工资最少员工的工资
Optional<Double> min = employees.stream().map(Employee::getSalary).min(Double::compare);
System.out.println(min.get());// 3333.33
2. 规约与收集
- reduce(T identity, BinaryOperator) / reduce(BinaryOperator),可以将流中的元素反复结合起来,得到一个值
- collect 收集,将流转换为其他形式,接收一个Collector接口的实现,用于给Stream元素做汇总的方法
示例:map-reduce模式,应用非常广泛
// 1.计算集合中所有元素的总和
// 计算过程,首先将初始值identity=0设置为x,从集合中一次取一个元素作为y,计算x+y,然后再将结果赋给x继续累加
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);// 55
// 2.计算employees所有员工的工资总和
// 这次reduce返回的结果为Optional和上面返回的结果为Integer不一样的原因是,这次结果可能为空,而上面因为设置了初始值0所以不可能为空
Optional<Double> optional = employees.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(optional.get());// 43333.29
// 3.使用collect方法收集employees集合中所有员工的名字到一个set集合中实现去重
Set<String> set = employees.stream().map(Employee::getName).collect(Collectors.toSet());
set.forEach(System.out::println);
// 4.使用collect方法收集employees集合中所有员工的名字到一个特殊集合hashset中实现去重
HashSet<String> hashSet = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);
// 5.使用collect方法根据员工的状态分组,返回一个map
Map<Status, List<Employee>> statusListMap = employees.stream().collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(statusListMap);
// 6.collect多级分组,先按照状态分组,再按照年龄分组
Map<Status, Map<String, List<Employee>>> map = employees.stream().collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(x -> {
if (x.getAge() <= 35) {
return "青年";
} else if (x.getAge() <= 50) {
return "壮年";
} else {
return "老年";
}
})));
System.out.println(map);
//{Free={老年=[Employee{name='李四', age=58, salary=5555.55}]},
//Bisy={青年=[Employee{name='张三', age=18, salary=9999.99}, Employee{name='赵六', age=35, salary=6666.66}, Employee{name='田七', age=12, salary=8888.88}, Employee{name='田七', age=12, salary=8888.88}]},
//Vocation={青年=[Employee{name='王五', age=26, salary=3333.33}]}}
// 7.collect分区/分片,满足条件的一个区,不满足条件的一个区
Map<Boolean, List<Employee>> map = employees.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
System.out.println(map);
//{false=[Employee{name='李四', age=58, salary=5555.55}, Employee{name='王五', age=26, salary=3333.33}, Employee{name='赵六', age=35, salary=6666.66}],
//true=[Employee{name='张三', age=18, salary=9999.99}, Employee{name='田七', age=12, salary=8888.88}, Employee{name='田七', age=12, salary=8888.88}]}
// 8.collect统计汇总函数
DoubleSummaryStatistics statistics = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(statistics.getSum());// 总和
System.out.println(statistics.getAverage());// 平均值
System.out.println(statistics.getCount());// 个数
System.out.println(statistics.getMax());// 最大值
System.out.println(statistics.getMin());// 最小值
// 9.collect连接字符串
String collect = employees.stream().map(Employee::getName).collect(Collectors.joining(","));
System.out.println(collect);// 张三,李四,王五,赵六,田七,田七
常用的Collector接口还有:
- Collectors.counting() 计数
- Collectors.averagingInt() 求平均值
- Collectors.summingInt() 求和
- Collectors.maxBy() 求最大值
- Collectors.minBy() 求最小值
- Collectors.groupingBy() 分组
- Collectors.partitioningBy() 分区
- Collectors.summarizingDouble() 统计汇总
- Collectors.joining() 连接字符串
4.并行流与串行流
Fork/Join框架: 就是在必要的情况下,将一个大任务拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个小任务的运行结果进行join汇总
Stream流就是使用了Fork/Join框架的原理
Stream流可以通过parallel()和sequential()进行并行流和串行流的切换jundong2
示例
// 使用并行流计算0~100000000000L的累加和,用时6118ms,运行过程中CPU利用率达到100%,16核都达到100%
Instant start = Instant.now();
long sum = LongStream.rangeClosed(0, 100000000000L).parallel().reduce(0, Long::sum);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗时:" + Duration.between(start, end).toMillis());// 6118
5.Optional类
作用:Optional是一个容器类,可以避免空指针异常
常用方法:
- Optional.of(T t)创建一个Optional实例
- Optional.empty() 创建一个空的Optional实例
- Optional.ofNullable(T t) 若t不为null,创建Optional实例,否则创建空实例
- isPresent() 判断是否包含值
- optional.orElse(T t) 如果调用对象包含值,返回该值,否则返回t
- orElseGet(Supplier s) 如果调用对象包含值,返回该值,否则返回s获取的值
- map(Function f) 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
- flatMap(Function mapper) 与map类似,要求返回的值必须是Optional
示例
// flatMap使用示例
MyTestNormal myTestNormal1 = new MyTestNormal();
myTestNormal1.setName("张三");
myTestNormal1.setAge("23");
Optional<MyTestNormal> myTestNormal = Optional.ofNullable(myTestNormal1);
Optional<String> optional = myTestNormal.flatMap((x) -> Optional.of(x.getName()));
System.out.println(optional.get());
6.接口中的默认方法与静态方法
Java8之前接口中只能有全局静态常量和抽象方法;Java8中允许有实现的default方法和实现的static方法
接口默认方法的“类优先”原则:
- 若一个接口中定义一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时,这时会执行父类中的方法,接口中的同名方法会被忽略
7.新时间日期API
常用时间API
LocalDate、LocalTime、LocalDateTime的实例是不可变对象、线程安全的,被人所熟悉的时间格式,分别表示ISO-8061日历系统的日期、时间、日期和时间。他们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息;
示例
//LocalDate、 LocalTime、LocalDateTime,以LocalDateTime为例演示
// 1.获取当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now); // 2021-10-26T15:29:20.519
// 2.指定设置一个时间
LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 24, 13, 06, 45);
System.out.println(localDateTime);// 2021-08-24T13:06:45
// 3.增加一个时间(年月日时分秒都行),增加一个月
System.out.println(now.plusMonths(1));// 2021-11-26T15:33:47.600
System.out.println(now.minusDays(6));// 2021-10-20T15:33:47.600
// 4.获取年月日时分秒
System.out.println("当前月份:" + now.getMonth()+ ", 当前小时:" + now.getHour());//当前月份:OCTOBER, 当前小时:15
Instant 时间戳(以Unix元年(1970年1月1日 00:00:00)到某个时间的毫秒值)
示例
// 获取当前时间
Instant instant = Instant.now();
System.out.println(instant);// 2021-10-26T07:46:05.031Z,与当前时间相差8小时,是因为默认获取的是UTC时区
System.out.println(instant.atOffset(ZoneOffset.ofHours(8)));// 2021-10-26T015:46:05.031Z,增加8小时,偏移到当前时区
// 2.获取一个时间戳
System.out.println(instant.toEpochMilli());// 1635234594156
Duration 计算两个时间之间的间隔
Period 计算两个日期之间的间隔
示例
// 分别设置两个时间戳,在Unix元年增加1000和6000ms,计算二者之间的毫秒值查
Instant instant = Instant.ofEpochMilli(1000);
Instant instant1 = Instant.ofEpochMilli(6000);
System.out.println(Duration.between(instant, instant1).toMillis());// 5000
// Duration也支持计算两个LocalDateTime类型的时间间隔
LocalDateTime localDateTime = LocalDateTime.of(2021, 10, 26, 17, 54, 00);
LocalDateTime localDateTime1 = LocalDateTime.of(2021, 10, 27, 17, 54, 00);
System.out.println(Duration.between(localDateTime, localDateTime1).toMillis());// 86400000
// 使用Period计算两个日期的间隔
LocalDate localDate = LocalDate.now();// 2021-10-26
LocalDate localDate1 = LocalDate.of(2019, 8, 23);
Period period = Period.between(localDate, localDate1);
System.out.println("间隔年:" + period.getYears() + ",间隔月:" + period.getMonths() + ",间隔日:" + period.getDays());// 间隔年:-2,间隔月:-2,间隔日:-3
时间校正器 TemporalAdjuster
有时可能需要将日期调整到 “下个周日” 等操作,TemporalAdjusters该类通过静态方法提供了大量的常用的TemporalAdjuster的实现
示例
LocalDateTime now = LocalDateTime.now();
System.out.println("now:" + now);// 2021-10-28T14:50:38.122
// 使用with指定月份为8月
System.out.println("使用with指定月份为8月:" + now.withMonth(8));// 2021-08-28T14:50:38.122
// 使用时间校验器TemporalAdjuster获取下个周六
System.out.println("获取下个周六:" + now.with(TemporalAdjusters.next(DayOfWeek.SATURDAY)));// 2021-10-30T14:50:38.122 正确
// 使用with自定义一个方法,返回下个工作日的日期
LocalDateTime nexWeekDay = now.with((x) -> {
LocalDateTime localDateTime = (LocalDateTime) x;
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
return localDateTime.plusDays(2);
} else {
return localDateTime.plusDays(1);
}
});
System.out.println("自定义获取下个工作日:" + nexWeekDay);// 2021-10-29T14:50:38.122
// 输出:
now:2021-10-28T14:58:22.480
使用with指定月份为8月:2021-08-28T14:58:22.480
获取下个周六:2021-10-30T14:58:22.480
自定义获取下个工作日:2021-10-29T14:58:22.480
日期格式化
LocalDateTime默认输出格式是:2021-10-29T14:58:22.480,即DateTimeFormatter.ISO_DATE_TIME
示例
// 1. 使用ISO_DATE_TIME格式化时间与不使用格式化时间效果一样,说明默认使用的ISO_DATE_TIME方式格式化
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);// 2021-10-31T19:08:22.123
System.out.println(ldt.format(DateTimeFormatter.ISO_DATE_TIME));// 2021-10-31T19:08:22.123
// 2. 只格式化日期或时间
System.out.println(ldt.format(DateTimeFormatter.ISO_DATE));// 2021-10-31
System.out.println(ldt.format(DateTimeFormatter.ISO_TIME));// 19:18:22.62
// 3. 自定义格式化
System.out.println(ldt.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")));// 2021年10月31日 19:18:22
// 4. 字符串转日期对象
System.out.println(LocalDateTime.parse("2021-10-31 19:18:22", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 2021-10-31T19:18:22
带时区的处理
- ZonedDate
- ZonedTime
- ZonedDateTime
每个时区都对应着ID,地区ID都为"{区域}/{城市}"的格式,例如:Asia/ShangHai
ZoneId该类包含了所有的时区信息,of(id):用指定的时区信息获取Zoned对象
示例
// 1.查看系统默认时区
System.out.println(ZoneId.systemDefault());// Asia/Shanghai
// 2.查看所有时区
ZoneId.getAvailableZoneIds().forEach(System.out::println);// Asia/Aden、 America/Cuiaba
// 3.查看某个时区下的当前时间
System.out.println(LocalDateTime.now(ZoneId.of("America/Cuiaba")));// 2021-10-31T07:55:00.906
// 4.获取当前系统带时区的时间ZonedDateTime,可见当前时区为Shanghai,相对于UTC偏移8个时区
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
System.out.println(zonedDateTime);// 2021-10-31T19:59:57.892+08:00[Asia/Shanghai]