深入探索java.util.Objects工具类

news/2025/3/18 8:15:44/文章来源:https://www.cnblogs.com/seven97-top/p/18778061

Java 的 Objects 类是一个实用工具类,包含了一系列静态方法,用于处理对象。它位于 java.util 包中,自 Java 7 引入。Objects 类的主要目的是降低代码中的空指针异常(NullPointerException) 风险,同时提供一些非常实用的方法供我们使用。

Objects - 对null的判断

对象判空

在 Java 中,万物皆对象,对象的判空可以说无处不在。Objects 的 isNull 方法用于判断对象是否为空,而 nonNull 方法判断对象是否不为空。例如:

String str = "null";if (Objects.isNull(str)) {System.out.println("对象为空");
}if (Objects.nonNull(str)) {System.out.println("对象不为空");
}

源码很简单,就是直接判断是否为 null:

public static boolean isNull(Object obj) {return obj == null;
}public static boolean nonNull(Object obj) {return obj != null;
}

对象为空时抛异常requireNonNull

如果想在对象为空时,抛出空指针异常,可以使用 Objects 的 requireNonNull 方法。例如:

String str = "null";Objects.requireNonNull(str);
Objects.requireNonNull(str, "参数不能为空");
Objects.requireNonNull(str, () - > "参数不能为空");

源码如下:

// 判断是否为空,空则返回异常,反之返回传入的参数 
public static < T > T requireNonNull(T obj) {if (obj == null)throw new NullPointerException();return obj;}// 与第一种一样,区别是可以传入一个自定义的提示public static < T > T requireNonNull(T obj, String message) {if (obj == null)throw new NullPointerException(message);return obj;}// 与第二种一样,区别是对自定义的提示做了一个判断,如果自定义的信息为null 则报错时就是默认的格式(和第一种一样,有点鸡肋了)public static < T > T requireNonNull(T obj, Supplier < String > messageSupplier) {if (obj == null)throw new NullPointerException(messageSupplier == null ?null : messageSupplier.get());return obj;}

requireNonNullElse

源码

// 对传进来的数据进行判断,返回第一个非空值(obj)否则返回第二个非空值(defaultObj)
// 可以看到里面调用了requireNoNull,若第二个也为空则报错,在错误后面提示defualtObj;
public static < T > T requireNonNullElse(T obj, T defaultObj) {return (obj != null) ? obj : requireNonNull(defaultObj, "defaultObj");
}

用法与requireNonNull相似

String str = null;
System.out.println(Objects.requireNonNullElse(str, "我不是null")); // 我不是null

requireNonNullElseGet

源码:

public static < T > T requireNonNullElseGet(T obj, Supplier <? extends T > supplier) {return (obj != null) ? obj : requireNonNull(requireNonNull(supplier, "supplier").get(), "supplier.get()");
}

在调用时传入一个对象,和一个Supplier的实现类;如果传入的对象是null则调用Supplier的get方法,非空则调用其toString方法
尖括号里的类型要与传入对象一样

String s = null;
String tips = "这是空的";System.out.println(Objects.requireNonNullElseGet(s, new Supplier <String> () {@Overridepublic String get() {// TODO Auto-generated method stubreturn tips;}
}));

Objects - 判断两个对象是否相等

有两个equals方法,一个是equals,另一个是deepEquals,这两个都可以比较传入的任意类型的数据是否相等,返回值均为boolean类型

Objects中的两个equals方法有一个特别的地方,就是它可以比较值为null的两个对象其他的equals方法在调用者的值为null时就直接报错了

equals

首先Objects中的equals源码为:
Objects中的equals首先就判断传进来的参数是否同一个对象;不是同一个对象判断第一个参数是否为null,若不为空则调用该参数的equals方法去比较。

public static boolean equals(Object a, Object b) {return (a == b) || (a != null && a.equals(b));
}

举例:

String s1 = null;
String s2 = null;
boolean result = Objects.equals(s1, s2);
System.out.println(result);
System.out.println(s1.equals(s2));结果为:true
Exception in thread "main" java.lang.NullPointerExceptionat test.ObjectsTest.main(ObjectsTest.java:12)

再看一个例子:

String[] s1 = {"q", "w", "e"};
String[] s2 = {"q", "w", "e"};
String[] s3 = {"a", "s", "d"};
System.out.println(Objects.equals(s1, s2));//false
System.out.println(Objects.equals(s1, s3));//false

因为数组是属于引用类型的变量,在进行比较时比较的是其内存地址,s1和s2虽然值一样,但是
内存地址不一样,所以为false

deepEquals

deepEquals字面意思就是比equlas进行更深度的比较,还是上面的例子,只是将equals换成了deepEquals

String[] s1 = {"q", "w", "e"};
String[] s2 = {"q", "w", "e"};
String[] s3 = {"a", "s", "d"};
System.out.println(Objects.deepEquals(s1, s2));//true
System.out.println(Objects.deepEquals(s1, s3));//false

源码:

public static boolean deepEquals(Object a, Object b) {if (a == b)return true;else if (a == null || b == null)return false;else//根据传入的类型进行比较,如果是数组则会一个个进行比较return Arrays.deepEquals0(a, b);
}
  1. 这两种方法的区别就在于deepEquals是比较传入对象的值完全一致才会返回ture
  2. 所以在比较类类型,数组等引用类型时用deepEquals比较好,当然也可以选择
  3. 但其实重写equalse方法会更符合要求

Arrays的相关源码可看下一篇文章

Optional

Optional<T> 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念,并且可以避免空指针异常。主要思想其实是为了告知调用者,这个方法可能会返回一个空值,需要进行判断。

在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException,如下面这段代码

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

如果要确保上面的代码不触发异常,就得在访问每一个值之前对其进行明确地检查:

if (user != null) {Address address = user.getAddress();if (address != null) {Country country = address.getCountry();if (country != null) {String isocode = country.getIsocode();if (isocode != null) {isocode = isocode.toUpperCase();}}}
}

上面这段代码显得很冗长,难以维护。为了简化这个过程,我们来看看用 Optional 类是怎么做的

创建 Optional 实例

重申一下,这个类型的对象可能包含值,也可能为空。可以使用同名方法创建一个空的 Optional。

public void test1() {//Optional.empty()返回的是一个Optional类的常量Optional<?> EMPTY = new Optional<>();Optional<User> emptyOpt = Optional.empty();emptyOpt.get();
}

毫不奇怪,emptyOpt不会为null,但是如果尝试访问 emptyOpt 变量的值会导致 NoSuchElementException。

可以使用 of() 创建包含值的 Optional

public void test2() {Optional<User> opt = Optional.of(user);
}

源码如下:可以看到如果传入了null,则of() 方法会抛出 NullPointerException:

public static < T > Optional < T > of(T value) {return new Optional < > (value);
}private Optional(T value) {//调用了Objects.requireNonNull方法,this.value = Objects.requireNonNull(value);
}

看到这里,好像并没有完全摆脱 NullPointerException?也就是说,在明确对象不为 null 的时候才能使用 of()。

也就是说,如果对象即可能是 null 也可能是非 null,就应该使用 ofNullable() 方法:

Optional<User> opt = Optional.ofNullable(user);

源码:

public static < T > Optional < T > ofNullable(T value) {//不为null才调用 of 方法,否则返回Optional类的常量Optional<?> EMPTY = new Optional<>();return value == null ? empty() : of(value);
}

访问 Optional 对象的值

get() 方法

从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法:

public void test3(){String name = "seven";Optional<String> opt = Optional.ofNullable(name);assertEquals("seven", opt.get());
}

但是这个方法会在值为 null 的时候抛出 NoSuchElementException 异常。要避免异常,可以选择首先验证是否有值:

public void test4() {User user = new User("seven97@qq.com", "1234");Optional<User> opt = Optional.ofNullable(user);assertTrue(opt.isPresent());assertEquals(user.getEmail(), opt.get().getEmail());
}

isPresent()方法很简单,就是检查下 opt.value 是否为 null

检查是否有值的另一个选择是 ifPresent() 方法。

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

该方法除了执行检查,还接受一个Consumer 参数,如果对象不是空的,就执行传入的 Lambda 表达式

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

这个例子中,只有 user 用户不为 null 的时候才会执行断言。

orElse()

见名知意,这个方法表示 如果有值则返回该值,否则返回传递给它的参数值:

public void test5() {User user = null;User user2 = new User("seven97@qq.com", "1234");User result = Optional.ofNullable(user).orElse(user2);assertEquals(user2.getEmail(), result.getEmail());
}

这里 user 对象是空的,所以返回了作为默认值的 user2。如果对象的初始值不是 null,那么默认值会被忽略

源码:

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

orElseGet()

这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier函数式接口,并返回其执行结果:

User result = Optional.ofNullable(user).orElseGet( () -> user2);

源码:

public T orElseGet(Supplier <? extends T > supplier) {return value != null ? value : supplier.get();
}

orElse() 和 orElseGet() 的区别

乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。创建一些示例来突出二者行为上的异同。

先来看看对象为空时他们的行为:

public void test6() {User user = nulllogger.debug("Using orElse");User result = Optional.ofNullable(user).orElse(createNewUser());logger.debug("Using orElseGet");User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}private User createNewUser() {logger.debug("Creating New User");return new User("seven97@qq.com", "1234");
}

上面的代码中,两种方法都调用了 createNewUser() 方法,会记录一个消息并返回 User 对象。代码输出如下:

Using orElse
Creating New User
Using orElseGet
Creating New User

由此可见,当对象为空而返回默认对象时,行为并无差异。

接下来看一个类似的示例,但这里 Optional 不为空:

public void test7() {User user = new User("seven97@qq.com", "1234");logger.info("Using orElse");User result = Optional.ofNullable(user).orElse(createNewUser());logger.info("Using orElseGet");User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User 对象。

这是因为使用Supplier能够做到 惰性计算,即 使用orElseGet时,只有在需要的时候才会计算结果。具体到我们的场景,使用orElse的时候,每次它都会执行计算结果的过程,而对于orElseGet,只有Optional中的值为空时,它才会计算备选结果。这样做的好处是可以避免提前计算结果的风险。

显然,在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。

往期推荐

  • 《SpringBoot》EasyExcel实现百万数据的导入导出
  • 《SpringBoot》史上最全SpringBoot相关注解介绍
  • Spring框架IoC核心详解
  • 万字长文带你窥探Spring中所有的扩展点
  • 如何实现一个通用的接口限流、防重、防抖机制
  • 万字长文带你深入Redis底层数据结构
  • volatile关键字最全原理剖析

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

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

相关文章

UML之泛化用例

UML用例可以泛化,泛化可简化模型、避免重复、易于扩展。通过抽象用例实现复用和模块化。讨论参与者及用例之间的泛化关系,指出不使用泛化可能导致模型复杂和重复工作的问题。在UML中,参与者和用例都可以被泛化或特化,它们在泛化或特化时遵循面向对象中泛化与特化的特性。 用…

01. Linux系统编程入门

入门系统编程,首先理解一下基本的系统调用和库函数的区别 一切皆文件的思想,都是通过文件描述符来进行操作 strace命令文件读写系统调用 #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int main (void) {in…

Macbook pro 打开pgAmin报错

当我们安装完postgresql,打开自带的pgAdmin时会报如下错误,这时候我需要去单独下载一个版本pgAdmin重新安装 下载地址:https://www.pgadmin.org/download/pgadmin-4-macos/

读DAMA数据管理知识体系指南23数据集成概念(上)

读DAMA数据管理知识体系指南23数据集成概念(上)1. 数据集成和互操作 1.1. 数据集成和互操作(DII)描述了数据在不同数据存储、应用程序和组织这三者内部和之间进行移动和整合的相关过程 1.2. 数据集成是将数据整合成物理的或虚拟的一致格式 1.3. 数据互操作是多个系统之间进行…

02. log WriteBatch 的结构和编码

在这样的情况之下,我就想来捋一下,这个代码的逻辑 首先从不同的模块说起吧include/leveldb : 这里面存储了要暴露给外部的API,这里面的结构,从使用者来说会比较熟悉,就是通过这里面的结构,实现它的功能,对不同的组件会有一个直观的定义 db : 这里面是对应的实现的类,不…

01. 非阻塞的Skiplist

首先学习LevelDB当中比较独立的一部分,当然的,读源码的话,一个很好的入门的感觉就是先从一个独立的组件模块开始,一个比较容易的开始,SkipList 然后跳表的基本概念什么的我不太想要去过多的赘述,就像二叉树那样希望能得到log(N)的性能,而又利用概率算法更好实现,可以看…

ROCm技术小结与回顾(下)

示例3–V_MFMA_F64_4x4x4F64 考虑V_MFMA_F64_4x4x4F64指令,它计算大小为44的四个独立矩阵块的MFMA。执行的操作是 ,其中 , , 和 都是大小为44元素的矩阵,N=0,1,2,3。下面的两张图显示了 1)输入参数A和B的四个分量的大小和形状,如图4-18所示。 2)分量映射到波阵面所拥有…

ROCm技术小结与回顾(上)

ROCm技术小结与回顾 在这一部分中,首先检查了Kernel 5在各种AMD GPU和问题大小上的性能,并注意到当网格超过一定大小阈值时,性能似乎会急剧下降。通过实验确定,LLC的大小是大型xy平面问题性能的限制因素。提出了两种不同的解决方法来规避缓存大小的问题,这两种方法都只需要…

有限差分法——拉普拉斯第4部分

有限差分法——拉普拉斯第4部分 提出了拉普拉斯算子有限差分法的HIP实现,并应用了四种不同的优化。在这些代码修改过程中,观察到由于全局内存的总取数减少,性能得到了逐步提高。然后,应用了进一步的优化,以在512512512上达到预期的性能目标MI250X GPU的单个GCD上的512个点…

推荐几本书1《AI芯片开发核心技术详解》、2《智能汽车传感器:原理设计应用》、3《TVM编译器原理与实践》、4《LLVM编译器原理与实践》,谢谢

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

WebKit Inside: CSS 的匹配原理

WebKit Inside: CSS 的匹配原理相关文章WebKit Inside: CSS 样式表的解析 WebKit Inside: CSS 样式表的匹配时机 WebKit Inside: Acitvie 样式表 当WebView解析完所有外部与内联样式表,就要进入到CSS样式表的匹配阶段。 1 相关类图 WebKit中参与CSS样式表匹配的主要类如下图所…

助记词-公私钥-子私钥派生-钱包地址原理及实现

0x01.简介 现在各种DEX、钱包插件中的钱包导入及创建,大部分是通过助记词来备份的; 助记词是明文私钥的一种表现形式,最早由BIP39提出,为了帮助用户记住复杂的私钥; 一组助记词可以生成各个链上的公私钥,进而可以算出钱包地址;掌握了助记词,就代表掌握了该组助记词上的…