Java函数式编程基础之【Optional类】用法详解

一、Java中的Optional<T>类概述

Optional 是 Java 8 引入的新特性,它是一种特殊的包装类,里面只存储一个元素(这一点与基本数据类型的包装类有点相似)。有的文档称其为容器类,但它不同于 Conllection框架中的集合类,它一个容器只能存储一个元素。

Optional是Java中的一个类,它的作用是用于解决空指针异常的问题,它提供了一些有用的方法,可以帮助我们避免显式进行空值检测。

Java中的Optional<T>类,可以包装类型T的值,这是一个可以为null的容器对象。Java 8 引入Optional<T>替代以前版本中的空值(null),可以避免空指针异常(NullPointerException)问题,表示一个值可能存在也可能不存在。
Optional<T> 可表示一个非空或空的Optional<T>对象。如果一个Optional<T>实例是非空的,表示它保存有一个类型为T的值;如果Optional<T>实例为空(null),则类型T的值不存在。

Optional提供很多有用的方法,以避免显式地进行空值检测。
如果值存在,那么方法isPresent()会返回true,调用get()方法会返回该对象;
如果值不存在,那么方法isPresent()会返回false,调用get()方法会产生空指针异常(NullPointerException, 简称 NPE)。

Optional类主要用于作为方法的返回类型,以明确表示方法可能返回空值。
Optional 类专用于防止空指针异常(NullPointerException, 简称 NPE)的问题。
Optional 类提供了一种优雅的方式来处理可能为空的值,避免显式进行空值检测,防止空指针异常的产生。通过合理使用 Optional,可以使代码更加简洁、可读性更高。

Optional类的缺点是引入额外的对象的开销。在有些情形不正确使用它,会影响系统性能。

Optional类的特性:

  1. 非空性:Optional类总是包含一个值或者为空。
  2. 不可变性:Optional对象一旦创建,就不可更改。
  3. 链式操作:可以连续调用多个方法,如map、filter等。

二、Optional 类的属性和构造器的定义及创建实例方法:

Optional 类位于 java.util 包中,OptionalOptional<T>的属性和构造器部分定义的源代码如下:
属性、类常量和构造器

public final class Optional<T> {
	//两个属性
	private static final Optional<?> EMPTY = new Optional<>();//空容器
	private final T value; //value 存储容器内唯一元素
	
	//两个构造器
	private Optional() {  //无参构造方法
        	this.value = null;
    }

    private Optional(T value) { //有参构造方法(value不允许为空)
        	this.value = Objects.requireNonNull(value);
    }
}

从上面的代码可以看到Optional类定义了一个属性value,当Optional<T> 不为空时其值就存放于属性value中。
另外,Optional类还定义了一个静态常量(类常量)EMPTY,表示值为空。创建空的Optional<T> 时,就返回这个EMPTY。
Optional 类还定义了两个构造器,但都是私有的,不能直接用构造器创建Optional 类的实例。

三个创建Optional<T>实例的静态方法
创建Optional 类的实例需要使用静态方法,下面是Optional 类创建实例的三个静态方法:
三个静态方法的定义的源码:

	//创建值为'null'的Optional 类实例(空容器)的静态方法
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
	//创建Optional 类实例(value不允许为空)的静态方法
	public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
	//创建Optional 类实例(value允许为空)的静态方法
	public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

第一个静态方法empty()用于创建空的Optional<T>实例。
第二个静态方法 of(T value) 专用于创建有确定值的Optional<T>实例。
第三个静态方法ofNullable(T value) 实际上是前面二个静态方法的组合应用,用于创建Optional<T>实例,当这个实例有确定值时就返回由of(value)创建的实例,否则由empty()创建空的Optional<T>实例。

因此,对于空的Optional<T>实例,实际上指向同一个对象,空的Optional<T>实例是单例的。Optional.empty() == Optional.ofNullable(null)返回的是true。

创建实例的示例:

	//创建空的 Optional 实例
	Optional<String> emptyOpt = Optional.empty();
	//创建非空的 Optional 实例
	Optional<String> opt = Optional.of("中国");
	//创建可以为空的 Optional 实例
	Optional<String> nullableOpt = Optional.ofNullable(null);

注意事项:
两个创建Optional<T>实例的静态方法:of() 和 ofNullable() 方法的区别:如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException:

        //如此创建Optional<User>,可能是null时,of() 方法会抛出 NullPointerException
        Optional<User> optUser = Optional.of(null);

因此,你应该明确对象不为 null 的时候才可使用 of()方法。
如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:

        //如此创建Optional<User>,可能是null时,可使用ofNullable()方法
        Optional<User> optUser = Optional.ofNullable(null);

三、包装类Optional 的常用方法:

  • Optional 类有四个获取Optional<T> 对象所包含值的拆箱方法(get、orElse、orElseGet和orElseThrow):

1,T get(): 如果调用对象包含值,返回该值;否则抛出NoSuchElementException异常。如果不想抛出异常,或者能够 100%确定不是空的Optional<T>实例,或者先行使用isPresent方法判断。
2,T orElse(T other) :如果值存在则将其返回,否则返回指定的other对象。
3,T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
4,T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则返回该值,否则是空值则抛出由Supplier接口实现提供的异常。

以下是Java核心类库Optional类中定义的四个获取Optional<T>调用对象所包含值的方法的源码:

	//获取容器中唯一元素
	public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    //获取容器唯一元素,若为空,返回指定值
    public T orElse(T other) {
        return value != null ? value : other;
    }
    //获取容器唯一元素,若为空,返回供应者(Supplier)提供的元素
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }
	//如果非空值则返回该值;否则抛出由Supplier接口提供的异常。
	public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }

说明:
对 Optional<T> 实例拆箱,取出包含的实际值对象的方法
方法get() :如果调用对象包含值,返回该值,否则抛异常。要避免异常,你首先要判断Optional容器中是否有值,然后再取;
方法orElse(T other):需要指定空值时返回的类型为T的一个默认值;
方法orElseGet(Supplier<? extends T> other):则需要指定一个供应者,供应一个类型为T的值。
方法orElseThrow(Supplier<? extends X> exceptionSupplier) :则需要指定一个供应者,供应一个异常。

  • 判别Optional实例值是否为空(值是否存在)的两个方法 isPresent() 和 ifPresent(Consumer<? super T> consumer)

boolean isPresent() : 用来判断调用对象的值是否存在,类似于obj != null。
void ifPresent(Consumer<? super T> consumer) :测试调用对象的值是否存在。需要传入一个函数接口Consumer的实例作为参数,如果值不为空(有值)时候,会执行Consumer函数。

Java核心类库Optional类中定义的二个判别方法的源码:

    public boolean isPresent() {
        return value != null;
    }

    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

请看一个使用方法isPresent()示例代码:

	User user = null;
	Optional<User> userOpt = Optional.ofNullable(user);
	if (userOpt.isPresent()) {
    	System.out.println("用户: " + userOpt.get());
	}

上面的代码等价于 使用方法ifPresent():

	User user = null;
	Optional<User> userOpt = Optional.ofNullable(user);
	userOpt.ifPresent( u -> System.out.println("用户: " + u ));

isPresent()判断的写法上是不是感觉很熟悉,在面向对象的编程版本可以直接写为:

	User user = null;
	if (user != null) {
    	System.out.println("用户: " + user);
	}

使用 isPresent() 方法判断感觉有些突兀,在函数式编程中使用 filter() 过滤或者 用map()方法来处理Optional<T> 实例更自然。

Optional 应用例程一:

	Optional<String> opt = Optional.of("江山如画!");
	if (opt.isPresent()) {
		System.out.println(opt.get());
	}
	//注意下面二个方法的不同之处	
	Optional<User> op = Optional.ofNullable(null);
	User user1 = op.orElse(new User("张三", 20));
	User user = op.orElseGet(() -> new User("张三", 20));
	
	Optional<User> userOpt = Optional.ofNullable(null);
	User user = userOpt.orElse(new User("王五",32));
	userOpt.orElseThrow(() -> new NullPointerException());

我们修改一下上面的代码,来说明一个性能问题:

	//注意下面二个方法的不同之处	
	Optional<User> op2 = Optional.ofNullable(new User("李四", 20));
	User user1 = op2.orElse(new User("张三", 20));
	User user = op2.orElseGet(() -> new User("张三", 20));

这个示例中,op2是非空值,最终结果两个 Optional 对象都包含相同的非空值(两个方法也都会返回对应的非空值)。看上去两种方式好像是等价的,但是,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不会创建 User 对象。
在执行较密集的调用的情形,例如频繁地调用 Web 服务或数据查询,这个差异会对性能产生重大影响。因此,在实际应用中,还是要权衡利弊选择合适的方法。

Optional 应用示例二:

		//isPresent() 判断是否存在非空的值
		Optional<String> opt = Optional.of("江山如画!");
		if (opt.isPresent()) {
			System.out.println(opt.get());
		}
		
		//ifPresent() 检查是否有值存在
		opt.ifPresent(System.out::println);
		
		User user = new User(6, "测试用户");
        Optional<User> userOpt = Optional.ofNullable(user);
        userOpt.ifPresent(u->u.setName("新用户"));
        System.out.println(user);
  • Optional筛选元素的过滤器方法filter()
    如果 Optional 中的值满足测试条件,返回这个 Optional,否则返回一个空的 Optional。
    Java核心类库Optional类中定义的过滤器方法filter()源码:
	    public Optional<T> filter(Predicate<? super T> predicate) {
        	Objects.requireNonNull(predicate);
        	if (!isPresent())
            	return this;
        	else
            	return predicate.test(value) ? this : empty();
    	}

Optional 应用示例三:

	Optional<String> longOpt = opt.filter(s -> s.length() > 3);

	Optional<User> op = Optional.of(new User("张三", 20));
	Optional<User> op2 = op.filter(user -> user.age > 18);
  • Optional的映射器方法map()、flatMap()与链式调用
    先来学习一下,Java核心类库Optional类中定义的映射器方法map()和flatMap()的源码:
	//map()源码
    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
	//flatMap()源码
    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

提示:
映射器方法map()和flatMap()的方法签名如下所示:

	Optional<U> map(Function<? super T, ? extends U> mapper)
	Optional<U> flatMap(Function<? super T, Optional<U>> mapper)

这两个映射器方法返回的流(Stream)元素的值都包装在 Optional 中。这就使得对返回值进行链试调用的操作成为可能。
map方法: 对Optional中的值进行转换(若值为空,则map方法什么也不做,直接返回空的Optional对象)。
这个变换基于提供该map的函数,并且这个变换是可选的,如果optional的值为空则不会做任何改变,并且map方法不会改变原始的Optional对象,而返回新的Optional对象,因此可以链式调用进行多个转换操作。
flatMap()方法: 用于扁平化嵌套的Optional结构,以避免引入不必要的嵌套层级。flatmap的映射转换函数返回的必须也是一个Optional对象;flatMap()方法可以用于嵌套的Optional情况,可以将两个互为嵌套关系的Optional对象转换为一个Optional对象。如果原始的Optional对象为空,或转换函数返回的Optional对象为空,那么最终得到的也是空的Optional对象。
两者区别: 若只需要对Optional对象中的值进行转换,而不需要处理嵌套的Optional,那么使用map方法更合适。
如果要对Optional对象进行一些操作返回另外一个Optional对象,flatmap方法更合适。

Optional 应用示例四:
如果 Optional 对象是非空值,则映射后返回一个新的 Optional对象,否则返回空的Optional对象。

	//假如names是文件名的数组
    Optional<FileInputStream> fis =
         names.stream().filter(name -> !isProcessedYet(name))
                       .findFirst()
                       .map(name -> new FileInputStream(name));

Optional 应用示例五:处理链式调用
当处理多个可能为空的值时,可以使用链式调用来避免嵌套的 if 判断。

	Optional<String> result = Optional.ofNullable(person)
    	.map(Person::getAddress)
    	.map(Address::getStreet);

综合实例:

package stream.Optional;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        // 创建 Optional 对象
        Optional<String> emptyOpt = Optional.empty();
        Optional<String> opt = Optional.of("江山如画!");
        Optional<String> nullableOpt = Optional.ofNullable(null);

        // 检查 Optional 对象
        if (opt.isPresent()) {
            System.out.println(opt.get());
        }
        opt.ifPresent(System.out::println);

        // 使用 orElse
        String value1 = opt.orElse("默认值");
        System.out.println(value1);

        // 使用 orElseGet
        String value2 = opt.orElseGet(() -> "供应一个默认值");
        System.out.println(value2);

        // 使用 orElseThrow
        try {
            String value3 = emptyOpt.orElseThrow(() -> new IllegalArgumentException("值缺失"));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        // 使用 map
        Optional<String> upperOpt = opt.map(String::toUpperCase);
        upperOpt.ifPresent(System.out::println);

        // 使用 flatMap
        Optional<Integer> lengthOpt = opt.flatMap(s -> Optional.of(s.length()));
        lengthOpt.ifPresent(System.out::println);

        // 使用 filter
        Optional<String> longOpt = opt.filter(s -> s.length() > 3);
        longOpt.ifPresent(System.out::println);

        // 结合 Optional 和流
        List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
        Optional<String> nameOpt = names.stream()
            .filter(name -> name.startsWith("J"))
            .findFirst();
        nameOpt.ifPresent(System.out::println);

        // 在实践中使用 Optional
        Optional<String> configValue = Optional.ofNullable(getConfig("key"));
        configValue.ifPresent(System.out::println);

        Optional<User> userOpt = Optional.ofNullable(findUserById(1));
        userOpt.ifPresent(System.out::println);

        Optional<String> paramOpt = Optional.ofNullable(getRequestParam("param"));
        paramOpt.ifPresent(System.out::println);
    }

    private static String getConfig(String key) {
        // 模拟配置项获取
        return "value";
    }

    private static User findUserById(int id) {
        // 模拟数据库查询
        return new User(id, "John Doe");
    }

    private static String getRequestParam(String param) {
        // 模拟 HTTP 请求参数获取
        return "paramValue";
    }
}

class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public void setName(String name) {
		this.name = name;
	}
 
    @Override
    public String toString() {
        return "User{ID号=" + id + ", 姓名='" + name + '\'' + '}';
    }
}

四、Optional 与归约reduce()方法?
归约reduce()方法,在java.util.stream.Stream中定义。本文讨论这二种reduce()重载方法。
在例程中使用了二种reduce:
Optional reduce(BinaryOperator accumulator)
Integer reduce(Integer identity, BinaryOperator accumulator)
一个演示例程:

package stream;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(2,3,5,8);
		/***这一种用法,可兼容空的流,而不出现异常***/
		Optional<Integer> optional = list.stream().reduce((a, b) -> a + b);
        Optional<Integer> optional1 = list.stream().reduce(Integer::sum);
        System.out.println(optional.orElse(0));
        System.out.println(optional1.orElse(0));
        /***这第二种用法,必须确保流不能为空,否则出现异常***/
        int reduce = list.stream().reduce(0, (a, b) -> a + b);
        System.out.println(reduce);
        int reduce1 = list.stream().reduce(0, Integer::sum);
        System.out.println(reduce1);   
	}
}

五、什么情形使用Optional?

在使用Optional的时候需要考虑一些事情,以决定什么时候如何使用?
Optional不支持 Serializable。因此,在需要序列化进行网络传输和存储的场景中,它不宜用作为类的属性字段。

Optional主要用作方法的返回类型。 在获取到这个类型的实例后,如果它有值,你可以取得这个值,否则可以进行一些替代行为。
Optional类有一个非常有用的场景,就是将其与流(Stream)或其它返回 Optional 的方法结合,以构建流畅的函数式编程的流管道。
我们来看一个示例,使用Stream的函数式编程中使用返回Optional对象的 findFirst() 方法:

    List<User> users = new ArrayList<>();
    User user = users.stream().findFirst().orElse(new User(15,"默认用户"));

Optional的应用场景:

    1. 方法返回多个结果:当一个方法需要返回多个结果,但其中某些结果可能不存在时。
    1. 处理链式调用:在链式调用中,某些操作可能返回null,使用Optional可以避免空指针异常。
    1. 集合操作:在处理集合时,特别是在需要对集合进行转换或过滤的场合,Optional可以提供更加清晰的逻辑。
    1. 函数式接口:与Function、Predicate等函数式接口结合使用,可以构建更加灵活的数据处理链。

六、借助Optional 类的函数式编程风格的链式调用的应用实例

Optional 是 Java 函数式编程的基础之一,它可帮助在函数式编程在流式的范式中实现。但是 Optional的意义显然不止于此。
当处理多层嵌套的对象时,使用 Optional 可以避免深层次的 null 检查,使代码更加简洁。
我们从一个简单的使用实例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 空指针异常(NullPointerException):

	String postcode = user.getAddress().getCountry().getPostcode().toUpperCase();

上面这行演示代码实现从一个用户(User)中获取邮政编码(postcode)的功能。如果我们需要确保不触发空指针异常,就得在访问每一个值之前对其进行明确的检查:

//普通的面向对象风格的版本
User user = new User(6, "测试用户","315040");
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String postcode = country.getPostcode();
            if (postcode != null) {
                postcode = postcode.toUpperCase();
            }
        }
    }
}

你看到了这个代码是没有Optional时的写法,代码变得冗长,难以阅读,也难以维护。
我们来看看傲使用Optional类如何简明实现这个功能。
从创建和验证实例,到调用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证 Optional奇迹的时刻。
首先,需要重构User类,使其 getter 方法返回 Optional 实例:

	public class User {
    	private Address address;
		    
		public User(int id, String name, String postcode) {
        	this.id = id;
       		this.name = name;
        	this.postcode = postcode;
    	}
    	public Optional<Address> getAddress() {
        	return Optional.ofNullable(address);
    	}

    	// ...
	}
	
	public class Address {
    	private Country country;

    	public Optional<Country> getCountry() {
        	return Optional.ofNullable(country);
    	}

    	// ...
	}

	public class Country {
    	private Postcode postcode;
    
    	public String getPostcode() {
        	return postcode;
    	}

    	// ...	
	}

上面的User类定义相关的嵌套结构,可以用下面的示意图来表示:
在这里插入图片描述
最终,我们把“普通的面向对象风格的版本”转换为“函数式编程风格的版本”,如下:

	//函数式编程风格的版本
	User user = new User(6, "测试用户","315040");
	String result = Optional.ofNullable(user)
  		.flatMap(User::getAddress)
		.flatMap(Address::getCountry)
  		.map(Country::getPostcode)
  		.orElse("未知的");

七、Optional错误用法的例子:

  • 不适合使用情形之一:直接使用optional与空(null)进行条件判断比较:if (optional != null)
	// 错误示例
	Optional<String> optional = Optional.ofNullable(null);
	if (optional != null) { // 无意义的检查
    	System.out.println(optional.get());
	}

由于Optional本身就是用来避免显式地进行空值(null)检测的,直接显式地用if (optional != null)检查是没有意义的,这个判断语句永远是false。
解决办法:可使用optional.isPresent()或optional.orElse(…)实现此功能。

  • 不适合使用情形之二:使用Optional作为方法或构造器的参数。
    将Optional对象作为方法或构造器的参数时。这样做会让代码变得复杂,完全没有必要。
	User user = new User(56, "测试用户", Optional.empty());
  • 不适合使用情形之三:将Optional用于作为集合的参数,集合已经能很好地处理空集合的情形,没必要使用Optional包装集合。

结束语:
Optional是 Java 语言的有益补充它旨在避免空指针异常( NullPointerException),尽管还不能完全消除这些异常。
但Java 8对Optional的精心设计,自然融入了函数式编程,并增强了函数式编程的功能。

参考文献:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值