1、lambda
1.1、lambda的基本语法格式是
(参数) -> {函数体}
1.2、什么时候可以不写(),什么时候可以不写{}
- 只有一个参数时,()可以不写,但是如果这个参数显式的声明类型的话还要加
- 函数体只有一条语句时,{}可以省略
1.3、什么是方法引用和构造器引用
1.3.1、方法引用
若Lambda体中的功能,已经有方法提供了实现,可以使用方法引用。 可以将方法引用理解为Lambda表达式的另一种表现形式。
方法引用一般有三种表现形式:
⑴实例对象名::实例方法名
用法:
public void test1(){
Consumer<String> con = (x) -> System.out.println(x);
con.accept("lambda");
Consumer<String> con1 = System.out::println;
con1.accept("方法引用");
}
⑵类名::静态方法名
用法:
public void test2(){
Comparator<Integer> com = (x, y) -> Integer.compare(x,y);
System.out.println("lambda:" + com.compare(2,3));
Comparator<Integer> com1 = Integer::compare;
System.out.println("方法引用:" + com1.compare(2,3));
}
⑶类名::实例方法名
用法:
public void test3(){
BiPredicate<String,String> bp = (x, y)->x.equals(y);
System.out.println("lambda:"+bp.test("aa","aa"));
BiPredicate<String,String> bp1 = String::equals;
System.out.println("方法引用:"+bp1.test("aa","bb"));
}
什么时候使用方法引用:
当要传递给Lambda体内的操作,已经有实现的方法了,就可以使用方法引用了
方法引用前提条件:
- 方法引用所引用的方法的参数列表必须要和函数式接口中的抽象方法的参数列表一致
- 方法引用所引用的方法的返回值必须要和函数式接口中的抽象方法的返回值一致
1.3.2、构造器引用
与函数式接口相结合,自动与函数式接口中的方法兼容,可以把构造器引用赋值给定义的方法。
构造器语法格式
类名::new
用法:
有参构造器
public void test4(){
Function<String,Example> fun = (x) -> new Example(x);
System.out.println("lambda:"+ fun.apply("123"));
//有参构造器取决于Function函数式接口中有几个参数,调用的就是哪个构造器
Function<String,Example> fun1 = Example::new;
System.out.println("方法引用:"+ fun1.apply("456"));
}
无参构造器
public void test5(){
Supplier<Example> sup = () -> new Example();
System.out.println("lambda:"+ sup.get());
Supplier<Example> sup1 = Example::new;
System.out.println("方法引用:"+ sup1.get());
}
什么时候用构造器引用
在使用lambda表达式时,满足使用构造器引用条件时可以引用
构造器引用前提
构造器参数列表要和接口中抽象方法列表一致!
1.4、解释下边代码作用以及输出
List list = Arrays.asList("abcd","Ab1","ABC2","abE","abCD");
list.stream()
.map(String::toUpperCase)
.filter("ABCDEFGH"::startsWith)
.map(String::toLowerCase)
.distinct()
.forEach(System.out::println);
作用:
- 将整个字符串字符都改为大写
- 过滤掉在所有字符串中“ABCDEFGH”没有将其作为前缀的字符串
- 将整个字符串字符都改为小写
- 去重
- 对字符串进行遍历
输出:
如果按上边代码运行会报错,因为List没有指定类型,无法调用toUpperCase,toLowerCase这两个方法
解决方法:为List指定类型:List,然后正常输出“abcd”字符串
1.5、java函数式编程接口有哪些以及作用
函数式编程接口定义:
有且仅有一个抽象方法的接口(允许有默认方法和抽象方法)
同时@FunctionalInterface注解可以起到一个检查作用,在编译期间检查所标注的接口是否符合函数式编程接口要求,如果不加这个注解,那么如果接口符合函数式编程接口,那么也是可以拿来用的
函数式编程接口:
- 消费性接口:Consumer 只接收一个参数t,但是没有返回值,方法:accept(T t)
- 供给性接口:Supplier 不接收任何参数,但有返回值,方法:get()
- 断言性接口:Predicate 主要用到test方法 其中参数t为输入参数,如果输入参数符合断言则返回true,否则false,方法:test(T t)
- 功能性接口:Function 接收一个功能参数t,并返回一个功能结果R,方法:apply(T t)
1.6、为什么新开一个线程可以这样写
new Thread(()-> {
// do sth.
}).strat();
上述写法与传入实现了run方法的Runnable实现类实例效果一致,并且阅读源码可知Runnable也是一个函数式编程接口,所以()-> {// do sth.}也就相当于返回一个Runnable接口的实现类实例。
1.7、lambda原理
在类编译时,会生成一个私有静态方法+一个内部类。
在内部类中实现了函数式接口,在实现接口的方法中,会调用编译器生成的静态方法。
在使用lambda表达式的地方,通过传递内部类实例,来调用函数式接口方法。
2、Stream API
2.1、练习
现有以下数据结构
class Student {
private int id;
private String name;
private int age;
private int classNo;
public Student (int id, String name, int age, int classNo) {
this.id = id;
this.name = name;
this.age = age;
this.classNo = classNo;
}
}
有以下数据集合
List<Student> stus = new Arraylist() {{
add(1, "小A", 13, 1);
add(2, "小B", 25, 2);
add(3, "小C", 33, 3);
add(4, "小D", 20, 2);
add(5, "小E", 36, 3);
add(6, "小F", 18, 2);
add(7, "小G", 45, 1);
}};
1)如何使用Stream API,获得stus中年龄介于15~25之间的学生列表?
像这个介于
List<Student> collect1 = stus.stream()
.filter(s -> s.getAge() >= 15 && s.getAge() <= 25)
.collect(Collectors.toList());
2)如何使用Stream API,对stus,按照classNo(班级编号)分组数据
Map<Integer, List<Student>> collect2 = stus.stream()
.collect(Collectors.groupingBy(Student::getClassNo));
3)如何求得每个班级的同学的平均年龄,结果包含两个字段:classNo+平均年龄,其中不包含name为"小F"的同学
Map<Integer, Double> collect3 = stus.stream()
.filter(e->!"小F".equals(e.getName()))
.collect(Collectors.groupingBy(Student::getClassNo, Collectors.averagingInt(Student::getAge)));
4)将3)的结果按照平均年龄倒叙排序
Map<Integer, Double> result = new LinkedHashMap<>();
stus.stream()
.filter(e->!"小F".equals(e.getName()))
.collect(Collectors.groupingBy(Student::getClassNo, Collectors.averagingInt(Student::getAge)))
.entrySet().stream().sorted(Map.Entry.<Integer,Double>comparingByValue().reversed())
.forEachOrdered(x->result.put(x.getKey(),x.getValue()));
这里穿插一个小技巧,如何查看当前方法中的泛型参数具体用到的什么类型:按住ctrl键不放,鼠标移动到要查看的方法上,然后就可以看到下边信息:
2.1、Stream API技术原理
1.下面这句代码,list集合中的元素总共被遍历了几次?
List<Integer> list = Arrays.asList(4, 3, 2, 1);
list.stream().filter(s-> 2 != s)
.filter(s-> {
System.out.println(s);
})
.map(s-> s+s)
.sorted(Comparator.comparingInt(Integer::intValue));
共被遍历两次,因为filter和map是无状态操作,可以一次遍历完成,sorted是有状态操作,依赖于之前的结果,所以sorted会再遍历一次,所以总共需要两次遍历
2.ReferencePipleLine原理,Stream API有哪些中间操作,有哪些结束操作,它们有什么区别?
Stream上的所有操作分为两类:中间操作和结束操作,中间操作只是一种标记,只有结束操作才会触发实际计算。
概念
- 中间操作:
无状态:无状态中间操作是指元素的处理不受前面元素的影响
有状态:有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果 - 结束操作:
短路操作:短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素
非短路操作:必须处理完全部元素才可以返回结果
函数
- 中间操作:
无状态:
unordered() ,filter() ,map(), mapToInt(), mapToLong(), mapToDouble(), flatMap(), flatMapToInt(), flatMapToLong() ,flatMapToDouble() ,peek()
有状态:
distinct(), sorted(), sorted() ,limit(), skip() - 结束操作:
非短路操作:
forEach() ,forEachOrdered() ,toArray() ,reduce(), collect(), max() ,min(), count()
短路操作:
anyMatch(), allMatch() ,noneMatch(), findFirst() ,findAny()