Featured image of post Java8新特性

Java8新特性

Lambda 表达式

函数式接口

Stream 流

Optional 类使用

案例一引入

Optional 简单介绍 :

  • null 值在编程中常常是令人头疼的问题,处理不好可能出现 NullPointerException 空指针异常
  • 为了避免的空指针异常,代码中会充斥着繁琐的 null 检查和条件嵌套
  • Optional 类可以让编程人员更加安全、优雅地处理可能为空的值,避免空指针异常

需求:通过用户名从数据库中查询对应名字的用户信息,封装成 User 类返回。

前期准备

  1. 定义 User.java 类,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package OptionalExample;  
  
public class User {  
    String name;  
    String fullname;  
  
  
    public User(String name, String fullname) {  
        this.name = name;  
        this.fullname = fullname;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public String getFullname() {  
        return fullname;  
    }  
}
  1. 定义 Repository 持久层对象查找接口 findUserByName,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package OptionalExample;  
  
public class UserRepository {  
  
    public User findUserByName(String name) {  
        if(name.equals("Albert")){  
            return new User("Albert", "Albert Shen");  
        } else {  
            return null;  
        }  
    }  
}

示例一:直接输出,不检查空指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package OptionalExample;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        User user = userRepository.findUserByName("Tom");  
        // Exception in thread "main" java.lang.NullPointerException  
        // at OptionalExample.OptInJava.main        
        // 示例一:如果直接输出,会报空指针异常  
        System.out.println(user.getFullname());  
    }  
}

示例二:使用条件判断空指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package OptionalExample;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        User user = userRepository.findUserByName("Tom");  
        // 示例二:条件判断空指针  
        if(user != null){  
            System.out.println(user.getFullname());  
        } else {  
            User defaultUser = new User("Neo", "Thoms Anderson");  
            System.out.println(defaultUser.getFullname());  
        }  
    }  
}

Optional 引入介绍

实际项目中,可能会处理大量为空的值,这样代码会充斥着大量的条件嵌套,会让代码显得非常臃肿,难以维护,这是为什么要引入 Optional 的原因。

Pasted image 20240609152012

Optinal 就像是一个容器,或者盒子,可以包含某种类型的值,也可以什么都不包含例如:null。并且提供一系列方法来方便地操作内部的值。

同时,Optional 的设计也考虑了函数式编程的原则,可以与 Lambda 表达式 和 Stream API 等特性结合使用。

Pasted image 20240609152301

可以优雅地进行一些链式调用,不用像以往那样用命令式编程 Imperative Programming 的方式编写大量的 if 语句来检查 null 值。

Optional 基本使用

  1. 使用 empty() 方法创建一个空对象,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package OptionalExample;  
  
import java.util.Optional;  
  
public class Main {  
  
    public static void main(String[] args) {  
        // 使用 empty 方法初始化  
        Optional<Object> optionalBox = Optional.empty();  
        // 使用 optional 内置对象的方法来检测内部值的状态  
        // 1. isPresent():用于检查 optional 对象中是否存在值,返回布尔值  
        // - 包含值,返回 true ; 不包含值,返回 false        
        System.out.println("isPresent : " + optionalBox.isPresent());  
        // 2. isEmpty():用于检查 optional 对象中是否为空,返回布尔值  
        // - 为空,返回 true ; 不为空,返回 false        
        System.out.println("empty : " + optionalBox.isEmpty());  
    }  
}
  1. 使用 of() 方法创建一个包含值的对象,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package OptionalExample;  
  
import java.util.Optional;  
  
public class Main {  
  
    public static void main(String[] args) {  

        // 创建一个包含值的 optional 对象  
        String value = "Albert";  
        Optional<String> optionalEx = Optional.of(value);  
	    System.out.println(optionalEx.isPresent());  
        System.out.println(optionalEx.isEmpty()); 
    }  
}

注意:用 of 方法创建的对象必须包含值,所以需要编程人员确保传递给 of 方法的值不为 null,否则会抛出 NullPointerException。

应用:用于那些能够确定值不为空的场景。

  1. 使用 ofNullable() 方法创建一个对象,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package OptionalExample;  
  
import java.util.Optional;  
  
public class Main {  
  
    public static void main(String[] args) {  
        // 创建一个包含值的 optional 对象  
        String value = "Albert";  
        // 使用 ofNullable 创建对象,传入盒子 value 既可以是有值也可以是 null        
        Optional<String> optionalEx = Optional.ofNullable(value);  
        System.out.println(optionalEx.isPresent());  
        System.out.println(optionalEx.isEmpty());  
    }  
}

应用:不确定一个对象是否有值的时候使用。

  1. 使用 get() 方法取值,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package OptionalExample;  
  
import java.util.Optional;  
  
public class Main {  
  
    public static void main(String[] args) {  
        // 创建一个包含值的 optional 对象  
        String value = "Albert";  
        // 使用 ofNullable 创建对象,传入盒子 value 既可以是有值也可以是 null        Optional<String> optionalEx = Optional.ofNullable(value);  
        // 将 optional 中的值取出来,使用 get() 方法  
        String val = optionalEx.get();  
        System.out.println(val);  
        System.out.println(optionalEx.isPresent());  
        System.out.println(optionalEx.isEmpty());  
    }  
}

注意:虽然使用 get() 方法取值非常简单与直观,但是一般不推荐使用这种方法。因为这不是 Optional 设计的初衷。

使用 Optional 改造案例一

  1. 在 UserRepository 中添加方法 findUserByNamePlus() 方法,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package OptionalExample;  
  
import java.util.Optional;  
  
public class UserRepository {  
  
    public User findUserByName(String name) {  
        if(name.equals("Albert")){  
            return new User("Albert", "Albert Shen");  
        } else {  
            return null;  
        }  
    }  
  
    public Optional<User> findUserByNamePlus(String name) {  
        if(name.equals("Albert")){  
            return Optional.of(new User("Albert", "Albert Shen"));  
        } else {  
            return Optional.empty();  
        }  
    }  
}
  1. 直接使用 get() 方法获取 Optional 中值,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  

    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");  
        User user = optionalUser.get();  
        System.out.println(user.getFullname());  
    }  
}

由于传入方法参数找不到,所以 Optional 盒子中没有值,使用 get() 方法报错如下:

1
2
3
Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.base/java.util.Optional.get(Optional.java:148)
	at OptionalExample.OptInJava.main(OptInJava.java:18)
  1. 添加 isPresent() 方法判断,避免 Optional 盒子中没有值使用 get() 获取不到的问题,如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");  
        if(optionalUser.isPresent()){  
            System.out.println(optionalUser.get().getFullname());  
        } else {  
            User defaultUser = new User("Neo", "Thoms Anderson");  
            System.out.println(defaultUser.getFullname());  
        }   
    }  
}

可以看到上边代码结构和之前的 if 条件嵌套没有本质上区别,依旧是命令式编程 imperative programming 方法在做判断和检查,这显然不是 Optional 设计的初衷和正确的使用方法。

函数式编程正确使用 Optional

  1. orElse() 与 orElseGet() 方法区别。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");  
        User user = optionalUser.orElse(new User("Neo", "Thoms Anderson"));  
        System.out.println(user.getFullname());  
        User user2 = optionalUser.orElseGet(() -> new User("Neo", "Thoms Anderson"));  
        System.out.println(user2.getFullname());  
    }  
}

orElse() 和 orElseGet() 方法功能上很类似,但是执行上有所不同。

  • 对于 orElse() 来说,不管是 Optional 对象为空或者非空,都会执行传入这个参数(用于设置默认值)。
  • 而 orElseGet() 只有在对象为空的情况下,才会去执行 Supplier 里边这个方法。

最佳实践:当默认值已经确定,并且获取默认值代价不高的时候,可以使用 orElse() 方法;当获取默认值的代价比较高,例如:需要进行一些计算或者其他操作时,使用 orElseGet() 方法是更好的选择,因为Supplier 中的方法只有在必要的时候才会执行。

  1. orElseThrow() 方法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");  
        optionalUser.orElseThrow();  
    }  
}

如果 Optional 对象为空,此时它就会抛出一个 NoSuchElementException 异常。和之前使用 get() 方法抛出异常是一样的。

Pasted image 20240609205516

orElseThrow() 方法接受一个 Supplier 的函数式接口作为参数,通过实现该接口可以生成并抛出一个自定义的异常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
	    UserRepository userRepository = new UserRepository();  
	    Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");  
	    optionalUser.orElseThrow(() -> new RuntimeException("User not found"));
    }
}
  1. ifPresent() 与 ifPresentOrElse() :根据值的不同进行不同的操作。

Pasted image 20240609205933

该方法接收一个 Comsumer 的函数式接口作为参数,可以使用 Lambda 表达式实现这个 Consumer 函数式接口。

  • 如果 Optional 类中含值,就会执行 Lambda 表达式中的方法;
  • 如果 Optional 类中不含值,则什么都不做。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  

    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");  
        Optional<User> optionalUser2 = userRepository.findUserByNamePlus("Albert");  
        optionalUser.ifPresent(user -> System.out.println(user.getFullname()));  
        optionalUser2.ifPresent(user -> System.out.println(user.getFullname())); 
    }  
}

如果希望值为空的时候执行其他操作,就需要使用 ifPresentOrElse() 方法, 如下:

Pasted image 20240609210603

该方法允许在 Optional 包含值的时候,执行 Comsumer 函数式编程接口 action;不包含值的时候,执行 Runnable 工作 emptyAction。示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Tom");  
        Optional<User> optionalUser2 = userRepository.findUserByNamePlus("Albert");  
        optionalUser.ifPresent(user -> System.out.println(user.getFullname()));  
        optionalUser2.ifPresent(user -> System.out.println(user.getFullname()));  
        optionalUser.ifPresentOrElse(  
                user -> System.out.println(user.getFullname()),  
                () -> System.out.println("User not found")  
        );  
    }  
}
  1. filter() 方法:如果想过滤 Optional 对象中的值,根据给定的条件来决定是否保留该值。

Pasted image 20240610104143

这个方法接收一个 Predicate 的函数式接口作为参数,该接口用于定义过滤条件。如果值存在且满足条件,返回一个包含原始值的新的 Optional 对象,否则返回一个空的 Optional 对象。

下边使用 Lambda 表达式实现 Predicate 函数式接口,示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");  
        // 若是 Predicate 函数式接口中,表达式返回的是 true,会返回一个包含值的 Optional 对象 ;  
        // 表达式返回 false ,返回一个空的 Optional 对象。  
        Optional<User> optionalUser2 = optionalUser.filter(user -> user.getFullname().equals("Albert Shen"));  
        System.out.println(optionalUser2.isPresent());  
    }  
}
  1. map() :用于对 Optional 中的值进行转换,并返回一个新的 Optional 对象,其中包含了转换后的值。

Pasted image 20240610105315

这个方法接收一个 Function 函数型接口,这个函数会被应用到 Optional 中的值上,即:在 Function 中应用该值,并且返回一个新的 Optional 对象;否则,返回一个空的 Optional 对象。

代码示例,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");  
        // map参数:执行内部的 user 的一个 getFullname 方法,然后将执行后的结果重新包装到另外一个 Optional 对象里边  
        Optional<String> optionalFullName = optionalUser.map(user -> user.getFullname());  
        // 上述写法等价以下简化写法:方法引用  
        optionalUser.map(User::getFullname);  
        System.out.println(optionalFullName.get());  
        System.out.println(optionalFullName.isPresent());  
    }  
}

补充:

  • 理解 map() 方法的关键就是它用于 Optional 中值的变换(类似矩阵变换),这个变换基于提供给 map() 方法的函数,并且这个变换是可选的。如果 Optional 本身为空,那么不会发生变换;如果 Optional 本身不为空,则会执行方法变换。
  • 此外,map() 方法不会改变原始的 Optional 对象,而是返回一个新的 Optional 对象。因此,这个方法可以链式调用,以进行多次的转换操作。
  1. flatMap():与 map() 方法类似,用于扁平化嵌套的 Optional 结构,以避免引入不必要的嵌套层级。具体表现为:flatMap() 方法返回的必须为另一个 Optional 对象,这意味着 flatMap() 方法可以用于嵌套的 Optional 情况,它会将两个 Optional 对象合并为一个。如果原始的 Optional 对象为空,或者转换函数返回的对象为空,最终得到的是一个空的 Optional 对象。

修改 User.java 类中 getFullName() 方法返回值,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package OptionalExample;  
  
import java.util.Optional;  
  
public class User {  
    String name;  
    String fullname;  
  
  
    public User(String name, String fullname) {  
        this.name = name;  
        this.fullname = fullname;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
//    public String getFullname() {  
//        return fullname;  
//    }  
// 方法重载意味着可以在同一个类中拥有多个同名方法,只要它们的参数列表不同即可。编译器根据方法的参数来决定调用哪个方法。  
// 为了使方法重载有效,以下两个条件至少有一个必须满足:  
//      1. 参数的数量不同。  
//      2. 参数的类型不同(Java会根据参数的类型和数量来区分方法)。  
// 如果两个 getFullName 方法的参数列表相同,但返回类型不同,Java编译器会报错,因为仅根据返回类型无法区分这两个方法。   
    public Optional<String> getFullName() {  
        return Optional.ofNullable(fullname);  
    }  
}  

示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package OptionalExample;  
  
import java.util.Optional;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");  
        Optional<Optional<String>> s = optionalUser.map(User::getFullName);  
        Optional<String> optional = optionalUser.flatMap(User::getFullName);  
    }  
}

可以发现,使用 map() 方法和使用 flatMap() 方法返回值有所区别。通常情况下,如果只需要对 Optional 的值进行转换,不在乎嵌套 Optional ,那么使用 map() 方法比较合适;如果需要进行一些额外的操作,需要返回另外的 Optional 对象,此时为了简化链式调用处理参数,可以使用 flatMap() 方法来扁平化嵌套 Optional 对象。

  1. stream() :将一个 Optional 对象转换为一个 Steam 流。

Optional 中提供了Stream 方法,可以将一个 Optional 对象转化为一个 Stream 对象,然后对其中的值进行流操作。如果 Optional 对象包含值,那么将这个值封装到一个 Stream 流对象中;如果 Optional 对象为空,那么将创建一个空的 Stream 流。

代码示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package OptionalExample;  
  
import java.util.Optional;  
import java.util.stream.Stream;  
  
public class OptInJava {  
  
    public static void main(String[] args) {  
        UserRepository userRepository = new UserRepository();  
        Optional<User> optionalUser = userRepository.findUserByNamePlus("Albert");  
        Stream<String> a = optionalUser  
                .map(User::getName)  
                .stream();  
        a.forEach(System.out::println);  
    }  
}

小结

Optional 推荐使用场景

Optional 普遍用于方法的返回类型,表示方法可能不返回结果。当有一个方法可能返回一个值,或者什么都不返回的时候(null),可以使用 Optional 封装该返回值。

Pasted image 20240610113440

当设计一个 API 时,可以引导 API 的使用者,告诉他们这个结果可能不存在,并强制调用者处理这种可能性,可以减少错误的发生。

Optional 不推荐使用的场景

  1. 不推荐用于类的字段。
1
2
3
public class User {
	Optional<String> name;
}

注:Optional 创建和管理有一定开销,并不适合用作类字段。使用 Optional 作为字段类型会增加内存消耗,并且会使得对象的序列化变得更加复杂。

  1. 不推荐用于方法的参数。
1
2
3
4
5
6
public class User {

	public void updateUser(Optional<String> name) {
		// ...
	}
}

注:将 Optional 对象作为方法的参数时,会使得方法的使用和理解变得复杂。如果希望方法接收一个可能为空的值,通常有更好设计选择,比如:方法重载等。

正确实例代码如下:

1
2
3
4
5
6
7
8
9
public class User {

	public void updateUser(String name) {
		// ...
	}
	public void updateUser() {
		// ...
	}
}
  1. 不推荐用于构造函数参数。
1
2
3
4
5
6
public class User {

	public User(Optional<String> name) {
		// ...
	}
}

注:类似于方法参数,Optional 不应该用于构造器参数,这种做法会迫使调用者创建 Optional 实例。应该通过构造器重载来解决。

正确实例代码如下:

1
2
3
4
5
6
7
8
9
public class User {

	public User(String name) {
		// ...
	}
	public User() {
		// ...
	}
}
  1. 不推荐作为集合参数类型。
1
2
3
public Optional<List<User>> getUsers() {
	return Optional.ofNullable(getList());
}

注:如果一个方法返回一个集合,并且这个集合可能为空,不应用使用 Optional 来包装它。集合已经可以很好处理空集合的情况,没必要使用 Optional 包装集合。

正确实例代码如下:

1
2
3
4
5
6
7
public List<User> getUsers() {
	if(getList() == null) {
		return Collections.emptyList();
	} else {
		return getList();
	}
}
  1. 不推荐使用 get() 方法。
1
2
3
4
5
6
String value = optionalValue.get();

// 或者
if (optionaValue.isPresent()){
	String value = optionalValue.get();
}

注:调用 Optional 的 get() 方法前,没有确认值是否存在,可能会导致 NoSuchElementException 异常抛出。即使使用 ifPresent() 和 get() 的组合也不是最好的选择,这种操作和直接调用可能为 null 的引用没有多大区别,仍然需要进行显示的检查,以避免异常。

应当使用 ifPresentOrElse() 或者 orElse() 或者 orElseThrow() 等方法,正确实例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
optionalValue.ifPresent(System.out::println);

optionalValue.ifPresentOrElse(
	System.out::println,
	() -> System.out.println("empty")
);

String value = optionalValue.orElse("default");

String value = optionalValue.orElseGet(() -> "default");

String value = optionalValue.orElseThrow();

附录

参考文献

版权信息

本文原载于kitebin.top,遵循CC BY-NC-SA 4.0协议,复制请保留原文出处。

CC BY-NC-ND
最后更新于 Jun 10, 2024 09:42 UTC
Built with Hugo
主题 StackJimmy 设计