Android 防止空指针异常

Posted 麦田里的守望者-Jiang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 防止空指针异常相关的知识,希望对你有一定的参考价值。

文章目录

空指针异常

先复习下异常。

异常分为 ExceptionError,Exception 和 Error 类都继承自Throwable类。

  • Exception(程序可恢复):表示程序可以处理的异常,可以捕获并且可恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
  • Error(程序不可恢复):一般指虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。这于这类错误,Java编译器不去检查也会导致应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议程序终止。

Exception 又分为运行时异常受检查的异常

  • 运行时异常:如空指针,参数错误等。
  • 受检查异常:这类异常如果没有try/catch语句也没有throws抛出,编译不通过。

空指针异常(NullPointerException)属于运行时异常,当应用程序试图在需要对象的地方使用 null 时,抛出该异常。这种情况包括:

  • 调用 null 对象的实例方法。
  • 访问或修改 null 对象的字段。
  • 将 null 作为一个数组,获得其长度。
  • 将 null 作为一个数组,访问或修改其时间片。
  • 将 null 作为 Throwable 值抛出。

遇到该异常,如果又没有try/catch语句,就将导致应用程序终止。从应用程序线上问题统计中发现,空指针异常是比较常见的。所以防止空指针异常非常有必要。


防止空指针异常

要防止空指针异常,可以从三方面来入手:

  1. Java 注解辅助
  2. 引入 Java 8 中的 Optional 类型
  3. 使用 Kotlin 区分可空类型和非空类型

Java 注解

Java 注解可以用来表达值的可空性,帮助静态扫描工具找到可能抛出 NullPointerException 的地方。

android 提供了可空注解@NonNull 和 非空注解@NonNull

    @Nullable
    public Fragment findFragment(@NonNull FragmentActivity activity, @NonNull String tag) 
        FragmentManager fragmentManager = activity.getSupportFragmentManager();
        return fragmentManager.findFragmentByTag(tag);
    

在使用者使用的时候,如果没有正确的使用可空类型和非空类型,就会得到编译器的提示:


所以注解只能用来提示使用者在使用的时候是可空类型还是非空类型,但并不能限定使用者传递的是可空类型还是非空类型。

Java 8 中的 Optional 类型

Java 8 中引入了 Optional 类型,它是一个可能包含或不包含非空值的容器对象,用更优雅的方式来防止 NullPointerException

Optional 类 提供的主要方法:

方法名字表示意思
of返回具有 Optional的当前非空值的Optional。
ofNullable返回一个 Optional指定值的Optional,如果非空,则返回一个空的 Optional 。
isPresent如果存在值,则返回 true ,否则为 false 。
get如果 Optional中存在值,则返回值,否则抛出 NoSuchElementException 。
map如果存在值,则应用提供的映射函数,如果结果不为空,则返回一个Optional结果的Optional 。 否则返回一个空的Optional 。
flatMap如果一个值存在,应用提供的Optional映射函数给它,返回该结果,否则返回一个空的Optional 。 这种方法类似于map(Function) ,但是提供的映射器是一个结果已经是Optional映射器,如果被调用, flatMap不会用额外的Optional 。
orElse返回值如果存在,否则返回 other 。

测试例子:

创建一个User类:

public class User 
	private String nickname;
	private Address address;

	public User() 
	

	public Optional<Address> getAddress() 
		return Optional.ofNullable(address);
	

	public void setAddress(Address address) 
		this.address = address;
	

	public String getNickname() 
		return nickname;
	

	public void setNickname(String nickname) 
		this.nickname = nickname;
	


获取 Nickname:

	private String getUserNickname(User user) 
		return Optional.ofNullable(user).map(new Function<User, String>() 

			@Override
			public String apply(User t) 
				return t.getNickname();
			
		).orElse("default");
	
	
	String nickname = getUserNickname(null);
    //运行结果:nickname="default"

ofNullable 方法主要将对象包装成一个Optional对象(用的就是组合):


    private static final Optional<?> EMPTY = new Optional<>();

    private final T value;

    private Optional() 
        this.value = null;
    

    public static<T> Optional<T> empty() 
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    

    private Optional(T value) 
        this.value = Objects.requireNonNull(value);
    

    public static <T> Optional<T> of(T value) 
        return new Optional<>(value);
    

    public static <T> Optional<T> ofNullable(T value) 
        return value == null ? empty() : of(value);
    

map 方法会检测当前值是否为空,如果为空就返回一个空的Optional对象,否则将接口Function的回调方法apply的返回值包装成一个新的Optional对象。

    public boolean isPresent() 
        return value != null;
    

    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));
        
    

orElse 方法检测当前值是否为空,如果为空,就返回传入的默认值:

    public T orElse(T other) 
        return value != null ? value : other;
    

从上面可以 看出,使用 Optional 类,主要是将对象包装成 Optional 对象来防止 NullPointerException

Optional 类 还可以解决在实际开过程中,经常遇到的一些链式调用引起的NullPointerException

下面通过User 类,获取 Country 类 的属性,一般会这样写:

		String countryName = null;
		Address address = user.getAddress();
		if(address!=null) 
			Country country = address.getCountry();
			if(country !=null) 
				countryName = country.getName();
			
		

上面代码中不得不对过程中遇到的每一个对象做判空处理,如果调用链很长,代码就会越不友好。修改user.getAddress()address.getCountry() 方法的返回值:

	public Optional<Address> getAddress() 
		return Optional.ofNullable(address);
	
	public Optional<Country> getCountry() 
		return Optional.ofNullable(country);
	

使用Optional 类 ,将链式调用中遇到的任何一个对象都包装成 Optional对象:

	private String  getCountryName(User user) 
		return Optional.ofNullable(user).flatMap(new Function<User, Optional<Address>>() 

			@Override
			public Optional<Address> apply(User t) 
				return t.getAddress();
			
		).flatMap(new Function<Address, Optional<Country>>() 

			@Override
			public Optional<Country> apply(Address t) 
				return t.getCountry();
			
		).map(new Function<Country, String>() 

			@Override
			public String apply(Country t) 
				return t.getName();
			
		).orElse("default");
	
	
	String countryName = getCountryName(user);
	//运行结果:countryName = "default"

在上面链式方法调用中,中间如果有任何一个对象为空,都不可能抛出空指针异常。

虽然 Optional 可以防止NullPointerException,但是代码还是很冗长,并且额外的包装接口还会影响运行时的性能。

Kotlin

Kotlin 区分可空类型和非空类型,并会在编译期间对可空类型和非空类型检查。

可空类型,问号添加在类型的后面表示这个类型的变量可以存储 null 引用:

    fun findFragment(activity: FragmentActivity?, tag: String?): Fragment? 
        val fragmentManager = activity?.supportFragmentManager
        return fragmentManager?.findFragmentByTag(tag)
    

如果没有正确使用,将得到编译器的错误提示,并无法编译:

非空类型,没有问号添加在类型的后面表示这个类型的变量不可以存储 null 引用:

    fun findFragment(activity: FragmentActivity, tag: String): Fragment? 
        val fragmentManager = activity.supportFragmentManager
        return fragmentManager?.findFragmentByTag(tag)
    

同样,如果没有正确使用,将得到编译器的错误提示,并无法编译:

可空类型和非空类型在运行时并没有什么区别,只是在编译期间对类型进行检查。所以,使用Kotlin 的可空类型并不会在运行时带来额外的开销。

对于可空类型的判断,Kotlin 还提供了安全调用运算符?.Elvis 运算符?:安全转换符as?非空断言!!来防止空指针异常,这里不再一一介绍。


总结

防止空指针异常,有三种方法:使用Java 注解辅助,引入 Java 8 中的 Optional 类型, 使用 Kotlin 区分可空类型和非空类型。使用Java 注解辅助,只能提示使用者在使用的时候是可空类型还是非空类型,并不能限定使用者传递的是可空类型还是非空类型;引入 Java 8 中的 Optional 类型,能防止空指针异常,但是代码还是很冗长,并且额外的包装接口还会影响运行时的性能;使用 Kotlin 区分可空类型和非空类型,会在编译期间对类型进行检查,并且不会在运行时带来额外的开销。总之,使用 Kotlin 是防止空指针异常的最好方式。

以上是关于Android 防止空指针异常的主要内容,如果未能解决你的问题,请参考以下文章

Android gps getlastknownlocation 返回空指针异常

Android自定义view,空指针异常求解决

android - LAN 套接字编程 - 获取空指针和地址使用异常

android.R.id.home 抛出空指针异常

Android 应用程序不断因空指针异常而崩溃

Android - 调用自定义 View 方法时出现空指针异常