JAVA8的lambda及stream详解

本文详细介绍了Java8的lambda表达式和Stream API的使用。从lambda的基本概念、函数式接口到各种使用方式,如匿名函数和方法引用,再到Stream的原理、创建、数据操作和API接口。通过实例展示了如何进行过滤、排序、映射等操作,帮助读者深入理解Java8的这两个重要特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JAVA8的lambda及stream详解

一、lambda学习

1. 什么是lambda

lambda是JAVA8中提供的一个语法糖,它使用代码编写更加简洁、紧凑、灵活。Lambda允许把函数当做当参数来使用,是面向函数式编辑的思想。而这种参数式函数有两种用法:

  • 匿名函数:用于实现抽象方法的一块代码段,而这个抽象方法是函数式接口中的抽象方法
  • 已有函数的引用:使用双冒号直接引用类或对象的已有方法

来一起看段代码:

        List<Integer> integers= Arrays.asList(1,3,5,4,2);
        integers.sort((a,b)->a-b);
        integers.forEach(System.out::println);
        //输出正序排列数字1 2 3 4 5

2.函数式接口介绍

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为 lambda 表达式,也就是所说lambda匿名函数的使用方式。

函数式接口具体有哪些特点呢?

  1. 接口有且仅有一个抽象方法
  2. JDK8接口中的静态方法和默认方法,都不算是抽象方法。
  3. 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
  4. 使用FunctionalInterface注解的接口,但注解也不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响,只是加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

看一下JAVA8中的定义的Comparator函数式接口源码


@FunctionalInterface
public interface Comparator<T> {
    
    int compare(T o1, T o2);

    
    boolean equals(Object obj);

    
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

  
    default <U extends Comparable<? super U>> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        return thenComparing(comparing(keyExtractor));
    }

    
    
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }

    
    @SuppressWarnings("unchecked")
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }

    

3.lambda使用方式

1)匿名函数

使用lambda特定的语法,编写接口实现代码段,代码段充当匿名函数做为参数使用。

基本语法:()->{},其中()为参数列表,{}用来描述方法体

为了使语法更加简捷、紧凑,lambda几种变种语法:

  1. 语法格式一:无参,无返回
        Runnable r=()-> System.out.println("接口内容");
        r.run();
  1. 语法格式二:一参,无返回
        Consumer<String> c=(x)-> System.out.println(x);
        c.accept("一参,无返回值");

        Consumer<String> c1=x-> System.out.println(x);
        c1.accept("一参,无返回值,省去括号");
  1. 语法格式三:两参,有返回,多语句
        Comparator<Integer> c=(x,y)->{
            System.out.println("多语句测试");
            return Integer.compare(x,y);
        };
        System.out.println(c.compare(3,2));
  1. 语法格式四:两参,有返回
		//省略大括号里和return
        Comparator<Integer> c=(x,y)->Integer.compare(x,y);
        System.out.println(c.compare(3,2));

语法糖的简写要旨:能省则省

  • 省略匿名函数的传统语法结构
  • 省略参数类型,JVM编译器会自动根据上下文推断参数类型
  • 一个参数,省略lambda左侧的括号
  • 一句方法体,省略lambda右侧的大括号和return

四大内置核心函数式接口

  1. Consumer : 消费型接口
	@FunctionalInterface
	public interface Consumer<T> {
	    void accept(T t);
	}
	//使用方法
	@Test
    public void consumerInvoker(String info,Consumer<String> consumer){
        System.out.println("consumer接口调用方法体");
        consumer.accept(info);
    }
	public void testConsumer() {
        consumerInvoker("consumerInvoker的参数",(x)-> System.out.println("consumer的接口实现,使用参数为:"+ x));
    }
  //输出
  	consumer接口调用方法体
	consumer的接口实现,使用参数为:consumerInvoker的参数
  1. Supplier : 供给型接口
	@FunctionalInterface
	public interface Supplier<T> {
	    T get();
	}
    @Test
    public void supplierTest(){
        List<Integer> integers = supplierInvoker(5, () -> (int) (Math.random() * 10));
        for (Integer integer : integers) {
            System.out.println(integer);
        }
    }

    public List<Integer> supplierInvoker(int num,Supplier<Integer> supplier){
        ArrayList<Integer> integers = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            integers.add(supplier.get());
        }
        return integers;
    }
  1. Predicate : 断言型接口
	@FunctionalInterface
	public interface Predicate<T> {
	    boolean test(T t);
	}
    @Test
    public void predicateTest() {
        List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8,9,0);
        List<Integer> list1 = predicateInvoker(list, (x) -> x % 2 == 0);
        for (Integer integer : list1) {
            System.out.println(integer);
        }
    }

    public List<Integer> predicateInvoker(List<Integer> list, Predicate<Integer> pre){
        ArrayList<Integer> integers = new ArrayList<>();
        for (Integer integer : list) {
            if (pre.test(integer)) {
                integers.add(integer);
            }
        }
        return integers;
    }
  1. Function: 函数型接口
	@FunctionalInterface
	public interface Function<T, R> {
	    R apply(T t);
	}
    @Test
    public void functionTest() {
        Double v = functionInvoker(10000d, (x) -> x * (1 + 0.03));
        System.out.println("理财后我有"+ v +"元钱");
        System.out.println("提现后我还有"+functionInvoker(v,(x)->x-10)+"元钱");
    }
    public Double functionInvoker(Double money, Function<Double,Double> fun){
        System.out.println("我有"+money+"元钱");
        return fun.apply(money);
    }
    //输出
    我有10000.0元钱
	理财后我有10300.0元钱
	我有10300.0元钱
	提现后我还有10290.0元钱

四大内置核心函数式接口的扩展接口

与四大内置核心函数接口同包(java.util.function)下的其它扩展接口

接口参数返回值
BiFunction<T,U,R>T,UR
UnaryOperator<T>
(Function子接口)
TT
BinaryOperator<T>
(BiFunction子接口)
T,TT
ToDoubleBiFunction<T, U>
ToLongBiFunction<T, U>
ToIntBiFunction<T, U>
T,Udouble
long
int
ToDoubleFunction<T>
……
Tdouble
……
DoubleFunction<R>
LongFunction<R>
IntFunction<R>
double
long
int
R
BiConsumer<T, U>T,Uvoid
ObjDoubleConsumer<T>T,doublevoid
………………

java.util.function包下总共43个常用函数式接口:
BiConsumer<T,U> BiFunction<T,U,R> BinaryOperator<T> BiPredicate<T,U> BooleanSupplier Consumer<T> DoubleBinaryOperator DoubleConsumer DoubleFunction<R> DoublePredicate DoubleSupplier DoubleToIntFunction DoubleToLongFunction DoubleUnaryOperator Function<T,R> IntBinaryOperator IntConsumer IntFunction<R> IntPredicate IntSupplier IntToDoubleFunction IntToLongFunction IntUnaryOperator LongBinaryOperator LongConsumer LongFunction<R> LongPredicate LongSupplier LongToDoubleFunction LongToIntFunction LongUnaryOperator ObjDoubleConsumer<T> ObjIntConsumer<T> ObjLongConsumer<T> Predicate<T> Supplier<T> ToDoubleBiFunction<T,U> ToDoubleFunction<T> ToIntBiFunction<T,U> ToIntFunction<T> ToLongBiFunction<T,U> ToLongFunction<T> UnaryOperator<T>


2)方法引用

如果lambda表达式的匿名函数方法体中实现的功能,已经有现成的方法时,我们可以通过方法的名称引用已有方法。

方法引用等价的lambda表达式
String::valueOfx -> Sting.valueOf(x)
Object::toStringx -> x.toString()
x::toString() -> x.toString()
ArrayList::new() -> new ArrayList<>()
  • 实例方法引用
        //例1
        Consumer<String> c=(x) -> System.out.println(x);
        Consumer<String> c1=System.out::println;

        //例2
        final SysUser sysUser=new SysUser();
        Supplier<String> s=() -> sysUser.getFullname();
        Supplier<String> s1=sysUser::getFullname;
  • 静态方法引用
        Function<List<Integer>,Integer> f=(x) -> Collections.max(x);
        Function<List<Integer>,Integer> f1=Collections::max;
  • 类的实例方法引用
        //当lambda参数列表中第一个参数为是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用类的实例方法,ClassName::method
        BiPredicate<String,String> p=(x,y) -> x.equals(y);
        BiPredicate<String,String> p1=String::equals;
  • 构造方法引用
        Supplier<SysUser> su=() -> new SysUser();
        Supplier<SysUser> su1=SysUser::new;
  • 数组构造方法引用
        IntFunction<Integer[]> intFunction=(x) -> new Integer[x];
        Integer[] apply = intFunction.apply(3);
        IntFunction<Integer[]> intFunction1=Integer[]::new;
        Integer[] apply1 = intFunction1.apply(3);

方法引用最明显的特点就是只有方法名,而没有参数,只能弄明白JVM是如何推导参数的,才能熟练使用方法引用。
适配方法一:引用的方法与函数式接口的抽象方法,参数列表相同,适用类型:实例方法引用 静态方法引用 构造方法引用
适配方法二:参数列表中第一个参数充当实例方法的调用者,而第二个参数充当实例方法的参数,适用类型:类的实例方法引用
适配方法三:整数型参数充当数组的构造方法的初始长度值,适用类型:数组构造方法引用

二、stream学习

1. 什么是stream

stream是把要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

你可以把它当做是是一个高级版本的Iterator,它对集合(Collection)对象功能进行了增强,它还提供并行模式进行数据操作,而无需编写多线程代码;同时它的短路操作可以在不用处理全部元素就可以返回结果,达到节省资源,提高性能的作用。

你也可以把Stream当做是一种可供流式操作的数据视图,类似数据库中视图的概念,它不改变源数据集合,如果对其有修改操作它会返回一个新的数据集合。

Stream API 借助 Lambda 表达式,极大的提高编程效率和程序可读性;它是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。

2.流的构成

当我们使用一个流的时候,通常包括三个基本步骤:

获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

3.创建Stream

  • Stream接口的静态工厂方法
  • Collection对象和数组对象转换成Stream

静态工厂方法

  • of
        Stream<String> stringStream=Stream.of("aaa","bbb","ccc","ddd");
        stringStream.forEach(System.out::println);
  • generator
        Stream.generate(Math::random).limit(5).forEach(System.out::println);
  • iterate
        Stream.iterate(1,(x)->x+1).limit(5).forEach(System.out::println);

Collection对象和数组对象转换成Stream

  • Collect对象转换成Stream
        List<String> arrayList=new ArrayList<>();
        Stream<String> stream = arrayList.stream();
        Stream<String> stringStream1 = arrayList.parallelStream();//并行stream
  • 数组对象转换成Stream
        String[] sArr = new String[10];
        Stream<String> stream1 = Arrays.stream(sArr);

4.数据操作

流的操作类型分为两种:

  • 中间操作Intermediate :一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。而中间操作又可以分为无状态操作和有状态操作

    • 无状态操作
      unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
    • 有状态操作
      distinct() sorted() sorted() limit() skip()

    无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果

  • 终止操作Terminal :一个流只能有一个 终止操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。终止操作的执行,才会真正开始流的遍历,并且会生成一个结果或产生数据操作效果,终止操作又可分为非短路操作和短路操作

    • 非短路操作
      forEach() forEachOrdered() toArray() reduce() collect() max() min() count()
    • 短路操作
      anyMatch() allMatch() noneMatch() findFirst() findAny()

    短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。

在对于一个 Stream 进行N中间操作时,是不是进行了N次for 循环?其实不是这样的,转换操作都是懒操作,多个转换操作只会在 终止操作的时候才会融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 终止操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

5.Stream API接口介绍

接口名称说明
filter过滤流,过滤流中的元素,返回一个符合条件的Stream
map转换流,将一种类型的流转换为另外一种流。(mapToInt、mapToLong、mapToDouble 返回int、long、double基本类型对应的Stream)
flatMap简单的说,就是一个或多个流合并成一个新流。(flatMapToInt、flatMapToLong、flatMapToDouble 返回对应的IntStream、LongStream、DoubleStream流。)
distinct返回去重的Stream。
sorted返回一个排序的Stream。
peek主要用来查看流中元素的数据状态。
limit返回前n个元素数据组成的Stream。属于短路操作
skip返回第n个元素后面数据组成的Stream。
forEach循环操作Stream中数据。
toArray返回流中元素对应的数组对象。
reduce聚合操作,用来做统计。
collect聚合操作,封装目标数据。
min、max、count聚合操作,最小值,最大值,总数量。
anyMatch短路操作,有一个符合条件返回true。
allMatch所有数据都符合条件返回true。
noneMatch所有数据都不符合条件返回true。
findFirst短路操作,获取第一个元素。
findAny短路操作,获取任一元素。
forEachOrdered按元素顺序执行循环操作。

6. 实例操作

创建一个学生对象的List对象,然后使用stream方式对学生列表对象进行聚合、过滤、排序、映射等操作。

创建学生List

    List<Student> students=new ArrayList<Student>(){{
        add(new Student(1,"张三","语文",88d));
        add(new Student(2,"张三","数学",90d));
        add(new Student(3,"张三","英语",82d));
        add(new Student(4,"李四","语文",90d));
        add(new Student(5,"李四","数学",80d));
        add(new Student(6,"李四","英语",72d));
        add(new Student(7,"王五","数学",68d));
        add(new Student(8,"王五","语文",70d));
        add(new Student(9,"王五","英语",92d));
    }};

获得学生名单

        //获取学生名单
        List<String> collect2 = students.stream().map(Student::getName)
                .distinct().collect(Collectors.toList());
        collect2.forEach(System.out::println);

        //获取学生名单
        Set<String> collect = students.stream().map(Student::getName)
                .collect(Collectors.toSet());
        collect.stream().forEach(System.out::println);

获得张三的成绩单

        //获得张三的成绩单
        Map<String, Double> zsDate = students.stream()
                .filter((x) -> x.getName().equals("张三"))
                .collect(Collectors.toMap(Student::getCourse, Student::getScore));
        zsDate.forEach((k,v)-> System.out.println(k+"成绩是:"+v));

查找每个人的总成绩,并进行排名

        //查找每个人的总成绩,并进行排名
        Map<String, Double> collect = students.stream().collect(Collectors.groupingBy(
                Student::getName, Collectors.summingDouble(Student::getScore)
        )).entrySet().stream().sorted(Entry.<String, Double>comparingByValue().reversed())
                .collect(Collectors.toMap(Entry::getKey
                        , Entry::getValue,(x,y)->x,LinkedHashMap::new));
        collect.forEach((k,v)-> System.out.println(k+"的总成绩为"+v));

查找各科成绩最高的

        //查找各科成绩最高的
        Map<String, Double> collect = students.stream().collect(Collectors.groupingBy(
                Student::getCourse,
                Collectors.collectingAndThen(
                        Collectors.maxBy(Comparator.comparingDouble(Student::getScore)),
                        (x) -> x.get().getScore()
                )
        ));
        collect.forEach((k,v)-> System.out.println(k+"的最高成绩为"+v));

查询成绩为优异的学生

        //查询成绩为优异的学生
        List<String> collect = students.stream().collect(Collectors.groupingBy(
                Student::getName, Collectors.collectingAndThen(Collectors.toList(),
                        x -> x.stream().noneMatch(y -> y.getScore() < 80))))
                .entrySet().stream().filter(Map.Entry::getValue)
                .map(Map.Entry::getKey).collect(Collectors.toList());
        collect.forEach(System.out::println);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值