Java8新特性_21

Lambda 表达式

  • Lambda 表达式是 Java 8 引入的一个重要特性,它提供了一种简洁的方式来表示匿名函数(即没有名称的函数)。Lambda 表达式主要用于简化函数式接口的实现,使得代码更加简洁和易读。

函数式接口

  • 函数式接口(Functional Interface)是Java中的一个概念,指的是只有一个抽象方法的接口。这种接口通常用于Lambda表达式或方法引用

  • 定义

    • 单一抽象方法:只能有一个抽象方法,但可以有多个默认方法或静态方法。
    • 注解:常用@FunctionalInterface标注,确保接口符合函数式接口的要求。
    • 用途:主要用于Lambda表达式,简化代码
    @FunctionalInterface
    interface MyFunctionalInterface {
        void execute();
        
        default void defaultMethod() {
            System.out.println("Default method");
        }
        
        static void staticMethod() {
            System.out.println("Static method");
        }
    }
    
  • 常见函数式接口

    • Runnablevoid run()
    • Callable<V>V call()
    • Comparator<T>int compare(T o1, T o2)

Lambda 表达式

  • 基本语法

    (parameters) -> expression
    或
    (parameters) -> { statements; }
    
    • parameters:参数列表,可以为空或包含多个参数。
    • ->:Lambda 操作符,将参数和表达式或语句块分开。
    • expression{ statements; }:Lambda 的主体,可以是一个表达式或一个代码块
  • 特点

    • 简洁性:Lambda 表达式可以替代匿名内部类,减少样板代码。
    • 函数式编程支持:Lambda 表达式是函数式编程的核心,可以与函数式接口结合使用。
    • 类型推断:Java 编译器可以根据上下文推断 Lambda 表达式的参数类型,因此通常不需要显式声明类型
  • 示例

    • 实现Runnable接口
    // 旧写法:使用匿名内部类
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World");
        }
    };
    
    // Lambda 表达式
    Runnable r = () -> System.out.println("Hello World");
    
    • 实现Comparator接口
    // 旧写法:使用匿名内部类
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer a, Integer b) {
            return a.compareTo(b);
        }
    };
    
    // Lambda 表达式
    Comparator<Integer> comparator = (a, b) -> a.compareTo(b);
    
    • 多行代码的 Lambda 表达式
    // 旧写法:使用匿名内部类
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello");
            System.out.println("World");
        }
    };
    
    // Lambda 表达式
    Runnable r = () -> {
        System.out.println("Hello");
        System.out.println("World");
    };
    
  • 注意事项

    • Lambda 表达式可以访问外部的局部变量,但这些变量必须是 final 或事实上 final(即不可变)

    • Lambda 表达式只能用于实现 函数式接口(即只有一个抽象方法的接口)。如果接口中有多个抽象方法,Lambda 表达式将无法使用

    • Lambda表达式中参数括号的问题

      • 如果 Lambda 表达式只有一个参数,且参数类型可以被编译器推断出来,则可以省略参数列表的括号
      • 如果 Lambda 表达式有多个参数,则参数列表的括号 不能省略
      • 如果 Lambda 表达式没有参数,则必须保留空括号
    • Lambda 表达式的返回值

      • 如果 Lambda 表达式的主体是一个表达式,则返回值是该表达式的结果

      • 如果 Lambda 表达式的主体是一个代码块,则需要使用 return 语句返回值

        Function<Integer, Integer> square = x -> x * x;
        
        Function<Integer, Integer> square = x -> {
            return x * x;
        };
        
    • Lambda 表达式的类型推断

      • Lambda 表达式的参数类型可以显式声明,也可以由编译器根据上下文推断

      • 如果编译器无法推断类型,则需要显式声明类型

        Comparator<Integer> comparator = (Integer a, Integer b) -> a.compareTo(b);
        
        Comparator<Integer> comparator = (a, b) -> a.compareTo(b);
        
    • Lambda 表达式中如果抛出受检异常(checked exception),需要在函数式接口的方法声明中抛出该异常,或者在 Lambda 表达式中捕获并处理异常

      @FunctionalInterface
      interface MyFunctionalInterface {
          void doSomething() throws IOException;
      }
      
      MyFunctionalInterface func = () -> {
          throw new IOException();
      };
      

方法引用

  • Java 8 引入了方法引用(Method Reference)这一特性,它允许通过名称来引用一个已经存在的方法,而不是直接调用它。方法引用可以简化代码,使其更加简洁和易读。方法引用通常与 Lambda 表达式一起使用,尤其是在函数式接口的上下文中
  • 方法引用本质上就是将已有的方法作为函数式接口的具体实现方法。它通过简化 Lambda 表达式,直接将已有方法绑定到函数式接口的抽象方法上,从而替代显式编写 Lambda 代码的过程

方法引用的基本要求

  • 方法引用的核心思想是直接引用一个已经存在的方法,因此它的使用前提是
    • 被引用的方法必须存在:无论是静态方法、实例方法还是构造方法,都必须已经定义。
    • 方法签名必须匹配:被引用的方法的参数列表和返回值类型必须与目标函数式接口的抽象方法匹配

类型

  1. 静态方法引用:引用类的静态方法。

    • 静态方法引用的语法格式为 类名::静态方法名。它适用于引用类的静态方法

    • Integer::parseInt 是对 Integer 类的静态方法 parseInt 的引用

    • // Lambda 表达式
      Function<String, Integer> lambda = s -> Integer.parseInt(s);
      
      // 方法引用
      Function<String, Integer> methodRef = Integer::parseInt;
      
  2. 实例方法引用:引用特定对象的实例方法。

    • 语法:对象::实例方法名

    • // Lambda 表达式
      Consumer<String> lambda = s -> System.out.println(s);
      
      // 方法引用
      Consumer<String> methodRef = System.out::println;
      
  3. 类的任意对象的实例方法引用:引用某个类的任意对象的实例方法。

    • 语法:类名::实例方法名

    • // Lambda 表达式
      Function<String, String> lambda = s -> s.toLowerCase();
      
      // 方法引用
      Function<String, String> methodRef = String::toLowerCase;
      
  4. 构造方法引用:引用类的构造方法。

    • 语法:类名::new

    • // Lambda 表达式
      Supplier<List<String>> lambda = () -> new ArrayList<>();
      
      // 方法引用
      Supplier<List<String>> methodRef = ArrayList::new;
      

Stream流

  • Java 8 引入了 Stream API,它提供了一种高效且声明式的方式来处理集合数据。

  • Stream 允许你以函数式编程的风格对数据进行操作,比如过滤、映射、排序、聚合等。

  • Stream 的核心思想是将数据的处理过程抽象为一系列的操作流水线(Pipeline),从而简化代码并提高可读性

Stream核心概念

  1. Stream 是什么?
    • Stream 是一个来自数据源(如集合、数组、I/O 通道等)的元素序列。
    • Stream 不是数据结构,它不存储数据,而是通过一系列操作(如过滤、映射、排序等)对数据进行处理。
    • Stream 的操作可以分为 中间操作(Intermediate Operations)和 终端操作(Terminal Operations)。
  2. Stream 的特点
    • 声明式编程:通过链式调用方法描述数据处理逻辑,而不是通过传统的循环和条件语句。
    • 惰性求值:中间操作不会立即执行,只有在终端操作触发时才会真正执行。
    • 不可重复使用:Stream 只能被消费一次,一旦执行了终端操作,Stream 就会被关闭
    • 大数据量性能优化:对于小数据量,Stream 的性能可能不如传统的 for 循环。但对于大数据量或复杂操作,Stream 的性能优势明显

Stream的操作类型

中间操作

  • 中间操作返回一个新的 Stream,可以链式调用多个中间操作
  • 常见的中间操作:
    • filter(Predicate<T>):过滤元素。
    • map(Function<T, R>):将元素映射为另一种类型。
    • flatMap(Function<T, Stream<R>>):将流中的每个元素转换为一个流,然后将所有流合并。
    • distinct():去重。
    • sorted():排序。
    • limit(long):限制流中元素的数量。
    • skip(long):跳过前 N 个元素。

终端操作

  • 终端操作会触发流的执行,并产生一个结果或副作用
  • 常见的终端操作:
    • forEach(Consumer<T>):遍历流中的每个元素。
    • collect(Collector<T, A, R>):将流中的元素收集到一个集合中。
    • reduce(BinaryOperator<T>):将流中的元素归约为一个值。
    • count():返回流中元素的数量。
    • anyMatch(Predicate<T>):判断是否有任意元素满足条件。
    • allMatch(Predicate<T>):判断是否所有元素都满足条件。
    • noneMatch(Predicate<T>):判断是否没有元素满足条件。
    • findFirst():返回流中的第一个元素。
    • findAny():返回流中的任意一个元素

Stream的创建

  1. 从集合创建

    List<String> list = Arrays.asList("a", "b", "c");
    Stream<String> stream = list.stream();
    
  2. 从数组创建

    String[] array = {"a", "b", "c"};
    Stream<String> stream = Arrays.stream(array);
    
  3. 使用 Stream.of()

    Stream<String> stream = Stream.of("a", "b", "c");
    
  4. 从文件创建

    Stream<String> lines = Files.lines(Paths.get("file.txt"));
    

Stream的常见操作

  1. 过滤与映射

    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
    List<String> result = names.stream()
        .filter(name -> name.startsWith("A")) // 过滤以 "A" 开头的名字
        .map(String::toUpperCase)             // 将名字转换为大写
        .collect(Collectors.toList());        // 收集到 List 中
    System.out.println(result); // 输出: [ALICE]
    
  2. 排序与去重

    List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
    List<Integer> result = numbers.stream()
        .distinct()                           // 去重
        .sorted()                             // 排序
        .collect(Collectors.toList());
    System.out.println(result); // 输出: [1, 3, 4, 5, 9]
    
  3. 归约操作

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    int sum = numbers.stream()
        .reduce(0, Integer::sum);             // 求和
    System.out.println(sum); // 输出: 15
    
  4. 分组与分区

    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
    Map<Integer, List<String>> grouped = names.stream()
        .collect(Collectors.groupingBy(String::length)); // 按名字长度分组
    System.out.println(grouped); // 输出: {3=[Bob], 5=[Alice, David], 7=[Charlie]}
    
  5. 并行流

    并行流自动将数据拆分为多个子集,分配给不同线程处理,最后将结果合并

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    int sum = numbers.parallelStream()         // 使用并行流
        .reduce(0, Integer::sum);
    System.out.println(sum); // 输出: 15
    
posted @ 2025-03-17 20:52  QAQ001  阅读(21)  评论(0)    收藏  举报