本文仅供学习参考!
相关教程地址:
https://juejin.cn/post/7234924083842154556
https://cloud.tencent.com/developer/article/1107739?areaSource=106005.3
https://www.developer.com/java/null-data-java/
将某些内容表示为空白或不存在始终是编程中的一个问题。例如,袋子里没有物品,仅仅意味着袋子是空的或者袋子里没有任何东西。但是如何表示计算机内存中缺少某些内容呢?例如,在内存中声明的对象包含一些值(变量是否初始化并不重要),即使它在上下文中可能没有任何意义 - 这称为垃圾值。程序员最多可以丢弃它,但重点是声明的对象不为空。我们可以通过一个值来初始化它或放置一个null。然而,这仍然具有一定的价值;甚至为空是不代表任何东西的东西。在本编程教程中,我们分析了数据缺失和null的情况,看看 Java 在处理这个问题方面提供了什么。
Java 中什么是 null?
计算机中没有数据只是一个概念性的想法;内部表述实际上与之相反。我们可以将其与集合论联系起来,它定义了一个基数为0的空集。但是,在实际的表达中,它使用一个称为null的符号来表示空。因此,如果我们问“空集包含什么?”这个问题,一个可能的答案是null,即什么都没有或为空。但是,在软件开发中,我们知道null也是一个值。
通常,值0或内存中所有为 0 的位用于表示常量,其名称为null。与其他变量或常量不同,null表示没有与名称关联的值,它表示为内置常量,其中包含0值。
一段数据实际上被表示为指向它的引用。因此,为了在缺乏数据的情况下表示某些东西,开发人员必须编造一些什么也表示不了的东西。所以null(在 Go 中被称为nil ——也许是因为他们发现nil比null少一个字符,而且越少越好)是被选择的。这就是我们所说的空指针。因此,我们可以看到null既是一个指针,又是一个值。在 Java 中,某些对象(静态变量和实例变量)默认使用null创建,但稍后可以更改为指向值。
这里值得一提的是,null作为编程中的参考,是由 Tony Hoare 在 1965 年设计ALGOL时发明的。在他晚年的时候,他后悔这是一个价值数十亿美元的错误,他说:
我称之为我的十亿美元错误。这是 1965 年空引用的发明。当时,我正在设计第一个面向对象语言(ALGOL W)中引用的综合类型系统。我的目标是确保所有引用的使用都绝对安全,并由编译器自动执行检查。但我无法抗拒放入空引用的诱惑,只是因为它很容易实现。这导致了无数的错误、漏洞和系统崩溃,在过去四十年中可能造成了数十亿美元的痛苦和损失。
这个看似无害的名为null的东西多年来却造成了一些严重的麻烦。但是,也许在编程中不能完全忽视null的重要性。这就是许多后来的编译器创建者认为保留遗产是明智之举的原因。然而,Java 8 及更高版本尝试提供一种名为Optional的类型,直接处理与使用null相关的一些问题。
Java中空指针的问题
NullPointerException是每个 Java 程序员经常遇到的常见错误**。当我们尝试取消引用一个不指向任何内容的标识符时,就会引发此错误 - 这仅仅意味着我们期望获取一些数据,但数据丢失了。我们试图访问的标识符指向null**。
下面是我们如何在 Java 中引发NullPointerException错误的代码示例:
public class Main {
public static void main(String[] args) {
Object obj = null;
System.out.println(obj.toString());
}
}
在集成开发环境 (IDE) 或代码编辑器中运行此代码将产生以下输出:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Object.toString()" because "obj" is null
at Main.main(Main.java:4)
通常在编程中,避免问题的最佳方法是知道如何创建问题。现在,尽管众所周知必须避免null引用,但 Java API 仍然大量使用null作为有效引用。一个这样的例子如下。java.net 包中Socket类构造函数的文档说明如下:
public Socket( InetAddress address, int port, InetAddress localAddr, int localPort ) throws IOException
这段Java代码:
- 创建一个套接字并将其连接到指定远程端口上的指定远程地址。Socket还将**bind()**到提供的本地地址和端口。
- 如果指定的本地地址为null,则相当于将该地址指定为AnyLocal地址(请参阅InetAddress.isAnyLocalAddress())。
- 本地端口号为零将使系统在绑定操作中选择一个空闲端口。
- 如果有安全管理器,则会调用其checkConnect方法,并以主机地址和端口作为参数。这可能会导致SecurityException。
根据Java文档,突出显示的点清楚地表明空引用被用作有效参数。这个null在这里是无害的,并用作哨兵值来表示缺少某些内容(此处是在套接字中缺少端口值的情况下)。因此,我们可以看到null并没有完全避免,尽管它有时很危险。Java中有很多这样的例子。
如何处理 Java 中数据缺失的情况
一个完美的程序员总是会编写完美的代码,实际上不会对null产生任何问题。但是,对于我们这些容易犯错误并需要某种更安全的替代方案来表示某些内容不存在而不诉诸 null 的创新用法的人来说,我们需要一些支持。因此,Java 引入了一种类型(称为Optional 的类),它以更体面的方式处理不是由于错误而发生的缺席值。
现在,在讨论任何代码示例之前,让我们先看一下来自 Java API 文档的以下摘录:
public final class Optional
extends Object
这段摘录展示了:
- 一个容器对象,可能包含也可能不包含非空值。如果存在值,isPresent()将返回true,而**get()**将返回该值。
- 还提供了取决于所包含值是否存在的其他方法,例如orElse()(如果值不存在则返回默认值)和ifPresent()(如果值存在则执行代码块)。
- 这是一门以价值观为基础的课程;在Optional实例上使用身份敏感操作(包括引用相等(== )、身份哈希码或同步)可能会产生不可预测的结果,开发人员应该避免。
事实上,Java 中有许多可选类,例如Optional、OptionalDouble、OptionalInt和OptionalLong——所有这些都处理开发人员不确定值是否存在的情况。在 Java 8 引入这些类之前,程序员习惯使用null值来表示值不存在。因此,称为NullPointerException的错误是一种常见现象,因为我们有意(或无意)尝试取消引用空引用;一种解决方法是经常检查空值以避免生成异常。
这些课程提供了更好的策略来应对这种情况。请注意,所有可选类都是基于值的,因此它们是不可变的并且具有各种限制,例如不使用实例进行同步并避免使用任何引用相等。在下一节中,我们将特别关注Optional类。其他可选类的功能类似。
Optional类中的T表示存储的值的类型,可以是T类型的任何值。它也可能是空的。尽管Optional类定义了多个方法,但没有定义任何构造函数**。开发人员可以判断某个值是否存在,如果存在则获取该值,如果不存在则获取默认值,或者构造一个可选值**。查看 Java 文档以获取有关这些类的可用函数的详细信息。
如何在Java中使用Optional
下面的代码示例展示了如何优雅地处理 Java 中返回null或缺少元素的对象。Optional类充当可能不存在的对象的包装器:
package org.app;
public class Employee {
private int id;
private String name;
private String email;
public Employee(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
package org.app;
import java.util.HashMap;
import java.util.Optional;
public class Main {
private HashMap <Integer,Employee>db = new HashMap<>();
public Main() {
db.put(101, new Employee(101, "Pravin Pal", "pp@gmail.com"));
db.put(102, new Employee(102, "Tuhin Shah", "ts@gmail.com"));
db.put(103, new Employee(103, "Pankaj Jain", "pj@gmail.com"));
db.put(104, new Employee(104, "Anu Sharma", "as@gmail.com"));
db.put(105, new Employee(105, "Bishnu Prasad", "bp@gmail.com"));
db.put(106, null);
db.put(107, null);
}
public Optional findEmployeeById(int id){
return Optional.ofNullable(db.get(id));
}
public Employee findEmployeeById2(int id){
return db.get(id);
}
public static void main(String[] args) {
Main m = new Main();
Optional opt = m.findEmployeeById(108);
opt.ifPresent(emp->{
System.out.println(emp.toString());
});
if(opt.isPresent()){
System.out.println(opt.get().toString());
} else {
System.out.println("Optional is empty.");
}
System.out.println(m.findEmployeeById2(106));
}
}
Optional类的一些关键函数是isPresent()和get()。isPresent ()函数确定该值是否存在。如果该值存在,则该函数返回布尔真值,否则返回假值。
可以使用**get()函数获取存在的值。但是,如果调用get()函数并且它没有值,则会引发NoSuchElementException 。理想情况下,在调用get()函数之前,始终使用ifPresent()**函数检查值是否存在。
END
如果有什么东西是编程无法消除的,但要提醒大家使用,那就是null。在数据库中,在存储值时,建议避免在表中存储空值。未正确规范化的数据库表可能有太多空值。一般来说,对于计算中价值缺失的含义并没有非常明确的定义。无论如何,在某种程度上,使用Java 中的Optional类可以处理与空值相关的问题。