1 Java 8 新特性简介
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。 Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
2 Lambda表达式
2.1 简介
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升
2.2 案例演示
从匿名类到 Lambda 的转换
案例一
public class LambdaDemo1 {
public static void main(String[] args) {
// Runnable mytask = new Runnable() {
// @Override
// public void run() {
// for (int i = 1; i <= 100; i++) {
// System.out.println(Thread.currentThread().getName()+ " "+ i);
// }
// }
// };
Runnable mytask = () ->{
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+ " "+ i);
}
};
for (int i = 0; i < 4; i++) {
new Thread(mytask).start();
}
System.out.println("main over");
}
}
案例二
public class LambdaDemo2 {
@FunctionalInterface
interface A{
void f1();
}
public static void show(A a) {
a.f1();
}
public static void main(String[] args) {
// A a = new A() {
// @Override
// public void f1() {
// System.out.println("a f1");
// }
//
// @Override
// public void f2() {
// System.out.println("a f2");
// }
//
// };
// show(a);
A a = ()->{
System.out.println("f1");
};
show(a);
}
}
2.3 语法
Lambda 表达式: 在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
- 匿名内部类实现的接口一定是函数接口(只有一个抽象方法的接口)
- 左侧: 指定了 Lambda 表达式需要的参数列表
- 右侧: 指定了 Lambda 体, 是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
2.4 类型推断
Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断” 。
案例演示
案例一:一个参数
public class LambdaDemo3 {
public static void show(A a){
a.f1("hello lambda 表达式");
}
@FunctionalInterface
interface A{
void f1(String s);
}
/**
* 匿名内部类
*/
@Test
public void test1(){
A a = new A() {
@Override
public void f1(String s) {
System.out.println(s);
}
};
show(a);
}
/**
* lambda表达式
*/
@Test
public void test2(){
// A a = (String s)->{
// System.out.println(s);
// };
// show(a);
show((String s)->{
System.out.println(s);
});
}
/**
* lambda表达式左边参数列表,可以根据上下文判断参数类型
*/
@Test
public void test3(){
show((s)->{
s += "abc";
System.out.println(s);
});
}
}
案例二:两个参数
public class LambdaDemo4 {
public static void show(A a){
a.f1(10,20);
}
@FunctionalInterface
interface A {
void f1(int x,int b);
}
@Test
public void test1(){
A a = new A() {
@Override
public void f1(int x, int y) {
System.out.println(x>y?x:y);
}
};
show(a);
}
@Test
public void test2(){
show((x, y) -> {
System.out.println(x>y?x:y);
});
}
@Test
public void test3() {
List list = new ArrayList();
list.add("555");
list.add("zzz");
list.add("xxx");
list.add("yyy");
list.add("mmm");
list.add("nnn");
list.add("aaa");
list.add("aaa");
list.add("111");
list.add("111");
list.add("222");
list.add("333");
list.add("444");
// Comparator com = new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// String s1 = (String) o1;
// String s2 = (String) o2;
// return -s1.compareTo(s2);
// }
//
// };
// Collections.sort(list, com); //定制排序
//
// System.out.println(list);
//定制排序器
Comparator<String> com = (s1, s2) -> {
return -s1.compareTo(s2);
};
Collections.sort(list, com); //定制排序
System.out.println(list);
}
}
3 函数式接口
3.1 接口的增强
- 函数式接口中有且只有一个抽象方法
- 函数式接口中可以有默认函数
@FunctionalInterface
public interface InterfaceTest {
void f1();
default void f2() {
System.out.println("aaaa");
}
}
3.2 什么是函数式(Functional)接口
- 只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
- 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
- 在java.util.function包下定义了Java 8 的丰富的函数式接口
3.3 如何理解函数式接口
- Java从诞生日起就是一直倡导“一切皆对象”, 在Java里面面向对象(OOP)编程是一切。但是随着python、 scala等语言的兴起和新技术的挑战, Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
- 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中, Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
- 简单的说,在Java8中, Lambda表达式就是一个函数式接口的实例。 这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
- 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写
3.4 函数式接口举例
自定义函数式接口
@FunctionalInterface
public interface MyInterface {
void method1();
// void method2();
}
3.5 Java内置函数式接口
四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费接口 | T | void | 对类型为 void accept(T t) T的对象应用操作,包含方法: |
Supplier 供给型接口 | 无 | T | 返回类型为T的对象,包含方法: T get() |
Function<T, R> 函数接口 | T | R | 对类型为 果是R类型的对象。包含 T的对象应用操作,并返回结果。结 方法: R apply(T t) |
Predicate 断定型接口 | T | boolean | 确定类型为 boolean 值。包含 T的对象是否满足某约束,并返回 方法: boolean test(T t) |
其他接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 对类型为 T, U 参数应用操作, 返回 R 类型的结 果。 包含方法为: R apply(T t, U u); |
UnaryOperator (Function子接口) | T | T | 对类型为 结果。 包含方法 T的对象进行一元运算 为: T apply(T t),; 并返回T类型的 |
BinaryOperator (BiFunction 子接口) | T, T | T | 对类型为 结果。 包含方法 T的对象进行二元运算 为: T apply(T,t1并返回 , T t2);T类型的 |
BiConsumer<T, U> | T, U | void | 对类型为T, U 参数应用操作。 包含方法为: void accept(T t, U u) |
BiPredicate<T,U> | T,U | boolean | 包含方法为: boolean test(T t,U u) |
ToIntFunction ToLongFunction ToDoubleFunction | T | int long double | 分别计算int、 long、 double值的函数 |
IntFunction LongFunction DoubleFunction | R | int long double | 参数分别为int、 long、 double 类型的函数 |
3.6 案例演示
案例一
public class ConsumerTest {
@Test
public void test1(){
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 18));
list.add(new User(2, "john", 18));
list.add(new User(3, "doc", 18));
list.add(new User(4, "cat", 18));
list.add(new User(5, "tom", 18));
list.add(new User(6, "tomcat", 18));
list.add(new User(7, "mysql", 18));
list.add(new User(8, "java", 18));
// Consumer<User> c = new Consumer<User>() {
// @Override
// public void accept(User o) {
// System.out.println(o);
// }
// };
Consumer<User> c = o -> System.out.println(o);
list.forEach(c);
}
@Test
public void test2(){
// Consumer<String> c = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer<String> c = s -> System.out.println(s);
Consumer<String> c2 = s -> {
System.out.println(s.substring(6));
};
c.accept("hello world");
c2.accept("hello world");
}
/**
* 处理金额
* @param c
* @param money
*/
public void f1(Consumer<Double> c, Double money) {
c.accept(money);
}
@Test
public void test3(){
f1(m ->{
DecimalFormat df = new DecimalFormat("#,###.##");
System.out.println(df.format(m));
},1234562345.789);
f1(m -> {
DecimalFormat df = new DecimalFormat("0,000.000");
System.out.println(df.format(m));
},1234562345.213241);
}
/**
* DecimalFormat保留2位小数,每三位一个多厚(金额)
*/
@Test
public void test4 (){
DecimalFormat df = new DecimalFormat("#,###.##");
System.out.println(df.format(12345623434.789));
}
}
案例二
public class PredicatTest {
@Test
public void test1(){
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 22));
list.add(new User(2, "john", 33));
list.add(new User(3, "doc", 19));
list.add(new User(4, "cat", 20));
list.add(new User(5, "tom", 20));
list.add(new User(6, "tomcat", 70));
list.add(new User(7, "mysql", 73));
list.add(new User(8, "java", 84));
/**
* 删除年龄是20的元素
*/
list.removeIf(user -> user.getAge() == 20);
System.out.println(list);
System.out.println("---------");
/**
* 查找>=5的用户,循环
*/
list.stream().filter(user -> {
return user.getUid() >= 5;
}).forEach(user -> {
System.out.println(user);
});
}
public static List<String> filterList(List<String> list, Predicate<String> p){
List<String> result = new ArrayList<>();
for (String s : list) {
if (p.test(s)){
result.add(s);
}
}
return result;
}
@Test
public void test2(){
List<String> list = new ArrayList<>();
list.add("hello abc");
list.add("welcome abc");
list.add("hello world");
list.add("你好 abc");
list.add("大家好 abc");
list.add("hello mickey");
filterList(list, s -> {
return s.contains("hello");
}).forEach(s -> System.out.println(s));
System.out.println("--------------");
filterList(list, s -> {
return s.contains("abc");
}).forEach(s -> System.out.println(s
));
}
}
案例三
public class FunctionTest {
@Test
public void test1(){
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 18));
list.add(new User(2, "john", 18));
list.add(new User(3, "doc", 18));
list.add(new User(4, "cat", 18));
list.add(new User(5, "tom", 18));
list.add(new User(6, "tomcat", 18));
list.add(new User(7, "mysql", 18));
list.add(new User(8, "java", 18));
// //得到集合中的id
// List<Integer> ids = new ArrayList<>();
// for (User user : list) {
// ids.add(user.getId());
// }
//得到集合中的所有的id
List<String> ids = list.stream().map(user -> user.getUid() + user.getName()).collect(Collectors.toList());
System.out.println(ids);
}
public Object performUser(Function<User,Object> f,User u){
return f.apply(u);
}
@Test
public void test2(){
User user = new User(1, "mickey", 18);
//得到学员名字
System.out.println(performUser(u -> {
return u.getName();
},user));
}
}
案例四
public class SupplierTest {
public static void printBean(Supplier s){
System.out.println(s.get());
}
@Test
public void test1(){
printBean(() -> new User(1,"mickey",18));
System.out.println("---------");
printBean(() -> "hello stream");
}
}
案例五
public class BinaryOperatorTest {
@Test
public void test1(){
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 18));
list.add(new User(2, "john", 18));
list.add(new User(3, "doc", 18));
list.add(new User(4, "cat", 18));
list.add(new User(5, "tom", 18));
list.add(new User(6, "tomcat", 18));
list.add(new User(7, "mysql", 18));
list.add(new User(8, "java", 18));
//得到所有的id,然后相加得到结果 -> List<Integer> ids =
Optional<Integer> optionalInteger = list.stream().map(user -> user.getUid()).reduce((d1,d2) -> d1 + d2);
System.out.println(optionalInteger.get());
}
}
4 方法引用
方法引用是Lambda表达式的一个语法糖:
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
- 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
- 要求: 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
- 格式: 使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
- 如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
4.1 语法
/**
* lambda表达式的语法糖
*/
@Test
public void test2() {
printBean(User :: new);
}
4.2 案例演示
案例一
@Test
public void test1() {
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 18));
list.add(new User(2, "john", 18));
list.add(new User(3, "doc", 18));
list.add(new User(4, "cat", 18));
list.add(new User(5, "tom", 18));
list.add(new User(6, "tomcat", 18));
list.add(new User(7, "mysql", 18));
list.add(new User(8, "java", 18));
//匿名内部类
list.forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
System.out.println("-------------");
/**
* stream中使用Consumer
*/
list.forEach(o -> System.out.println(o));
System.out.println("---------------");
//方法引用
list.forEach(System.out :: println);
}
案例二
@Test
public void test5() {
//匿名内部类
int i1 = f1(new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer d1, Integer d2) {
return Integer.max(d1, d2);
}
}, 12, 15);
System.out.println("--------");
//lambda表达式
int i2 = f1((d1, d2) -> Integer.max(d1, d2), 12, 15);
//方法引用-lambda表达式的语法糖
int i3 = f1(Integer::max, 12, 15);
System.out.println(i1 + " " + i2 + " " + i3);
}
5 Stream API
5.1 Stream简介
- Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
- Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之, Stream API 提供了一种高效且易于使用的处理数据的方式
- Stream 和 Collection 集合的区别: Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。 前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
- Stream到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据, Stream讲的是计算!”
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
5.2 Stream使用
- 创建 Stream:通过一个数据源(如:集合、数组),获取一个流
- 中间操作:一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作):一旦执行终止操作, 就执行中间操作链,并产生结果。之后,不会再被使用
5.3 创建 Stream方式
- Collection 接口,提供了两个获取流的方法:
- default Stream stream() : 返回一个顺序流
- default Stream parallelStream() : 返回一个并行流
- Arrays 的静态方法 stream() 可以获取数组流
- static Stream stream(T[] array): 返回一个流
- 通过Stream的of() 获取
- public static Stream of(T… values) : 返回一个流
5.4 Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” 。
- 筛选与切片
方 法 | 描 述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前 个空流。与 limit(n) 互补 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
- 映 射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流 |
- 排 序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
5.5 Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List、 Integer,甚至是 void 。
流进行了终止操作后,不能再次使用。
- 匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反, Stream API 使用内部迭 代——它帮你把迭代做了) |
- 归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一 个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一 个值。返回 Optional |
- 收集
方 法 | 描 述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总 的方法 |
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、 Set、Map)。另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
5.6 案例演示
public class StreamTest1 {
/**
* 收集:stream的最终操作
* - forEach
* - allMatch
* - collect
*/
@Test
public void test7() {
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 22));
list.add(new User(2, "johna", 33));
list.add(new User(3, "doca", 19));
list.add(new User(4, "cat", 20));
list.add(new User(5, "toma", 20));
list.add(new User(6, "tomcat", 70));
list.add(new User(7, "mysqla", 73));
list.add(new User(8, "java", 84));
//得到年龄30以上的用户的id组成的set集合
Set<Integer> ids = new HashSet<>();
for (User user : list) {
if (user.getAge() > 30) {
ids.add(user.getId());
}
}
System.out.println(ids);
System.out.println("------------");
//Stream写法
Set ids2 = list.stream().filter(u -> u.getAge() > 30).map(u -> u.getId()).collect(Collectors.toSet());
System.out.println(ids);
}
/**
* allMatch
*/
@Test
public void test6() {
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 22));
list.add(new User(2, "johna", 33));
list.add(new User(3, "doca", 19));
list.add(new User(4, "cat", 20));
list.add(new User(5, "toma", 20));
list.add(new User(6, "tomcat", 70));
list.add(new User(7, "mysqla", 73));
list.add(new User(8, "java", 84));
System.out.println(list.stream().allMatch(u -> u.getName().contains("a")));
}
@Test
public void test3() {
//以()代表流,[]为数组,""为字符串
List<String> list = Arrays.asList("a-b-c", "d-e-f");
//map中的第一个t是"a-b-c",返回的是[a,b,c]一个数组
//map以后流中的元素为([a,b,c],[e,d,f])两个元素,每个元素是数组
//map: ("a-b-c", "d-e-f")---->([a,b,c],[e,d,f]) ,size=2
list.stream().map(t -> t.split("-")).forEach(e -> System.out.println(Arrays.toString(e)));
System.out.println("-----------");
//flatMap第一个t是"a-b-c",返回的是(a,b,c)的一个子流,最后聚合所有子流合并为一个流
//flatMap: ("a-b-c", "d-e-f")---->((a,b,c),(d,e,f))-->(a,b,c,d,e,f) ,size=6
list.stream().flatMap(t -> Stream.of(t.split("-"))).forEach(System.out::println);
}
@Test
public void test2() {
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 22));
list.add(new User(2, "john", 33));
list.add(new User(3, "doc", 19));
list.add(new User(4, "cat", 20));
list.add(new User(5, "tom", 20));
list.add(new User(6, "tomcat", 70));
list.add(new User(7, "mysql", 73));
list.add(new User(8, "java", 84));
//对age排序
//匿名内部类
list.stream().sorted(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
}).forEach(System.out :: println);
System.out.println("----------------");
//对age排序
//lambda
list.stream().sorted((o1, o2) -> Integer.compare(o1.getAge(), o2.getAge())).forEach(System.out :: println);
}
@Test
public void test1() {
List<User> list = new ArrayList<>();
list.add(new User(1, "mickey", 22));
list.add(new User(2, "john", 33));
list.add(new User(3, "doc", 19));
list.add(new User(4, "cat", 20));
list.add(new User(5, "tom", 20));
list.add(new User(6, "tomcat", 70));
list.add(new User(7, "mysql", 73));
list.add(new User(8, "java", 84));
//得到上面集合id >=4的用户的age,得到age的总和
// list.stream().filter(u -> u.getId() >= 4).map(u -> u.getAge()).forEach(System.out :: println);
System.out.println(list.stream().filter(u -> u.getId() >= 4).map(u -> u.getAge()).reduce((d1, d2) -> d1 + d2).get());
List<Integer> ages = list.stream().filter(u -> u.getId() <= 6).map(u -> {
return u.getAge();
}).collect(Collectors.toList());
System.out.println(ages);
//查找id<=6的用户,所有的age相加得到结果
Optional<Integer> optionalInteger = list.stream().filter(u -> u.getId() <= 6).map(u -> {
return u.getAge();
}).collect(Collectors.toList()).stream().reduce((d1, d2) -> d1 + d2);
System.out.println(optionalInteger.get());
}
}
6 Optional 类
- 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常, Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发, Optional类已经成为Java 8类库的一部分。
- Optional 类(java.util.Optional) 是一个容器类, 它可以保存类型T的值, 代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
- Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
- Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
- 创建Optional类对象的方法:
- Optional.of(T t) : 创建一个 Optional 实例, t必须非空;
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t): t可以为null
- 判断Optional容器中是否包含对象:
- boolean isPresent() : 判断是否包含对象
- void ifPresent(Consumer<? super T> consumer) : 如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
- 获取Optional容器的对象:
- T get(): 如果调用对象包含值,返回该值,否则抛异常
- T orElse(T other) : 如果有值则将其返回,否则返回指定的other对象。
- T orElseGet(Supplier<? extends T> other) : 如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
- T orElseThrow(Supplier<? extends X> exceptionSupplier) : 如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
案例演示
public class Girl {
private String name;
@Override
public String toString() {
return "Girl{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Girl() {
}
public Girl(String name) {
this.name = name;
}
}
public class Boy {
private Girl girl;
@Override
public String toString() {
return "Boy{" +
"girl=" + girl +
'}';
}
public Girl getGirl() {
return girl;
}
public void setGirl(Girl girl) {
this.girl = girl;
}
public Boy() {
}
public Boy(Girl girl) {
this.girl = girl;
}
}
测试类
public class OptionalTest {
/*
Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):t可以为null
*/
@Test
public void test1(){
Girl girl = new Girl();
// girl = null;
//of(T t):保证t是非空的
Optional<Girl> optionalGirl = Optional.of(girl);
}
@Test
public void test2(){
Girl girl = new Girl();
// girl = null;
//ofNullable(T t):t可以为null
Optional<Girl> optionalGirl = Optional.ofNullable(girl);
System.out.println(optionalGirl);
//orElse(T t1):如果单前的Optional内部封装的t是非空的,则返回内部的t.
//如果内部的t是空的,则返回orElse()方法中的参数t1.
Girl girl1 = optionalGirl.orElse(new Girl("赵丽颖"));
System.out.println(girl1);
}
public String getGirlName(Boy boy){
return boy.getGirl().getName();
}
@Test
public void test3(){
Boy boy = new Boy();
boy = null;
String girlName = getGirlName(boy);
System.out.println(girlName);
}
//优化以后的getGirlName():
public String getGirlName1(Boy boy){
if(boy != null){
Girl girl = boy.getGirl();
if(girl != null){
return girl.getName();
}
}
return null;
}
@Test
public void test4(){
Boy boy = new Boy();
boy = null;
String girlName = getGirlName1(boy);
System.out.println(girlName);
}
//使用Optional类的getGirlName():
public String getGirlName2(Boy boy){
Optional<Boy> boyOptional = Optional.ofNullable(boy);
//此时的boy1一定非空
Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));
Girl girl = boy1.getGirl();
Optional<Girl> girlOptional = Optional.ofNullable(girl);
//girl1一定非空
Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));
return girl1.getName();
}
@Test
public void test5(){
Boy boy = null;
boy = new Boy();
boy = new Boy(new Girl("貂蝉"));
String girlName = getGirlName2(boy);
System.out.println(girlName);
}
}