Java 中的 Optional

文章介绍了Java中的Optional类如何帮助开发者更安全地处理可能出现null值的情况,通过示例展示了Optional的使用方法,如创建、检查值、转换和扁平化操作,以及其在避免空指针异常和提升代码可读性方面的优势。
摘要由CSDN通过智能技术生成

参考视频(B 站 UP 主:AlbertShen
):https://www.bilibili.com/video/BV1dc411X7nW/?spm_id_from=333.788.top_right_bar_window_history.content.click&vd_source=d55a4a2d31b98315ece09779a92b618b

null 值在编程中常常是一个令人头疼的问题,处理不好,就会导致 NullPointerException 空指针异常,为了避免空指针异常,代码中就会充斥着繁琐的 null 检查和条件嵌套,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;
	}
}
public class UserRepository {
	public User findUserByName(String name) {
		if (name.equals("tkzc")) {
			return new User("tkzc", "tkzc00");
		} else {
			return null;
		}
	}
}

接下类在 main 方法中进行调用,模拟从数据库中查询数据:

public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		User user = userRepository.findUserByName("tkzc2");
		System.out.println(user.getFullName());
	}
}

此时会报空指针异常,如果要避免空指针异常,在代码中就需要进行一些判断:

public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		User user = userRepository.findUser("tkzc2");
		if (user != null) {
			System.out.println(user.getFullName());
		} else {
			User defaultUser = new User("Neo", "Thomas Neo");
			System.out.println(defaultUser.getFullName());
		}
	}
}

然而在实际的项目中,我们会处理大量可能为空的值,这样一来,代码中就会充斥着条件判断,会让代码显得非常臃肿,难以阅读和维护,这就是为什么要引入 Optional 的原因。

Optional 就像是一个容器,或者盒子,它可以包含某种类型的值,也可以什么都不包含,比如 null,并且提供一系列方法,来方便的操作内部的值,同时 Optional 的设计,也考虑了函数式编程的原则,可以与 Lambda 表达式和 Stream API 等特性结合使用,你可以优雅的进行一些链式调用,而不用像以往那样,用命令式编程(Imperative Programing)的方式,编写大量的 if 语句来检查 null 值。

首先,我们来看一下 Optional 的基本使用方法:

首先创建一个包含空值的 Optional 对象:

Optional<Object> optionalBox = Optional.empty();

接下来,用 Optional 的内部方法来检测内部值的状态:

方法说明
isPresent()检查 Optional 中是否存在值,存在返回 true,否则返回 false
isEmpty()检查 Optional 是否为空,为空返回 true,否则返回 false
of(T value)通过 value 来创建一个包含 valueOptional 对象,value 不能为空
ofNullable(T value)通过 value 来创建一个包含 valueOptional 对象,value 可以为空
get()获取 Optional 中存储的值
String value = "tkzc";
Optional<String> optionalBox = Optional.of(value);
System.out.println(optionalBox.isPresent()); // true
System.out.println(optionalBox.isEmpty()); // false

of() 方法创建的 Optional 对象必须包含值,这就意味着传递给 of() 方法的值不能为 null,否则会报空指针异常,主要用于确定值不为空的场景。

对于不确定是否为空的值,可以使用 ofNullable() 方法来创建 Optional 对象,此时传入的对象可以有值,也可以为空。

String value = "tkzc";
Optional<String> optionalBox = Optional.ofNullable(value);
String value2 = optionalBox.get();
System.out.println(value2); // tkzc00

虽然用 get() 方法取值非常简单,但并不推荐这样做,因为这并不是 Optional 设计的初衷。

接下来,我们用 Optional 来改造 UserRepository 中的 findUserByName 方法:

public class UserRepository {
	public Optional<User> findUserByName(String name) {
		if (name.equals("tkzc")) {
			return new Optional.of(new User("tkzc", "tkzc00"));
		} else {
			return Optional.empty();
		}
	}
}

下面是改造 main 函数:

public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		User user = optionalUser.get();
		System.out.println(user.getFullName());
	}
}

此时会报 NoSuchElementException 异常,因为此时 Optional 中装的是空值,所以并不能取出 User 对象,更不能获得 getFullName() 方法。此时,就需要进行判断:

public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		if (optionalUser.isPresent()) {
			User user = optionalUser.get();
			System.out.println(user.getFullName());
		} else {
			User defaultUser = new User("Neo", "Thomas Neo");
			System.out.println(defaultUser.getFullName());
		}
	}
}

此时会发现,当前的代码结构和之前的并没有本质上的区别,依旧是用命令时编程(Imperative Programing)的方式在做判断和检查,这显然不是 Optional 设计的初衷和正确的使用方法,我们应该用函数式编程的方式来操作 Optional

方法说明
orElse(T other)Optional 中有值时则返回 Optional 中的值,否则返回 other
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		User user = optionalUser.orElse(new User("Neo", "Thomas Neo"));
		System.out.println(user.getFullName()); // Thomas Neo
	}
}
方法说明
orElseGet(Supplier<? extends T> supplier)orElse() 的作用一致,只是使用 Supplier 来返回一个默认值
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		User user = optionalUser.orElseGet(() -> new User("Neo", "Thomas Neo"));
		System.out.println(user.getFullName()); // Thomas Neo
	}
}

orElse()orElseGet() 功能上很相似,但执行方式上有所不同,对 orElse() 来说,不管 Optional 对象是否为空,都会执行传入的参数,也就是无论 optionalUser 是否为空,都会 new 一个默认的 User 对象,而 orElseGet() 只有在 Optional 对象为空的时候,才会去执行传入的 Supplier 方法,也就是只有当 optionalUser 内部值为空时,才会去 new 一个默认的 User 对象。

当默认值已经确定,并且获取默认值的代价不是很高的时候,可以使用 orElse() ;当获取默认值的代价比较高,或者获取默认值需要经过一些计算的时候,使用 orElseGet() 是更好的选择,因为它只有在必要的时候才会执行。

方法说明
orElseThrow()如果 Optional 对象为空,就会抛出一个 NoSuchElementException 的异常
orElseThrow(Supplier<? extends RuntimeException> supplier)如果 Optional 对象为空,就会抛出一个指定的异常
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		optionalUser.orElseThrow(() -> new RuntimeException("用户不存在"));
	}
}
方法说明
ifPresent(Consumer<? super T> action)Optional 中有值时执行 action 操作,没有值则什么都不做
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		optionalUser.ifPresent(user -> System.out.println(user.getFullName()));
	}
}
方法说明
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)Optional 中有值时执行 action 操作,没有值则执行 emptyAction 操作
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		optionalUser.ifPresentOrElse(
			user -> System.out.println(user.getFullName()),
			() -> System.out.println("用户不存在")
		);
	}
}
方法说明
filter(Predicate<? super T> predicate)根据给定的条件过滤值,如果值存在并且满足条件,返回包含该值的新 Optional 对象;否则,返回一个空的 Optional 对象
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc2");
		Optional<User> optioanlUser2 = optionalUser.filter(
			user -> user.getFullName().equals("tkzc00')
		);
		System.out.println(optionalUser2.siPresent()); // false
	}
}
方法说明
map(Function<? super T, ? extends U> mapper)Optional 中的值进行转换,并返回一个新的 Optional 对象,其中包含了转换后的值;如果 Optioanl 是空的,则什么也不做,直接返回一个空的 Optional 对象
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc");
		Optional<String> optionalFullName = optionalUser.map(User::getFullName());
		System.out.println(optionalFullName.get()); // tkzc00
	}
}
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc1");
		Optional<String> optionalFullName = optionalUser.map(User::getFullName());
		System.out.println(optionalFullName.isPresent()); // false
	}
}
方法说明
flatMap(Function<? super T, ? extends U> mapper)扁平化嵌套的 Optional 结构

flatMap() 方法会将两个 Optional 对象合并成一个,如果原始的 Optional 对象为空,或者转换函数返回的 Optional 对象为空,那么最终得到的也是一个空的 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 Optional<String> getFullName() {
		return Optional.ofNullable(fullName);
	}
}
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc1");
		Optional<Optional<String>> optionalFullName = optionalUser.map(User::getFullName());
	}
}
public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc1");
		Optional<String> optionalFullName = optionalUser.flatMap(User::getFullName());
	}
}

通常情况下,如果你只需要对 Optional 对象中的值进行转换,而不需要嵌套的 Optional,那么使用 map() 方法是更合适的选择;但如果你需要进行一些操作,可能会返回另外一个 Optional 对象,那么 flatMap() 方法更适合。

方法说明
stream()Optional 对象转换为 Stream 对象

如果 Optional 对象包含值,那么就将这个值封装到一个 Stream 流中;如果 Optional 对象为空,那么将创建一个空的 Stream 流。

public class Main {
	public static void main(String[] args) {
		UserRepository userRepository = new UserRepository();
		Optional<User> optionalUser = userRepository.findUser("tkzc");
		Stream<String> stream = optionalUser
				.map(User::getName())
				.stream();
		stream.forEach(System.out::println); // tkzc
	}
}

总的来所,Optional 普遍用于方法的返回类型,表示方法可能不返回结果,当你有一个方法可能返回一个值,或者什么都不返回,即返回 null 时,你可以使用 Optional。比方说当你设计一个 API 时,它能引导 API 的使用者,告诉他们这个结果可能不存在,并强制调用者处理这种可能性,可以减少错误的发生。

尽管 Optional 很有用,但也有一些不推荐的使用场景,比方说不应该用于类的字段,由于 Optional 对象的创建和管理有一定的开销,并不适合用作类的字段,使用 Optional 作为字段类型会增加内存消耗,并且会使得对象的序列化变得复杂;也不应该用作方法的参数,将 Optional 用作方法参数,会使方法的使用和理解变得复杂,如果你希望方法接收一个可能为空的值,通常有更好的设计选择,比如方法重载;不应该用于构造函数参数,类似与方法参数,Optional 也不应该用于构造器参数,这种做法,还会迫使调用者创建 Optional 实例,应该通过构造器重载来解决;不应该用作集合的参数类型,如果你的方法返回一个集合,而且这个集合可能为空,不应该用 Optional 来包装它,集合已经可以很好地处理空集合的情况,没必要用 Optional 包装集合;不建议使用 get() 方法,调用 Optionalget() 方法前,没有确认值是否存在,可能会导致 NoSuchElementException,即使使用 isPresent()get() 的组合也不是一个最好的选择,这样做其实和直接调用可能为 null 的引用没多大区别,因为你仍然需要进行显式的检查,以避免异常,应当使用 ifPresent()orElse() 或者 orElseThrow() 等方法

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值