介绍Java8之前,先回想一下基础知识:Java中的接口和抽象类 - 简书 (jianshu.com)
1. 函数式接口
先举一个例子,我们经常使用到的:Java中在用到线程的时候,经常是new一个类实现Runnable
接口重写run()
,然后将这个类的实例放到Thread()
中。我们看一下Runnable
接口

我们使用这个接口时,也可以用匿名内部类(叫这个名字,我感觉是因为,平时我们用接口都会new一个类去实现接口,然后用这个类的实例,这里没有new类,所以没有名字,但是像类一样,重写了方法,也有实例了)的方法:
new Runnable() {
@Override
public void run() {
System.out.println("新线程处理业务");
}
};
这是以前经常使用的代码样例。为了简化代码,Java8引入了Lambda
表达式,而Lambda
表达式的基础就是函数式接口。
函数式接口有以下特点:
- 1.在接口中只能够允许有一个抽象方法,但是可以有多个非抽象方法
- 2.在函数接口中可以重写
Object
父类中的方法,如:toString()
、hashCode()
等 - 3.可以加
@FunctionalInterface
注解表示该接口为函数接口
1.1 Java8内置的函数式接口
Java8内置了很多函数式接口,在java.util.function
包中,平时用的最多的主要是以下几个,其他的可以ctrl+shift+t
自己看
函数式接口 | 入参 | 返回 | 用法 |
---|---|---|---|
Conusmer<T> | T | 无 | 消费型接口,内含方法:void accept(T t) ,接收入参,无返回值 |
Supplier<T> | 无 | T | 供给型接口,内含方法:T get() ,无入参,有返回值 |
Function<T ,R> | T | R | 函数型接口,内含方法:R apply(T t) ,有入参,有返回值 |
Predicate<T> | T | boolean | 断言型接口,内含方法:boolean test(T t) ,接收入参,返回boolean值 |
2. Lambda表达式
2.1 语法结构
-
()
参数列表,是指接口中,抽象方法要传的参数 -
->
分隔,我感觉这个就代表执行添加方法体的方法 -
{}
方法体,是指我们要重写的抽象方法的方法体
(a,b)->{ }
2.2 例子
都已Java8内置的函数式接口为例。
- 无参无返回接口
Runnable r = () -> {
System.out.println("无参无返回接口");
};
r.run();
// 直接调用
((Runnable)() -> {
System.out.println("无参无返回接口");
}).run();
- 有参无返回
Consumer<Integer> c = (n) -> {
System.out.println("有参无返回");
};
c.accept(9);
((Consumer<Integer>)(n) -> {
System.out.println("有参无返回");
}).accept(3);
- 无参有返回接口
Supplier<String> s = () -> {
return "无参有返回";
};
s.get();
((Supplier<String>)() -> {
return "无参有返回";
}).get();
规则1:若lambda表达式有且只有1个参数,可以省略()
规则2:若lambda表达式方法体只有一条语句,可以省略{},也可以省略return关键字
。
所以,上面几个简写:
((Runnable)() -> System.out.println("无参无返回接口")).run();
((Consumer<Integer>)n -> System.out.println("有参无返回")).accept(3);
((Supplier<String>)() -> "无参有返回").get();
2.4 实用
Lambda
表达式在代码中很实用的,我在处理集合的时候,有些API中经常用到,典型的:
List<User> list = userDao.selectList(null);
// 排序
list.sort((o1, o2) -> o2.getId() - o1.getId());
// 轮循
list.forEach(o1 -> System.out.println(o1.toString()));
2.5 方法引用
什么是方法引用呢?当我们需要一个函数式接口的实例对象,于是我们使用Lambda
表达式来创建,lambda的方法体需要我们自己实现,这个时候如果有另外的方法,正好满足我们的需要,我们就不用写了,直接把那个方法拿过来用就行了,但是我们需要的是lambda表达式创建的接口的实例对象,此时 lambda表达式可以引用已有的方法,称为方法引用
。
比如,我想要Consumer<String>
的实例对象,方法体就打印传入的参数,用lambda表达式效果如下面1
所示。但是打印传参这个功能呢,在PrintStream
这个类中已经有了,我可以直接拿过来用,作为我lambda表达式要实现的方法。

这个时候就用到了方法引用,用
::
来表示,如下2
所示。
// 1. lambda表达式
Consumer<String> con = text -> System.out.println(text);
con.accept("测试方法引用");
// 2. 方法引用
Consumer<String> con1 = System.out::println;
con1.accept("test success");
方法引用使用条件:被引用方法的参数列表
和返回类型
必须要和函数接口中方法的参数列表
和返回类型
保持一致。
几种常见的方法引用:
- 1.静态方法引入
- 2.实例方法引入
- 3.
对象方法引入
- 4.
构造方法引入
前2种方法比较正常,如下面代码中的1和2,我的理解是:双冒号前面的 能点出来 冒号后面的方法;
对象方法:是指函数式接口中方法的第1个参数 类型是 要引入的方法所在的类的类型,比较特殊,所以有简便写法,也可以按前2种来写,简便写法认识就行;
构造方法引入,下面代码中的3,class :: new
// 1
Consumer<String> con1 = System.out::println;
con1.accept("test success");
// 2
Consumer<String> con2 = new User()::setName;
con2.accept("张三");
// 3. 构造方法引用
Supplier<User> su = User::new;
3. Stream Api
Stream 是JDK1.8 中处理集合的关键抽象概念,Lambda 和 Stream 是JDK1.8新增的函数式编程最有亮点的特性了,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL执行的数据库查询。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
特点:
- stream自己不会存储元素
- Stream不会改变源对象,它是返回一个持有结果的新Stream
- Stream操作是延迟执行的,因为要等拿到了需要的结果的时候才执行
Stream操作分成3步:
- 创建Stream流,(用集合或者数组等作为原数据)
- 中间操作(过滤、相加、分组等)
- 终止操作
3.1 创建Stream流
Stream流分为2种:
- Stream流,采用单线程执行
- parallelStream流,为并行流采用多线程执行,效率更高
几种方式:
public static void main(String[] args) {
// 1. 通过集合的stream()方法
List<String> list = new ArrayList<String>();
Stream<String> s1 = list.stream();
// 2. 通过Arrays的静态方法stream()
User[] userArr = new User[10];
Stream<User> s2 = Arrays.stream(userArr);
// 3. 通过Stream的静态方法of()
Stream<String> s3 = Stream.of("a", "b", "c");
// 4. 创建无限流
Stream<Integer> s4 = Stream.iterate(1, x -> x+2);
s4.limit(10).forEach(System.out::println);
Stream<Double> s5 = Stream.generate(() -> Math.random());
s5.limit(10).forEach(System.out::println);
}
3.2 Stream流操作
- 筛选 filter
List<User> list = userDao.selectList(null);
list.stream()
.filter(u -> u.getAge() == 22)
.forEach(System.out::println);
// foreach是终止操作,有终止操作,才有结果
- 截断 limit
List<User> list = userDao.selectList(null);
list.stream()
.limit(3)
.forEach(System.out::println);
- 跳过 skip
List<User> list = userDao.selectList(null);
list.stream()
.skip(2)
.forEach(System.out::println);
- 去重 distinct
需要注意,去重比较的是Hashcode和equals方法
- 去重 distinct
List<User> list = userDao.selectList(null);
list.stream()
.distinct()
.forEach(System.out::println);
- 5 映射 map 理解为 从集合元素(对象)中拿到想要的属性,并将拿到的数据放到了新流中
List<User> list = userDao.selectList(null);
list.stream()
.map(User::getName) // 拿到每个元素的名字
.forEach(System.out::println);
- 6 排序 sorted
List<User> list = userDao.selectList(null);
list.stream()
.sorted((u1, u2) -> u1.getName().compareTo(u2.getName()))
.forEach(System.out::println);
- 7 终止操作 查找和匹配
-
allMatch
-- 是否匹配所有元素 -
anyMatch
-- 是否至少匹配一个元素 -
noneMatch
-- 是否没有匹配的元素 -
findFirst
-- 找到第一个元素 -
findAny
-- 任意一个 -
count
-- 流中元素个数 -
max
-- 流中最大值 -
min
-- 流中最小值
-
List<User> list = userDao.selectList(null);
boolean b1 = list.stream()
.allMatch((u) -> u.getAge() > 20);
boolean b2 = list.stream()
.anyMatch((u) -> u.getAge() > 20);
boolean b3 = list.stream()
.noneMatch((u) -> u.getAge() > 20);
System.out.println(b1 + "---" + b2 + "----" + b3);
Optional<User> f = list.stream()
.findFirst();
if (f.isPresent()) {
System.out.println(f.get());
}
long count = list.stream()
.count();
list.stream()
.max((u1, u2) -> Integer.compare(u1.getAge(), u2.getAge()));
- 8 规约 reduce 提供处理数据的算法,和map结合,map是拿数据,拿到后reduce做处理
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y); // 求和
List<User> list1 = userDao.selectList(null);
Optional<Integer> sum1 = list1.stream()
.map(User::getAge)
.reduce(Integer::sum); // 求和
- 9 收集 collect
收集功能比较多样,Java8中提供了好多种结果,可以把指定数据收集为集合、map、set等类型,也可以收集为平均值、总数、统计个数等,都在java.util.stream.Collectors
这个类的方法中。
List<User> list = userDao.selectList(null);
List<String> nameList = list.stream()
.map(User::getName)
.collect(Collectors.toList());
Map<Integer, User> userMap = list.stream()
.collect(Collectors.toMap(user -> user.getId(), user -> user));
Long count = list.stream()
.collect(Collectors.counting());
Double average = list.stream()
.collect(Collectors.averagingInt(User::getAge));
4. Optional类
Optional 是个容器,它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 常用方法:
-
Optional.of(T value)
-- 构建一个Optional实例,参数不能为空 -
Optional.empty()
-- 构建空实例,不能有参 -
Optional.ofNullable(T value)
-- T不为null,构建实例,T为null,构建空实例 -
Optional.isPresent()
-- 判断是否有值 -
Optional.orElse(T other)
-- 如果有值,返回值,没有返回T -
Optional.orElseGet(Supplier s)
-- 如果有值,返回值,没有返回s获取的值 -
Optional.map(Function f)
-- 如果有值,返回f处理后的Optional,没有返回Optional.empty()
5.
串行流:单线程的方式操作; 数据量比较少的时候。
并行流:多线程方式操作;数据量比较大的时候,原理:
Fork join 将一个大的任务拆分n多个小的子任务并行执行,
最后在统计结果,有可能会非常消耗cpu的资源,确实可以
提高效率。
6. 新日期时间Api
- 时间戳
Instant now = Instant.now();
System.out.println(now);
System.out.println(now.atOffset(ZoneOffset.ofHours(8)));
System.out.println(now.atOffset(ZoneOffset.ofHours(8)).toEpochSecond());
- 时间
LocalDate
LocalTime
LocalDateTime
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
LocalDateTime ldt1 = LocalDateTime.of(2022, 3, 31, 11, 50, 20);
System.out.println(ldt1);
System.out.println(ldt.plusDays(1));
System.out.println(ldt.minusMonths(1));
System.out.println(ldt.getDayOfMonth());
- 时间差
Duration dur = Duration.between(ldt1, ldt);
System.out.println(dur.getSeconds());
System.out.println(dur.toMillis());
- 日期差
LocalDate ld1 = LocalDate.of(2020, 5, 31);
LocalDate ld2 = LocalDate.now();
Period pe = Period.between(ld1, ld2);
System.out.println(pe.getYears());
System.out.println(pe.getMonths());
System.out.println(pe.getDays());
- 时间日期修改更正
System.out.println(ldt.withDayOfMonth(1));
System.out.println(ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
- 格式化
ldt.format(DateTimeFormatter.ISO_DATE);
ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); System.out.println(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
- 时区
ZoneDate
ZoneTime
ZoneDateTime
// 所有时区
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.forEach(System.out::println);
System.out.println(ldt.atOffset(ZoneOffset.ofHours(8)));
System.out.println(ldt.atZone(ZoneId.of("Asia/Shanghai")));
网友评论