基本概念
函数式接口:接口只有一个抽象方法的接口,称之为函数式接口。
Lambda 表达式由 ->分隔为两部分,前面是方法的参数,后面的{}内是方法的代码。
lambda没有名称和文档,如果计算不是自解释的,或者超过几行,则不要将其放入lambda表达式中。
/**
* Lambda 表示
*/
public class TestLamdba2 {
@Test
// 无参数,无返回值 形式
void test1() {
// 在jdk1.7之前,内部类使用外部变量,该变量必须是常量,1.8之后则没了.是一个常量
int num = 1;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("hello word" + num);
}
};
System.out.println("------------------");
Runnable runnable1 = () -> System.out.println("hello 无参数 无返回值");
runnable1.run();
}
// 语法格式二: 有一个参数,无返回值
@Test
public void test2() {
Consumer<String> consumer = System.out::println;
consumer.accept("是顶级so");
}
// 语法格式三:剪头左侧如果只有一个参数,小括号可以不写
@Test
public void test3() {
Consumer<String> consumer = x -> System.out.println(x);
consumer.accept("是顶级so");
}
// 语法格式四:有多个参数,并且 lambda 体中有多条语句,必须有大括号
@Test
public void test4() {
Comparator<Integer> comparable = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
}
// 语法格式5:如果有多个参数,并且lambda 体有返回值,且只有一条语句
@Test
public void test5() {
Comparator<Integer> comparable = (x, y) -> Integer.compare(x, y);
}
// 语法格式6:类型推断
}
内置函数式接口
更多看 java.util.function 包下的接口类
函数式接口 | 函数描述符 |
---|---|
Predicate | (T) -> boolean |
Consumer | (T) -> void |
Function< T, R > | (T) -> R |
Supplier | ( ) -> T |
UnaryOperator | (T) -> T |
BinaryOperator | (T, T) -> T |
BiPredicate<L, R> | (L, R) -> boolean |
BiConsumer<T, U> | (T, U) -> void |
BiFunction<T, U, R> | (T, U) -> R |
/**
* java8 内置的四大核心函数式接口。
* Consumer<T> :消费性接口
* void accept(T t)
* Supplier<T> :供给型接口
* T get();
* Function() 函数型接口
* R apply(T t)
* predicate<T> 断言型接口
* boolean test(T t)
*/
public class TestLambda4 {
// 消费性接口
public void happy(double money, Consumer<Double> consumer) {
consumer.accept(money);
}
@Test
public void test1() {
happy(1000, (m) -> System.out.println("大保健啦啦啦"));
}
// 供给型接口
// 需求:产生一些整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> supplier) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = supplier.get();
list.add(n);
}
return list;
}
// 测试供给型接口
@Test
public void test2() {
List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
for (Integer integer : numList) {
System.out.println(integer);
}
}
// 函数型接口
// 需求:写个方法专门处理字符串
public String strHandler(String s, Function<String, String> function) {
return function.apply(s);
}
// 测试函数型接
@Test
public void test3() {
String s = strHandler("/t/t/t 是尼克斯", (str) -> str.trim());
System.out.println(s);
// 其他字符串操作
}
// 断言型接口
// 需求: 将满足条件的字符串,放入集合
public List<String> filterStr(List<String> list, Predicate<String> pre) {
List<String> stringList = new ArrayList<>();
for (String s : stringList) {
if (pre.test(s)) {
stringList.add(s);
}
}
return stringList;
}
// 测试断言型接口
@Test
public void test4() {
List<String> list = Arrays.asList("hello", "fjc", "djfocfc");
List<String> filterStr = filterStr(list, (s) -> s.length() > 3);
for (String s : filterStr) {
System.out.println(s);
}
}
}
lambda方法引用
/**
*注意:还有构造器方法引用。
* 方法引用:若lambda体中有方法已经实现了,我们可以使用“方法引用”
* 主要有三种语法格式:、
* 1:对象::实例方法名
* 2: 类:: 静态方法
* 3, 类::实例方法
*
* */
public class TestLambd5 {
@Test
public void test1(){
Consumer<String> consumer = System.out::println;
// 等价于 PrintStream ps = System.out ; Consumer<String> consumer = ps::println
}
@Test
public void test2(){
Employee employee = new Employee();
Supplier<String> sup=()->employee.getName();
String s = sup.get();
System.out.println(s);
// 可以改写为
Supplier<Integer> sup2 = employee::getAge;
}
// 类::静态方法
@Test
public void test3(){
Comparator<Integer> comparator = (x,y)->Integer.compare(x,y);
Comparator<Integer> comparator1 = Integer::compare;
}
// 类::实例方法名
@Test
public void test4(){
BiPredicate<String,String> bip = (x,y)->x.equals(y);
BiPredicate<String,String> bip2 = String::equals;
}
// lambda 构造器引用
// 注意:函数式接口的参数列表要和接口抽象方法的参数列表保持一致
// 调用的是无参构造器
@Test
public void test5(){
Supplier<Employee> sup = ()->new Employee();
// 构造器引用
Supplier<Employee> supplier = Employee::new;
Employee employee = supplier.get();
System.out.println(employee);
}
}
流操作
集合是存储数据的,Stream是操作数据的,在俩个端中间进行一系列操作,不会改变,原来的数据源。
StreamAPI 针对常见的集合数据处理,是对容器类的增强,将多个集合数据的多个操作以流水线的方式组合一起。
针对接口而非具体类型进行编程,可以降低程序的耦合性,提高灵活性,提高复用性。接口常被用于传递代码。
(通过接口传递行为代码,就要传递一个实现了该接口的实例对象,最简洁是使用匿名类)
匿名类和表达式的区别
lambda表达式与匿名类很像,主要就是简化了语法,那它不是语法糖。java会为每个匿名类生成一个类,但lambda表达式不会,lambda表达式通常比较短,为每个表达式生成一个类会生成大量的类,性能会受到影响。
public File[] listFiles(FilenameFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss)
if ((filter == null) || filter.accept(this, s))
files.add(new File(s, this));
return files.toArray(new File[files.size()]);
}
// 进行对比,去理解接口传递代码
File f = new File(".");
File[] files = f.listFiles(new FilenameFilter(){
@Override
public boolean accept(File dir,String name){
if(name.endsWoth(".txt")){
return true;
}
return false;
}
})
使用
/**
* 使用三个步走
* 1,创建stream
* 2,中间操作
* 3,终止操作
*
* */
public class TestStreamAPI1 {
@Test
public void test1(){
// 1.从Collection集合中提供的Stream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
// 2,通过Arrays 中静态方法Stream()获取数组
Employee[] employees = new Employee[10];
Stream<Employee> stream1 = Arrays.stream(employees);
// 3.通过Stream类的静态方法of()
Stream<String> stream2 = Stream.of("aa", "vv", "cc");
// 4,创建无限流
// 迭代
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
iterate.limit(10)
.forEach(System.out::println);
// 生成
}
}
中间步骤
filter
该方法接受Predicate ,T->boolean,用于集合进行筛选,返回满足条件的元素。
例子:
// 筛选满足,以字母“J”开头的元素
sourceList.stream()
.filter(s-> s.startsWith("J"))
.map(String :: toUpperCase)
.forEach(System.out :: println);
只展示做什么,而不展示怎么做 这是Stream的一个优点
long count = asList.stream()
.filter(s -> s.length() > 3)
.count();
List<String> lengthUp4List = asList.stream()
.filter(s -> s.length() > 4)
.collect(Collectors.toList());
distinct
去重。filter和map都是无状态的,distinct不同,它是有状态的,
例子
List<Integer> integerList = Arrays.asList(1, 2, 4, 5, 6, 6, 4, 2);
integerList.stream()
.filter(i-> (i%2==0))
.distinct()
.forEach(System.out::println);
skip/limit
skip跳过流中的n个元素(若元素不足,则返回空流)limit处理skip之后的流,处理maxSize个,后序的不在处理,和流水线做工一样。
例子:将学生列表按照分数排序,返回第三名到第五名
// 注意skip和limit的用法区别,以及如何配合使用
List<Student> list = students.stream().
sorted(Comparator.comparing(Student::getScore).reversed())
.skip(2)
.linit(3)
.collect(Cokkectors.toList());
注意:skip和limit 只处理部分元素,就进入下一个流程,提前结束的操作称之为短路操作。
map
还有几个变形,方便后序进行求和等等处理
List<String> sourceList = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");
// 接受一个函数,这个函数会应用到每个元素上,并将其映射为一个新元素。
// 返回是个流
sourceList.stream()
.map(String::length)
.forEach(System.out::print);
// 输出 4,10,6,3
flatMap
概述:flatMap用于将多个流合并成一个流,俗称流的扁平化。
注意点:Arrays.Stream可以将一个数组转换为一个流
例子
// 目标返回 hello,word,老马,编程
List<String> lines = Arrays.asList("hello word","老马 编程");
第一次尝试
// 下面返回是
/* Stream<String[]> 数组,并不是想要的 Stream<String>
* [Ljava.lang.String;@60438a68
[Ljava.lang.String;@140e5a13
* */
lines.stream()
.map(s->s.split(" "))
.forEach(System.out::println);
第二次
/*
返回的是:各个流,需要把这些流连接为一个流,使用flatMap
* java.util.stream.ReferencePipeline$Head@192d3247
java.util.stream.ReferencePipeline$Head@3ecd23d9
* */
lines.stream()
.map(s->s.split(" "))
.map(Arrays::stream)
.forEach(System.out::println);
idea还会提示,牛逼
最终
lines.stream()
.map(s->s.split(" "))
.flatMap(Arrays::stream)
.forEach(System.out::println);
// 或者
List<String> words = lines.stream()
.flatMap(line->Arrays.stream(line.split("\\s+")))
.collect(Collectors.toList());
还有几个变形
/**
* 流的中间操作
*/
public class TestStreamAPI2 {
/*
* 中间操作:
* 筛选与切片
* filter-接受Lambda ,从流中排除某些元素
* limit-截断流,使其元素不超过给定元素
* skip-跳过元素,返回一个丢掉前n个元素的不足n个
* distinct-筛选
*
* */
List<Employee> employees = Arrays.asList(
new Employee("张三1", 12, 2222),
new Employee("张三2", 12, 2222),
new Employee("张三3", 40, 2222),
new Employee("张三4", 12, 22222),
new Employee("张三5", 12, 2222)
);
@Test
public void test1() {
Stream<Employee> stream = employees.stream()
//中间操作
.filter((e) -> e.getAge() > 32);
// 终止操作
stream.forEach(System.out::println);
}
@Test
public void test2() {
employees.stream()
.filter((e) -> {
System.out.println("短路"); // 只迭代两次。要2个,就迭代俩次
return e.getSalary() > 200;
}).limit(2)
.forEach(System.out::println);
}
@Test
public void test4(){
employees.stream()
.filter((e)->e.getSalary()>2)
.skip(2) // 跳过2个结果
.forEach(System.out::println);
}
// 映射操作
@Test
public void test5(){
List<String> list = Arrays.asList("aaa","bb","cc","dddd");
list.stream()
.map((str)->str.toUpperCase())
.forEach(System.out::println);
System.out.println("------------");
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
System.out.println("------------");
// todo 注意体会
Stream<Stream<Character>> streamStream = list.stream().map(TestStreamAPI2::filterCharacter);
streamStream.forEach((sm)->{
sm.forEach(System.out::println);
});
System.out.println("------------");
}
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
}
终端操作
中间操作不触发实际的执行,返回值是stream。而终端操作触发执行,返回一个具体的值。
Match
接受一个谓词 predicate 返回一个boolean 值。用于判定流中的元素是否满足一定的条件。
allMatch(都满足返回true)、anyMatch(任意一个满足),noneMatch(都不满足)
注意:如果流为空,那么这几个函数的返回值都是true。
例子
List<String> sourceList = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");
boolean allMatch = sourceList.stream()
.allMatch(s -> s.equals("Java"));
System.out.println(allMatch);
find
包括 findFirst(取第一个),findAny(取满足条件任意一个)
例子:
List<String> sourceList = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");
//满足长度小于4,元素有 "PHP", "C#"
sourceList.stream()
.filter(s -> s.length()<4)
.findFirst()
.ifPresent(System.out::println);
sourceList.stream()
.filter(s -> s.length()<4)
.findAny()
.ifPresent(System.out::println);
reduce
reduce----折叠。是max,min,count 的更加通用的函数,将流中的元素归约为一个值。
三个入参的,比前俩个更加灵活以及强大
有三个重载方法
例子:
List<Integer> integerList = Arrays.asList(1, 2, 4, 5, 6, 6, 4, 2);
// 一个入参的
integerList.stream()
.reduce(Integer::max)
.ifPresent(System.out::println);
integerList.stream()
.reduce(Integer::min)
.ifPresent(System.out::println);
// 二个个入参的
Integer sum = integerList.stream()
.filter(i -> (i % 2 == 0))
.reduce(0, Integer::sum);
System.out.println(sum);
// 三个入参的
// 前两个灵活性不足,元素是什么类型的,输出也是什么类型的。比如:元素是int,累加之后int放不下,需要Long类型。下面这个就可以。
// 当然不局限这个
String string = integerList.stream()
.reduce(0L, Long::sum, (a, b) -> 0L)
.toString();
System.out.println(string);
max,min,count,foreach
toArray
toArray 将流转换为数组
Object[] toArray();
<A> A[] toArrya(IntFunction<A []> generator)
例子 获取90分以上的学生数组
Student[] above90Arr = students.stream().filter(t->t.getScore()> 90 ).toArray(Student[] :: new);
// 这里时lamdba 表达式 的构造器引用(还有方法引用)
collect
collect方法用于收集流中的元素,并放到不同类型的结果中,比如List、Set或者Map
例子
List<String> filterList = list.stream()
.filter(s -> s.startsWith("J"))
.collect(Collectors.toList());
Stream toMap
list集合toMap 的时候,若是出现key冲突,并且你没有显示指定合并规则,讲抛出异常
// 发生重复key,不处理(或者说保留之前的)
allLegalAccountName.stream().collect(Collectors.toMap(LegalAccountRefEntity::getLegalUnitId,LegalAccountRefEntity::getGroupAccountName,(k1,k2)->k1));
函数式数据处理思维
流定义了很多数据处理的基本函数,对于一个具体的数据处理问题,解决的主要思路就是组合利用这些基本函数,以声明式的方式简洁的实现期望的功能,这种思路就是函数式数据处理思维,想比直接利用容器类API命令思维,。
Stream API也与各种基于Unix 系统的管道命令类似。 Unix有很多命令,大部分命令只是专注于完成一件事情,但是可以通过管道的方式将多个命令链接起来,完成一些复杂的功能。
例子-分析访问nginx 日志,统计出访问次数最多的20个ip地址,及其访问次数
cat nginx_asscess.log | awk '{print $1 }'| sort | uniq -c | sort -rnk l | head -n 20
理解Collect
例子
List<Student> above90List() = student.stream().filter(t->t.getSorce()>90).collect(Collectors.toList())