java8 新特性

164 阅读10分钟


一、函数编程(lambda 表达式)

1.1 简介

面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。

核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。

对核心类库的改进主要包括集合类的API和新引入的流Stream。

流使程序员可以站在更高的抽象层次上对集合进行操作。

1.2 lambda 表达式

1.2.1 为什么使用 lambda 表达式

使用 Lambda 表达式可以对一个接口的方法进行非常简洁的实现

1.2.2 基本语法

(parameters) -> expression
或
(parameters) -> { statements; }

():用来描述参数列表
{}:用来描述方法体 有时可以省略
->: Lambda 运算符 读作goes to

1.2.3 语法实例

public class LambdaTest {

    public static void main(String[] args) {

        // 无参无返回
        LambdaNoneReturnNoneParmeter lambda1=()->{
            System.out.println("hello word");
        };
        lambda1.test();


        // 无返回值 单个参数
        LambdaNoneReturnSingleParmeter lambda2=(int n)->{
            System.out.println("参数是:"+n);
        };
        lambda2.test(10);
        // -> 2. 参数小括号精简 ,如果参数列表中,参数的数量只有一个 此时小括号可以省略
        LambdaNoneReturnSingleParmeter lambda22= n->{
            System.out.println("精简小括号后->参数是:" + n);
        };
        lambda22.test(10);
        // -> 3. 方法大括号精简 ,如果方法体中只有一条语句,此时大括号可以省略
        // -> 如果方法体中唯一的一条语句是一个返回语句,则省略大括号的同时 也必须省略return
        LambdaNoneReturnSingleParmeter lambda23 = n -> System.out.println("方法大括号精简后->参数是:" + n);
        lambda23.test(10);


        // 无返回值 多个参数
        LambdaNoneReturnMutipleParmeter lambda3=(int a,int b)->{
            System.out.println("参数和是:"+(a+b));
        };
        lambda3.test(10,12);

        // -> 1. 参数类型精简 ,由于在接口的抽象方法中,已经定义了参数的数量类型 所以在Lambda表达式中参数的类型可以省略
        LambdaNoneReturnMutipleParmeter lambda33=(a,b)-> {
            System.out.println("精简 参数类型 后->参数和是:"+(a+b));
        };
        lambda33.test(10,12);

        // 有返回值 无参数
        LambdaSingleReturnNoneParmeter lambda4=()->{
            System.out.println("lambda4:");
            return 100;
        };
        int ret=lambda4.test();
        System.out.println("返回值是:"+ret);

        // 有返回值 单个参数
        LambdaSingleReturnSingleParmeter lambda5=(int a)->{
            return a*2;
        };
        int ret2= lambda5.test(3);
        System.out.println("单个参数,lambda5返回值是:"+ret2);

        //有返回值 多个参数
        LambdaSingleReturnMutipleParmeter lambda6=(int a,int b)->{
            return a+b;
        };
        int ret3=lambda6.test(12,14);
        System.out.println("多个参数,lambda6返回值是:"+ret3);
        // -> 4.错参数精简
        LambdaSingleReturnMutipleParmeter lambda61=(a,b)->a+b;
        System.out.println("精简后->多个参数,lambda6返回值是:"+lambda61.test(12,14));

    }

    @FunctionalInterface
    public interface LambdaNoneReturnNoneParmeter {
        void test();
    }

    //无返回值有单个参数
    @FunctionalInterface
    public interface LambdaNoneReturnSingleParmeter {
        void test(int n);
    }
    //无返回值 多个参数的接口
    @FunctionalInterface
    public interface LambdaNoneReturnMutipleParmeter {
        void test(int a,int b);
    }
    //有返回值 无参数接口
    @FunctionalInterface
    public interface LambdaSingleReturnNoneParmeter {
        int test();
    }
    //有返回值 有单个参数的接口
    @FunctionalInterface
    public interface LambdaSingleReturnSingleParmeter {
        int test(int n);
    }
    //有返回值 有多个参数的接口
    @FunctionalInterface
    public interface LambdaSingleReturnMutipleParmeter {
        int test(int a,int b);
    }
}

@FunctionalInterface 用于标识一个接口是函数式接口

函数式接口 是指有且仅有一个抽象方法,但是可以有多个非抽象方法的接口

二、Stream 流式编程

3.1 简介

流式编程(Stream流)是一种在计算机编程中用于处理数据集合的新型方法。

核心思想是以一种类似于流水线的方式来处理数据,通过一系列的操作来对数据进行筛选、转换、合并和归约,从而实现更简洁、更高效、更可读的代码编写。

3.2 Stream 流的工作图

3.3 stream & parallelStream

每个 Stream 都有两种模式: 顺序执行和并行执行

下面为性能测试对比:

long t0 = System.nanoTime();

//初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法

int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray(); // 顺序流

long t1 = System.nanoTime();

//和上面功能一样,这里是用并行流来计算

int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray(); // 并行流

long t2 = System.nanoTime();

//我本机的结果是 serial: 0.08s, parallel 0.06s,证明并行流确实比顺序流快

System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);

3.4 Stream中常用方法

  • stream(), parallelStream()
  • filter()
  • findAny()findFirst()
  • sort
  • forEach void
  • map(), reduce()
  • flatMap() - 将多个Stream连接成一个Stream
  • collect(Collectors.toList())
  • distinct, limit
  • count
  • min, max, summaryStatistics

3.4.1 Filter & Predicate

过滤(filter) :对Stream流中的数据进行过滤

filter 方法是用来对列表进行过滤操作的。它接受一个 Predicate 参数,根据这个参数定义的条件来过滤列表中的元素。

什么是 predicate 参数:在 Java 中,Predicate 是一个函数式接口,用于表示一个断言(即一个布尔类型的函数)。它接受一个输入参数,并返回一个布尔值结果,通常用于对输入参数进行判断或筛选。

public static void main(String[] args) {
    // 声明语言列表的类型为 List<String>
    List<String> languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");

    System.out.println("Languages which start with J :");
    languages.stream()
        .filter(language -> language.startsWith("J"))
        .forEach(
            name -> System.out.println(name + " ")
        );

    System.out.println("Languages which start with J :");
    filter(languages, str -> str.startsWith("J"));

    System.out.println("Languages which end with a :");
    filter(languages, str -> str.endsWith("a"));

    System.out.println("Print all languages:");
    filter(languages, str -> true);

    System.out.println("Print no language:");
    filter(languages, str -> false);

    System.out.println("Print language whose length is greater than 4:");
    filter(languages, str -> str.length() > 4);

    // 多个Predicate组合filter
    // 可以用and()、or()和xor()逻辑函数来合并Predicate,
    // 例如要找到所有以J开始,长度为四个字母的名字,你可以合并两个Predicate并传入
    Predicate<String> startsWithJ = (n) -> n.startsWith("J");
    Predicate<String> fourLetterLong = (n) -> n.length() == 4;
    languages.stream()
        .filter(startsWithJ.and(fourLetterLong))
        .forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is -> " + n + "\n"));

}

// 声明泛型方法以确保类型安全
public static <T> void filter(List<T> names, Predicate<T> condition) {
    names.stream()
        .filter(condition)
        .forEach(name -> System.out.println(name + " "));
}

3.4.2 Map&Reduce

Map 和 Reduce 是函数式编程中常用的两种操作,它们通常与集合(如列表、数组)结合使用,用于对集合中的元素进行处理和聚合操作。

Map 操作用于将一个集合中的每个元素映射成另一个元素,通常是根据某种规则进行转换。

Reduce 操作用于将一个集合中的元素进行聚合操作,最终得到一个单一的值。

public static void main(String[] args) {

    List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);

    // 将 Integer 列表转换为 Double 列表
    costBeforeTax.stream()
    .map(Double::valueOf)
    .collect(Collectors.toList());

    double bill = costBeforeTax.stream().
    map((cost) -> cost * 1.12)
    .reduce((sum, cost) -> sum + cost)
    .get();
    System.out.println("Total : " + bill);

}

3.4.3 Collectors

Collectors 是 Java 8 中引入的一个工具类,位于 java.util.stream 包中,它提供了一系列静态方法,用于在流式操作中对元素进行收集和归约。通过 Collectors 可以将流中的元素收集到各种数据结构中,比如集合、映射、字符串或者其他自定义的数据结构。

Collectors 提供了丰富的功能,可以简化对流的操作,例如将元素收集到列表、集合、映射等数据结构中,进行分组、分区、聚合运算等。

public static void main(String[] args) {
    List<String> items = Arrays.asList("apple", "banana", "orange", "apple", "banana", "apple");

    /**
         *  基本收集器:
         *      1. toList():将流中的元素收集到一个列表中。
         *      2. toSet():将流中的元素收集到一个集合中。
         *      3. toMap():将流中的元素收集到一个映射中。
         *      4. joining():连接流中的字符串元素。
         */
    // toList
    List<String> list = items.stream().collect(Collectors.toList());
    System.out.println("List: " + list); // List: [apple, banana, orange, apple, banana, apple]

    // toSet
    Set<String> set = items.stream().collect(Collectors.toSet());
    System.out.println("Set: " + set); // Set: [banana, orange, apple]

    // toMap
    Map<String, Integer> map = items.stream().collect(Collectors.toMap(Function.identity(), String::length, (item1, item2) -> item1));
    System.out.println("Map: " + map); // Map: {orange=6, banana=6, apple=5}

    // joining
    String joined = items.stream().collect(Collectors.joining(", "));
    System.out.println("Joined: " + joined); // Joined: apple, banana, orange, apple, banana, apple

    /**
         *  聚合运算:
         *      1. counting():计算流中元素的个数。
         *      2. summarizingInt()、summarizingDouble()、summarizingLong():对流中数值元素进行摘要统计。快速计算流中元素的最小值、最大值、总和以及平均值
         *      3. summingInt()、summingDouble()、summingLong():对流中的数值元素求和。
         *      4. averagingInt()、averagingDouble()、averagingLong():计算流中数值元素的平均值。
         *      5. maxBy()、minBy():根据提供的比较器找出流中的最大或最小元素。
         */
    // counting
    long count = items.stream().collect(Collectors.counting());
    System.out.println("Count: " + count); // Count: 6

    // summarizingInt
    System.out.println("summarizingInt 开始");
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    // 使用 summarizingInt 计算流中元素的最小值、最大值、总和和平均值
    IntSummaryStatistics stats = numbers.stream()
    .collect(summarizingInt(Integer::intValue));
    System.out.println("Min: " + stats.getMin());
    System.out.println("Max: " + stats.getMax());
    System.out.println("Sum: " + stats.getSum());
    System.out.println("Average: " + stats.getAverage());
    System.out.println("Count: " + stats.getCount());
    System.out.println("summarizingInt 结束");

    // summingInt
    int totalLength = items.stream().collect(Collectors.summingInt(String::length));
    System.out.println("Total Length: " + totalLength); // Total Length: 33

    // averagingInt
    double averageLength = items.stream().collect(Collectors.averagingInt(String::length));
    System.out.println("Average Length: " + averageLength); // Average Length: 5.5

    // maxBy
    Optional<String> maxItem = items.stream().collect(Collectors.maxBy(Comparator.comparingInt(String::length)));
    maxItem.ifPresent(item -> System.out.println("Max Item: " + item)); // Max Item: banana

    /**
         *  分组与分区:
         *      1. groupingBy():根据分类函数对流中的元素进行分组。
         *      2. partitioningBy():根据谓词将流中的元素分为两部分。
         */

        // groupingBy
        Map<Integer, List<String>> groupedByLength = items.stream().collect(Collectors.groupingBy(String::length));
        System.out.println("Grouped by Length: " + groupedByLength); // Grouped by Length: {5=[apple, apple, apple], 6=[banana, orange, banana]}

        // partitioningBy
        Map<Boolean, List<String>> partitioned = items.stream().collect(Collectors.partitioningBy(s -> s.length() > 5));
        System.out.println("Partitioned: " + partitioned); // Partitioned: {false=[apple, apple, apple], true=[banana, orange, banana]}

        /**
         *  归约操作
         *      reducing():通过累加器将流中的元素归约为一个值。
         *      collectingAndThen(): 执行收集操作后对结果进行进一步的转换。
         *      mapping(): 用于在收集过程中对元素进行转换。
         */
        // reducing
        Optional<String> concatenated = items.stream().collect(Collectors.reducing((s1, s2) -> s1 + ", " + s2));
        concatenated.ifPresent(result -> System.out.println("Concatenated: " + result)); // Concatenated: apple, banana, orange, apple, banana, apple

        // collectingAndThen
        List<String> unmodifiableList = items.stream().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
        System.out.println("Unmodifiable List: " + unmodifiableList); // Unmodifiable List: [apple, banana, orange, apple, banana, apple]

        // mapping
        List<Integer> lengths = items.stream().collect(Collectors.mapping(String::length, Collectors.toList()));
        System.out.println("Lengths: " + lengths); // Lengths: [5, 6, 6, 5, 6, 5]
    }

3.4.4 flatMap

用于处理嵌套的集合或将一个流中的每个元素映射到一个新的流,然后将这些新流合并成一个单一的流。

public static void main(String[] args) {
    List<List<String>> listOfLists = Arrays.asList(
        Arrays.asList("a", "b", "c"),
        Arrays.asList("d", "e"),
        Arrays.asList("f", "g", "h")
    );

    List<String> flattenedList = listOfLists.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());

    System.out.println(flattenedList); // 输出: [a, b, c, d, e, f, g, h]
}

3.4.5 distinct

用于去除流中的重复元素,并返回一个新的流。它基于元素的 equals() 方法来判断是否重复。

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 3, 2, 1);

    List<Integer> distinctNumbers = numbers.stream()
        .distinct()
        .collect(Collectors.toList());

    System.out.println(distinctNumbers); // 输出: [1, 2, 3, 4]
}

3.4.6 count

count 是一个终端操作,用于计算流中元素的数量。它返回一个 long 类型的结果,表示流中元素的总数。

public static void main(String[] args) {
    List<String> items = Arrays.asList("apple", "banana", "orange", "kiwi", "blueberry");
    long count = items.stream()
        .filter(item -> item.length() > 5)
        .count();
    System.out.println(count); // 输出: 3
}

3.4.7 Match

  1. allMatch(Predicate<? super T> predicate): 检查是否所有元素都匹配给定的谓词。
  2. anyMatch(Predicate<? super T> predicate): 检查是否至少有一个元素匹配给定的谓词。
  3. noneMatch(Predicate<? super T> predicate): 检查是否没有元素匹配给定的谓词。
public static void main(String[] args) {
    // allMatch 方法用于检查流中的所有元素是否都满足指定的条件。如果所有元素都满足条件,则返回 true,否则返回 false。
    List<Integer> numbers1 = Arrays.asList(2, 4, 6, 8, 10);
    boolean allEven = numbers1.stream().allMatch(n -> n % 2 == 0);
    System.out.println(allEven); // 输出: true

    // anyMatch 方法用于检查流中是否有至少一个元素满足指定的条件。如果有任意一个元素满足条件,则返回 true,否则返回 false。
    List<Integer> numbers2 = Arrays.asList(1, 2, 3, 4, 5);
    boolean anyEven = numbers2.stream().anyMatch(n -> n % 2 == 0);
    System.out.println(anyEven); // 输出: true

    // noneMatch 方法用于检查流中是否没有元素满足指定的条件。如果没有任何元素满足条件,则返回 true,否则返回 false。
    List<Integer> numbers3 = Arrays.asList(1, 3, 5, 7, 9);
    boolean noneEven = numbers3.stream().noneMatch(n -> n % 2 == 0);
    System.out.println(noneEven); // 输出: true
}

3.4.8 peek

peek 一个中间操作,它可以在流的元素上执行某些操作,而不会改变流的内容。该操作通常用于调试、日志记录或观察流中的元素。

  1. peek 是一个中间操作,它返回与原始流相同类型的新流。
  2. peek 操作不会消费流,因此它可以与其他操作链式调用。
  3. peek 操作只用于调试、日志记录或观察流中的元素,不应修改元素。
public static void main(String[] args) {
    List<String> fruits = Arrays.asList("apple", "banana", "orange", "kiwi");

    fruits.stream()
        .peek(fruit -> System.out.println("Processing: " + fruit))
        .map(String::toUpperCase)
        .forEach(System.out::println);
}
// 运行结果
Processing: apple
APPLE
Processing: banana
BANANA
Processing: orange
ORANGE
Processing: kiwi
KIWI

3.5 内置四大函数接口

  1. Predicate : 接受一个输入参数,返回一个布尔值结果。
  2. Function<T, R> : 接受一个输入参数,返回一个结果。
  3. Supplier : 不接受任何参数,返回一个结果。
  4. Consumer : 接受一个输入参数,不返回结果。
// 1. Predicate<T> 接口通常用于进行条件判断或过滤操作。它包含一个抽象方法 test,该方法接受一个输入参数并返回一个布尔值。
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // 输出: true
System.out.println(isEven.test(5)); // 输出: false

// 2. Function<T, R> 接口用于接受一个输入参数并返回一个结果。它包含一个抽象方法 apply。
Function<String, Integer> stringLength = s -> s.length();
System.out.println(stringLength.apply("Hello")); // 输出: 5
System.out.println(stringLength.apply("Java")); // 输出: 4

// 3. Supplier<T> 接口不接受任何参数,返回一个结果。它包含一个抽象方法 get。
Supplier<String> supplier = () -> "Hello, World!";
System.out.println(supplier.get()); // 输出: Hello, World!

// 4. Consumer<T> 接口接受一个输入参数,不返回结果。它包含一个抽象方法 accept。
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello, World!"); // 输出: Hello, World!
printer.accept("Java"); // 输出: Java