Java 8高级特性以及在项目中实战
概要
Java 8带来了大量的特性,首当其冲的就是Lambda, Streaming API。我从内容由点到线,由线到面,系统介绍Java 8高级特性。希望通过该分享能为大家带来编程上的帮助。
新特性
- Lambda
- Method Reference
- Type Inference
- InterfaceFunction
- Higher-order Function
- Stream
- Collectors
- Optional
特性
Lambda
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它 有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
哪里可以使用Lambda:
- 方法体内
- 方法入参
- 方法返回值
看一个最简单的Lambda:
@Test
public void test() {
Consumer<String> consumer = (x) -> log.info("有入参无返回值:{}", x);
consumer.accept("Percy");
}
接口 | 参数 | 返回类型 | 描述 |
---|---|---|---|
Predicate | T | boolean | 判断 |
Consumer | T | void | 处理一个参数 |
Function<T,R> | T | R | 转换T到R |
Supplier | None | T | 工厂方法 |
BinaryOperator | (T, T) | T | 处理两个参数返回一个相同的参数,求和 |
总结
** 可以使用变量的地方都可以使用Lambda **
Method Reference
方法引用 :: 使用连续两个双引号进行表示。方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。
类型 示例
引用静态方法 ContainingClass::staticMethodName
引用某个对象的实例方法 containingObject::instanceMethodName
引用某个类型的任意对象的实例方法 ContainingType::methodName
引用构造方法 ClassName::new
类型 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用某个对象的实例方法 | containingObject::instanceMethodName |
引用某个类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
List<User> users = new ArrayList<>();
User u = new User();
u.setName("Zhangsan");
users.add(u);
users.stream()
.map(User::getName)
.collect(Collectors.toList());
Collections.sort(users, Comparator.comparing(User::getAge));
https://www.cnblogs.com/JohnTsai/p/5806194.html
Type Inference
Java 8改进了类型推断,在Java 7中使用简单的推断变量。例如
List<String> contents = new ArrayList<>();
contents.add("1");
log.info("简单的类型推断:{}", contents);
上述代码中在ArrayList<>并没有指定类型,由编译器进行推断。
Java 8类型推断增强:
Runnable runnable = () -> {
log.info("推断使用Runnable, 无需返回结果");
};
Callable<String> callable = () -> {
log.info("推断使用Runnable, 需要返回结果");
return "返回结果";
};
ExecutorService es = Executors.newSingleThreadExecutor();
es.submit(runnable);
es.submit(callable);
Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的,接着看下一个示例:
// 入参是一个参数的
Predicate<Integer> predicate = (x) -> x > 30;
// 可以转换对象,入参T, 返回R
Function<Integer, User> converter = (it) -> {
User u = new User();
u.setAge(it);
u.setName("Percy");
return u;
};
List<Integer> ages = new ArrayList<>();
ages.add(22);
ages.add(28);
ages.add(31);
ages.add(32);
ages.add(33);
ages.add(34);
ages.add(24);
ages.add(37);
List<User> users = ages.stream()
.filter(predicate)
.map(converter)
.collect(Collectors.toList());
两个参数的类型推断:
// 两个参数的推断
BinaryOperator<Long> add = (x, y) -> x + y;
Long addResult = add.apply(3L, 5L);
log.info("addResult结果:{}", addResult);
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
Integer r = comparator.compare(3, 1);
log.info("参数比较大小结果:{}", r);
总结
上述代码的两个Lambda函数都是一个入参,从编译器来说根本不关心出参概念。只要是一个入参就认为是相同的,所以编译器可以清晰的推断出返回的类型函数。
Higher-order Function
高阶函数:
函数编程,函数是第一公民。高阶函数就是接受至少一个函数作为参数或者返回的结果是一个函数的函数。
- 至少一个函数作为参数
- 返回的结果是一个函数
public Function<String, User> getFunc(Integer age) {
log.info("可以根据不同入参进行逻辑判断,返回不同实现的函数:{}", age);
Function<String, User> func = (name) -> {
User user = new User();
user.setAge(30);
user.setName(name);
return user;
};
return func;
}
@Test
public void testHigherOrderFunction() {
List<String> names = new ArrayList<>();
names.add("some");
Function<String, User> func = getFunc(20);
List<User> users = names.parallelStream()
.map(func)
.collect(Collectors.toList());
log.info("users:{}", users);
}
Stream
生成流
中间操作
操 作 | 类 型 | 返回类型 | 操作参数 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream | Predicate | T -> boolean |
map | 中间 | Stream | Function<T, R> | T -> R |
limit | 中间 | Stream | Predicate | T -> boolean |
sorted | 中间 | Stream | Comparator | (T, T) -> int |
distinct | 中间 | Stream | Predicate | T -> boolean |
终端操作
操 作 | 类 型 | 返回类型 |
---|---|---|
forEach | 终端 | 消费流中的每个元素并对其应用 Lambda。这一操作返回 void |
count | 终端 | 返回流中元素的个数。这一操作返回 long |
collect | 终端 | 把流归约成一个集合,比如 List、Map 甚至是 Integer |
parallel()
并行化
List<Integer> ints = Arrays.asList(3, 45, 53, 342, 2, 23, 44);
ints.stream().parallel().forEach(it -> {
log.info("===:{}", it);
});
输出的日志:
2019/03/23 16:24:19.916 ForkJoinPool.commonPool-worker-1 [INFO] LambdaTest (LambdaTest.java:132) ===:45
2019/03/23 16:24:19.916 ForkJoinPool.commonPool-worker-2 [INFO] LambdaTest (LambdaTest.java:132) ===:3
2019/03/23 16:24:19.916 main [INFO] LambdaTest (LambdaTest.java:132) ===:2
2019/03/23 16:24:19.916 ForkJoinPool.commonPool-worker-6 [INFO] LambdaTest (LambdaTest.java:132) ===:23
2019/03/23 16:24:19.916 ForkJoinPool.commonPool-worker-4 [INFO] LambdaTest (LambdaTest.java:132) ===:342
2019/03/23 16:24:19.916 ForkJoinPool.commonPool-worker-5 [INFO] LambdaTest (LambdaTest.java:132) ===:53
2019/03/23 16:24:19.916 ForkJoinPool.commonPool-worker-3 [INFO] LambdaTest (LambdaTest.java:132) ===:44
1、并行化以后不是按照特性顺序,随机化。
2、内部使用的是ForkJoinPool简化多线程操作。
3、如果程序不是必须按照特性顺序可以使用并行流,读取数据库或调用下游服务。
Collectors
收集器用作高级归约
count()
min()
max()
joining()
reducing()
BinaryOperator<Integer> aa = (x, y) -> x + y;
Integer a1 = ints.stream().reduce(Integer::sum).get();
Integer a2 = ints.stream().collect(Collectors.reducing(Integer::sum)).get();
Integer a3 = ints.stream().collect(Collectors.reducing(0, x -> x, Integer::sum));
log.info("========{}, {}, {}", a1, a2, a3);
groupingBy
Map<Integer, Long> countMapping = users.stream()
.collect(Collectors.groupingBy(User::getAge, Collectors.counting()));
log.info("==========={}", countMapping);
// {25=2, 27=1, 28=1, 30=2}
通常有时候我们需要把一个数据库的List转换为Map结构,例如key为主键id:
// 初始化数据
List<User> users = new ArrayList<>();
User u = new User();
u.setId(1L);
u.setAge(30);
u.setName("Zhangsan301");
users.add(u);
u = new User();
u.setId(2L);
u.setAge(25);
u.setName("Zhangsan251");
users.add(u);
u = new User();
u.setId(3L);
u.setAge(25);
u.setName("Zhangsan252");
users.add(u);
u = new User();
u.setId(4L);
u.setAge(27);
u.setName("Zhangsan271");
users.add(u);
u = new User();
u.setId(5L);
u.setAge(28);
u.setName("Zhangsan281");
users.add(u);
u = new User();
u.setId(6L);
u.setAge(30);
u.setName("Zhangsan302");
users.add(u);
// List -> Map
Map<Long, User> userIdMapping = users.stream()
.collect(Collectors.toMap(User::getId, it -> it, (x, y) -> x));
log.info("===========userIdMapping{}", userIdMapping);
输出结果:
===========userIdMapping{1=User(id=1, age=30, name=Zhangsan301), 2=User(id=2, age=25, name=Zhangsan251), 3=User(id=3, age=25, name=Zhangsan252), 4=User(id=4, age=27, name=Zhangsan271), 5=User(id=5, age=28, name=Zhangsan281), 6=User(id=6, age=30, name=Zhangsan302)}
是不是很便利,之前可能还要循环一次,现在一行就可以解决了问题。
partitioningBy
这个用途不多,先忽略
自定义收集器
为了适应自己特殊业务可以自定义收集器。
Optional
总结
Java8的核心功能就是Lambda和Streaming API,本次分享着重讲解了核心的语法糖Lambda.只要可以使用变量参数的地方都可以使用Lambda, Lambda内部要求无状态化,就是final类型的,不能变更状态。