java 删除二维数组中的null_避免在Java中检查Null语句

bb727a76891334fd851ac29d1a6c3532.png

1.概述

通常,在Java代码中处理null变量、引用和集合很棘手。它们不仅难以识别,而且处理起来也很复杂。事实上,在编译时无法识别处理null的任何错误,会导致运行时NullPointerException。在本教程中,我们将了解在Java中检查null的必要性以及帮助我们避免在代码中进行空检查的各种替代方法。

2.什么是NullPointerException?

根据 Javadoc for NullPointerException,当应用程序在需要对象的情况下尝试使用null时抛出它,例如:

  • 调用null对象的实例方法
  • 访问或修改空对象的字段
  • 取null的长度,就好像它是一个数组一样
  • 访问或修改null的插槽,就像它是一个数组一样
  • 抛出null就好像它是一个Throwable值

让我们快速查看导致此异常的Java代码的几个示例:

publicvoid doSomething(){String result = doSomethingElse();if(result.equalsIgnoreCase("Success"))// success}}privateString doSomethingElse(){returnnull;}

在这里,我们尝试调用null引用的方法调用。这将导致NullPointerException。另一个常见示例是,如果我们尝试访问空数组:

publicstaticvoid main(String[] args){findMax(null);}privatestaticvoid findMax(int[] arr){int max = arr[0];//check other elements in loop}

这会在第6行导致 NullPointerException。因此,访问空 对象的任何字段,方法或索引会导致 NullPointerException,如上面的示例所示。避免 NullPointerException的 常见方法是检查 null:

publicvoid doSomething(){String result = doSomethingElse();if(result !=null&& result.equalsIgnoreCase("Success")){// success}else// failure}privateString doSomethingElse(){returnnull;}

在现实世界中,程序员发现很难识别哪些对象可以为 null。积极安全的策略可能是为每个对象检查 null。但是,这会导致大量冗余空值检查,并使我们的代码可读性降低。在接下来的几节中,我们将介绍Java中的一些备选方案,以避免这种冗余。

3.通过API约定处理null

如上一节所述,访问null对象的方法或变量会导致NullPointerException。 我们还讨论了在访问对象之前对对象进行空 检查可以消除NullPointerException的可能性。但是,通常有API可以处理空值。例如:

publicvoid print(Object param){System.out.println("Printing "+ param);}publicObject process()throwsException{Object result = doSomething();if(result ==null){thrownewException("Processing fail. Got a null response");}else{return result;}}

在 print()方法调用将只打印 null,但不会抛出异常。同样, process()永远不会在其响应中返回 null。它反而抛出异常。因此对于访问上述API的客户端代码,不需要进行空检查。但是此类API必须在约定中明确说明。API发布此类约定的常见位置是JavaDoc。但是,这并未明确指出API约定,因此依赖于客户端代码开发人员来确保其合规性。在下一节中,我们将看到一些IDE和其他开发工具如何帮助开发人员解决这个问题。

4.自动化API约定

4.1.使用静态代码分析

静态代码分析工具有助于提高代码质量。一些这样的工具也允许开发人员维护null约定(Null Contracts)。一个例子是 FindBugs。 FindBugs通过 @Nullable和 @NonNull注解帮助管理null约定。我们可以在任何方法,字段,局部变量或参数上使用这些注释。这使得对客户端代码明确指出注释类型是否为 null。我们来看一个例子:

publicvoid accept(@NonnullObject param){System.out.println(param.toString());}

在这里, @NonNull清楚地表明参数不能为 null。如果客户端代码在不检查 null参数的情况下调用此方法 ,则 FindBugs将在编译时生成警告。

4.2.使用静态代码分析

开发人员通常依靠IDE来编写Java代码。使用代码自动补全和有用警告等功能,例如可能没有声明变量,在很大程度上对编码有帮助。一些IDE还允许开发人员管理API约定(API Contracts),从而消除对静态代码分析工具的需求。IntelliJ IDEA提供 @NonNull和 @Nullable注解。要在IntelliJ中添加对这些注释的支持,我们必须添加以下Maven依赖项:

<dependency><groupId>org.jetbrains</groupId><artifactId>annotations</artifactId><version>16.0.2</version></dependency>

现在,如果没有对 Null进行检查,IntelliJ将生成警告,就像我们在上一个示例中一样。IntelliJ还提供了用于处理复杂API约束的Contract注释。

5.断言

到目前为止,我们只讨论过从客户端代码中去除空检查的必要性。但是,这很少适用于实际应用。现在,假设我们正在使用一个不能接受空参数的API,或者可以返回必须由客户端处理的空响应。这表明我们需要检查参数或空值的响应。这里,我们可以使用Java Assertions代替传统的 null检查条件语句:

publicvoid accept(Object param){assert param !=null;doSomething(param);}

在第2行中,我们检查null参数。如果启用了断言,则会导致 AssertionError。尽管这是断言非空参数等前置条件的好方法,但这种方法主要存在两个问题:

  1. 通常在JVM中禁用断言
  2. 一个虚假的声明将导致在未经检查的错误无法恢复

因此,建议程序员不要使用断言来检查条件。在以下部分中,我们将讨论处理null检查的其他方法

6.通过编码实践避免NULL检查

6.1.前提条件

编写早期失败的代码通常是一种很好的做法。因此,如果一个API不允许接受有多个参数为空,更好地方法是预先检查API中的每一个非空参数。

例如,让我们看看两个方法:一个早期失败,另一个不失败:

publicvoid goodAccept(String one,String two,String three){if(one ==null|| two ==null|| three ==null){thrownewIllegalArgumentException();}process(one);process(two);process(three);}publicvoid badAccept(String one,String two,String three){if(one ==null){thrownewIllegalArgumentException();}else{process(one);}if(two ==null){thrownewIllegalArgumentException();}else{process(two);}if(three ==null){thrownewIllegalArgumentException();}else{process(three);}}

显然,我们应该更喜欢 goodAccept()而不是 badAccept()。作为替代方案,我们也可以使用Guava的前置条件来验证API参数。

6.2.使用原语而不是包装类

由于 null对于像int这样的原语来说不是一个可接受的值,我们应该尽可能优先于它们的包装对象,如 Integer。考虑一个对两个整数求和的方法的两个实现:

publicstaticint primitiveSum(int a,int b){return a + b;}publicstaticInteger wrapperSum(Integer a,Integer b){return a + b;}

6.3.空集合

有时,我们需要将一个集合作为方法的响应返回。对于这样的方法,我们应该总是尝试返回一个空集合而不是 null

publicList<String> names(){if(userExists()){returnStream.of(readName()).collect(Collectors.toList());}else{returnCollections.emptyList();}}

因此,我们在调用此方法时避免了客户端执行空检查的需要。

7.使用 Objects

Java 7引入了新的Objects API。此API有几个静态 实用程序方法,可以消除大量冗余代码。让我们看看一个这样的方法, requireNonNull():

publicvoid accept(Object param){Objects.requireNonNull(param);// doSomething()}

现在,让我们测试 accept方法:

assertThrows(NullPointerException.class,()-> accept(null));

因此,如果将null 作为参数传递,则 accept()会抛出 NullPointerException。此类还具有 isNull()和 nonNull()方法,可用作谓词来检查对象是否为null。

8.使用Optional

Java8在该语言中引入了一个新的 OptionalAPI。与null相比,这为处理可选值提供了更好的约定。让我们看看 Optional如何消除对空检查的需求:

publicOptional<Object> process(boolean processed){String response = doSomething(processed);if(response ==null){returnOptional.empty();}returnOptional.of(response);}privateString doSomething(boolean processed){if(processed){return"passed";}else{returnnull;}}

通过返回一个 Optional,如上所示,该 process()方法使得明确告诉调用者,响应可能是Null,并且必须在编译时处理。 这显然消除了客户端代码中对空检查的需求。可以使用 OptionalAPI的声明性样式以不同方式处理空响应:

assertThrows(Exception.class,()-> process(false).orElseThrow(()->newException()));

此外,它还为API开发人员提供了一个更好的约定,以向客户端表明API可以返回空响应。虽然我们不需要对此API的调用者进行空检查,但我们使用它来返回空响应。为避免这种情况, Optional提供了一个 ofNullable方法,该方法返回具有指定值的 Optional,如果值为 null,则返回 empty:

publicOptional<Object> process(boolean processed){String response = doSomething(processed);returnOptional.ofNullable(response);}

9. 库

9.1. 使用Lombok

Lombok是一个很棒的库,可以减少项目中样板代码的数量。它附带了一组注释,取代了我们经常在Java应用程序中编写的代码的常见部分,例如getter,setter和toString(),仅举几例。

另一个注释是 @NonNull。 因此,如果项目已经使用Lombok来消除样板代码,则 @NonNull可以代替作为空检查。

在继续查看一些示例之前,添加一个Maven依赖项引入Lombok:

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency>

现在,我们可以在需要进行空检查的地方 使用 @NonNull:

publicvoid accept(@NonNullObject param){System.out.println(param);}

因此,我们只是注解了需要进行null检查的对象,并且Lombok生成了已编译的类:

publicvoid accept(@NonNullObject param){if(param ==null){thrownewNullPointerException("param");}else{System.out.println(param);}}

如果 param为null,则此方法抛出 NullPointerException。该方法必须在其约定中明确说明,并且客户端代码必须处理异常。

9.2.使用StringUtils

一般来说,字符串验证包括除空值检查空值。因此,常见的验证声明是:

publicvoid accept(String param){if(null!= param &&!param.isEmpty())System.out.println(param);}

如果我们必须处理很多 String类型,这很快就会变得多余。这就是 StringUtils派上用场的地方。在我们看到这个动作之前,让我们为commons-lang3添加一个Maven依赖项:

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8.1</version></dependency>

现在让我们用 StringUtils重构上面的代码 :

publicvoid accept(String param){if(StringUtils.isNotEmpty(param))System.out.println(param);}

因此,我们使用静态实用程序方法 isNotEmpty()替换了 null或空检查。此API提供了其它强大而实用方法来处理常见的String函数。

10.结论

在本文中,我们研究了发生 NullPointerException的各种原因以及难以识别的原因。然后,我们使用了各种方法来避免代码中的冗余,以及对使用参数,返回类型和其他变量进行空检查。所有示例都可以在GitHub上找到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537168.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

simulink显示多个数据_如何在 Simulink 中使用 PID Tuner 进行 PID 调参?

作者 | 安布奇责编 | 胡雪蕊出品 | CSDN(ID: CSDNnews)本文为一篇技术干货&#xff0c;主要讲述在Simulink如何使用PID Tuner进行PID调参。PID调参器( PIDTuner)概述1.1 简介使用PID Tuner可以对Simulink模型中的PID控制器&#xff0c;离散PID控制器&#xff0c;两自由度PID控制…

Java并发编程之堵塞队列介绍以及SkipList(跳表)

堵塞队列 先了解一下生产者消费者模式&#xff1a; 生产者就是生产数据的一方&#xff0c;消费者就是消费数据的另一方。在多线程开发中&#xff0c;如果生产者处理速度很快&#xff0c;而消费者处理速度很慢&#xff0c;那么生产者就必须等待消费者处理完&#xff0c;才能继…

Java并发编程之线程池ThreadPoolExecutor解析

线程池存在的意义 平常使用线程即new Thread()然后调用start()方法去启动这个线程&#xff0c;但是在频繁的业务情况下如果在生产环境大量的创建Thread对象是则会浪费资源&#xff0c;不仅增加GC回收压力&#xff0c;并且还浪费了时间&#xff0c;创建线程是需要花时间的&…

Java并发编程之线程定时器ScheduledThreadPoolExecutor解析

定时器 就是需要周期性的执行任务&#xff0c;也叫调度任务&#xff0c;在JDK中有个类Timer是支持周期性执行&#xff0c;但是这个类不建议使用了。 ScheduledThreadPoolExecutor 继承自ThreadPoolExecutor线程池&#xff0c;在Executors默认创建了两种&#xff1a; newSin…

Spring中BeanFactory和FactoryBean的区别

先介绍一下Spring的IOC容器到底是个什么东西&#xff0c;都说是一个控制反转的容器&#xff0c;将对象的控制权交给IOC容器&#xff0c;其实在看了源代码之后&#xff0c;就会发现IOC容器只是一个存储单例的一个ConcurrentHashMap<String, BeanDefinition> BeanDefiniti…

Spring中Aware的用法以及实现

Aware 在Spring当中有一些内置的对象是未开放给我们使用的&#xff0c;例如Spring的上下文ApplicationContext、环境属性Environment&#xff0c;BeanFactory等等其他的一些内置对象&#xff0c;而在我们可以通过实现对应的Aware接口去拿到我们想要的一些属性&#xff0c;一般…

Spring Bean的生命周期以及IOC源码解析

IOC源码这一块太多只能讲个大概吧&#xff0c;建议还是去买本Spring IOC源码解析的书来看比较好&#xff0c;我也是自己看源代码以及视频整理的笔记 Bean的生命周期大概可以分为四个阶段&#xff0c;具体的等会再说&#xff0c;先看看IOC的源码吧 1、bean的创建 2、bean的属…

MongoDB位运算基本使用以及位运算应用场景

最近在公司业务上用到了二进制匹配数据&#xff0c;但是MongoDB进行二进制运算&#xff08;Bitwise&#xff09;没用过&#xff0c;网上博客文章少&#xff0c;所以就上官网看API&#xff0c;因此记录一下&#xff0c;顺便在普及一下使用二进制位运算的一些应用。 在MongoDB的…

Mybatis源码日志模块分析

看源码需要先下载源码&#xff0c;可以去Mybatis的github上的仓库进行下载&#xff0c;Mybatis 这次就先整理一下日志这一块的源码分析&#xff0c;这块相对来说比较简单而且这个模块是Mybatis的基础模块。 之前的文章有谈到过Java的日志实现&#xff0c;大家也可以参考一下&…

python手机端给电脑端发送数据_期货交易软件有哪些比较好用?分手机端和电脑端...

一、电脑端交易软件期货电脑端交易软件目前市场上用的最多的是文华财经和博易大师&#xff0c;这两个软件都是免费交易使用的。从投资者使用角度来看&#xff0c;目前电脑端文华财经的评价比博易大师高一些。当然每个投资者有自己的使用习惯&#xff0c;博易大师也有自己优点&a…

Find the Difference(leetcode389)

2019独角兽企业重金招聘Python工程师标准>>> Given two strings s and t which consist of only lowercase letters. String t is generated by random shuffling string s and then add one more letter at a random position. Find the letter that was added in …

Mybatis源码之数据源模块分析

先来看看java纯jdbc查询数据的示例&#xff1a; try {//加载对应的驱动类Class.forName("com.mysql.cj.jdbc.Driver");//创建连接Connection connection DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?serverTimezoneUTC", "roo…

Mybatis源码之缓存模块分析

缓存这个东西在很多应用中都能看到它们的身影&#xff0c;这次就讲讲在Mybatis中的缓存是怎么应用的&#xff0c;虽然说吧Mybatis中的缓存基本不怎么用&#xff0c;用的更多是第三方组件redis、MongoDB、MemCache等等。 Mybatis的缓存是基于Map实现的&#xff0c;从缓存中读写…

Mybatis源码之核心流程分析

终于谈到了Mybatis最核心的东西了&#xff0c;最核心的就是通过配置XML文件或注解中的SQL&#xff0c;直接调用接口就能执行配置好的SQL语句并封装成对应的返回类型的数据。 先看一下Mybatis使用示例&#xff1a; //创建Builder对象 SqlSessionFactoryBuilder builder new S…

Mybatis源码之与Spring集成包

这次讲讲Mybatis与Spring的整合&#xff0c;作为两款优秀的开源框架&#xff0c;被大众广泛使用&#xff0c;自然是需要强强联合的。 使用示例 先看一下怎么使用&#xff0c;首先需要引用这两款框架的jar包&#xff1a; <dependency>//spring-webmvc会自动去引入其他S…

Mybatis源码之插件模块分析

总结完这个Mybatis的整体主要功能基本上就差不多完&#xff0c;还有一些细节的部分&#xff0c;后续都会记录补充。 插件这个东西一般用的比较少&#xff0c;就算用的多的插件也算是PageHelper分页插件&#xff1b; PageHelper官网&#xff1a;https://github.com/pagehelper…

AMD推出7nm高端显卡Radeon VII,直指英伟达RTX 2080

显卡战争已经发展到了2019年&#xff0c;并且变得比任何人预想的都要激烈。 CES 2019大会上&#xff0c;AMD发布了第一款消费级的 7nm GPU&#xff0c;取名&#xff1a;Radeon VII。据了解&#xff0c;这不是 AMD 的第一颗 7nm 处理器&#xff08;早期以 AI 运算为主的 Radeon …

Spring集成Mybatis多数据源配置

既然在整理Mybatis那就把经常用的这个多数据源的笔记也整一下吧。 Spring集成Mybatis在之前就已经提到了。Spring集成Mybatis 集成Mybatis多数据源有两种方式&#xff1a; 1、创建多个SqlSessionFactory&#xff0c;扫描每个SqlSessionFactoryBean对应的包&#xff0c;形成了…

Spring文件上传

2019独角兽企业重金招聘Python工程师标准>>> Spring文件上传 1、所需依赖包&#xff1a;commons-fileupload-1.3.1.jar2、Maven配置文件pom.xml文件中加入依赖Jar包<dependency><groupId>commons-fileupload</groupId><artifactId>commons-…