(Java8新特性)Java学习路线(漫漫人生路,步步架构梦)Java Learning Path (Long Life Path, Step by step Structure Dream)

前言

本篇博客主要分享Java8的新特性,O(∩_∩)O哈哈~

Lambda表达式总结

Lambda表达式是Java 8中添加的一个新特性。它可以让我们的代码更加简洁紧凑。
Lambda表达式的基本格式是:

(parameters) -> expression

或者

(parameters) -> { statements; }

基本规则:

  • 参数的数据类型可以省略,编译器可以推导出来
  • 如果只有一个参数,圆括号()也可以省略
  • 表达式或语句块的{}也可以省略,前提是语句只有一行
  • Lambda表达式主要用来定义行内执行的方法类型接口,例如Runnable、Comparator等
    例子:

    // 之前的匿名内部类
    new Thread(new Runnable() {
    public void run() {
        System.out.println("Hello");
    }
    }).start();
    // 使用Lambda表达式
    new Thread(() -> System.out.println("Hello")).start();
    // Comparator接口
    Comparator<Integer> c = (a, b) -> a - b; 
    // 如果只有一个参数,圆括号可以省略
    Comparator<Integer> c = a -> a * 2;  
    // 代码块如果只有一行,可以省略花括号和return关键字
    Comparator<Integer> c = (a, b) -> a - b; 
    // 有多个语句,需要花括号和return 
    Comparator<Integer> c = (a, b) -> { 
    int result = a - b;
    return result; 
    };

    函数式接口 + Lamda表达式推导过程

    函数式接口:是一个只包含一个抽象方法的接口,这种接口可以通过Lambda表达式来实例化。Java 8中有许多内置的函数式接口,比如:

    • Runnable
    • Comparator
    • Predicate
    • Consumer
    • Supplier
    • Function
      这些接口只包含一个抽象方法。
      Lambda表达式的推导过程:

      1. 确定目标接口的抽象方法。Lambda表达式需要实现的接口,通常是一个函数式接口。
      2. 抽象方法的参数列表肯定就是Lambda表达式的参数列表。
      3. 确认Lambda表达式的主体依赖于抽象方法的返回值:
    • 如果返回值为void,Lambda表达式的主体可以是一条语句或语句块。
    • 如果返回值不是void,主体应该是一个表达式。该表达式的类型应与返回值类型兼容。
    • 如果返回值不是void,但是存在参数,Lambda表达式不需要返回值,也可以使用语句块。
      1. 确认参数的数据类型:
    • 如果参数的数据类型可以从上下文推导出来,那么可以省略其数据类型。
    • 否则必须定义全类型。
      1. 移除抽象方法的modifiers,异常和返回值:在转换为Lambda表达式时忽略抽象方法的修饰符、异常和返回值。
        例如,对于 Runnable 接口:

        @FunctionalInterface
        public interface Runnable {
        public abstract void run(); 
        }

        对应的Lambda表达式为:

        () -> System.out.println("Hello")

        推导过程:

      2. 确定Runnable接口的抽象方法run(),没有参数和返回值。
      3. 所以Lambda表达式的参数列表为空。
      4. 由于run()方法返回void,所以Lambda表达式的主体可以是一条语句或语句块。
      5. 不需要定义参数的数据类型。
      6. 移除public和abstract关键字,不考虑异常和返回值。
        所以对应得出的Lambda表达式是() -> System.out.println("Hello")。

        Supplier简介

        Supplier接口是一个函数式接口,它只包含一个无参的get()方法。该方法返回一个任意泛型类型的值。
        Supplier接口用于表示一个提供值的源头,允许一个方法在没有输入参数的情况下产生一个值。
        Supplier接口的定义如下:

        @FunctionalInterface
        public interface Supplier<T> {
        T get();
        }

        例子:

        // 不使用Supplier,普通方法
        public String getHello() {
        return "Hello"; 
        }
        // 使用Supplier接口 + Lambda表达式
        Supplier<String> supplier = () -> "Hello";
        String result = supplier.get();  // Returns "Hello"

        Supplier接口常用在需要一个方法来生成某个值时,如工厂方法等。例如:

        Supplier<Person> personSupplier = Person::new;  
        // 使用方法引用简化Lambda表达式
        Person p = personSupplier.get(); 

        这在需要一个随机数或当前时间戳时也很有用:

        Supplier<Integer> randomInt = () -> (int) (Math.random() * 100D); 
        int num = randomInt.get();
        Supplier<Long> timestamp = () -> System.currentTimeMillis();
        long now = timestamp.get(); 

        所以,在需要一个不需要输入参数,但可以提供一个值的方法时,可以考虑使用Supplier函数式接口,从而使用Lambda表达式简化代码。
        总结:
        Supplier接口用于表示一个提供值的方法,它允许一个方法在没有输入参数的情况下产生一个值。
        常用于工厂方法、随机值或时间戳的生成等。可以和Lambda表达式配合使用简化代码。

        Optional

        Optional是一个容器类,它可能包含非空值,也可能为空。它可以避免NullPointerException。
        Optional类的三个基本操作:

  • Optional.of(T value): 创建一个Optional实例,value必须非空;
  • Optional.empty(): 创建一个空的Optional实例;
  • Optional.ofNullable(T value): value可以为null,创建Optional实例;
    Optional类提供很多有用的方法,如:
  • isPresent(): 判断是否包含值;
  • get(): 获取值,若 calling get() on an empty Optional抛出NoSuchElementException;
  • orElse(T other): 获取Optional包含的值,或返回other的值;
  • orElseGet(Supplier<? extends T> other): 获取Optional包含的值,或调用Supplier接口返回的值;
  • map(Function<? super T,? extends U> mapper): 如果有值,则对其执行mapping函数并返回Optional,否则返回Optional.empty();
  • flatMap(Function<? super T,Optional> mapper): 与map()类似,但mapping函数返回Optional的值;
  • filter(Predicate<? super T> predicate): 如果Optional有值且使predicate返回true,则保留,否则返回Optional.empty();
    示例:

    // of()方法
    Optional<Integer> optional = Optional.of(5);
    // empty() 方法
    Optional<Integer> optional = Optional.empty();
    // ofNullable() 方法   
    Optional<Integer> optional = Optional.ofNullable(null); 
    // isPresent()方法
    optional.isPresent();   // Returns true if optional contains a value
    // get()方法  
    optional.get();        // Returns the value, throws NoSuchElementException if empty
    // orElse()方法  
    optional.orElse(10);   // Returns 10 if optional is empty, otherwise returns the value
    // map()方法
    optional.map(x -> 2 * x);  // Returns Optional[10] if optional contains 5, otherwise empty 
    // flatMap()方法
    Optional<Integer> x = Optional.of(5);
    Optional<Double> y = x.flatMap(a -> Optional.of(a * 2.0));  // Returns Optional[10.0] 
    // filter()方法
    Optional<Integer> x = Optional.of(5);
    x.filter(a -> a > 1);   // Returns Optional[5]
    x.filter(a -> a > 10);  // Returns Optional.empty 

    Optional类是一个很好的容器,可以避免空指针异常,并且提供 many有用的方法操作Optional实例。在Java 8以前一般使用null来表示一个缺失的值,现在推荐使用Optional代替null。

    Java8中最重磅的升级Stream

    Java 8中最大的升级就是Stream API。Stream是一个来自数据源的元素队列,它支持聚合操作,汇总统计,过滤,映射等多种操作。
    Stream API提供了一种高效且易用的处理数据的方式。主要特征如下:

  • 不会改变原始数据源,会返回一个持有结果的新Stream。
  • 惰性求值:许多Stream操作是向后延迟的,意味着他们会等到需要结果的时候才执行。
  • 可消费性:Stream只能被“消费”一次,一旦遍历过就会被关闭,就像一个Iterator。
  • 内部迭代:Stream使用内部迭代来遍历元素,而不是像集合的外部迭代。
  • 可并行化:当一个Stream是并行的,许多Stream操作可以自动并行化执行。
    Stream的构成主要分为三部分:

    1. 数据源:可以从Collection、数组等构建Stream。
    2. 中间操作:一步一步构建我们的Stream,并且可以被链式调用。主要包含:
      • filter - 过滤掉某些元素
  • map - 转换每个元素
  • flatMap - 把Stream中的每个元素转换成另一个Stream,然后把所有的Stream连接起来
    • distinct - 去除重复的元素
  • sorted - 对Stream进行排序
  • peek - 对每个元素执行操作,主要用于调试
  • limit - 截取前n个元素
    • skip - 跳过前n个元素
      1. 终止操作:产生结果、或导致副作用的操作。主要包含:
        • allMatch、anyMatch、noneMatch - 检查是否匹配全部、任意、没有元素
        • findFirst、findAny - 返回第一个或任意一个元素
  • count - 返回元素总数
  • reduce - 把Stream中的元素组合起来
  • collect - 把Stream中的元素归集到一个Collection中
    • forEach - 对每个元素执行操作
  • min、max - 返回最小值或最大值
    • toArray - 把Stream中的元素转成数组
      Stream执行顺序是:首先使用一个数据源构造Stream,然后进行0个或多个中间操作,最后进行一个终止操作。在结束操作时,Stream就被使用“消费”掉了,无法再次被操作。
      这就是Java 8中引入的Stream的基本概念和用法。
      它大大提高了Java的表达能力,可以快速对集合等进行过滤、映射、分组、汇总等操作。

      函数式接口Predicate

      Predicate是一个函数式接口,它包含一个抽象方法test()。该方法接受一个泛型参数,并返回一个boolean。
      Predicate接口用于表示一个断言(判断)方法,可以对任何输入参数进行判断并返回true或false结果。
      Predicate接口的定义如下:

      @FunctionalInterface
      public interface Predicate<T> {
      boolean test(T t);
      }

      例子:

      // 普通方法实现 
      public boolean isAdult(int age) {
      return age > 18; 
      }
      // 使用Predicate接口 + Lambda表达式
      Predicate<Integer> predicate = age -> age > 18; 
      boolean result = predicate.test(20);   // Returns true

      Predicate接口常用在过滤器、判断逻辑等场景。例如:

      List<Person> persons = Arrays.asList(
      new Person("John", 20), 
      new Person("Sara", 22),
      new Person("Mark", 15) 
      );
      // 过滤成年人
      List<Person> adults = filter(persons, p -> p.getAge() > 18);
      // predicate方法参数使用方法引用 Person::getAge 
      List<Person> adults = filter(persons, Person::getAge); 
      // Predicate接口作为方法的参数
      public List<Person> filter(List<Person> persons, Predicate<Person> predicate) {
      return persons.stream() 
                .filter(predicate)
                .collect(Collectors.toList());
      }

      所以,Predicate接口主要用于表达一个boolean值的判断逻辑,它和Lambada表达式以及方法引用配合使用,可以让我们的代码更加简洁。
      总结:
      Predicate接口表示一个断言(判断)方法,包含一个抽象方法test()用来判断输入参数并返回boolean值。
      常用于过滤器,判断逻辑等场景,可以和Lambda表达式及方法引用配合使用。

      自定义函数式接口

      我们也可以自定义自己的函数式接口。自定义函数式接口需要遵循一定的规则:

      1. 必须是接口,且只能包含一个抽象方法。
      2. 可选地提供@FunctionalInterface注解,这个注解可以检查接口是否真的只包含一个抽象方法。如果违反了规则编译器会报错。
      3. 可以包含default方法和静态方法。
        例子:自定义一个字符串过滤器接口

        @FunctionalInterface
        public interface StringPredicate {
        boolean test(String str);
        }

        使用自定义接口:

        StringPredicate predicate = str -> str.length() > 5;
        boolean result = predicate.test("Hello");  // Returns true

        我们也可以作为方法参数使用:

        public boolean filter(List<String> list, StringPredicate predicate) {
        return list.stream().anyMatch(predicate);
        }

        调用方法:

        List<String> list = Arrays.asList("Apple", "Banana", "Orange"); 
        boolean result = filter(list, str -> str.startsWith("B")); // Returns true

        当然,函数式接口不一定要使用Lambda表达式,也可以使用匿名内部类的方式实现:

        StringPredicate predicate = new StringPredicate() {
        @Override
        public boolean test(String str) {
        return str.length() > 5;  
        }
        };

        但是Lambda表达式的方式无疑更加简洁。
        所以,自定义函数式接口需要遵循:

      4. 必须只包含一个抽象方法
      5. 可以使用@FunctionalInterface注解检查
      6. 可以包含default和静态方法
      7. 既可以通过Lambda表达式实现,也可以通过匿名内部类实现
        自定义函数式接口增加了程序的扩展性,让我们可以根据自己的需求定义特定的函数式接口。
(Java8新特性)Java学习路线(漫漫人生路,步步架构梦)Java Learning Path (Long Life Path, Step by step Structure Dream)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

滚动到顶部