Optional类
以前对null的处理方式:
public class Test {
public static void main(String[] args) {
String userName = "凤姐";
// String userName = null;
if (userName != null) {
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
}
}
//控制台打印:用户名为:凤姐
Optional类介绍
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。
Optional类的创建方式:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
Optional类的常用方法:
isPresent() : 判断是否包含值,包含值返回true,不包含值返回false
get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
import java.util.Optional;
public class Test {
public static void main(String[] args) {
// Optional<String> userNameO = Optional.of("凤姐");
// Optional<String> userNameO = Optional.of(null);
// Optional<String> userNameO = Optional.ofNullable(null);
Optional<String> userNameO = Optional.empty();
// isPresent() : 判断是否包含值,包含值返回true,不包含值返回false。
if (userNameO.isPresent()) {
// get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException。
String userName = userNameO.get();
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
}
}
//控制台输出:用户名不存在
import java.util.Optional;
public class Test {
public static void main(String[] args) {
Optional<String> userName0 = Optional.of("张三");
Optional<String> userName1 = Optional.empty();
// 如果调用对象包含值,返回该值,否则返回参数t
System.out.println("用户名为" + userName0.orElse("null"));
System.out.println("用户名为" + userName1.orElse("null"));
//如果调用对象包含值,返回该值,否则返回 s 获取的值,public T orElseGet(Supplier<? extends T> other),可用Lambda表达式
String s1 = userName0.orElseGet(() -> {return "未知用户名";});
System.out.println("s1 = " + s1);
}
}
//控制台打印:
用户名为张三
用户名为null
s1 = 张三
Optional是一个可以为null的容器对象!
Stream流
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
// 1.拿到所有姓张的
ArrayList<String> zhangList = new ArrayList<>(); // {"张无忌", "张强", "张三丰"}
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// 2.拿到名字长度为3个字的
ArrayList<String> threeList = new ArrayList<>(); // {"张无忌", "张三丰"}
for (String name : zhangList) {
if (name.length() == 3) {
threeList.add(name);
}
}
// 3.打印这些数据
for (String name : threeList) {
System.out.println(name);
}
}
}
//控制台打印:
张无忌
张三丰
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使用另一个循环从头开始。
Stream的写法:
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
//控制台打印:
张无忌
张三丰
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。
Stream流式思想概述
注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约;
获取Stream流的两种方式
java.util.stream.Stream 是JDK 8新加入的流接口;
获取一个流非常简单,有以下几种常用的方式:
- 所有的 Collection 集合都可以通过 stream 默认方法获取流;
- Stream 接口的静态方法 of 可以获取数组对应的流;
根据Collection获取流:
java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流:
public interface Collection {
default Stream<E> stream()
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
// 集合获取流
// Collection接口中的方法: default Stream<E> stream() 获取流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//Set获取流
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
//Vector获取流
Vector<String> vector = new Vector<>();
Stream<String> stream3 = vector.stream();
//java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:
Map<String, String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
Stream中的静态方法of获取流
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
// Stream中的静态方法: static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] arr = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(arr);
Integer[] arr2 = {11, 22, 33};
Stream<Integer> stream8 = Stream.of(arr2);
// 注意:基本数据类型的数组不行
int[] arr3 = {11, 22, 33};
Stream<int[]> stream9 = Stream.of(arr3);
//会报错
Integer[] arr4 = {11, 22, 33};
Stream<int[]> stream10 = Stream.of(arr4);
}
}
Stream常用方法
Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取用前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
- 终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括 count 和forEach 方法;
- 非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法);
Stream注意事项(重要):
- Stream只能操作一次;
- Stream方法返回的是新的流;
- Stream不调用终结方法,中间的操作不会执行
Stream流的forEach方法
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
//Lambda表达式
one.stream().forEach((String s) ->{
System.out.println(s);
});
// 简写
one.stream().forEach(s -> System.out.println(s));
//方法引用
one.stream().forEach(System.out::println);
}
}
//控制台输出:
迪丽热巴
宋远桥
苏星河
老子
庄子
孙子
Stream流的count方法
Stream流提供 count 方法来统计其中的元素个数:
long count();
该方法返回一个long值代表元素个数。基本使用:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
System.out.println(one.stream().count());
}
}
//控制台输出:6
Stream流的filter方法
filter用于过滤数据,返回符合过滤条件的数据:
可以通过 filter 方法将一个流转换成另一个子集流。方法声明:
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。Stream流中的 filter 方法基本使用的代码如:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
one.stream().filter(s -> s.length() == 2).forEach(System.out::println);
}
}
//控制台输出:
老子
庄子
孙子
在这里通过Lambda表达式来指定了筛选的条件:姓名长度为2个字。
Stream流的limit方法
limit 方法可以对流进行截取,只取用前n个。方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
one.stream().limit(3).forEach(System.out::println);
}
}
//控制台输出:
迪丽热巴
宋远桥
苏星河
Stream流的skip方法
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
one.stream().skip(5).forEach(System.out::println);
}
}
//控制台输出:孙子
Stream流的map方法
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream流中的 map 方法基本使用的代码如:
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Stream<String> original = Stream.of("11", "22", "33");
Stream<Integer> result = original.map(Integer::parseInt);
result.forEach(s -> System.out.println(s + 10));
}
}
//控制台打印:
21
32
43
Stream流的sorted方法
如果需要将数据排序,可以使用 sorted 方法。方法签名:
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
Stream流中的 sorted 方法基本使用的代码如:
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
// sorted(): 根据元素的自然顺序排序
// sorted(Comparator<? super T> comparator): 根据比较器指定的规则排序
Stream.of(33, 22, 11, 55)
.sorted()
.sorted((o1, o2) -> o2 - o1)
.forEach(System.out::println);
}
}
//控制台打印:55 33 22 11
sorted 方法根据元素的自然顺序排序,也可以指定比较器排序;
Stream流的distinct方法
如果需要去除重复数据,可以使用 distinct 方法。方法签名:
Stream<T> distinct();
Stream流中的 distinct 方法基本使用的代码如:
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Stream.of(22, 33, 22, 11, 33)
.distinct()
.forEach(System.out::println);
}
}
//控制台打印:22 33 11
如果是自定义类型如何是否也能去除重复的数据呢?
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
// 省略set/get
}
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("张学友", 56),
new Person("黎明", 52))
.distinct()
.forEach(System.out::println);
}
}
//控制台打印:
Person@448139f0
Person@7cca494b
Person@7ba4f24f
Person@3b9a45b3
发现并没有清除,自定义类型是根据对象的hashCode和equals来去除重复元素的,重写Person类的equals()与hashCode():
import java.util.Objects;
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) &&
Objects.equals(age, person.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
再运行main方法,控制台打印:
Person@26c2bf4c
Person@2c9f1054
Person@26082f5
发现重复的已经被去除;
Stream流的match方法
如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法。方法签名:
boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
Stream流中的 Match 相关方法基本使用的代码如:
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
boolean b = Stream.of(5, 3, 6, 1)
// .allMatch(e -> e > 0); // allMatch: 元素是否全部满足条件
// .anyMatch(e -> e > 5); // anyMatch: 元素是否任意有一个满足条件
.noneMatch(e -> e < 0); // noneMatch: 元素是否全部不满足条件
System.out.println("b = " + b);
}
}
//控制台打印:b = true
Stream流的find方法
如果需要找到某些数据,可以使用 find 相关方法。方法签名:
Optional<T> findFirst();
Optional<T> findAny();
import java.util.Optional;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst();
System.out.println("first = " + first.get());
Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny(); //默认找第一个
System.out.println("any = " + any.get());
}
}
//控制台打印:
first = 5
any = 5
Stream流的max和min方法
如果需要获取最大和最小值,可以使用 max 和 min 方法。方法签名:
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
Stream流中的 max 和 min 相关方法基本使用的代码如:
import java.util.Optional;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Optional<Integer> max = Stream.of(5, 3, 6, 1).max((o1, o2) -> o1 - o2);
System.out.println("first = " + max.get());
Optional<Integer> min = Stream.of(5, 3, 6, 1).min((o1, o2) -> o1 - o2);
System.out.println("any = " + min.get());
}
}
//控制台打印:
first = 6
any = 1
Stream流的reduce方法
T reduce(T identity, BinaryOperator<T> accumulator);
Stream流中的 reduce 相关方法基本使用的代码如:
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
//Lambda表达式
int reduce = Stream.of(4, 5, 3, 9)
.reduce(0, (a, b) -> {
System.out.println("a = " + a + ", b = " + b);
return a + b;
});
// reduce:
// 第一次将默认做赋值给x, 取出第一个元素赋值给y,进行操作
// 第二次,将第一次的结果赋值给x, 取出二个元素赋值给y,进行操作
// 第三次,将第二次的结果赋值给x, 取出三个元素赋值给y,进行操作
// 第四次,将第三次的结果赋值给x, 取出四个元素赋值给y,进行操作
System.out.println("reduce = " + reduce);
//Lambda表达式简化
int reduce2 = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return Integer.sum(x, y);
});
//方法引用
int reduce3 = Stream.of(4, 5, 3, 9).reduce(0, Integer::sum);
int max = Stream.of(4, 5, 3, 9)
.reduce(0, (x, y) -> {
return x > y ? x : y;
});
System.out.println("max = " + max);
}
}
//控制台输出:
a = 0, b = 4
a = 4, b = 5
a = 9, b = 3
a = 12, b = 9
reduce = 21
max = 9
Stream流的concat方法
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
Stream<String> result = Stream.concat(streamA, streamB);
result.forEach(System.out::println);
Long count = streamA.count();
System.out.println(count);
}
}
合并产生一个新的流,但是之前的流就已经被关闭了,再进行操作将会报错:
张三
李四
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
at java.util.stream.LongPipeline.<init>(LongPipeline.java:91)
at java.util.stream.LongPipeline$StatelessOp.<init>(LongPipeline.java:574)
at java.util.stream.ReferencePipeline$5.<init>(ReferencePipeline.java:221)
at java.util.stream.ReferencePipeline.mapToLong(ReferencePipeline.java:220)
at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
at Test.main(Test.java:9)
Stream综合案例
现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下
若干操作步骤:
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
- 根据姓名创建 Person 对象;
- 打印整个队伍的Person对象信息。
两个队伍(集合)的代码如下:
List<String> one = List.of("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
List<String> two = List.of("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱","张三");
Person.java:
public class Person {
private String name;
}
传统方式:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
List<String> two = new ArrayList<>();
Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 第一个队伍只要名字为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);
// 根据姓名创建Person对象;
List<Person> totalPersonList = new ArrayList<>();
for (String name : totalNames) {
totalPersonList.add(new Person(name));
}
// 打印整个队伍的Person对象信息。
for (Person person : totalPersonList) {
System.out.println(person);
}
}
}
//控制台打印:
Person{name='宋远桥'}
Person{name='苏星河'}
Person{name='洪七公'}
Person{name='张二狗'}
Person{name='张天爱'}
Person{name='张三'}
Stream方式:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
List<String> two = new ArrayList<>();
Collections.addAll(one,"迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
Collections.addAll(two,"古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
// 第一个队伍只要名字为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).map(Person::new).forEach(System.out::println);
}
}
//控制台打印与上面是一样的
Stream流中的结果到集合中
Stream流提供 collect 方法,其参数需要一个 java.util.stream.Collector<T,A, R> 接口对象来指定收集到哪种集合中。java.util.stream.Collectors 类提供一些方法,可以作为 Collector`接口的实例:
public static <T> Collector<T, ?, List<T>> toList() :转换为 List 集合。
public static <T> Collector<T, ?, Set<T>> toSet() :转换为 Set 集合。
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("aa", "bb", "cc");
//转换为 List 集合。
List<String> list = stream.collect(Collectors.toList());
//转换为 Set 集合。
Set<String> set = stream.collect(Collectors.toSet());
//转换为 ArrayList集合。
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
//转换为 HashSet集合。
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
}
}
Stream流中的结果到数组中
Stream提供 toArray 方法来将结果放到一个数组中,返回值类型是Object[]的:
Object[] toArray();
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("aa", "bb", "cc");
//Object类型的数组
// Object[] objects = stream.toArray();
// for (Object obj : objects) {
// System.out.println();
// }
//转为String类型的数组
String[] strings = stream.toArray(String[]::new);
for (String str : strings) {
System.out.println(str);
}
}
}
//控制台打印:
aa
bb
cc