Lambda表达式&Stream流(从入门到精通,通俗易懂)

目录

Lambda表达式

概述

正确使用

省略规则

Stream流(重要)

Optional

函数式接口

Consumer 消费接口

Function 计算转换接口

Predicate 判断接口

Supplier 生产型接口

其他

方法引用

引用类的静态方法

引用对象的实例方法

引用类的实例方法

构造器引用


 

Lambda表达式

概述

Lambda表达式是Java编程语言中引入的一个重要特性,它是一种轻量级的匿名函数,也可以理解为一种简洁的语法糖。Lambda表达式的引入主要是为了支持函数式编程风格,让Java更好地适应现代编程的需求。

正确使用

我将举几个例子方便你理解什么是Lambda表达式,以及怎么使用:

例子1 创建线程

我们先看传统的写法,使用匿名内部内来写:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程中run方法被执行");
    }
}).start();

接下来我们使用Lambda表达式来实现:

/**
*  1,lambda表达式不需要你关注方法名是什么
*  2,只需要关注有什么参数就行了
*  3,没有参数的话只需要写()
*/
new Thread(() -> {
        System.out.println("线程中run方法被执行");
}).start();

来,不懂的话我多给你举几个例子,记住:我们只需要关注参数和写方法体就行了

例子2  

public static void main(String[] args) {
    // 使用匿名内部内实现
    int sum = calculateNum(new IntBinaryOperator() {
        @Override
        public int applyAsInt(int left, int right) {
            return 0;
        }
    });

    // 使用lambda表达式实现
    /**
     *  记住我们不需要关注接口、方法名是什么
     *  只需要关注有什么参数和写方法体,所以我们把
     *  new IntBinaryOperator() {
     *   @Override
     *   public int applyAsInt  都给去掉,就变成下面这个样子了
     */
    
    int sum1 = calculateNum((int left, int right)-> {
            return 0;
        }
    );
}

// IntBinaryOperator为一个接口  里面只有一个方法: int applyAsInt(int left, int right);
public static int calculateNum(IntBinaryOperator operator) {
    int a = 10;
    int b = 20;
    return operator.applyAsInt(a, b);
}

注意:以上代码还可以简化,但是我们循序渐进,慢慢来

例子3  输出所有的偶数

public static void main(String[] args) {
        // 使用匿名内部内实现
        printNum(new IntPredicate() {
            @Override
            public boolean test(int value) {
                return value%2 == 0;
            }
        });
        
        // 使用lambda表达式实现
        /**
         *  记住我们不需要关注接口、方法名是什么
         *  只需要关注有什么参数和写方法体,所以我们把
         *  new IntPredicate() {
         *  @Override
         *  public boolean test 给去掉,就变成下面这个样子了
         */
        printNum((int value) ->{
            return value%2 == 0;
        });
    }

    /**
     * 用于练习
     * @param predicate
     */
    public static void printNum(IntPredicate predicate) {
        int[] arr = {1,2,3,4,5,6,7,8,9,10};
        for (int i : arr) {
            if (predicate.test(i)) {
                System.out.println(i);
            }
        }
    }

例子4

public static void main(String[] args) {
        // 先使用匿名内部内实现
        String str = typeConver(new Function<String, String>() {
            public String apply(String s) {
                return s + " hxp";
            }
        });
        System.out.println(str);
        
        // 使用lambda表达式实现  注意:我们按照前两步教你的来就变成以下
        String lambda = typeConver((String s) ->{
                return s + " hxp";
        });
    }

    public static <R> R typeConver(Function<String,R> function) {
        String str = "hello";
        R result = function.apply(str);
        return result;
    }

现在我们就开始进阶!

省略规则

  • 参数类型可以省略
  • 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
  • 方法只有一个参数时,小括号可以省略

现在我们回到上面这个例子:

public static void main(String[] args) {
        /**
         *  原来的写法
         *  String lambda = typeConver((String s) ->{
         *        return s + " hxp";
         *  });
         */

        // 参数类型可以省略 就变成
        String lambda = typeConver((s) ->{
                return s + " hxp";
        });

        // 方法只有一个参数时,小括号可以省略
        String lambda1 = typeConver(s ->{
            return s + " hxp";
        });

        // 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
        String lambda2 = typeConver(s -> s+" hxp");
    }

    /**
     * 练习
     */
    public static <R> R typeConver(Function<String,R> function) {
        String str = "hello";
        R result = function.apply(str);
        return result;
    }

前面几个例子都可以进行简化,大家可以作为练习

Stream流(重要)

stream流的话可以学习这篇文章,里面有详细讲到怎么使用:

Stream流(从入门到实战,详细教程)

Optional

Optional 是 Java 8 引入的一个类,用于解决在处理可能为 null 的对象时引发的空指针异常问题。它的设计目标是防止在代码中出现 null 值,使得代码更加健壮,并提高可读性。

以下是 Optional 的主要特点和用法:

1,避免空指针异常: Optional 主要用于包装可能为 null 的值,通过一系列的方法调用,可以避免直接操作可能为空的对象而引发的空指针异常。

2,创建 Optional 对象: 可以使用静态方法 Optional.of(value) 来创建一个包含非 null 值的 Optional 对象。如果要允许值为 null,可以使用 Optional.ofNullable(value)

// 不为null的情况下
String value = "Hello, Optional!";
Optional<String> optionalValue = Optional.of(value);
String value = // 当不确定,有可能为null的情况下
Optional<String> optionalValue = Optional.ofNullable(value);

3,检查是否有值:

第一种:

可以使用 isPresent() 方法检查 Optional 对象是否包含值,避免直接对可能为 null 的对象进行操作。

if (optionalValue.isPresent()) {
    // 执行操作
}

第二种:

  • ifPresent(Consumer<? super T> action) 方法: 如果 Optional 对象包含值,则执行给定的操作,否则不执行。

  • ifPresent(value -> System.out.println("Value: " + value));

4,获取值: 使用 get() 方法可以获取 Optional 对象中的值,前提是该值不为 null。但最好避免直接使用 get(),而是通过其他方法来获取值,以避免空指针异常。

  • orElseGet

如果数据不为空则获取到该数据,如果为空则返回你设置的默认值。例如:

public static void main(String[] args) {
    Optional<String> optionalValue = Optional.ofNullable(getValueFromExternalSource());
    // 如果optionalValue中的数据不为null的话则返回该数据  否则返回:"设置的默认值"
    String result = optionalValue.orElseGet(() -> "设置的默认值");
    System.out.println("Result: " + result); //Result: 设置的默认值
}

private static String getValueFromExternalSource() {
    return null;
}
  • orElseThrow

获取数据,如果数据不为空则获取数据,如果为空则抛出异常。

public static void main(String[] args) {
    Optional<String> optionalValue = Optional.ofNullable(getValueFromExternalSource());
    // 如果optionalValue中的数据不为null的话则返回该数据  否则抛出异常
    String result = optionalValue.orElseThrow(() -> {
        // 写你的业务...
        return new RuntimeException("optionalValue为空");
    });
}

private static String getValueFromExternalSource() {
    return null;
}

5,函数式风格操作: Optional 提供了一系列函数式风格的方法,如 mapfilter 等,可以方便地对 Optional 进行链式操作。

  • 使用 map 方法将 Optional 中的字符串转换为大写:
Optional<String> name = Optional.of("Alice");
Optional<String> upperName = name.map(String::toUpperCase); // Optional["ALICE"]
  • 使用 filter 方法根据条件过滤 Optional 中的值,如果不满足条件则返回空的 Optional:
Optional<Integer> age = Optional.of(18);
Optional<Integer> adultAge = age.filter(a -> a >= 21); // Optional.empty

函数式接口

只有一个抽象方法的接口我们称为函数接口。

JDK的函数式接口都加上了@FunctionalInterface注解进行标识。但是无论是否加上该注解,只要接口中只有一个抽象方法,就是函数式接口。

以下的函数式接口是我们能常用到的:

Consumer 消费接口

05ff4fb7cf2c4234b9b8cd4f007bf817.png

这个接口表示一个消费者,它的作用是接收一个参数,然后对这个参数进行一些操作,比如打印、修改、存储等,但是不返回任何结果。你可以把它看成是一个没有返回值的方法,只是为了产生一些副作用。比如,你可以用Consumer接口来遍历一个集合,对每个元素进行打印,就像这样:

第一种方法

//创建一个Consumer接口的实例,用lambda表达式来实现它的accept方法,打印参数
Consumer<String> consumer = s -> System.out.println(s);
//创建一个字符串列表
List<String> list = Arrays.asList("Hello", "World", "Java");
//用forEach方法遍历列表,传入Consumer接口的实例作为参数
list.forEach(consumer);
//输出结果:
//Hello
//World
//Java

第二种方法

//创建一个Consumer接口的实例,用lambda表达式来实现它的accept方法,打印参数
List<String> list = Arrays.asList("Hello", "World", "Java");
testConsumer(list,s -> System.out.println(s));
}
/**
 *  数据使用打印
 * @param list       数据
 * @param consumer   用Consumer接口来遍历一个集合,对每个元素进行打印
 * @param <T>   定义的一个泛型,可以任意对象
 */
public static <T> void testConsumer(List<T> list, Consumer<T> consumer){
    // forEach中的参数本身就是Consumer<? super T> action
    list.forEach(consumer);
}

Function 计算转换接口

50c39907b0984810ad5aea504efae4ee.png

这个接口表示一个函数,它的作用是接收一个参数,然后对这个参数进行一些计算或转换,然后返回一个结果。你可以把它看成是一个有返回值的方法,用来实现一些功能。比如,你可以用Function接口来实现一个字符串转换为整数的功能,就像这样:

//创建一个Function接口的实例,用lambda表达式来实现它的apply方法,将字符串转换为整数
Function<String, Integer> function = s -> Integer.parseInt(s);
//创建一个字符串
String str = "123";
//用apply方法调用Function接口的实例,传入字符串作为参数,得到返回值
Integer num = function.apply(str);
//输出结果:
//123

Predicate 判断接口

61732aa1ea08440099d776e510441ea9.png

这个接口表示一个谓词,它的作用是接收一个参数,然后对这个参数进行一些判断,返回一个布尔值。你可以把它看成是一个有返回值的条件语句,用来实现一些逻辑判断。比如,你可以用Predicate接口来实现一个判断字符串是否为空的功能,就像这样:

//创建一个Predicate接口的实例,用lambda表达式来实现它的test方法,判断字符串是否为空
Predicate<String> predicate = s -> s == null || s.isEmpty();
//创建一个字符串
String str = "";
//用test方法调用Predicate接口的实例,传入字符串作为参数,得到返回值
boolean result = predicate.test(str);
//输出结果:
//true

Supplier 生产型接口

966b3f7b42e647dc89feaefb1d89690c.png

这个接口表示一个供应者,它的作用是不接收任何参数,但是返回一个结果。你可以把它看成是一个无参数的方法,用来提供一些数据。比如,你可以用Supplier接口来实现一个生成随机数的功能,就像这样:

//创建一个Supplier接口的实例,用lambda表达式来实现它的get方法,返回一个随机数
Supplier<Double> supplier = () -> Math.random();
//用get方法调用Supplier接口的实例,得到返回值
Double num = supplier.get();
//输出结果:
//0.4567890123456789

其他

4f86fab0d4cf43a998dc1e143c9d97c9.png

stream流中的各个方法就使用了这些函数式接口作为参数,使得我们可以灵活的操作

  • filter(Predicate<T> predicate):使用Predicate接口来对流中的元素进行过滤,只保留满足条件的元素。
  • map(Function<T, R> mapper):使用Function接口来对流中的元素进行转换,将每个元素映射为另一个元素。
  • forEach(Consumer<T> action):使用Consumer接口来对流中的元素进行消费,执行一些操作,但不返回结果。
  • reduce(BinaryOperator<T> accumulator):使用BinaryOperator接口来对流中的元素进行归约,将所有元素累积为一个结果。
  • max(Comparator<T> comparator):使用Comparator接口来对流中的元素进行比较,找出最大的元素。
  • min(Comparator<T> comparator):使用Comparator接口来对流中的元素进行比较,找出最小的元素。
  • anyMatch(Predicate<T> predicate):使用Predicate接口来对流中的元素进行判断,如果有任意一个元素满足条件,就返回true。
  • allMatch(Predicate<T> predicate):使用Predicate接口来对流中的元素进行判断,如果所有元素都满足条件,就返回true。
  • noneMatch(Predicate<T> predicate):使用Predicate接口来对流中的元素进行判断,如果没有元素满足条件,就返回true。

方法引用

我们在使用lambda时,如果方法体中只有一个方法的调用的话,我们可以用方法引用进一步简化代码。

引用类的静态方法

基本格式:类名::方法名

使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。

List<User> users= getUsers();
users.stream()
        .map(user-> user.getAge())
        .map(new Function<Integer, String>() {
            @Override
            public String apply(Integer age) {
                return String.valueOf(age);
            }
        });

使用lambda表达式优化之后:

List<User> users= getUsers();
users.stream()
        .map(user-> user.getAge())
        .map(String::valueOf);

引用对象的实例方法

基本格式对象名::方法名

使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法。

匿名内部内实现:

List<User> users = getUsers();
StringBuilder sb = new StringBuilder();
users.stream()
        .map(user -> user.getName())
        .forEach(new Consumer<String>() {
            @Override
            public void accept(String name) {
                sb.append(name);
            }
        });

lambda表达式实现:

List<User> users = getUsers();
StringBuilder sb = new StringBuilder();
users.stream()
        .map(user -> user.getName())
        .forEach(sb::append);

引用类的实例方法

格式:类名::方法名

使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入这个成员方法中,这个时候我们就可以引用类的实例方法。

List<User> users = getUsers();
users.stream()
    .map(user -> user.getName())
    .forEach(System.out::println);

// 优化后,使用User::getName

users.stream()
    .map(User::getName)
    .forEach(System.out::println);

如果不明白的话可以仔细看这两点:
1,调用了第一个参数的成员方法

第一参数是:user
user调用了成员方法:user.getName()

 

2,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入这个成员方法中

因为只有user这一个参数,而且getName()也不需要参数
所以也满足了这一点

构造器引用

如果方法体中的一行代码是构造器的话就可以使用构造器引用。

格式:类名::new

使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。

List<User> users = getUsers();
users.stream()
        .map(User::getName)
        .map(name -> new StringBuilder(name))
        .forEach(System.out::println);

优化后:

List<User> users = getUsers();
users.stream()
        .map(User::getName)
        .map(StringBuilder::new)
        .forEach(System.out::println);

写到这里就差不多结束了,喜欢博主的可以关注一波,后续会分享更多技术知识,让我们一起进步!

 

  • 71
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值