共计 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 接口的匿名外部类对象来指定工作内 容,再将其交给一个线程来启动。
传统写法剖析:
- Thread 类须要 Runnable 接口作为参数,其中的形象 run 办法是用来指定线程工作内容的外围;
- 为了指定 run 的办法体,不得不须要 Runnable 接口的实现类;
- 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不应用匿名外部类;
- 必须笼罩重写形象 run 办法,所以办法名称、办法参数、办法返回值不得不再写一遍,且不能写错;
- 而实际上,仿佛只有办法体才是关键所在。
编程思维的转变
咱们真的心愿创立一个匿名外部类对象吗?不。咱们只是为了做这件事件而不得不创立一个对象。咱们真正心愿做的事件是:将 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 个局部组成:
- 一些参数
- 一个箭头
- 一段代码
- 规范格局:
(参数类型 参数名称) ‐> {代码语句}
-
格局阐明:
- 小括号内的语法与传统办法参数列表统一:无参数则留空;多个参数则用逗号分隔。
->
是新引入的语法格局,代表指向动作。- 大括号内的语法与传统办法体要求基本一致。
无参无返回值代码,匿名外部类与 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 name
和int 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 规范格局的根底上,应用省略写法的规定为:
- 小括号内参数的类型能够省略;
- 如果小括号内 有且仅有一个参,则小括号能够省略;
- 如果大括号内 有且仅有一个语句,则无论是否有返回值,都能够省略大括号、return 关键字及语句分号。
1.4-Lambda 的前提条件(理解)
Lambda 的语法十分简洁,齐全没有面向对象简单的解放。然而应用时有几个问题须要特地留神:
- 应用 Lambda 必须具备接口,且要求 接口中有且仅有一个形象办法 。
无论是 JDK 内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中的形象办法存在且惟一时,才能够应用 Lambda。 - 应用 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 循环的循环体才是“做什么”
为什么应用循环?因为要进行遍历。但循环是遍历的惟一形式吗?遍历是指每一个元素逐个进行解决,而并不是从第一个到最初一个依次解决的循环。前者是目标,后者是形式。
试想一下,如果心愿对汇合中的元素进行筛选过滤:
- 将汇合 A 依据条件一过滤为 子集 B ;
- 而后再依据条件二过滤为 子集 C 。
那怎么办?在 Java 8 之前的做法可能为:
这段代码中含有三个循环,每一个作用不同:
- 首先筛选所有姓张的人;
- 而后筛选名字有三个字的人;
- 最初进行对后果进行打印输出。
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
那样的链式调用。本大节中,终结办法包含count
和forEach
办法。 - 非终结办法 :返回值类型依然是
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 循环)顺次 进行以下若干操作步骤:
- 第一个队伍只有名字为 3 个字的成员姓名;
- 第一个队伍筛选之后只有前 3 集体;
- 第二个队伍只有姓张的成员姓名;
- 第二个队伍筛选之后不要前 2 集体;
- 将两个队伍合并为一个队伍;
- 打印整个队伍的姓名信息。
两个队伍(汇合)的代码如下:
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- 函数拼接与终结办法(理解)