本文,前段是原理,后半段是案例,如果懒得看原理的朋友,可以直接跳到案例
敲黑板,跟我边做边学,直接到案例那一段,非常详细。
目前已经到了java14了,JDK8是Oracle在2014年3月19日发布正式版的,最大的改进是Lambda表达式(以及因之带来的函数式接口,很多原有类都做了变更,但能够与以往版本兼容,堪称奇功!),还有Stream API流式处理,joda-time等等一些新特性。
虽然看着很先进,其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。
Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
在 Java8 以前,我们想要让一个方法可以与用户进行交互,比如说使用方法内的局部变量;
这时候就只能使用接口做为参数,让用户实现这个接口或使用匿名内部类的形式,把局部变量通过接口方法传给用户。
传统匿名内部类缺点:代码臃肿,难以阅读
使用Lambda 表达式
Lambda 表达式将函数当成参数传递给某个方法,或者把代码本身当作数据处理;
// 使用匿名内部类
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
// 或者使用 lambda expression
btn.setOnAction(event -> System.out.println("Hello World!"));
Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。
由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等。
下图,查看容器map的foreach的方法:
我们基本不需要定义自己的函数式接口,Java8 已经给我们提供了大量的默认函数式接口,基本够用,在 rt.jar 包的 java.util.function 目录下可以看到所有默认的函数式接口,大致分为几类
Function<T,R> T 作为输入,返回的 R 作为输出
Predicate<T> T 作为输入 ,返回 boolean 值的输出
Consumer<T> T 作为输入 ,没有输出
Supplier<R> 没有输入 , R 作为输出
BinaryOperator<T> 两个 T 作为输入 ,T 同样是输出
UnaryOperator<T> 是 Function 的变种 ,输入输出者是 T
java8中的集合支持一个新的Stream方法,它会返回一个流,到底什么是流呢?
流的使用包括三件事:
经典案例:
//题目,排序,删选大于6的
//初始化
List<Integer> integers = new ArrayList<>();
integers.add(5);
integers.add(7);
integers.add(3);
integers.add(8);
integers.add(4);
//传统 1先排序(倒叙),2比较大小
Collections.sort(integers, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()){
Integer next = iterator.next();
if (next > 6){
iterator.remove();
}
}
integers.forEach(System.out::println);
//使用Stream
List<Integer> collect = integers.stream().filter(i -> i < 6).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
collect.forEach(System.out::println);
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;
高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如:
所有元素求和
过滤掉长度大于 10 的字符串
获取每个字符串的首字母
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作
Stream 的另外一大特点是,数据源本身可以是无限的
我是用springboot做案例的,版本是 2.2.5.RELEASE
@SpringBootTest
class LambdsStreamApplicationTests {
@Test
public void printTest(){
//打印list
List<String> list = Arrays.asList("帅哥","灿灿");
list.forEach(System.out::println);
//打印map
Map<Integer,String> map = new HashMap<>(5);
map.put(1,"帅哥");
map.put(2,"灿灿");
map.forEach((k,v)->System.out.println("key: "+k+" value: "+v));
}
}
输出:
帅哥
灿灿
key: 1 value: 帅哥
key: 2 value: 灿灿
用了上面的打印后,循环遍历打印感觉low出天际了
@Test
public void printTest2(){
//打印list
List<String> list = Arrays.asList("帅哥","灿灿");
list.forEach(v->System.out.println("至理名言:"+v));
//打印map
Map<Integer,String> map = new HashMap<>(5);
map.put(1,"帅哥");
map.put(2,"灿灿");
map.forEach((k,v)->{
if(k>1){
System.out.println("key大于1,输出至理名言, 我是"+v);
}
});
}
输出:
为什么我要举两个打印的例子,因为真的很重要,要消化一下。
采用对比,来学匿名内部类和lambds的转换,其实我看来,lambds就是用来解决匿名内部类的复杂问题
@Test
public void lambdsTest(){
//之前,线程,匿名内部类,java8 之前
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
//之后,lambds表达式
new Thread(()->System.out.println(Thread.currentThread().getName())).start();
//==============================================================================
//之前,线程,匿名内部类,java8 之前
Runnable race1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello ccan !");
}
};
//之后,lambds表达式
Runnable race2 = () -> System.out.println("Hello ccan !");
race1.run();
race2.run();
}
由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等。
@Test
public void funcTest(){
//函数式接口,这个非常重要,接口中只能有一个接口方法,可以有静态方法和默认方法,使用 @FunctionalInterface 标记,默认方法可以被覆写
//函数式接口存在的意义非常重要, 默认方法允许在不打破现有继承体系的基础上改进接口
//给 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等
Function<String,String> function = (x) -> {return x+"Function";};
// hello world Function
System.out.println(function.apply("hello world"));
//==================================
//使用函数式接口的map
Map<Integer,String> map = new HashMap<>(5);
map.put(1,"帅哥");
map.put(2,"灿灿");
//以下BiConsumer为函数式接口,源码贴有 @FunctionalInterface
BiConsumer<Integer, String> integerStringBiConsumer = (k, v) -> {
if (k > 1) {
System.out.println("key大于1,输出至理名言, 我是" + v);
}
};
map.forEach(integerStringBiConsumer);
@Test
public void sortTest(){
String[] players = {"Xiaoming", "Jack",
"Cancan", "Tom","Alin"};
//使用匿名内部类根据 name 排序 players
//Arrays.sort(players, new Comparator() {
// @Override
// public int compare(String s1, String s2) {
// return (s1.compareTo(s2));
// }
//});
//for (String player : players) {
// System.out.println(player);
//}
//使用lambds表达式
Arrays.sort(players, Comparator.naturalOrder());
for (String player : players) {
System.out.println(player);
}
}
流的使用包括三件事:
@Test
public void streamTest(){
//直接复值的形式
Stream stream = Stream.of("a", "b", "c", 23);
stream.forEach(key -> System.out.println(key));
System.out.println("===============");
//通过数组生成
String[] array = new String[]{"abc", "efg"};
stream = Arrays.stream(array);
stream.forEach(key -> System.out.println(key));
System.out.println("===============");
//通过list生成
List<String> list = Arrays.asList(array);
stream = list.stream();
stream.forEach(key -> System.out.println(key));
System.out.println("===============");
//IntStream、LongStream、DoubleStream
IntStream stream2 = IntStream.of(1, 2, 3, 3);
DoubleStream stream4 = DoubleStream.of(1, 2, 3, 3.4);
stream2.forEach(key -> System.out.println(key));
stream4.forEach(key -> System.out.println(key));
}
写在前面,java8新特性其实不难,唯一难的就是没有多练,只有不断的敲代码,java8新特性的原理自然了然于胸,下面都是代码,请读者跟着我一起做一起练,掌握基础的,遇到问题自然迎刃而解
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
//学生编号
private String sNo;
//学生名字
private String name;
//性别
private String gender;
// 住宿地址编号
private Integer addressId;
// 个人评分
private Double score;
}
测试类(不要嫌弃多,跟着练,敲重点)
@SpringBootTest
class LambdsStreamApplicationTests {
static List<Student> students = new ArrayList<>();
@BeforeEach
public void init(){
students.add(new Student("XS1001","大军","男",1,4.5));
students.add(new Student("XS1011","河马","男",2,1.4));
students.add(new Student("XS1002","小刀","女",1,3.0));
students.add(new Student("XS1022","柯灵","男",1,3.9));
students.add(new Student("XS1003","钟归","男",2,4.9));
}
@Test
public void test1(){
//遍历打印
students.forEach(System.out::println);
}
@Test
public void filterTest(){
//去掉频繁为3以下的学生
//中间操作,流水线 .filter(student -> student.getScore() >= 3)
//终端操作 .collect(Collectors.toList());
List<Student> collect = students.stream().filter(student -> student.getScore() >= 3).collect(Collectors.toList());
collect.forEach(System.out::println);
//Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
//Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
//Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
//Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
}
@Test
public void mapTest(){
//对一个 List
List<String> collect = students.stream().map(Student::getSNo).collect(Collectors.toList());
collect.forEach(System.out::println);
//XS1001
//XS1011
//XS1002
//XS1022
//XS1003
}
@Test
public void groupTest(){
// 按照 地址Id 进行分组
Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getAddressId));
collect.forEach((k,v)->{
v.forEach(t->System.out.println("k: "+k+" v: "+t));
});
//k: 1 v: Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
//k: 1 v: Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
//k: 1 v: Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
//k: 2 v: Student(sNo=XS1011, name=河马, gender=男, addressId=2, score=1.4)
//k: 2 v: Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
}
@Test
public void group2Test(){
Map<Integer, Double> collect = students.stream().collect(Collectors.groupingBy(Student::getAddressId, Collectors.summingDouble(Student::getScore)));
collect.forEach((k,v)->System.out.println("k: "+k+" ,v: "+v));
//k: 1 ,v: 11.4
//k: 2 ,v: 6.300000000000001
}
@Test
public void sortTest(){
//按照某个熟悉排序
students.sort((v1,v2)-> v2.getScore().compareTo(v1.getScore()));
students.forEach(System.out::println);
}
@Test
public void sortSTest(){
//流处理不会改变原列表,需要接受返回值才能得到预期结果
List<Student> collect = students.stream().sorted(Comparator.comparing(Student::getScore).reversed()).collect(Collectors.toList());
collect.forEach(System.out::println);
//Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
//Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
//Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
//Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
//Student(sNo=XS1011, name=河马, gender=男, addressId=2, score=1.4)
}
@Test
public void sort2STest(){
//多列排序, score 降序, companyId升序
List<Student> collect = students.stream().sorted(Comparator.comparing(Student::getAddressId).reversed()
.thenComparing(Comparator.comparing(Student::getGender).reversed()))
.collect(Collectors.toList());
collect.forEach(System.out::println);
//Student(sNo=XS1011, name=河马, gender=男, addressId=2, score=1.4)
//Student(sNo=XS1003, name=钟归, gender=男, addressId=2, score=4.9)
//Student(sNo=XS1001, name=大军, gender=男, addressId=1, score=4.5)
//Student(sNo=XS1022, name=柯灵, gender=男, addressId=1, score=3.9)
//Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
}
@Test
public void reduceSTest(){
//总分和
Double reduce = students.stream().parallel().map(Student::getScore).reduce(0d, Double::sum);
System.out.println(reduce);
//17.700000000000003
}
}