Object类
# Java Object 类
Java Object
类是所有类的父类,也就是说 Java
的所有类都继承了 Object
,子类可以使用 Object 的所有方法。
如果在类的声明中未使用extends
关键字指明其父类,则默认父类为java.lang.Object
类,如果使用了extends
关键字指明了父类,那么也算是间接继承Object
。
如何证明?
package ObjectDemo;
import com.sun.xml.internal.ws.wsdl.writer.document.Types;
public class ObjectTest {
public static void main(String[] args) {
Test test = new Test();
System.out.println("Test的父类是:" + test.getClass().getSuperclass());
TestB testB = new TestB();
System.out.println("TestB的父类的父类是:"+testB.getClass().getSuperclass().getSuperclass());
}
}
class TestB extends Test{
}
class Test{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Test的父类是:class java.lang.Object
TestB的父类的父类是:class java.lang.Object
2
# 常用方法
NO. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object() | 构造 | 构造器 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得Hash 码 |
4 | public String toString() | 普通 | 转换为字符串,对象打印时调用 |
Object
类只声明了一个空参的构造器。
# finalize()
方法
Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
当GC垃圾回收器发现没有任何引用指向对象时,垃圾收集器就会调用当前的finalize
方法。
Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. The general contract of finalize is that it is invoked if and when the Java™ virtual machine has determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, except as a result of an action taken by the finalization of some other object or class which is ready to be finalized. The finalize method may take any action, including making this object available again to other threads; the usual purpose of finalize, however, is to perform cleanup actions before the object is irrevocably discarded. For example, the finalize method for an object that represents an input/output connection might perform explicit I/O transactions to break the connection before the object is permanently discarded.
The finalize method of class Object performs no special action; it simply returns normally. Subclasses of Object may override this definition.
The Java programming language does not guarantee which thread will invoke the finalize method for any given object. It is guaranteed, however, that the thread that invokes finalize will not be holding any user-visible synchronization locks when finalize is invoked. If an uncaught exception is thrown by the finalize method, the exception is ignored and finalization of that object terminates.
After the finalize method has been invoked for an object, no further action is taken until the Java virtual machine has again determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, including possible actions by other objects or classes which are ready to be finalized, at which point the object may be discarded.
The finalize method is never invoked more than once by a Java virtual machine for any given object.
Any exception thrown by the finalize method causes the finalization of this object to be halted, but is otherwise ignored
google翻译:
当垃圾回收确定不再有对该对象的引用时,由垃圾回收器在对象上调用。子类覆盖finalize方法以处置系统资源或执行其他清除。 finalize的一般约定是,当Java™虚拟机确定不再有任何手段可以使尚未死亡的任何线程可以访问该对象时(除非是由于操作而导致),调用finalize。由完成的其他某些对象或类的完成确定。 finalize方法可以采取任何措施,包括使该对象可再次用于其他线程。但是,finalize的通常目的是在清除对象之前将其清除。例如,代表输入/输出连接的对象的finalize方法可能会执行显式I / O事务,以在永久丢弃该对象之前中断连接。
Object类的finalize方法不执行任何特殊操作;它只是正常返回。 Object的子类可以覆盖此定义。
Java编程语言不能保证哪个线程将为任何给定对象调用finalize方法。但是,可以保证,在调用finalize时,调用finalize的线程将不持有任何用户可见的同步锁。如果finalize方法抛出未捕获的异常,则该异常将被忽略,并且该对象的终结将终止。
在为对象调用finalize方法之后,直到Java虚拟机再次确定不再有任何方法可以由尚未死亡的任何线程访问该对象之后,才采取进一步的措施,包括可能的措施可以通过其他准备完成的对象或类来完成,此时可以丢弃该对象。
对于任何给定的对象,Java虚拟机都不会多次调用finalize方法。
由finalize方法引发的任何异常都将导致该对象的终止终止,但否则将被忽略。
垃圾回收机制关键点
垃圾回收机制只回收JVM堆内存里的对象空间。对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力。
现在的JVM有多种垃圾回收实现算法,表现各异。
垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
可以将对象的引用变量设置为null
,暗示垃圾回收机制可以回收该对象。
程序员可以通过System.gc()
或者Runtime.getRuntime().gc()
来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。(你家煤气坏了打电话通知物业来修,物业也不一定能来👿)
垃圾回收机制回收任何对象之前,总会先调用它的finalize
方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活对象)。
永远不要主动调用某个对象的finalize
方法,应该交给垃圾回收机制调用。
有兴趣可以去买本书或者自己钻研JVM原理。
现在简单测试一下
package JVMTest;
import java.lang.ref.PhantomReference;
import java.lang.ref.WeakReference;
public class JvmDemo {
public static void main(String[] args) {
Person person = new Person("Saul", 18);
System.out.println(person);
//指向空指针
person = null;
//强制回收
System.gc();
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected void finalize() throws Throwable {
System.out.println("对象被释放:" + this);
}
@Override
public String toString() {
return "Person [name=" + this.name + ",age=" + this.age + "]";
}
}
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
Person [name=Saul,age=18]
对象被释放:Person [name=Saul,age=18]
2
发现当我调用System.gc()
强制回收之前,会自动调用finalize
方法。
有一个面试题,
final
、finally
、finalize
这三个有什么区别?
前面2个是关键字 ,而finalize
会垃圾回收机制回收任何对象之前,自动调用。
# ==
和equals
在讲equals
之前先来复习一下==
。
# ==
可以使用再基本数据类型变量中和引用数据类型变量中。
基本类型比较值:只要两个变量的值相等,即为
true
。引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,实际上比较的两个对象的地址值,如果相同才返回
true
- 用“
==
”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错。
- 用“
==
基本类型比较值
int j = 10;
double k = 10.0;
System.out.println(j == k)//true
2
3
因为j
将会自动提升为double
类型,然后再跟k
比较,自然返回true
。
同理,以下也是相等的:
int i = 10;
char c = 10;
System.out.println(i == c)//true
2
3
char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2)//true
2
3
因为==
比较的是基本数据类型,只要两个变量的值相等,就返回true
。
==
引用类型比较引用
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//省略setter和getter
}
2
3
4
5
6
7
8
9
10
11
12
13
Person p1 = new Person("张三",18);
Person p2 = new Person("张三",18);
System.out.println(p1 == p2);//false
2
3
==
在比较引用数据类型时,只有两个对象的内存地址值相等,才会返回true
。
对象也是一种引用数据类型,而每次new
一个实例,都会在堆内存中开辟新的空间,来存放对象,显然这两个对象都是不一样的内存地址值,所以返回false
。
# equlas
所有类都继承了Object
,也就获得了equals()
方法。还可以重写。
- 只能比较引用类型,其作用与“
==
”相同,比较是否指向同一个对象。 - 格式:
obj1.equals(obj2)
基本数据类型不是对象,所以只适用于引用数据类型。
用刚才的例子来比较:
Person p1 = new Person("张三",18);
Person p2 = new Person("张三",18);
System.out.println(p1.equals(p2);//false
2
3
我们来看看Object
类中的源码:
public boolean equals(Object obj) {
return (this == obj);
}
2
3
equlas
方法也是比较内存值是否相同。
# 重写equals
方法
既然是继承关系,那么子类肯定可以重写继承的方法,java也有一些类已经重写了equals
方法:String
,Date
,File
java.lang.String
String str1 = new String("ABC");
String str2 = new String("ABC");
System.out.println(str1.equals(str2)); // true
String str3 = "ABC";
String str4 = "ABC";
System.out.println(str3.equals(str4));// true
String str5 = "ABC";
String str6 = new String("ABC");
System.out.println(str5.equals(str6));// true
2
3
4
5
6
7
8
9
10
11
我们来看看String
类的源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
发现它一开始直接比较地址值,如果引用的同一个内存地址,直接返回true
。
内存地址不相同,就判断是否String
类型,如果不相同,直接返回false
,
如果相同类型,就类型强制转换,大家都是String
,这时就去比较长度,长度相同再接下去比较,如果不相同直接返回false
。
如果长度一样,就取出一个个字符去比较,如果都相同,才返回true
,否则返回false
。
很快你会发现String
类是特殊的,它不用==
也能返回true
:
String str3 = "ABC";
String str4 = "ABC";
System.out.println(str3 == str4);//true
2
3
那是因为它们都是将引用指向ABC
的地址。String
一般存放在常量池当中,常量池有一个特点,当你创建一个引用,如果已经有这个值,那么就指向这个内存地址,它会自己复用。
当然,要是你已经明确是new
出来的,肯定不不是同一个地址
String str1 = new String("ABC");
String str2 = new String("ABC");
System.out.println(str1 == str2);//false,引用数据类型,比较引用地址
System.out.println(str1.equals(str2));//true,equlas已经被重写,比较具体值
2
3
4
因为每次通过new
来实例化对象,肯定是在堆内存中开辟新的空间,自然内存的地址值肯定不一样。
String
类具体分析,我们后面的章节再讲,这里我们只需要知道String
重写了equals
方法,只要值相等,就返回true
。
java.util.Date
Date date1 = new Date(1235235L);
Date date2 = new Date(1235235L);
System.out.println(date1.equals(date2));//true
2
3
来看看源码:
public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
2
3
也是比较具体值。
在我们实际开发中,自己自定义的类,我肯定也是想要比较具体值是否相同。
如果有需要,也可以重写equals
方法:
@Override
public boolean equals(Object o) {
//比较是否引用同一个内存地址
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
//最后才比较具体值,如果age和name都相同,才返回true
return this.age == person.age && Objects.equals(this.name, person.name);
}
2
3
4
5
6
7
8
9
10
11
12
13
也可以参考String
的类型比较。改完了测试一下
Person p1 = new Person("张三", 18);
Person p2 = new Person("张三", 18);
System.out.println(p1.equals(p2));
2
3
现在已经返回true
了。
真正开发当中,一般使用IDE开发集成工具自动生成重写的equals
方法。但是还是建议学会手写,不仅是面试还是将来集合中能够学得更好。
# 小结
equals()
是一个方法,而非运算符- 是适用于引用数据额理性
- 像
String
、Date
、File
、包装类等都重写了Object
类中的equals()
方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的具体值是否相同。 - 通常情况下,我们自定义的类如果使用
equals()
的话,也是希望比较两个对象的具体值是否相同。那么就需要在这个自定义的类对Object
类中的equals()
方法重写。重写的规则一般是每个字段逐一比较。
# 面试题
==和equals的区别
- == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
- equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
- 具体要看自定义类里有没有重写Object的equals方法来判断。
- 通常情况下,重写equals方法,会比较类中的每个属性是否都相等,如果那个属性还是引用数据类型,还要用
equals
来判断。
# 重写equals()
方法的原则
- 对称性:如果
x.equals(y)
返回是“true
”,那么y.equals(x)
也应该返回是“true
”。 - 自反性:
x.equals(x)
必须返回是“true
”。 - 传递性:如果
x.equals(y)
返回是“true
”,而且y.equals(z)
返回是“true
”,那么z.equals(x)
也应该返回是“true”。 - 一致性:如果
x.equals(y)
返回是“true
”,只要x和y内容一直不变,不管你重复x.equals(y)
多少次,返回都是“true
”。 - 任何情况下,
x.equals(null)
,永远返回是“false
”;x.equals(和x不同类型的对象)
永远返回是“false
”。
# toString()
方法
Object类中的toString()
一般配合System.out.println()
使用。
toString()
方法在Object
类中定义,其返回值是String
类型,返回类名和它的引用地址。
我们用回刚才的Person
类:
Person p = new Person("张三",18);
System.out.println(p);
System.out.println(p.toString());
2
3
你会发现它们输出的结果是一样的,为什么?来看看源码:
首先看System.out.println
:
public void print(Object obj) {
write(String.valueOf(obj));
}
2
3
发现它用了valueOf
方法,再去看看这个方法具体做了什么:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
2
3
我们可以看到,当它输出引用数据类型时,会调用对象的toString()
方法,
那么我们来看看Object
类的toString()
方法:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
2
3
返回类名和它的引用地址。
这个引用地址其实是在堆空间的位置转换为16进制。
那么引用地址是真实内存地址吗?
不是,我们都知道Java是在JVM上运行的,所以它本质上是JVM中的虚拟地址,并不是计算机中的真实内存地址。
像
String
、Date
、File
、包装类
等都重写了Object
类中的toString()
方法。使得在调用对象的toString()
方法时,返回对象的实体内容信息。
平时输出String
类等,为什么没有输出类名+引用地址?因为已经被toStirng()
方法重写了。
String str = new String("ABC");
System.out.println(str);//ABC
2
源码:
String的源码:
public String toString() {
return this;
}
2
3
Date的源码:
public String toString() {
// "EEE MMM dd HH:mm:ss zzz yyyy";
BaseCalendar.Date date = normalize();
StringBuilder sb = new StringBuilder(28);
int index = date.getDayOfWeek();
if (index == BaseCalendar.SUNDAY) {
index = 8;
}
convertToAbbr(sb, wtb[index]).append(' '); // EEE
convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM
CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd
CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH
CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm
CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss
TimeZone zi = date.getZone();
if (zi != null) {
sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz
} else {
sb.append("GMT");
}
sb.append(' ').append(date.getYear()); // yyyy
return sb.toString();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
自定义类也可以重写
toStirng()
方法,当调用此方法时,返回对象的"实体内容"。
既然别人可以重写,那么我们也可以重写,现在重写Person
类的toString()
方法:
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
2
3
4
5
6
7
跟equals
一样,也可以用ide工具自动生成,简单快捷,而且其实也没什么技术含量,还是用ide工具自动生成吧。
重写后,你下次输出这个对象时,就能知道具体值信息了。
当然如果自定义类有字段也是引用数据类型,那么还是要手动调用一下它的toStirng
方法。
# 小结
- 当我们输出一个对象的引用时,实际上就是调用当前对象的
toString()
方法 Object
类中toString()
的定义:类名+在堆空间的位置转换为16进制(内存地址),本质上JVM中的虚拟地址,并不是计算机中的真实内存地址。- 像
String
、Date
、File
、包装类
等都重写了Object
类中的toString()
方法。使得在调用对象的toString()
方法时,返回对象的实体内容信息。 - 自定义类也可以重写
toStirng()
方法,当调用此方法时,返回对象的"实体内容"。
# 面试题
以下输出结果是?
public void test1() {
char[] arr1 = new char[]{'a', 'b', 'c', 'd'};
System.out.println(arr1);
int[] arr2 = new int[]{1, 2, 3, 4};
System.out.println(arr2);
double[] arr3 = new double[]{1.1, 2.2, 3.3, 4.4,5.6};
System.out.println(arr3);
}
2
3
4
5
6
7
8
9
10
abcd
[I@37d31475
[D@27808f31
2
3
因为System.out.println
输出char数组时,是逐一输出,其他2个是直接输出是调用它的toString
方法,而它们又没有重写toString
方法,自然是输出类名+引用地址。
char数组
先来看源码:
public void println(char x[]) {
synchronized (this) {
print(x);
newLine();
}
}
2
3
4
5
6
public void print(char s[]) {
write(s);
}
2
3
所以这char数组是直接输出具体值。
int数组
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
2
3
4
5
6
7
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
2
3
所以int数组直接输出是类名+引用地址。
double数组跟int数组一样。
既然都看了源码我们把8个基本数据类型的数组的输出方式都看了吧,以后面试就知道该怎么回答了。
数据类型 | 直接输出结果 |
---|---|
byte[] | 类名+引用地址 |
short[] | 类名+引用地址 |
int[] | 类名+引用地址 |
long[] | 类名+引用地址 |
float[] | 类名+引用地址 |
double[] | 类名+引用地址 |
char[] | 具体值 |
boolean[] | 类名+引用地址 |
这么看来,除了char数组,其他数据类型直接输出都是类名+引用地址,以后就知道怎么回答了。
那么现在我问你,下面结果输出是什么?
char[] arr1 = new char[]{'a', 'b', 'c', 'd'};
System.out.println("arr1 = "+ arr1);
2
arr1 = [C@37d31475
为什么?因为字符串拼接成这样,arr1这个变量指向的是内存地址,如果字符串拼接,肯定是和内存地址拼接起来。所以就是输出这个结果。
所以记住了,在直接输出的情况下,除了char数组,其他数据类型都是类名+引用地址。
直接输出!!
直接输出!!
直接输出!!
重要的事情说三遍
如果是字符串拼接数组,自然是类名+内存地址。
当然String还有一个concat方法,这个具体后面再讲吧。