在Java中检查空值?使用 "If Else "最小化
在这篇文章中,我将尝试给出一些不同类型的空检查或NPE(NullPointerException)避免技术的例子。
关于这个话题有很多好的文章,但我将尝试从我自己的经验出发,重点介绍一些具体的例子。
请注意,我仍然在学习过程中--可能是永无止境的:)--所以,如果你看到任何错误,请随时让我知道它。
1. java.util.Optional
一个容器对象,可能包含也可能不包含一个非空值。如果有一个值,
isPresent()
,将返回true
,get()
,将返回该值¹。
在我目前正在做的项目中,Optional最常用的地方之一是在从数据库中检索数据的时候。
比方说,你有3样东西。
- 一个模型类,叫做
Student.java
- 一个服务接口和它的实现:
StudentService.java
和StudentServiceImpl.java
- 一个DAO接口和它的实现:
StudentDao.java
和StudentDaoImpl.java
而你想用相关的id来检索学生。
Optional.ofNullable()
如果非空,返回一个描述给定值的Optional,否则返回一个空的Optional。
这个方法的返回值永远不会是空的。如果是空的,返回值将是Optional.empty()
。这样,如果这个方法的结果被用在其他地方,就不会有机会得到NPE。
Optional.orElseThrow()
如果有一个值存在,则返回该值,否则抛出NoSuchElementException.¹。
在null的情况下,如果你想抛出一个异常,你可以直接使用orElseThrow()
。
可选.orElse()
如果有一个值存在,则返回该值,否则返回其他值。
在null的情况下,如果你不想抛出一个异常,但你想返回一个样本学生实例,可以使用orElse()
。
@Overridepublic Student getMockStudent(Long id) { final var student = Student.builder() .id(1L) .name("ege") .build(); return studentDao.findStudentsById(id).orElse(student);}
Optional.get()
如果有一个值存在,则返回该值,否则抛出NoSuchElementException.¹。
@Overridepublic Student getStudentDirectly(Long id) { return studentDao.findStudentsById(id).get();}
在这种情况下,如果你使用IntelliJ,它会立即给出一个警告。
基本上它是在说 "首先检查你的学生是否为空,然后再继续"。
// correct way to do it@Overridepublic Student getStudentDirectly(Long id) { final var studentOptional = studentDao.findStudentsById(id); if(studentOptional.isPresent()){ // do your stuff here }}
在单元测试中,当我确定有数据从我调用的方法中返回时,我大多使用get()
。但在实际代码中,如果没有isPresent()
,我不会使用它,即使我确定不会有null。
另一个例子;下面这段代码试图获取具有相关id的学生,如果没有这样的学生,则返回一个默认的名字("Hayley")。当有学生但没有名字时,传递给rElse()
的值也会返回。
@Overridepublic String getStudentName(Long id) { return studentDao.findStudentsById(id) .map(Student::getName) .orElse("Hayley");}
2.apache.commons的实用类
- 对于集合实例:
CollectionUtils.isEmpty()
或CollectionUtils.isEmpty()
- 对于地图实例:
MapUtils.isEmpty()
或MapUtils.isNotEmpty()
- 对于字符串:
StringUtils.isEmpty()
或StringUtils.isNotEmpty()
对于列表、地图等,isEmpty()
,检查集合/地图是否为空或大小为0。同样,对于String
,检查String
是否为空或长度为0。
为了使用CollectionUtils
和MapUtils
,你需要在build.gradle
文件中添加以下依赖项。
implementation org.apache.commons:commons-collections4:4.4
而对于StringUtils
,你将需要。
implementation org.apache.commons:commons-lang3:3.0
3.流中的Objects::nonNull
如果提供的引用是非空的,则返回
true
,否则返回false
.²。
假设你有一个数据流,你将对这个数据流进行一些链式操作,但在这之前你想过滤掉空值(如果有的话)。
final var list = Arrays.asList(1, 2, null, 3, null, 4);list.stream() .filter(Objects::nonNull) .forEach(System.out::print);
其结果是。1234
4.java.util.Objects的requireNonNull方法
requireNonNull()
检查指定的对象引用是否为
null
,如果是,则抛出一个自定义的*NullPointerException*
。这个方法主要是为在有多个参数的方法和构造函数中进行参数验证而设计的。
requireNonNullElse()
如果第一个参数是非
null
,则返回第一个参数,否则返回非null
的第二个参数。³
requireNonNullElseGet()
如果第一个参数是非
null
,则返回该参数,否则返回非null
的值supplier.get()
。³
我最常使用这三个参数的地方主要是构造函数。
让我们通过上面的例子来看看它们。
requireNonNull on id
:我们在说 "这个字段是必须的,所以如果它是空的,就抛出一个 "id is required "的NPE消息"。
requireNonNullElse on name
:我们在说 "这个字段是必须的,所以如果它是空的,不要抛出一个异常,而是为它设置一个默认值。"在我们的例子中,默认值是 "hayley"。
requireNonNullElseGet on classes
:我们在说 "这个字段是必须的,所以如果它是空的;不要抛出异常,而是为它设置一个默认值。"。
与requireNonNullElse
的不同之处在于,这个方法希望将Supplier
作为第二个参数。
因此,我们可以使用方法引用requireNonNullElseGet.
它在处理Lists、Maps、Sets时特别有用,如果你想把它们初始化为空的Lists、Maps、Set等。
requireNonNullElseGet没有Supplier - 1
requireNonNullElseGet不含Supplier - 2
requireNonNullElseGet 替换为 requireNonNullElse
让我们看看行动。
结果。
Student 1 name: hayleyStudent 2: Student(id=1, name=ege, classes=[], teacherMap={})Student 3: Student(id=null, name=null, classes=null, teacherMap=null)Student 4: Student(id=1, name=ege, classes=[], teacherMap={})Student 5: Student(id=1, name=ege, classes=[SchoolClass(id=null, name=null, type=null, teacher=null, students=null, extras=null, startDate=null)], teacherMap={})
请注意,这些验证只有在相关的构造函数被调用时才有效。
另一个例子;假设你正在从你当前的方法(x)中调用一个方法(y),用(y)返回的结果,你将在x中进行其他操作。如果你不检查从y返回的结果是否为空,就有可能得到NPE。
的结果。
Students 1: nullStudents 2: []Students 3: [Student(id=1, name=ege, classes=[], teacherMap={}), Student(id=2, name=itir, classes=[], teacherMap={})]Students 4: [Student(id=1, name=itir, classes=[], teacherMap={}), Student(id=2, name=ege, classes=[], teacherMap={})]Default student name: hayleyAll names:[itir, ege]
5.Lombok的Builder.Default
如果你不熟悉Lombok,我强烈建议你去看看它。我个人很喜欢Lombok,它让开发者的生活变得更容易 :)
假设你有Student.java
,有id、name和class等字段。你可以使用把@Builder.Default
放在相关字段之前,并给它一个默认值。
当这个Student
类的实例被创建时,它的 "classes "将是一个空列表,而不是空。
final var student1 = Student.builder().build();final var student2 = new Student();final var student3 = Student.builder().id(1L).name("ege").build();System.out.println(student1.getClasses());System.out.println(student2.getClasses());System.out.println(student3.getClasses());System.out.println(student1.getName());System.out.println(student2.getName());System.out.println(student3.getName());
其结果是。
[][][]hayleyhayleyege
如果你像这样简单地在Student.java
中陈述字段。
private Long id;private String name;private List<SchoolClass> classes;
的结果。
nullnullnullnullnullege
在处理列表、地图等时,它对我特别有用。因为对于我目前正在做的项目,列表或地图比其他字段更有可能为空。另外,如果对这些列表/地图进行更多的操作,当它们为空时,很可能会出现NPE。
比方说,你想得到一个学生所上的课的名字,而你没有在 "classes "列表上使用Builder.Default
。
final var student1 = Student.builder().build();System.out.println(student1.getClasses().stream().map(SchoolClass::getName));
抛出NPE。
6.NotNull, NotEmpty, NotBlank注解
@NotNull
:"被注解的元素不能是空的。接受任何类型。"⁴
@NotEmpty
:"被注解的元素不能是空的或空的。支持的类型有CharSequence, Collection, Map, Array。"⁵
@NotBlank
:"被注释的元素不能为空,并且必须至少包含一个非空格字符。接受CharSequence" ⁶
假设你有一个控制器,其中有一个saveStudent()
方法。当你希望id、name和classes字段不为空时,你可以在Student类中加入以下注释。
@NotNullprivate Long id;@NotEmptyprivate String name;@NotEmptyprivate List<SchoolClass> classes;private Map<Integer, Teacher> teacherMap;
如果你使用的是Spring Boot,你可以将这些注解与@Validated
注解结合起来,用于API的请求体,如下所示。
@PostMapping()public void saveStudent(@Validated @RequestBody Student student){ studentService.saveStudent(student);}
例如,你有这样的请求。
如你所见,你会得到 "400 "错误,在你的应用程序的控制台中你会看到。
DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.demodemo.controller.StudentController.saveStudent(com.example.demodemo.model.Student) with 2 errors: [Field error in object 'student' on field 'classes': rejected value [null]; codes [NotEmpty.student.classes,NotEmpty.classes,NotEmpty.java.util.List,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.classes,classes]; arguments []; default message [classes]]; default message [must not be empty]] [Field error in object 'student' on field 'id': rejected value [null]; codes [NotNull.student.id,NotNull.id,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.id,id]; arguments []; default message [id]]; default message [must not be null]] ]
如果愿意,你可以捕捉这个MethodArgumentNotValidException
,并返回一个自定义错误。
需要的依赖性。
implementation 'org.springframework.boot:spring-boot-starter-validation'
这就是我目前的知识。当我了解到更多做空检查的方法时,我将尝试更新这个列表。有些时候,我仍然需要使用老式的 "if else "块来进行空值检查,但只要有可能,我就会尝试应用这些方法。
希望你喜欢这篇文章。
参考文献。
转载自:https://juejin.cn/post/7057869426313887780