之前我们了解到Object类是所有类的基类,其中包含了很多方法,现在我们去一一了解一下。
在JDK文档中找到Object类,看看它有哪些方法
这里我们主要看一些常用的就行。比如:equals()、finalize()、hashcode()、toString()等。
1. equals方法
1.1 == 和equals的对比 [面试题]
== 是一个比较运算符
- ==:既可以判断基本类型,也可以判断引用类型
- ==:如果判断基本类型,判断的是值是否相等。
例如:int i = 10; double d = 10.0 System.out.println( i == d);- ==:如果判断的是引用类型,判断的是地址是否相同,即判定是不是同一个对象。(如下代码)注意区分对象引用和对象实例的概念!
public class Equals01 {
public static void main(String[] args) {
A a = new A();
A b = a;
A c = b;
System.out.println(a == c); // ture
System.out.println(b == c); // ture
// 判断不同类型的引用
B obj = a; //A类向B类的向上转型
System.out.println(obj == c); //true
}
}
class A extends B{}
class B{}
4. equals:是Object类中的方法,只能判断引用类型。
5. 默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer,String,这两个类已经默认重写了equals方法。
1.2 equals方法在JDK中的源码
为了更清楚的明白equals的本质,看下equals方法是怎么写的
public boolean equals(Object anObject) {
if (this == anObject) { // 如果是同一个对象
return true; // 返回true
}
if (anObject instanceof String) { // 判断运行类型是不是相同或者其子类
String anotherString = (String)anObject; // 向下转型
int n = value.length;
if (n == anotherString.value.length) { // 传入的是字符串,如果字符串长度相同
char v1[] = value; //这里是把String 转成了char 数组
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { // 然后一个一个的比较字符
if (v1[i] != v2[i]) // 如果有一个不同
return false; // 返回flase
i++;
}
return true; // 如果两个字符串的所有字符都相同,返回true
}
}
return false; // 如果比较的不是字符串,直接返回flase
}
再看一下equals在Object类中的源码是怎么写的(对应1.1中的第4条)
public boolean equals(Object obj) {
return (this == obj);
}
如上述代码所示,Obejct类的equals方法默认就是比较对象是否相同,也就是判断两个对象是不是同一个对象。
在源码中也能看到Integer重写了Object的equals方法,变成了判断两个值是否相同。如下:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
1.3 如何重写equals方法
public class Equals01 {
public static void main(String[] args) {
Person person1 = new Person("jack", 10, '男');
Person person2 = new Person("jack", 10, '男');
System.out.println(person1.equals(person2));
}
}
class Person{
private String name;
private int age;
private char gender;
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
//重写Object的equals方法
public boolean equals(Object obj){
// 判断如果比较的两个对象是同一个对象,则直接返回true
if(this == obj){
return true;
}
// 类型判断
if (obj instanceof Person){ // 是Person类型,才进行比较
Person p = (Person)obj; //进行向下转型,因为需要得到obj的各个属性
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
// 如果不是Person,则直接返回false
return false;
}
}
2. hashCode 方法
2.1 hashCode概念
hashCode在API文档中的介绍如下图:
2.2 hsdCode的要点
- 提高具有哈希结构的容器的效率
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要是根据地址号来的。不能完全将哈希值等价于地址
- 在集合中,hashCode如果需要的话,也会进行重写操作
看一下两个对象和同一个对象的hash值是什么样的
public class HashCode {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
A a3 = a1;
System.out.println("a1.hashCode()="+ a1.hashCode());
System.out.println("a2.hashCode()="+ a2.hashCode());
System.out.println("a3.hashCode()="+ a3.hashCode());
}
}
class A{}
输出的结果如下:
需要注意的是,哈希值可以当作地址来看,但它并不是真实的地址,因为java是跑在java虚拟机上的,没有办法得到底层真正的地址。
3. toString方法
3.1 toString概念
toString在API文档中的介绍如下图:
toString在Object类中的源码
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// 其中getClass().getName()为全类名(就是包名+类名)
// @ 为间隔符
// Integer.toHexString(hashCode())是把哈希值的整数调用Integer.toHexString转成16进制的字符串
3.2 toString方法重写
3.2.1 没重写之前的调用
没重写之前调用的是Object类的toString方法,默认输出的是全类名和哈希值转换后的十六进制字符串
public class ToString {
public static void main(String[] args) {
Monster monster = new Monster("jack", "打杂", 1000);
//没重写之前调用的是Object类的toString方法,输出的是全类名和哈希值转换后的十六进制字符串
//我们把哈希值也输出,用计算器算一下是否正确
System.out.println(monster.toString() +" "+ monster.hashCode());
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
}
输出结果:
计算器转换的结果:
结果是一样的。
3.2.2 重写后的调用
重写的目的:
打印对象或拼接对象时,都会自动调用该对象的toString形式。
public class ToString {
public static void main(String[] args) {
Monster monster = new Monster("jack", "打杂", 1000);
//没重写之前调用的是Object类的toString方法,输出的是全类名和哈希值转换后的十六进制字符串
//我们把哈希值也输出,用计算器算一下是否正确
System.out.println(monster.toString() +" "+ monster.hashCode());
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
//重写toString方法,输出对象的属性
@Override
public String toString() { //一般是把对象的属性值输出,当然也可以自己定制
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
重写之后的输出结果:
当直接输出一个对象时,toString方法会被默认的调用。
换言之,System.out.println(monster.toString());和System.out.println(monster)的效果是一样的
4. finalize方法
4.1 finalize概念
finalize在JDK文档中的描述:
4.2 finalize要点
-
当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
-
什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制销毁该对象,在销毁该对象前,会先调用finalize方法
-
垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动触发垃圾回收机制。
通过一个案例来看一下:
public class Finalize {
public static void main(String[] args) {
Car Audi = new Car("奥迪");
Audi = null;
System.gc(); //主动调用垃圾回收器
//断掉引用,使Car对象变成垃圾,垃圾回收器就会销毁(回收)对象,在销毁前会调用该对象的finalize方法
//程序员可以在finalize中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件)
//如果程序员不重写finalize,那么就会调用Object类的finalize,即默认处理
//如果重写了finalize,就可以实现自己的逻辑
System.out.println("程序退出了");
}
}
class Car{
private String name;
public Car(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("销毁 汽车"+name);
System.out.println("释放了某些资源...");
}
}
这里主动调用了gc垃圾回收器,如果没有主动调用垃圾回收器输出的结果如下
我们看到我们重写的finalize方法并没有被执行,这里有个问题是不是对象变成垃圾后马上就回收了
它是有自己的一个算法,比较复杂,后续再说。所以我们主动调用了gc垃圾回收器,输出结果如下
本次分享结束,seeya!