Java8-Lambda表达式及Stream API

Java 8 中最重要的两个东西 Lambda 表达式与 Stream API 。

Lambda表达式相信大家都很熟悉了,在日常编码中也经常会用到。所谓温故而知新,本文就是通过对知识点的复习进行整理出来的。希望可以帮到需要的小伙伴。

Stream 流操作,经常和 Lambda 一同使用,下面我们来逐一讲解。

1、Lambda

1.1 何为 Lambda 表达式?

 Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);

可以写出更简洁、更灵活的代码;

作为一种更紧凑的代码风格,是Java语言表达能力得到提升。

1.2 Lambda 表达式如何使用?

1.2.1 基础语法

操作符:->

表达式左侧:参数列表,例如: 无参形式,直接使用 () -> {}

表达式右侧:执行代码块/Lambda 体

首先,我们来看一个例子,体验一下 Lambda表达式。

在没有 Lambda 之前,比如我们最熟悉的 Comparator 比较两个值的大小的匿名内部类:

@Test
public void test01(){
    //匿名内部类
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1,o2);
        }

        @Override
        public boolean equals(Object obj) {
            return false;
        }
    };
    //调用
    TreeSet<Integer> set = new TreeSet<>(comparator);
}

当使用了 Lambda 表达式之后,我们可以写成下面这种样子:

@Test
public void test02(){
    // Lambda 表达式
    Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);

    TreeSet<Integer> set = new TreeSet<>(comparator);
}

 怎么样?代码看起来是不是舒服多了 !由原来的一坨,变成了一行,非常清晰,这就是 Lambda 表达式的魅力。

下面,我们一起来学习一下 Lambda 表达式的其他语法及用法。

1.2.2 Lambad表达式的几种常用格式

  • 无参数,无返回值

        例如 Runnable 接口

public class Test02 {
	int num = 10; //jdk 1.7以前 必须final修饰
    
    @Test
    public void test01(){
        //匿名内部类
        new Runnable() {
            @Override
            public void run() {
                //在局部类中引用同级局部变量
                //只读
                System.out.println("Hello World" + num);
            }
        };
    }

    @Test
    public void test02(){
        //语法糖
     	Runnable runnable = () -> {
         	System.out.println("Hello Lambda");
     	};
    }
}
  • 有一个参数,无返回值
    @Test
    public void test03(){
        Consumer<String> consumer = (a) -> System.out.println(a);
        consumer.accept("Lambda 一个参数,无返回值写法测试");
    }
    
    
    /**
    * 只有一个参数的时候,表达式左侧的小括号,可以不写
    */
    @Test
    public void test03(){
        Consumer<String> consumer = a -> System.out.println(a);
        consumer.accept("我觉得还行!");
    }
    
    
  • 有两个及以上的参数,有返回值,并且 Lambda 体中有多条语句
    @Test
    public void test04(){
        Comparator<Integer> comparator = (a, b) -> {
            System.out.println("比较接口");
            return Integer.compare(a, b);
        };
    }
    
  • 有两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写)
    @Test
    public void test04(){
        Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
    }
    

    注意:Lambda 表达式 参数的数据类型可以省略不写 Jvm可以自动进行 “类型推断

1.2.3 案例

        调用 Collections.sort() 方法,通过定制排序 比较两个 Employee (先按照年龄比,年龄相同按照姓名比),使用 Lambda 表达式作为参数传递。

  • 定义实体类
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Employee {
        
        private Integer id;
        private String name;
        private Integer age;
        private Double salary;
    }
    
  • 定义 List 传入数据
    List<Employee> emps = Arrays.asList(
        new Employee(101, "Z3", 19, 9999.99),
        new Employee(102, "L4", 20, 7777.77),
        new Employee(103, "W5", 35, 6666.66),
        new Employee(104, "Tom", 44, 1111.11),
        new Employee(105, "Jerry", 60, 4444.44)
    );
    
  • 测试

    @Test
    public void test01(){
        Collections.sort(emps, (e1, e2) -> {
            if (e1.getAge() == e2.getAge()){
                return e1.getName().compareTo(e2.getName());
            } else {
                return Integer.compare(e1.getAge(), e2.getAge());
            }
        });
    
        emps.forEach(System.out::println);
    }
    

2、Java8 内置四大核心函数式接口

2.1 什么是函数式接口?

接口中只有一个抽象方法的接口,使用 @FunctionalIterface 注解修饰

 我们通过使用自定义的函数式接口来配合 Lambda 表达式写一个例子。

  • 定义一个函数式接口
    @FunctionalInterface
    public interface MyFun {
    
        /**
         * 两个整数操作,操作类型由用户决定
         */
        Integer operate(Integer a, Integer b);
    }
    
  • 具体的使用
    public Integer operation(Integer a, Integer b, MyFun myFun) {
    	return myFun.operate(a, b);
    }
    
    @Test
    public void test() {
    	Integer result = operation(1, 2, (x, y) -> x + y);
    	System.out.println(result);
    }

    上面的例子,是我们自己定义的,那么后续当使用 Lambda 表达式的时候,是不是每次都需要去自定义一个类似的函数式接口呢?大家不用操作,Java8 已经内置了 4 个核心函数接口,我们来逐一看一下。

2.2 内置函数式接口

函数式接口参数类型返回类型用途
Consumer
消费型接口
Tvoid对类型为T的对象应用操作:void accept(T t)
Supplier
提供型接口
T返回类型为T的对象:T get()
Function<T, R>
函数型接口
TR对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t)
Predicate
断言型接口
Tboolean确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t)

2.2.1 消费型接口

@Test
public void test01(){
    //Consumer
    Consumer<Integer> consumer = (x) -> System.out.println("消费型接口" + x);
    //test
    consumer.accept(100);
}

2.2.2 提供型接口

@Test
public void test02(){
    List<Integer> list = new ArrayList<>();
    List<Integer> integers = Arrays.asList(1,2,3); 
    list.addAll(integers);
    //Supplier<T>
    Supplier<Integer> supplier = () -> (int)(Math.random() * 10);
    list.add(supplier.get());
    System.out.println(supplier);
    for (Integer integer : list) {
        System.out.println(integer);
    }
}

2.2.3 函数型接口

@Test
public void test03(){
    //Function<T, R>
    String oldStr = "abc123456xyz";
    Function<String, String> function = (s) -> s.substring(1, s.length()-1);
    //test
    System.out.println(function.apply(oldStr));
}

2.2.4 断言型接口

@Test
public void test04(){
    //Predicate<T>
    Integer age = 35;
    Predicate<Integer> predicate = (i) -> i >= 35;
    if (predicate.test(age)){
        System.out.println("你该退休了");
    } else {
        System.out.println("我觉得还OK啦");
    }
}

2.2.5 其他接口

在开发工作中,合理运用 Lambda 表达式,可以有效提高我们代码的可读性及美观性,这也是官方建议使用的方式。

3、 Stream API

3.1 什么是 Stream?

流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

集合讲的是数据,流讲的是计算。

注意:

Stream 自己不会存储元素。

Stream 不会改变源对象。相反,他们会发挥一个持有结果的新 Stream。

Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

3.2 Stream 操作

3.2.1 创建 Stream

        一个数据源(如:集合、数组)获取一个流。

/**
* 创建流
*/
@Test
public void test01(){
    /**
    * 集合流
    *  - Collection.stream() 串行流
    *  - Collection.parallelStream() 并行流
    */
    List<String> list = new ArrayList<>();
    Stream<String> stream1 = list.stream();

    //数组流
    //Arrays.stream(array)
    String[] strings = new String[10];
    Stream<String> stream2 = Arrays.stream(strings);

    //Stream 静态方法
    //Stream.of(...)
    Stream<Integer> stream3 = Stream.of(1, 2, 3);

    //无限流
    //迭代
    Stream<Integer> stream4 = Stream.iterate(0, (i) -> ++i+i++);
    stream4.forEach(System.out::println);

    //生成
    Stream.generate(() -> Math.random())
        .limit(5)
        .forEach(System.out::println);
}

3.2.2 中间操作

        一个中间操作链,对数据源的数据进行处理。

        多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。

  •  筛选与切片
方法描述
filter接收 Lambda ,从流中排除某些元素
limit截断流,使其元素不超过给定数量
skip跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补
distinct筛选,通过流所生成的 hashCode() 与 equals() 取除重复元素

 

List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test01(){
    emps.stream()
        .filter((x) -> x.getAge() > 35)
        .limit(3) //短路?达到满足不再内部迭代
        .distinct()
        .skip(1)
        .forEach(System.out::println);

}
  • 映射
方法描述
map

接收 Lambda ,将元素转换为其他形式或提取信息;

接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

flatMap

接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流

@Test
public void test02(){
    List<String> list = Arrays.asList("a", "b", "c");
    list.stream()
        .map((str) -> str.toUpperCase())
        .forEach(System.out::println);
}

flatMap:

public Stream<Character> filterCharacter(String str){
    List<Character> list = new ArrayList<>();
    for (char c : str.toCharArray()) {
        list.add(c);
    }

    return list.stream();
}

@Test
public void test03(){
    List<String> list = Arrays.asList("a", "b", "c");
    Test02 test02 = new Test02();
    list.stream()
        .flatMap(test02::filterCharacter)
        .forEach(System.out::println);
}
  • 排序
方法描述
sorted()自然排序
sorted(Comparator c)

定制排序

        Comparable:自然排序 

@Test
public void test04(){
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    list.stream()
        .sorted() //comparaTo()
        .forEach(System.out::println);
}

        Comparator:定制排序

@Test
public void test05(){
    emps.stream()
        .sorted((e1, e2) -> { //compara()
            if (e1.getAge().equals(e2.getAge())){
                return e1.getName().compareTo(e2.getName());
            } else {
                return e1.getAge().compareTo(e2.getAge());
            }
        })
        .forEach(System.out::println);
}
  • 查找、匹配 (终止操作)
方法描述
allMatch检查是否匹配所有元素
anyMatch检查是否至少匹配一个元素
noneMatch检查是否没有匹配所有元素
findFirst返回第一个元素
findAny返回当前流中的任意元素
count返回流中元素的总个数
max返回流中最大值
min返回流中最小值
public enum Status {
    FREE, BUSY, VOCATION;
}

@Test
public void test01(){
    List<Status> list = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION);

    boolean flag1 = list.stream()
        .allMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag1);

    boolean flag2 = list.stream()
        .anyMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag2);

    boolean flag3 = list.stream()
        .noneMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag3);

    // 避免空指针异常
    Optional<Status> op1 = list.stream()
        .findFirst();
    // 如果Optional为空 找一个替代的对象
    Status s1 = op1.orElse(Status.BUSY);
    System.out.println(s1);

    Optional<Status> op2 = list.stream()
        .findAny();
    System.out.println(op2);

    long count = list.stream()
        .count();
    System.out.println(count);
}
  • 归约、收集
方法描述

归约:

reduce(T identity, BinaryOperator) / reduce(BinaryOperator)

可以将流中的数据反复结合起来,得到一个值
收集:collect将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法

        reduce:

/**
* Java:
*  - reduce:需提供默认值(初始值)
* Kotlin:
*  - fold:不需要默认值(初始值)
*  - reduce:需提供默认值(初始值)
*/
@Test
public void test01(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer integer = list.stream()
        .reduce(0, (x, y) -> x + y);
    System.out.println(integer);
}

3.2.3 终止操作

  • 收集 collect
方法描述
collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream 中元素做汇总的方法
List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test02(){
    //放入List
    List<String> list = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toList()); 
    list.forEach(System.out::println);
    
	//放入Set
    Set<String> set = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toSet());
    set.forEach(System.out::println);

    //放入LinkedHashSet
    LinkedHashSet<String> linkedHashSet = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toCollection(LinkedHashSet::new));
    linkedHashSet.forEach(System.out::println);
}

@Test
public void test03(){
    //总数
    Long count = emps.stream()
        .collect(Collectors.counting());
    System.out.println(count);

    //平均值
    Double avg = emps.stream()
        .collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println(avg);

    //总和
    Double sum = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println(sum);

    //最大值
    Optional<Employee> max = emps.stream()
        .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
    System.out.println(max.get());

    //最小值
    Optional<Double> min = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.minBy(Double::compare));
    System.out.println(min.get());
}

@Test
public void test04(){
    //分组
    Map<Integer, List<Employee>> map = emps.stream()
        .collect(Collectors.groupingBy(Employee::getId));
    System.out.println(map);

    //多级分组
    Map<Integer, Map<String, List<Employee>>> mapMap = emps.stream()
        .collect(Collectors.groupingBy(Employee::getId, Collectors.groupingBy((e) -> {
            if (e.getAge() > 35) {
                return "开除";
            } else {
                return "继续加班";
            }
        })));
    System.out.println(mapMap);
    
    //分区
    Map<Boolean, List<Employee>> listMap = emps.stream()
        .collect(Collectors.partitioningBy((e) -> e.getSalary() > 4321));
    System.out.println(listMap);
}

@Test
public void test05(){
    //总结
    DoubleSummaryStatistics dss = emps.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println(dss.getMax());
    System.out.println(dss.getMin());
    System.out.println(dss.getSum());
    System.out.println(dss.getCount());
    System.out.println(dss.getAverage());
    
    //连接
    String str = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.joining("-")); //可传入分隔符
    System.out.println(str);
}
  •  case案例

        案例一:给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?(如:给定【1,2,3,4,5】,返回【1,4,9,16,25】)

@Test
public void test01(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    list.stream()
        .map((x) -> x * x)
        .forEach(System.out::println);
}

        案例二:怎样使用 map 和 reduce 数一数流中有多少个 Employee 呢?

List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test02(){
    Optional<Integer> result = emps.stream()
        .map((e) -> 1)
        .reduce(Integer::sum);
    System.out.println(result.get());

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值