Java中的Lambda表达式

1. 概念

1.1. 为什么要引入Lambda表达式

简化实现函数式接口(Functional Interface,@FunctionalInterface

函数式接口: 有且只有一个抽象方法,但可以有多个非抽象方法的接口。

  • 结构:
    • 注解:@FunctionalInterface,可以不加此注解,只要符合只有一个抽象方法的定义,那编译器也会识别为一个函数式接口;
    • 一个抽象方法:必须要有,且只能有一个;
    • 一个或多个非抽象方法。
      @FunctionalInterface
      public interface GreetingService {
          // 抽象方法
          String greet(String name);
      
          // 默认方法(非抽象)
          default void printGreeting(String name) {
              String greeting = greet(name);
              System.out.println("Hello, " + greeting + "!");
          }
      
          // 静态方法(非抽象)
          static GreetingService createDefaultGreetingService() {
              return (name) -> "Hello, " + name;
          }
      }
      
  • 作用:

简化匿名内部类

我现在有一个函数式接口:

public interface Authorization {
    int calculate(int a, int b);
}

我需要使用这个接口中的calculate方法,用来实现a+b:

public class Addition implements Authorization{
    @Override
    public int calculate(int a, int b) {
        return a + b;
    }
}
    public static void main(String[] args) {
        Authorization auth = new Addition();
        int calculate = auth.calculate(1, 2);
        System.out.println(calculate);// return 3
    }

如果我使用匿名内部类的写法,那就是这样的:

    public static void main(String[] args) {
        Authorization auth = new Authorization() {
            @Override
            public int calculate(int a, int b) {
                return a + b;
            }
        };
        System.out.println(auth.calculate(1, 2));// return 3
    }

如果我用Lambda表达式的写法,那就是这样:

    public static void main(String[] args) {
    	// 也可以不声明参数类型 Authorization authorization = (a, b) -> a + b;
        Authorization authorization = (int a, int b) -> a + b;
        authorization.calculate(1, 2);// return 3
    }

美化代码

1.2. 概念说明


抽象方法

  • 概念:仅有方法签名(包括返回类型、方法名和参数列表)而没有具体实现(方法体)的成员方法,必须在抽象类或接口中声明,并要求子类或实现类提供其实现。
  • 识别:在抽象类中使用abstract来修饰的方法,以及在接口中没有方法体的方法(接口中的方法都默认是抽象方法,不过在JDK8引入了default方法,此方法不是抽象的)。
  • 案例:
    • 抽象类中的抽象方法

      public abstract class Shape {
          
          // 非抽象方法(已实现)
          public String getDescription() {
              return "This is an abstract shape.";
          }
      
          // 抽象方法(未实现,子类必须提供实现)
          public abstract double calculateArea();
      
          // 可选:其他成员变量、构造方法、非抽象方法等
      }
      
    • 接口

      public interface MyInterface {
          // 抽象方法
          void abstractMethod();
      
          // 默认方法(非抽象)
          default void nonAbstractMethod() {
              System.out.println("This is a default method implementation.");
          }
      }
      

为什么函数式接口是Lambda表达式的基石?
由于Lambda表达式本身是一种简洁、轻量级的匿名函数形式,它没有名称,也没有明确的类定义。为了让Lambda表达式能够与现有的面向对象结构兼容,Java要求Lambda表达式必须能够被赋值给一个类型明确的对象。函数式接口恰好提供了这种类型,它的单个抽象方法定义了Lambda表达式可以具有的行为签名。通过将Lambda表达式赋值给函数式接口的实例,Lambda表达式获得了类型,从而能够在Java代码中被安全地使用。


简化行为参数化
函数式接口使得方法可以接受行为作为参数。这意味着方法可以要求调用者传递一个特定操作的实现(如一个计算规则、一个筛选条件、一个转换逻辑等),而不是具体的业务数据。这极大地提升了代码的灵活性和可重用性。


支持函数式编程风格
使用Lambda表达式、函数式接口、Stream API等工具来编写简洁、声明式、无副作用的代码。

  • 使用Lambda表达式

        public static void main(String[] args) {
            List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    
            // 使用Lambda表达式过滤长度大于3的名字
            names.stream()
                    .filter(name -> name.length() > 3)
                    .forEach(System.out::println);  // 输出:Bob, Charlie
        }
    
  • 使用函数式接口作为方法参数

    public class FunctionInterfaceExample {
        public static void main(String[] args) {
            List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
    
            // 使用Lambda表达式作为Function接口参数
            List<Integer> squaredNumbers = transform(numbers, number -> number * number);
    
            System.out.println(squaredNumbers);  // 输出:[1, 4, 9, 16, 25]
        }
    
        public static <T, R> List<R> transform(List<T> input, Function<T, R> mapper) {
            List<R> result = new ArrayList<>();
            for (T item : input) {
                result.add(mapper.apply(item));
            }
            return result;
        }
    }
    
  • 使用Stream API进行复杂数据处理

    public class StreamApiExample {
        public static void main(String[] args) {
            List<Person> people = Arrays.asList(
                    new Person("Alice", 25),
                    new Person("Bob", 3½),
                    new Person("Charlie", ¼));
    
            // 使用Stream API进行复杂数据处理
            List<String> adultNames = people.stream()
                                             .filter(person -> person.getAge() >= 18)
                                             .map(Person::getName)
                                             .sorted()
                                             .collect(Collectors.toList());
    
            System.out.println(adultNames);  // 输出:[Alice, Bob]
        }
    
        static class Person {
            private String name;
            private double age;
    
            public Person(String name, double age) {
                this.name = name;
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public double getAge() {
                return age;
            }
        }
    }
    

促进API设计与扩展
函数式接口使得API设计更加模块化和可扩展。标准库和第三方库可以提供一系列通用的函数式接口(如 Function<T, R>Predicate<T>Consumer<T> 等),这些接口定义了常见的操作模式。开发人员可以轻松地以Lambda表达式的形式提供这些操作的具体实现,而无需创建大量的小型匿名内部类。此外,函数式接口可以通过添加默认方法和静态方法来增强接口的功能,而不会增加实现类的负担。


促进API设计与扩展
使用函数式接口,编译器可以在编译时检查Lambda表达式或方法引用是否与函数式接口的抽象方法签名匹配,确保类型安全。加上 @FunctionalInterface 注解后,编译器还会进一步检查接口是否确实符合函数式接口的定义(仅有一个抽象方法),提供额外的保障。


内部类

  • 内部类
    嵌套在另一个类内部定义的类,能够访问其外部类的所有成员(包括私有成员),并与其外部类之间存在一种特殊的包含与被包含关系。

    public class InnerClassExample {
        private String secretMessage = "This is a secret message.";
    
        public class MessagePrinter {
            public void printMessage() {
                System.out.println(secretMessage);
            }
        }
    
        public static void main(String[] args) {
            InnerClassExample outer = new InnerClassExample();
            InnerClassExample.MessagePrinter printer = outer.new MessagePrinter();
            printer.printMessage();  // 输出:This is a secret message.
        }
    }
    
  • 局部内部类
    在方法或块内部定义的类,只在该范围内可访问,且能够访问其封闭范围内的局部变量。

    public class LocalInnerClassExample {
        public void processData(int value) {
            // 定义局部内部类,用于封装特定逻辑
            class DataProcessor {
                int process(int input) {
                    return input * 2;
                }
            }
    
            // 创建局部内部类的实例并使用
            DataProcessor processor = new DataProcessor();
            int processedValue = processor.process(value);
            System.out.println("Processed value: " + processedValue);
        }
    
        public static void main(String[] args) {
            LocalInnerClassExample example = new LocalInnerClassExample();
            example.processData(10);  // 输出:Processed value: 20
        }
    }
    
  • 静态内部类
    嵌套在另一个类内部、使用 static 修饰的类,不依赖于外部类实例,可独立访问,且不能直接访问外部类的非静态成员。

    public class StaticInnerClassExample {
        private static final int MAX_VALUE = 100;
    
        public static void main(String[] args) {
            // 使用静态内部类实现策略模式
            ValidationStrategy strategy = new ValidationStrategy.IsInRangeStrategy(MAX_VALUE);
            boolean isValid = strategy.validate(50);
            System.out.println("Is valid: " + isValid);  // 输出:Is valid: true
        }
    
        // 静态内部类,表示验证策略
        static abstract class ValidationStrategy {
            public abstract boolean validate(int value);
    
            // 具体的验证策略实现
            static class IsInRangeStrategy extends ValidationStrategy {
                private final int max;
    
                public IsInRangeStrategy(int max) {
                    this.max = max;
                }
    
                @Override
                public boolean validate(int value) {
                    return value >= 0 && value <= max;
                }
            }
        }
    }
    
  • 匿名内部类
    在某个类或方法的内部,直接定义且不声明类名的特殊类。它继承自某个父类或实现某个接口,并在创建时立即实例化。这种设计常用于简化代码,特别是在仅需一次性使用、与外部环境紧密相关的类场景中,避免了为这类短暂存在的类单独命名。
    如Runnable接口,我需要使用它的run方法,但我不想写一个类来实现Runnable接口,再去重写run,然后用这个子类的run,这样比较麻烦,所以就用了匿名内部类的写法,如下:

    • 不使用Lambda:
      new Thread(new Runnable() {
          @Override
          public void run() {
              System.out.println("Running in a thread using an anonymous inner class");
          }
      }).start();
      
    • 使用Lambda:
      new Thread(() -> System.out.println("Running in a thread using a lambda expression")).start();
      

2. Lambda表达式的语法

2.1. 基础语法

Lambda表达式的结构:

  • 参数列表:
    • 参数列表以括号 () 包围,可以包含零个、一个或多个参数。
    • 如果只有一个参数且参数类型可以被编译器推断,可以省略括号。例如:x -> ...
    • 多个参数之间用逗号 , 分隔。例如:(x, y) -> ...
    • 对于参数类型,如果编译器可以从上下文中推断出来,可以省略类型声明;否则需要显式声明。例如:(int x, double y) -> ...
  • 箭头符号:箭头符号用于分隔参数列表和 Lambda 表达式主体,表示“参数”到“操作”的映射关系。
  • 方法主体:
    • 函数体紧跟箭头符号之后,可以是一个表达式或一个代码块。
    • 表达式形式(单行):当主体是一个简单的表达式时,可以直接写出表达式,编译器会自动返回表达式的值作为 Lambda 结果。例如:x -> x + 1
    • 代码块形式(多行):如果需要执行多个语句或需要显式使用 return 语句,可以使用大括号 {} 包裹代码块。例如:(x, y) -> { return x * y; }
  • 返回类型(省略):在Java中,Lambda表达式的返回类型总是由编译器根据上下文推断得出,无需显式声明。

::

上面的Lambda表达式还可以这样写:

:: 在 Java 中被称为方法引用来创建 Lambda,它用于引用已有方法作为 Lambda 表达式。这种写法适用于以下两种情况:

  • 对象方法引用: 当要引用一个已有的特定对象的方法作为 Lambda 表达式时,使用 对象名::方法名 的形式。例如:

    import java.util.Comparator;
    
    public interface StringComparator extends Comparator<String> {
    
    }
    
    StringComparator comparator = String::compareToIgnoreCase;
    
  • 类(静态)方法引用: 当要引用一个已有的类(静态)方法作为 Lambda 表达式时,使用 类名::方法名 的形式。例如:

    Authorization authorization = Integer::sum;
    

2.2. 搭配流(Stream API)


案例: 不用Lambda和Stream API遍历ArrayList:

public class TraditionalForEachExample {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "date", "elderberry"));

        // 使用传统的for-each循环遍历ArrayList
        for (String word : words) {
            // 使用if语句进行筛选和打印
            if (word.length() >= 5) {
                System.out.println(word);
            }
        }
    }
}

使用Lambda表达式搭配Stream API来实现ArrayList的遍历:

public class LambdaStreamExample {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "date", "elderberry"));

        // 使用Lambda表达式搭配Stream API遍历ArrayList
        words.stream()
             .filter(word -> word.length() >= 5) // 过滤出长度大于等于5的单词
             .forEach(System.out::println);       // 打印每个满足条件的单词
    }
}

对比上述案例,可见使用Stream+Lambda之后,代码好看了不少,可读性也更好。


Lambda表达式与Java 8的Stream API结合使用时,通常遵循的基本步骤和语法:

  • 创建Stream:
    从集合、数组或其他支持流式处理的数据源获取一个 Stream 对象。例如,对于 ArrayList,可以调用其 stream() 方法:

    List<String> list = ...; // 假设有一个ArrayList实例
    Stream<String> stream = list.stream();
    
  • 中间操作:
    对 Stream 应用一系列中间操作(如 filter、map、sorted 等),形成一个新的 Stream。这些操作是延迟执行的,即它们不会立即计算结果,而是等到终端操作触发时才执行。中间操作通常接收Lambda表达式作为参数

    Stream<String> filteredStream = stream.filter(s -> s.startsWith("a")); // 过滤以"a"开头的元素
    
  • 终端操作:
    应用一个终端操作(如 forEach、collect、count、anyMatch 等),触发中间操作的执行,并产生最终结果或副作用。终端操作也经常使用Lambda表达式。

    List<String> list = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
    
    list.stream() // 创建Stream
        .filter(s -> s.startsWith("a")) // Lambda表达式作为中间操作的参数,过滤以"a"开头的元素
        .forEach(System.out::println); // Lambda表达式作为终端操作的参数,打印符合条件的元素
    

哪些类可以使用Stream API?

  • Collections

    List<String> employeeNames = Arrays.asList("Alice", "Bob", "Charlie");
    
    List<String> upperCaseNames = employeeNames.stream()
                                               .map(String::toUpperCase)
                                               .collect(Collectors.toList());
    
    System.out.println(upperCaseNames); // 输出:[ALICE, BOB, CHARLIE]
    
  • Map

            Map<String, Integer> data = new HashMap<>();
            data.put("one", 11);
            data.put("two", 8);
            data.put("three", 15);
            data.put("four", 17);
    
            Map<String, Integer> filteredMap = data.entrySet().stream()
                    .filter(entry -> entry.getValue() > 10)
                    .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue));
    
  • 数组:基本类型数组(如 int[], double[], char[] 等)和对象类型数组(如 String[], MyClass[] 等)都可以通过 java.util.Arrays 类的静态方法 stream() 转化为流。

    int[] numbers = {1, 2, 3, 4, 5};
    IntStream intStream = Arrays.stream(numbers); // 基本类型数组转为流
    
    String[] words = {"apple", "banana", "cherry"};
    Stream<String> stringStream = Arrays.stream(words); // 对象类型数组转为流
    
  • 第三方库

说明:

  • Java 8 的 Stream API 主要针对的是 java.util.Collection、java.util.Map 及其子接口和实现类,这些类已经内置了 stream() 方法,可以直接用来创建相应的流;
  • .collect() 是 Java 8 引入的 Stream API 中的一个关键方法,用于将中间操作产生的中间流转换为最终的单一结果。它通常用于完成对流中元素的聚合操作,将流中的数据结构化或汇总成一个新的数据结构,如 List、Set、Map 或者自定义的聚合结果。.collect() 方法接收一个 Collector 实例作为参数,该实例定义了如何将流元素累积到最终结果中。

2.3. 常用的Stream API


创建 Stream

  • Collection.stream():将 Collection(如 List、Set)转换为 Stream。
  • Arrays.stream(T[] array):将数组转换为 Stream。
  • Stream.of(...elements):直接创建包含指定元素的 Stream。
  • Stream.generate(Supplier<T>):生成无限序列的 Stream,由提供的 Supplier 不断产生新元素。
  • Stream.iterate(initial, UnaryOperator<T>):创建一个无限序列的 Stream,从初始值开始,每次应用指定的函数更新元素。

中间操作

  • filter(Predicate<? super T>):基于指定条件过滤流中的元素。
  • map(Function<? super T, ? extends R>):对流中的每个元素应用一个函数,生成新的元素。
  • flatMap(Function<? super T, ? extends Stream<? extends R>>):扁平化操作,将流中的每个元素转换为另一个 Stream,然后将所有生成的 Stream 元素连接成一个单一的 Stream。
  • peek(Consumer<? super T>):对流中的每个元素执行一个操作(如打印或更新状态),但不影响流的整体处理。
  • limit(long maxSize):限制流最多包含指定数量的元素。
  • skip(long n):跳过流的前 n 个元素。
  • distinct():去除流中重复的元素。

终止操作

  • forEach(Consumer<? super T>):遍历流中的每个元素,对其执行指定的操作。
  • toArray():将流转换为数组。
  • collect(Collector<? super T, A, R>):将流元素收集到一个容器(如 List、Set、Map)或其他自定义数据结构中,通常结合 Collectors 类的工厂方法使用。
  • reduce(BinaryOperator<T>):通过一个二元操作符将流中的元素累积为一个单一结果,如求和、求积、求最大值等。
  • min(Comparator<? super T>) / max(Comparator<? super T>):返回流中最小/最大的元素。
  • anyMatch(Predicate<? super T>) / allMatch(Predicate<? super T>) / noneMatch(Predicate<? super T>):检查流中是否存在至少一个元素/所有元素/没有元素满足指定条件。
  • count():返回流中元素的总数。
  • findFirst() / findAny():返回流中的第一个元素/任意一个元素(在并行流中可能更快)。

并行流相关

  • parallel() / sequential():将流转换为并行流/顺序流,以便利用多核处理器进行并行处理或恢复为单线程处理。
  • unordered():去除流的排序特性,允许在并行处理时提高性能。

补充:

  • 无线序列:无限序列的 Stream 是 Java 8 引入的一种特殊类型的 Stream,它能够表示一个理论上无止境的元素序列。这类 Stream 不像传统集合那样预先存储所有元素,而是通过某种机制动态生成元素,使得在处理过程中可以源源不断地获取新的值。无限序列 Stream 主要用于需要处理大量数据或者无法预知数据规模的场景,同时它们充分利用了 Stream API 的惰性求值特性,确保只有在真正需要时才生成和消耗数据。

3. 补充

此部分只贴上部分代码案例,不做过多解释,毕竟流的可读性还是很高的。

3.1. forEach 与 removeIf

    private JSONArray reconDetailStats(List<BillCashAPo> reconInfo) {
        JSONArray reconDetailStats = new JSONArray();
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        Map<String, List<BillCashAPo>> groupedByDate = reconInfo.stream()
                .collect(Collectors.groupingBy(
                        bill -> {
                            LocalDate localDate = bill.getBillDate().toInstant()
                                    .atZone(ZoneId.systemDefault())
                                    .toLocalDate();
                            return localDate.format(dateFormatter);
                        },
                        Collectors.toList()));
        // 遍历map,装载JSONArray
        groupedByDate.forEach((date, billsForDate) -> {
            BigDecimal amount = billsForDate.stream()
                    .map(BillCashAPo:: getCashPay)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            BigInteger count = BigInteger.valueOf(billsForDate.size());
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("amount", amount);
            jsonObject.put("date", date);
            jsonObject.put("count", count);
            reconDetailStats.add(jsonObject);
        });
        return reconDetailStats;
    }
    @Override
    public List<ChannelVo> getChData(String merchantNo) {
        List<HpMerchantPo> hpMerchantPos = merchantReadService.queryMerchantAll();
        List<ChannelVo> chData = null;
        if (!merchantNo.equals("1")) {
            HpMerchantPo hpMerchantPo = hpMerchantPos.stream()
                    .filter(item -> item.getMerchantNo().equals(merchantNo))
                    .findFirst()
                    .orElse(null);
            chData = billReadMapper.getChData(hpMerchantPo.getMerchantId());
        } else {
            chData = billReadMapper.getChData(null);
        }
        HashMap<String, String> chTypes = new HashMap<>();
        chTypes.put("10", "航运");
        chTypes.put("11", "空运");
        chTypes.put("12", "汽运");
        chData.removeIf(channel -> chTypes.containsKey(channel.getChType()));
        return chData;
    }
  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Lambda表达式Java 8引入的一个新特性,它可以被视为一种匿名函数,它没有名称,但有参数列表、函数体和可能的返回类型。Lambda表达式是一种简便的语法形式,用于表示Java的函数式接口。 Lambda表达式的语法形式如下: ``` (parameters) -> expression ``` 或者 ``` (parameters) -> { statements; } ``` 其,parameters表示参数列表,可以为空或包含一个或多个参数;箭头->用于分隔参数列表和Lambda表达式的主体;expression或statements表示Lambda表达式的主体,可以是一个表达式或一组语句。 以下是一些Lambda表达式的示例: ``` // 无参数的Lambda表达式 () -> System.out.println("Hello, world!"); // 一个参数的Lambda表达式 (x) -> x * x // 多个参数的Lambda表达式 (x, y) -> x + y // 包含多条语句的Lambda表达式 (x, y) -> { int sum = x + y; System.out.println("The sum is " + sum); return sum; }; ``` Lambda表达式通常与Java的函数式接口一起使用,函数式接口是只有一个抽象方法的接口。例如,以下是一个函数式接口及其使用的示例: ``` @FunctionalInterface interface MyFunction { int apply(int x, int y); } MyFunction add = (x, y) -> x + y; MyFunction subtract = (x, y) -> x - y; int result1 = add.apply(2, 3); // result1 = 5 int result2 = subtract.apply(5, 2); // result2 = 3 ``` 在此示例,我们定义了一个带有一个抽象方法apply的函数式接口MyFunction,并使用Lambda表达式来实现该接口的单个方法。然后,我们创建了两个MyFunction实例来执行加法和减法。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

364.99°

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值