Java源码解读(一):Objects

Java源码解读(一):Objects

其实阅读Java源码本身并没有那么难,只要我们踏出第一步,后面反而比我们想象的要简单。可能很多Java初学者并不知道从哪里下手,所以本篇博客将挑选一个源码较为简单的工具类—— java.util.Objects 作为切入点,走入Java源码的世界。

相关说明

  • IDE: IDEA
  • Java Version: 1.8.0_161

Objects

Objects 位于 java.util 包下,看名称就能知道是一个工具类,除去私有的构造方法外,只有12个静态方法供我们使用,为了方便阅读,这里我已经去掉注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package java.util;

import java.util.function.Supplier;


public final class Objects {
private Objects() {
throw new AssertionError("No java.util.Objects instances for you!");
}


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


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


public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}


public static int hash(Object... values) {
return Arrays.hashCode(values);
}


public static String toString(Object o) {
return String.valueOf(o);
}


public static String toString(Object o, String nullDefault) {
return (o != null) ? o.toString() : nullDefault;
}


public static <T> int compare(T a, T b, Comparator<? super T> c) {
return (a == b) ? 0 : c.compare(a, b);
}


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


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


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


public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null)
throw new NullPointerException(messageSupplier.get());
return obj;
}
}

看上去很熟悉,并不会很陌生对吧,其实阅读源码也没有想象的那么难。

构造方法

这个工具类首先引入眼帘的便是其私有的构造方法:

1
2
3
private Objects() {
throw new AssertionError("No java.util.Objects instances for you!");
}

如果你有阅读上面的源码就会发现,Objects 提供的方法除此外皆为静态方法,当我们使用这些方法的时候无需显示实例化对象,而是直接通过 类名.方法名() 来调用其提供的方法,而私有其构造方法便是以防我们无意义创建对象浪费空间,所以这里抛出一个断言异常—— "No java.util.Objects instances for you!"

说到构造方法私有化想必大家有点耳熟,没错,我们在学习单例模式的时候便将对象的构造方法私有化,不允许用户去 new 该对象,而是通过我们提供的方法去获取已经创建好的实例。

equals

接下来我们正式开始介绍 Objects 最为常用的方法 equals

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

这也很简单对吧,我们也写过这样的代码,判断两个对象是否相等,代码就一行,翻译一下就是判断参数列表中的两个对象是否为同一个对象(引用是否指向同一内存地址),或者在参数 a 不为 null 的情况下,调用 aequals 方法,而a的 equals 方法要看其具体的实现,如果是Object(所有对象的父类),则:

1
2
3
4
public boolean equals(Object obj) {
// 判断是否为同一对象,即引用是否指向同一内存地址
return (this == obj);
}

equals of String

但这不能满足我们日常使用需求,实际开发过程中,假如 Studentid 相同,那么我们就可以认为 equalstrue ,所以Objectequals方法并不能满足这种情况,所以有必要的时候大家都会重写一下equals方法(重写 equals 方法的同时最好重写一下 hashCode ),这里就介绍一下Stringequals实现(为了方便阅读,我添加了些许注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public boolean equals(Object anObject) {
if (this == anObject) {
// 判断是否为同一对象
return true;
}
if (anObject instanceof String) {
// 如果anObject是String的实例(通俗来讲就是String是否为String类型)
// 如果是,则强转为String类型(通过instanceof判断避免类型转化异常)
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
// 其实阅读过String源码我们就明白这里的用途(String底层使用的是char[]数组)
// 1、判断底层数组的长度,如果不想等说明两个字符肯定不一样,就无需继续判断
// 2、如果是,一个字符一个字符去判断是否相等
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
// 从最后一个字符开始对比
if (v1[i] != v2[i])
// 如果同一位置的字符不相等,则字符串不相等
return false;
i++;
}
return true;
}
}
return false;
}

可能大家对最后一段 while 那里有点疑惑,其实我们完全可以用 for 来替代 while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int n = value.length;
...
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}

// 上面while的写法可以用for改写
for (int i = 0; i < value.length; i++) {
if (v1[i] != v2[i]) {
return false;
}
}

其实第一次看到 Stringequals 方法就差点笑出声,感觉这个作者写的小心翼翼(提高性能),判断字符串是否相等经历如下步骤:

  1. 判断两个对象是否相等,对象都想相等了,我们还用判断吗?
  2. 什么?对象不相等,那么只能遍历 char[] 一个个字符去判断了。。。不对!如果数组长度不一样那肯定也不相等,所以我们在循环前线判断一下字符串底层数组的长度吧。
  3. oops!连底层数组长度都一样,这下没办法了,老老实实去一个个判断吧,还好只要判断相同位置上的字符是否相等就行,一个 for 循环搞定!

说好的阅读 Objects 源码,去花费大量篇章去介绍 Stringequsla 方法了,赶紧回到正题。

其实Objects.equals() 实现还是通过调用其 参数的自身实现,不过在这之前贴心的帮我们做了非空判断以及一些优化—— a == b ,没错,就是这个小小的 a == b ,为什么说是优化呢?首先是避免空指针异常,其次是通过String的equals方法我们知道,判断两个字符串是否相等的原理是一个一个去对比其底层char数组里面的元素,但是如果a b 是同一对象,我们判断其内存地址便可,比对底层数组里面的元素未免显得有些浪费性能了。

其他

篇幅有限,其他方法无法一个个扩展开来去讲,就稍微介绍一下吧:

  1. deepEquals 是通过Arrays.deepEquals0() 来支持两个数组是否相等(顺序),如果对象不是数组,则返回false;
  2. hashCode 调用了对象自身的 hashCode 实现
  3. hash 调用了Arrays.hashCode 的实现,来计算数组的 hash
  4. toString 是调用了 String.valueOf 方法,这点和Object 的实现不一样(ObjectgetClass().getName() + "@" + Integer.toHexString(hashCode())
  5. oString(Object o, String nullDefault) 如果对象本身不为空则调用其toString方法,为空就使用其默认值nullDefault
  6. compare(T a, T b, Comparator<? super T> c) 方法,如果我们了解过Java8的新特性就会知道这么一个词——行为参数话,就拿Student 对象来举例,compare两个Student,我们可以compare的属性有很多,比如年龄,身高,成绩等等。这样就导致一个compare方法我们可能要重载多次,但是通过行为参数化,我们将比较的这个行为(比较年龄?比较身高?)当作参数传入,就只用写一个compare方法,比如这里面的最后一个参数——Comparator<? super T> c,这是一个函数式接口,是我们进行对象比较的具体行为,可以参考我之前写的 行为参数化 这篇文章
  7. 以及一些针对null封装的一些方法,自行阅读即可。

Java的源码解读是我很早就想做的一件事,但由于自身水平所限,写出来的文章达不到我理想的效果,请多见谅。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×