关于java:JavaSE第22篇Lambda表达式函数式接口

7次阅读

共计 14597 个字符,预计需要花费 37 分钟才能阅读完成。

外围概述:在开发中,咱们常常应用匿名外部类作为实参传递参数,咱们能够发现匿名外部类的格局比拟繁琐,那么如何简化呢?本篇咱们将会学习到 Lambda 表达式来帮忙咱们解决问题。另外咱们也将学习与 Lambda 表达式相干的函数式接口,以及 Stream 流。

第一章:Lambda 表达式

1.1- 函数式编程介绍(理解)

在数学中,函数就是有输入量、输出量的一套计算计划,也就是“拿什么货色做什么事件”。相对而言,面向对象过分强调“必须通过对象的模式来做事件”,而函数式思维则尽量疏忽面向对象的简单语法——强调做什么,而不是以什么模式做。

面向对象的思维: 做一件事件, 找一个能解决这个事件的对象, 调用对象的办法, 实现事件。

函数式编程思维: 只有能获取到后果, 谁去做的, 怎么做的都不重要, 器重的是后果, 不器重过程。

1.2- 为什么要用 Lambda 表达式(理解)

以 Runnable 为例

当须要启动一个线程去实现工作时,通常会通过 java.lang.Runnable 接口来定义工作内容,并应用 java.lang.Thread 类来启动该线程。

传统写法:

public class Demo01Runnable {public static void main(String[] args) { 
        // 匿名外部类 
        Runnable task = new Runnable() { 
            @Override 
            public void run() {
              // 笼罩重写形象办法 
              System.out.println("多线程工作执行!");
            } 
        };
        new Thread(task).start(); // 启动线程}
}

本着“所有皆对象”的思维,这种做法是无可非议的:首先创立一个 Runnable 接口的匿名外部类对象来指定工作内 容,再将其交给一个线程来启动。

传统写法剖析:

  1. Thread 类须要 Runnable 接口作为参数,其中的形象 run 办法是用来指定线程工作内容的外围;
  2. 为了指定 run 的办法体,不得不须要 Runnable 接口的实现类;
  3. 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不应用匿名外部类;
  4. 必须笼罩重写形象 run 办法,所以办法名称、办法参数、办法返回值不得不再写一遍,且不能写错;
  5. 而实际上,仿佛只有办法体才是关键所在。

编程思维的转变

咱们真的心愿创立一个匿名外部类对象吗?不。咱们只是为了做这件事件而不得不创立一个对象。咱们真正心愿做的事件是:将 run 办法体内的代码传递给 Thread 类通晓。

传递一段代码——这才是咱们真正的目标。而创建对象只是受限于面向对象语法而不得不采取的一种伎俩形式。

那有没有更加简略的方法?如果咱们将关注点从“怎么做”回归到“做什么”的实质上,就会发现只有可能更好地达 到目标,过程与模式其实并不重要

Lambda 优化体验

借助 Java 8 的全新语法,上述 Runnable 接口的匿名外部类写法能够通过更简略的 Lambda 表达式达到等效:

public class Demo01LambdaRunnable {public static void main(String[] args) {new Thread(() -> System.out.println("多线程工作执行!")).start(); // 启动线程}

这段代码和方才的执行成果是齐全一样的,能够在 1.8 或更高的编译级别下通过。从代码的语义中能够看出:咱们启动了一个线程,而线程工作的内容以一种更加简洁的模式被指定。

不再有“不得不创立接口对象”的解放,不再有“形象办法笼罩重写”的累赘,就是这么简略!

1.3-Lambda 格局(重要)

格局

  • Lambda 省去面向对象的条条框框,格局由 3 个局部组成:

    1. 一些参数
    2. 一个箭头
    3. 一段代码
  • 规范格局: (参数类型 参数名称) ‐> {代码语句}
  • 格局阐明:

    1. 小括号内的语法与传统办法参数列表统一:无参数则留空;多个参数则用逗号分隔。
    2. -> 是新引入的语法格局,代表指向动作。
    3. 大括号内的语法与传统办法体要求基本一致。

无参无返回值代码,匿名外部类与 lambda 比照

代码:

new Thread(new Runnable() {
            @Override
            public void run() {System.out.println("多线程工作执行!");
            }
}).start();

仔细分析该代码中,Runnable接口只有一个 run 办法的定义:

  • public abstract void run();

即制订了一种做事件的计划(其实就是一个办法):

  • 无参数:不须要任何条件即可执行该计划。
  • 无返回值:该计划不产生任何后果。
  • 代码块(办法体):该计划的具体执行步骤。

同样的语义体现在 Lambda 语法中,要更加简略:

() -> System.out.println("多线程工作执行!")

代码阐明:

  • 后面的一对小括号即 run 办法的参数(无),代表不须要任何条件;
  • 两头的一个箭头代表将后面的参数传递给前面的代码;
  • 前面的输入语句即业务逻辑代码。

有参有返回值代码,Comparator 接口的应用

上面举例演示 java.util.Comparator<T> 接口的应用场景代码,其中的形象办法定义为:

  • public abstract int compare(T o1, T o2);

当须要对一个对象数组进行排序时,Arrays.sort办法须要一个 Comparator 接口实例来指定排序的规定。假如有一个 Person 类,含有 String nameint age两个成员变量:

public class Person { 
    private String name;
    private int age;
    
    // 省略结构器、toString 办法与 Getter Setter 
}

传统写法

如果应用传统的代码对 Person[] 数组进行排序,写法如下:

public class Demo05Comparator {public static void main(String[] args) {
          // 原本年龄乱序的对象数组
        Person[] array = { new Person("古力娜扎", 19),            new Person("迪丽热巴", 18),               new Person("马尔扎哈", 20) };

          // 匿名外部类
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {return o1.getAge() - o2.getAge();}
        };
        Arrays.sort(array, comp); // 第二个参数为排序规定,即 Comparator 接口实例

        for (Person person : array) {System.out.println(person);
        }
    }
}

这种做法在面向对象的思维中,仿佛也是“天经地义”的。其中 Comparator 接口的实例(应用了匿名外部类)代表了“依照年龄从小到大”的排序规定。

代码剖析

上面咱们来搞清楚上述代码真正要做什么事件。

  • 为了排序,Arrays.sort办法须要排序规定,即 Comparator 接口的实例,形象办法 compare 是要害;
  • 为了指定 compare 的办法体,不得不 须要 Comparator 接口的实现类;
  • 为了省去定义一个 ComparatorImpl 实现类的麻烦,不得不 应用匿名外部类;
  • 必须笼罩重写形象 compare 办法,所以办法名称、办法参数、办法返回值 不得不 再写一遍,且不能写错;
  • 实际上,只有参数和办法体才是要害

Lambda 写法

public class Demo06ComparatorLambda {public static void main(String[] args) {Person[] array = {new Person("古力娜扎", 19),
              new Person("迪丽热巴", 18),
              new Person("马尔扎哈", 20) };

        Arrays.sort(array, (Person a, Person b) -> {return a.getAge() - b.getAge();});

        for (Person person : array) {System.out.println(person);
        }
    }
}

省略格局

省略规定

在 Lambda 规范格局的根底上,应用省略写法的规定为:

  1. 小括号内参数的类型能够省略;
  2. 如果小括号内 有且仅有一个参,则小括号能够省略;
  3. 如果大括号内 有且仅有一个语句,则无论是否有返回值,都能够省略大括号、return 关键字及语句分号。

1.4-Lambda 的前提条件(理解)

Lambda 的语法十分简洁,齐全没有面向对象简单的解放。然而应用时有几个问题须要特地留神:

  1. 应用 Lambda 必须具备接口,且要求 接口中有且仅有一个形象办法
    无论是 JDK 内置的 RunnableComparator 接口还是自定义的接口,只有当接口中的形象办法存在且惟一时,才能够应用 Lambda。
  2. 应用 Lambda 必须具备接口作为办法参数。
    也就是办法的参数或局部变量类型必须为 Lambda 对应的接口类型,能力应用 Lambda 作为该接口的实例。

有且仅有一个形象办法的接口,称为“函数式接口”。

第二章:函数式接口

2.1- 概述(理解)

介绍

函数式接口在 Java 中是指:有且仅有一个形象办法的接口

函数式接口,即实用于函数式编程场景的接口。而 Java 中的函数式编程体现就是 Lambda,所以函数式接口就是能够实用于 Lambda 应用的接口。只有确保接口中有且仅有一个形象办法,Java 中的 Lambda 能力顺利地进行推导。

从利用层面来讲,Java 中的 Lambda 能够看做是匿名外部类的简化格局。

格局

只有确保接口中有且仅有一个形象办法即可:

修饰符 interface 接口名称 {public abstract 返回值类型 办法名称(可选参数信息);
    // 其余非形象办法内容
}

public abstract 能够省略

FunctionalInterface 注解

@Override 注解的作用相似,Java 8 中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解可用于一个接口的定义上:

@FunctionalInterface
public interface MyFunctionalInterface {void myMethod();
}

一旦应用该注解来定义接口,编译器将会强制查看该接口是否的确有且仅有一个形象办法,否则将会报错。不过,即便不应用该注解,只有满足函数式接口的定义,这依然是一个函数式接口,应用起来都一样。

2.2- 罕用的函数式接口(重点)

JDK 提供了大量罕用的函数式接口以丰盛 Lambda 的典型应用场景,它们次要在 java.util.function 包中被提供。上面是最简略的几个接口及应用示例。

Supplier 接口

java.util.function.Supplier<T>接口,它意味着 ” 供应 ” , 对应的 Lambda 表达式须要“对外提供”一个合乎泛型类型的对象数据。

形象办法 : get

仅蕴含一个无参的办法:T get()。用来获取一个泛型参数指定类型的对象数据。

public class Demo08Supplier {private static String getString(Supplier<String> function) {return function.get();
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        System.out.println(getString(() -> msgA + msgB));
    }
}

求数组元素最大值

应用 Supplier 接口作为办法参数类型,通过 Lambda 表达式求出 int 数组中的最大值。提醒:接口的泛型请应用 java.lang.Integer 类。

代码示例:

public class DemoIntArray {public static void main(String[] args) {int[] array = {10, 20, 100, 30, 40, 50};
        printMax(() -> {int max = array[0];
            for (int i = 1; i < array.length; i++) {if (array[i] > max) {max = array[i];
                }
            }
            return max;
        });
    }

    private static void printMax(Supplier<Integer> supplier) {int max = supplier.get();
        System.out.println(max);
    }
}

Consumer 接口

java.util.function.Consumer<T>接口则正好相同,它不是生产一个数据,而是 生产 一个数据,其数据类型由泛型参数决定。

形象办法:accept

Consumer接口中蕴含形象办法void accept(T t),意为生产一个指定泛型的数据。根本应用如:

import java.util.function.Consumer;

public class Demo09Consumer {private static void consumeString(Consumer<String> function , String str) {function.accept(str);
    }

    public static void main(String[] args) {consumeString(s -> System.out.println(s));
      
    }
}

Function 接口

java.util.function.Function<T,R>接口用来依据一个类型的数据失去另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为“函数 Function”。

形象办法:apply

Function接口中最次要的形象办法为:R apply(T t),依据类型 T 的参数获取类型 R 的后果。应用的场景例如:将 String 类型转换为 Integer 类型。

public class Demo11FunctionApply {private static void method(Function<String, Integer> function, Str str) {int num = function.apply(str);
        System.out.println(num + 20);
    }

    public static void main(String[] args) {method(s -> Integer.parseInt(s) , "10");
    }
}

Predicate 接口

有时候咱们须要对某种类型的数据进行判断,从而失去一个 boolean 值后果。这时能够应用 java.util.function.Predicate<T> 接口。

形象办法:test

Predicate接口中蕴含一个形象办法:boolean test(T t)。用于条件判断的场景,条件判断的规范是传入的 Lambda 表达式逻辑,只有字符串长度大于 5 则认为很长。

public class Demo15PredicateTest {private static void method(Predicate<String> predicate,String str) {boolean veryLong = predicate.test(str);
        System.out.println("字符串很长吗:" + veryLong);
    }

    public static void main(String[] args) {method(s -> s.length() > 5, "HelloWorld");
    }
}

第三章:Stream 流

在 Java 8 中,得益于 Lambda 所带来的函数式编程,引入了一个 全新的 Stream 概念,用于解决已有汇合类库既有的弊病。

3.1- 为什么要用 Stream 流(理解)

传统汇合的多步遍历代码

简直所有的汇合(如 Collection 接口或 Map 接口等)都反对间接或间接的遍历操作。而当咱们须要对汇合中的元素进行操作的时候,除了必须的增加、删除、获取外,最典型的就是汇合遍历。例如:

public class Demo10ForEach {public static void main(String[] args) {List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        for (String name : list) {System.out.println(name);
        }
    }  
}

这是一段非常简单的汇合遍历操作:对汇合中的每一个字符串都进行打印输出操作。

循环遍历的弊病

Java 8 的 Lambda 让咱们能够更加专一于 做什么 (What),而不是 怎么做(How),这点此前曾经联合外部类进行了比照阐明。当初,咱们认真领会一下上例代码,能够发现:

  • for 循环的语法就是“怎么做
  • for 循环的循环体才是“做什么

为什么应用循环?因为要进行遍历。但循环是遍历的惟一形式吗?遍历是指每一个元素逐个进行解决,而并不是从第一个到最初一个依次解决的循环。前者是目标,后者是形式。

试想一下,如果心愿对汇合中的元素进行筛选过滤:

  1. 将汇合 A 依据条件一过滤为 子集 B
  2. 而后再依据条件二过滤为 子集 C

那怎么办?在 Java 8 之前的做法可能为:

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 而后筛选名字有三个字的人;
  3. 最初进行对后果进行打印输出。
public class Demo11NormalFilter {public static void main(String[] args) {List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        List<String> zhangList = new ArrayList<>();
        for (String name : list) {if (name.startsWith("张")) {zhangList.add(name);
            }
        }

        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {if (name.length() == 3) {shortList.add(name);
            }
        }

        for (String name : shortList) {System.out.println(name);
        }
    }
}

每当咱们须要对汇合中的元素进行操作的时候,总是须要进行循环、循环、再循环。这是天经地义的么?不是。循环是做事件的形式,而不是目标。另一方面,应用线性循环就意味着只能遍历一次。如果心愿再次遍历,只能再应用另一个循环从头开始。

那,Lambda 的衍生物 Stream 能给咱们带来怎么更加优雅的写法呢?

Stream 的更优写法

上面来看一下借助 Java 8 的 Stream API,什么才叫优雅:

public class Demo12StreamFilter {public static void main(String[] args) {List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        list.stream()
              .filter(s -> s.startsWith("张"))
            .filter(s -> s.length() == 3)
            .forEach(s -> System.out.println(s));
    }
}

间接浏览代码的字面意思即可完满展现无关逻辑形式的语义:获取流、过滤姓张、过滤长度为 3、逐个打印。代码中并没有体现应用线性循环或是其余任何算法进行遍历,咱们真正要做的事件内容被更好地体现在代码中。

3.2- 流式思维(理解)

留神:请临时遗记对传统 IO 流的固有印象!

整体来看,流式思维相似于工厂车间的“生产流水线”。

当须要对多个元素进行操作(特地是多步操作)的时候,思考到性能及便利性,咱们应该首先拼好一个“模型”步骤 计划,而后再依照计划去执行它。

这张图中展现了过滤、映射、跳过、计数等多步操作,这是一种汇合元素的解决计划,而计划就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的办法,能够从一个流模型转换为另一个流模型。而最右侧的数字 3 是最终后果。

3.3- 获取流的形式(重点)

java.util.stream.Stream<T>是 Java 8 新退出的最罕用的流接口。(这并不是一个函数式接口。)

获取一个流非常简单,有以下几种罕用的形式:

  • 所有的 Collection 汇合都能够通过 stream 默认办法获取流;
  • Stream接口的静态方法 of 能够获取数组对应的流。

形式 1 : 依据 Collection 获取流

首先,java.util.Collection接口中退出了 default 办法 stream 用来获取流,所以其所有实现类均可获取流。

import java.util.*;
import java.util.stream.Stream;
/*
    获取 Stream 流的形式

    1.Collection 中 办法
        Stream stream()
    2.Stream 接口 中静态方法
        of(T...t) 向 Stream 中增加多个数据
 */
public class Demo13GetStream {public static void main(String[] args) {List<String> list = new ArrayList<>();
        // ...
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        // ...
        Stream<String> stream2 = set.stream();}
}

形式 2: 依据数组获取流

如果应用的不是汇合或映射而是数组,因为数组对象不可能增加默认办法,所以 Stream 接口中提供了静态方法of,应用很简略:

import java.util.stream.Stream;

public class Demo14GetStream {public static void main(String[] args) {String[] array = {"张无忌", "张翠山", "张三丰", "张一元"};
        Stream<String> stream = Stream.of(array);
    }
}

of办法的参数其实是一个可变参数,所以反对数组。

3.4- 罕用办法(重点)

流模型的操作很丰盛,这里介绍一些罕用的 API。这些办法能够被分成两种:

  • 终结办法 :返回值类型不再是Stream 接口本身类型的办法,因而不再反对相似 StringBuilder 那样的链式调用。本大节中,终结办法包含 countforEach办法。
  • 非终结办法 :返回值类型依然是Stream 接口本身类型的办法,因而反对链式调用。(除了终结办法外,其余办法均为非终结办法。)

更多办法,请自行参考 API 文档。

逐个解决办法:forEach

尽管办法名字叫 forEach,然而与 for 循环中的“for-each”昵称不同。

void forEach(Consumer<? super T> action);

该办法接管一个 Consumer 接口函数,会将每一个流元素交给该函数进行解决。

java.util.function.Consumer<T> 接口是一个消费型接口。Consumer 接口中蕴含形象办法 void accept(T t),意为生产一个指定泛型的数据。

根本应用

public class Test01 {public static void main(String[] args) {List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("李四");
    list.add("王五");
    list.stream().forEach(name-> System.out.println(name));
  }
}

过滤办法:filter

能够通过 filter 办法将一个流转换成另一个子集流。办法签名:

Stream<T> filter(Predicate<? super T> predicate);

该接口接管一个 Predicate 函数式接口参数(能够是一个 Lambda 或办法援用)作为筛选条件。

温习 Predicate 接口

java.util.stream.Predicate函数式接口,其中惟一的形象办法为:

boolean test(T t);

该办法将会产生一个 boolean 值后果,代表指定的条件是否满足。如果后果为 true,那么 Stream 流的 filter 办法 将会留用元素;如果后果为 false,那么 filter 办法将会舍弃元素。

根本应用

Stream 流中的 filter 办法根本应用的代码如:

public class Test02 {public static void main(String[] args) {List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("李四");
    list.add("张三丰");
    list.add("王五");
    list.add("张无忌");
    list.stream()
            .filter(name->name.startsWith("张"))
            .forEach(name-> System.out.println(name));
  }
}

在这里通过 Lambda 表达式来指定了筛选的条件:必须姓张。

映射办法:map

如果须要将流中的元素映射到另一个流中,能够应用 map 办法。办法签名:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口须要一个 Function 函数式接口参数,能够将以后流中的 T 类型数据转换为另一种 R 类型的流。

温习 Function 接口

此前咱们曾经学习过 java.util.stream.Function 函数式接口,其中惟一的形象办法为:

R apply(T t);

这能够将一种 T 类型转换成为 R 类型,而这种转换的动作,就称为“映射”

根本应用

public class Test03 {public static void main(String[] args) {String[]strs = {"11","22","33","44"};
    Stream.of(strs)
            .map((s -> Integer.parseInt(s)))
            .forEach(i-> System.out.println(i+2));
  }
}

这段代码中,map 办法的参数通过办法援用,将字符串类型转换成为了 int 类型(并主动装箱为 Integer 类对 象)。

统计个数办法:count

正如旧汇合 Collection 当中的 size 办法一样,流提供 count 办法来数一数其中的元素个数:

long count();

该办法返回一个 long 值代表元素个数(不再像旧汇合那样是 int 值)。根本应用:

  public static void main(String[] args) {List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("李四");
    list.add("张三丰");
    list.add("王五");
    list.add("张无忌");
    long i = list.stream()
            .filter(name->name.startsWith("张"))
            .count();
    System.out.println(i);//3
  }

取用前几个办法:limit

limit 办法能够对流进行截取,只取用前 n 个。办法签名:

Stream<T> limit(long maxSize);

参数是一个 long 型,如果汇合以后长度大于参数则进行截取;否则不进行操作。根本应用:

public class Test06 {public static void main(String[] args) {List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("李四");
    list.add("张三丰");
    list.add("王五");
    list.add("张无忌");
    list.stream()
            .filter(name->name.startsWith("张"))
            .limit(2)
            .forEach(name-> System.out.println(name));
  }
}
// 后果 - 张三、张三丰

跳过前几个办法:skip

如果心愿跳过前几个元素,能够应用 skip 办法获取一个截取之后的新流:

Stream<T> skip(long n);

如果流的以后长度大于 n,则跳过前 n 个;否则将会失去一个长度为 0 的空流。根本应用:

public class Test07 {public static void main(String[] args) {List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("李四");
    list.add("张三丰");
    list.add("王五");
    list.add("张无忌");
    list.stream()
            .filter(name->name.startsWith("张"))
            .skip(2)
            .forEach(name-> System.out.println(name));
  }
}
// 后果:张无忌

组合办法,concat

如果有两个流,心愿合并成为一个流,那么能够应用 Stream 接口的静态方法 concat:

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

这是一个静态方法,与 java.lang.String 当中的 concat 办法是不同的。

该办法的根本应用代码如:

public class Test08 {public static void main(String[] args) {Stream<String>  s1 = Stream.of("张三","李四");
    Stream<String>  s2 = Stream.of("王五","赵六");
    Stream.concat(s1,s2).forEach(name-> System.out.println(name));
  }
}
// 后果:张三、李四、王五、赵六

流转汇合办法,collect

从 Stream 流对象转成汇合对象,应用 Stream 接口办法collect:

public class StreamDemo06 {public static void main(String[] args) {List<String> list = new ArrayList<String>();

        list.add("张无忌");
        list.add("周芷若");
        list.add("张三丰");

        Stream<String> stream = list.stream();
        //Stream 流对象 collect()传递 Collectors 静态方法 toList() 流对象元素转成汇合
        List<String> newList =  stream.filter(s->s.startsWith("张")).collect(Collectors.toList());
        System.out.println(newList);
    }
}

3.5- 综合案例(练习)

需要

当初有两个 ArrayList 汇合存储队伍当中的多个成员姓名,要求应用传统的 for 循环(或加强 for 循环)顺次 进行以下若干操作步骤:

  1. 第一个队伍只有名字为 3 个字的成员姓名;
  2. 第一个队伍筛选之后只有前 3 集体;
  3. 第二个队伍只有姓张的成员姓名;
  4. 第二个队伍筛选之后不要前 2 集体;
  5. 将两个队伍合并为一个队伍;
  6. 打印整个队伍的姓名信息。

两个队伍(汇合)的代码如下:

public class Demo21ArrayListNames {public static void main(String[] args) {List<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("孙子");
        one.add("洪七公");

        List<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三");
        // ....
    }
}

传统形式

应用 for 循环 , 示例代码:

public class Demo22ArrayListNames {public static void main(String[] args) {List<String> one = new ArrayList<>();
        // ...

        List<String> two = new ArrayList<>();
        // ...

        // 第一个队伍只有名字为 3 个字的成员姓名;List<String> oneA = new ArrayList<>();
        for (String name : one) {if (name.length() == 3) {oneA.add(name);
            }
        }

        // 第一个队伍筛选之后只有前 3 集体;List<String> oneB = new ArrayList<>();
        for (int i = 0; i < 3; i++) {oneB.add(oneA.get(i));
        }

        // 第二个队伍只有姓张的成员姓名;List<String> twoA = new ArrayList<>();
        for (String name : two) {if (name.startsWith("张")) {twoA.add(name);
            }
        }

        // 第二个队伍筛选之后不要前 2 集体;List<String> twoB = new ArrayList<>();
        for (int i = 2; i < twoA.size(); i++) {twoB.add(twoA.get(i));
        }

        // 将两个队伍合并为一个队伍;List<String> totalNames = new ArrayList<>();
        totalNames.addAll(oneB);
        totalNames.addAll(twoB);        

        // 打印整个队伍的姓名信息。for (String name : totalNames) {System.out.println(name);
        }
    }
}

运行后果为:

宋远桥
苏星河
洪七公
张二狗
张天爱
张三

Stream 形式

等效的 Stream 流式解决代码为:

public class Demo23StreamNames {public static void main(String[] args) {List<String> one = new ArrayList<>();
        // ...

        List<String> two = new ArrayList<>();
        // ...

        // 第一个队伍只有名字为 3 个字的成员姓名;// 第一个队伍筛选之后只有前 3 集体;Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);

        // 第二个队伍只有姓张的成员姓名;// 第二个队伍筛选之后不要前 2 集体;Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);

        // 将两个队伍合并为一个队伍;// 依据姓名创立 Person 对象;// 打印整个队伍的 Person 对象信息。Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s));
    }
}

运行成果齐全一样:

宋远桥
苏星河
洪七公
张二狗
张天爱
张三

3.6- 函数拼接与终结办法(理解)

正文完
 0