摘要:在云服务业务开发中,善于使用代码新特性,往往能让开发效率大大提升,这里简单介绍下 lambad 表达式及函数式接口特性。
在云服务业务开发中,善于使用代码新特性,往往能让开发效率大大提升,这里简单介绍下 lambad 表达式及函数式接口特性。
1.Lambda 表达式
Lambda 表达式也被称为箭头函数、匿名函数、闭包。他允许把函数作为一个方法的参数(函数作为参数传递到方法中),体现出轻量级函数式编程思想。
为什么引入 lambda?
Model Code as Data,编码及数据,尽可能轻量级的将代码封装为数据。
解决方案:接口 &实现类(匿名内部类)
存在问题:语法冗余,this 关键字、变量捕获、数据控制等
public static void main (String[] args){
// 1. 传统模式下,新线程的创建
new Thread (new Runnable() {
@Override
public void run() {
System.out.println("threading..." + Thread.currentThread().getId())
}
}).start();
// 2. lambda表达式优化线程模式
new Thread(()->{
System.out.println("lambda threading..." + Thread.currentThread().getId());
})
}
复制代码
复制代码
1. 不是解决未知问题的新技术
2. 对现有问题的语义化优化
3. 需要根据实际需求考虑性能问题
2.函数式接口(Functional Interface)
函数式接口就是 Java 类型系统中的接口,是只包含一个抽象方法的特殊接口(可以有很多非抽象方法)。
语言化检测注解:@FunctionalInterface 检测合法性
java1.8 支持接口内包含:抽象方法、默认接口方法、静态接口方法、来自 Object 继承的方法
/**
* 用户身份认证标记接口
*/
@FunctionalInterface
public interface IUserCredential {
/**
* 通过用户账号,验证用户身份信息的接口
* @param username 要验证的用户账号
* @return 返回身份信息[系统管理员、用户管理员、普通用户]
*/
String verifyUser(String username);
default String getCredential(String username) {
if ("admin".equals(username)) {
return "admin + 系统管理员用户";
} else if("manager".equals(username)){ return "manager + 用户管理员用户"; } else { return "commons + 普通会员用户"; } } String toString(); /** * 消息合法性验证方法 * @param msg 要验证的消息 * @return 返回验证结果 */ static boolean verifyMessage(String msg) { if (msg != null) { return true; } return false; }} // 匿名内部类,实现接口的抽象方法 IUserCredential ic = new IUserCredential() { @Override
public String verifyUser(String username) { return "admin".equals(username)?"管理员":"会员"; } }; // lambda表达式是函数式接口的一种简单实现 IUserCredential ic2 = (username) -> { return "admin".equals(username)?"lbd管理员": "lbd会员"; };
复制代码
复制代码
JDK 1.8 之前已有的函数式接口:
JDK 1.8 新增加的函数接口:
3.lambda 表达式的基本语法
基本语法
[接口声明] = (参数) -> {执行代码块};
// 没有参数,没有返回值的lambda表达式绑定的接口
interface ILambda1{
void test();
}
// 带有参数,没有返回值的lambda表达式
interface ILambda2{
void test(String name, int age);
}
// 带有参数,带有返回值的lambda表达式
interface ILambda3 {
int test(int x, int y);
}
ILambda1 i1 = () -> System.out.println("hello boys!");
i1.test();
ILambda2 i21 = ( n, a) -> {
System.out.println(n + "say: my year's old is " + a);
};
i21.test("jerry", 18);
ILambda2 i22 = (n, a) ->
System.out.println(n + " 说:我今年" + a + "岁了.");
i22.test("tom", 22);
ILambda3 i3 = (x, y) -> {
int z = x + y;
return z;
};
System.out.println(i3.test(11, 22));
ILambda3 i31 = (x, y) -> x + y;
System.out.println(i31.test(100, 200));
复制代码
复制代码
总结:
变量捕获
总结:Lambda 表达式优化了匿名内部类类型中的 this 关键字,不再单独建立对象作用域,表达式本身就是所属类型对象的一部分,在语法语义上使用更加简洁。
类型检查
对于语法相同的表达式,Jvm 在运行的过程中,在底层通过解释及重构,进行类型的自动推导。
方法重载
interface Param1 {
void outInfo(String info);
}
interface Param2 {
void outInfo(String info);
}
// 定义重载的方法
public void lambdaMethod(Param1 param) {
param.outInfo("hello param1 imooc!");
}
public void lambdaMethod(Param2 param) {
param.outInfo("hello param2 imooc");
}
test.lambdaMethod(new Param1() {
@Override
public void outInfo(String info) {
System.out.println(info);
}
});
test.lambdaMethod(new Param2() {
@Override
public void outInfo(String info) {
System.out.println("------");
System.out.println(info);
}
});
/*
lambda表达式存在类型检查-> 自动推导lambda表达式的目标类型
lambdaMethod() -> 方法 -> 重载方法
-> Param1 函数式接口
-> Param2 函数式接口
调用方法-> 传递Lambda表达式-> 自动推导->
-> Param1 | Param2
*/
// 报错 Ambigus Method call
// test.lambdaMethod( (String info) -> {
// System.out.println(info);
// });
复制代码
复制代码
总结:出现方法重载的类型中参数都是函数式接口的情况,需使用匿名内部类实现替代 lambda 表达式。
底层构建原理
public class Test{
public static void main(String args[]){
ITest it = (message) -> System.out.println(message);
it.markUp("lambda!");
// new Test$$Lambda$1().markUp("lambda");
}
}
interface ITest{
void markUp(String msg);
}
复制代码
复制代码
javac Test.java
javap -p Test.class (javap 反解析工具 -p 显示所有类与成员)
1. 声明一个私有静态方法,对 Lambda 表达式做一个具体的方法实现
2. 声明一个 final 内部类型并实现接口
3. 在实现接口后的重写方法中利用外部类调用该私有静态方法
4.方法引用
方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象(实例)的方法或构造器。与 lambda 联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
5.Stream
新添加的 Stream 流—是一个来自数据源的元素队列并支持聚合操作。把真正的函数式编程风格引入到 Java 中。
不存储数据,也不修改原始源。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
// 1. for 循环实现 List list = new ArrayList(); for (String s : list) { if (s.length() > 3) { lista.add(s); } } System.out.println(lista);
// 2. 迭代器实现
List<String> listb = new ArrayList<>();
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String s = it.next();
if(s.length() > 3) {
listb.add(s);
}
}
System.out.println(listb);
// 3. stream实现
List listc = list.stream().filter(s->s.length()>3)
.collect(Collectors.toList());
System.out.println(listc);
复制代码
复制代码
几者关系
lambda 表达式是传统方法的语法糖,简化并且改造传统内部类实现设计方案的另一种实现模式。
方法引用又是 lambda 基础上的语法糖,和 Stream 没有关系,简化方法调用的。
Stream 是针对数据和集合的强化优化操作,可以和 lambda 结合起来简化编码过程。
常见 API 介绍
1.聚合操作
2.Stream 的处理流程
3.获取 Stream 对象
Collection.stream(), 如 list.stream()
Collection.parallelstream(), 获得支持并发处理的流
Arrays.stream(T t)
BufferReader.lines()-> stream()
java.util.stream.IntStream.range()..
java.nio.file.Files.walk()..
java.util.Spliterator
Random.ints()
Pattern.spiltAsStream()..
4.中间操作 API{intermediate}:
无状态:即处理数据时,不受前置中间操作的影响
5.终结操作|结束操作{Terminal}
一个 steam 对象只能有一个 Terminal 操作。这个操作不可逆,一旦发生,就会真实处理数据生成对应结果
forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator
anyMatch/AllMatch/noneMatch/findfirst/findAny 等
short-circuiting : 在无限大的 stream 中返回有限大的 stream 需要包含短路操作是有必要的
Stream 转换
// 1. 批量数据 -> Stream对象
// 多个数据
Stream stream = Stream.of("admin", "tom", "jerry");
// 数组
String [] strArrays = new String[] {"xueqi", "biyao"};
Stream stream2 = Arrays.stream(strArrays);
// 列表
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
Stream stream3 = list.stream();
// 集合
Set<String> set = new HashSet<>();
set.add("aaa");
set.add("bbb");
set.add("ccc");
Stream stream4 = set.stream();
// Map
Map<String, Integer> map = new HashMap<>();
map.put("tom", 1000);
map.put("jerry", 1200);
map.put("shuke", 1000);
Stream stream5 = map.entrySet().stream();
//2. Stream对象对于基本数据类型的功能封装
//int / long / double
IntStream.of(new int[] {10, 20, 30}).forEach(System.out::println); //只做一次拆箱装箱
IntStream.range(1, 5).forEach(System.out::println);
IntStream.rangeClosed(1, 5).forEach(System.out::println);
// 3. Stream对象 --> 转换得到指定的数据类型
// 数组
Object [] objx = stream.toArray(String[]::new);
// 字符串
String str = stream.collect(Collectors.joining()).toString();
System.out.println(str);
// 列表
//List<String> listx = (List<String>) stream.collect(Collectors.toList());
System.out.println(listx);
// 集合
//Set<String> setx = (Set<String>) stream.collect(Collectors.toSet());
System.out.println(setx);
// Map
//Map<String, String> mapx = (Map<String, String>) stream.collect(Collectors.toMap(x->x, y->"value:"+y));
System.out.println(mapx);
复制代码
复制代码
Stream 常见操作
// Stream中常见的API操作
List<String> accountList = new ArrayList<>();
accountList.add("tom");
accountList.add("jerry");
accountList.add("apha");
accountList.add("beta");
accountList.add("shuke");
// map() 中间操作,map()方法接收一个Functional接口
accountList = accountList.stream().map(x->"name:" + x).collect(Collectors.toList());
// filter() 添加过滤条件,过滤符合条件的用户
accountList = accountList.stream().filter(x-> x.length() > 3).collect(Collectors.toList());
// forEach 增强型循环
accountList.forEach(x-> System.out.println("forEach->" + x));
// peek() 中间操作,迭代数据完成数据的依次处理过程
accountList.stream()
.peek(x -> System.out.println("peek 1: " + x))
.peek(x -> System.out.println("peek 2:" + x))
.forEach(System.out::println);// 合并多个过程 迭代只发生一次
accountList.forEach(System.out::println);
// Stream中对于数字运算的支持
List<Integer> intList = new ArrayList<>();
intList.add(20);
intList.add(19);
intList.add(7);
intList.add(8);
intList.add(86);
intList.add(11);
intList.add(3);
intList.add(20);
// skip() 中间操作,有状态,跳过部分数据
intList.stream().skip(3).forEach(System.out::println);
// limit() 中间操作,有状态,限制输出数据量
intList.stream().skip(3).limit(2).forEach(System.out::println);
// distinct() 中间操作,有状态,剔除重复的数据
intList.stream().distinct().forEach(System.out::println);
// sorted() 中间操作,有状态,排序
// max() 获取最大值
Optional optional = intList.stream().max((x, y)-> x-y);
System.out.println(optional.get());
// min() 获取最小值
// reduce() 合并处理数据
Optional optional2 = intList.stream().reduce((sum, x)-> sum + x);
System.out.println(optional2.get());
复制代码
复制代码
6.案例
问题一:将实例 List 转化为 Map
对于 List
来说,我需要将其形变为 Map<Table.id,Table>,用如下流处理代码
//Table类
public class DmTable {
private Integer id;
private String tableName;
private String tableComment;
private Integer datasourceId;
private Integer directoryId;
private Boolean partitionFlag;
private Integer columnNum;
// ......
}
tableMap=TableList.stream().collect(Collectors.toMap(Table::getId, b -> b);
// 等效于
tableMap=TableList.stream().collect(Collectors.toMap(Table::getId, Function.identity()));// 静态方法 实现 return t -> t;
复制代码
复制代码
问题二:将集合分成若干类别
使用问题一中的 Table 类,对于 List
,我需要将其按照 partitionFlag 分类,Collector 提供两种方法 partitioningBy()、groupingBy()。前者分成满足条件与不满足条件两类,后者可按条件分成若干类别的 Map。
Map<Boolean, List<Table>> tablePartition = tableList
.stream().collect(Collectors.partitioningBy(item -> item.getPartitionFlag() == true));
复制代码
复制代码
> tablePartition = tableList.stream().collect(Collectors.partitioningBy(item -> item.getPartitionFlag() == true,Collectors.counting()));
可输出符合要求的个数。
groupingBy()可对字符串长度分组。
List<String> strings=Arrays.asList(“this”,”is”,”a”,”test”);
Map<Integer, List<String>> stringsMap = strings
.stream().collect(Collectors.groupingBy(String::length);
复制代码
复制代码
结果输出多分类的 map,key 值为字符串长度。
注意:如果是从数据库获取数据,务必将分组操作放在数据库中执行,java8 新增方法只适合处理内存中的数据。
问题三:从 list 中得到某个特定的对象
获得 List
中 columnNum 最多的 table 对象
tableList.stream().sorted(comparingInt(Table::getColumnNum)).collect(Collectors.toList()).get(tableList.size() - 1);
复制代码
复制代码
添加中间操作 reversed() 可获取最小 columnNum 的对象
问题四: 得到 Map<Table,Table.columnNum>中最大 columnNum 的 table
List<Map.Entry<Table, Integer>> list = new ArrayList(tableMap.entrySet());
Collections.sort(list, (o1, o2) -> (o2.getValue() - o1.getValue()));
list.get(0).getKey();
复制代码
复制代码
7.性能与安全
本文分享自华为云社区《如何善用函数式接口简化云服务业务代码开发》,原文作者:luanzhen 。
点击关注,第一时间了解华为云新鲜技术~
评论