Object类的概述(重点)
基本概念
java.lang.Object类是Java语言中类层次结构的根类,也就是说任何一个类都是该类的直接或者间
接子类。
如果定义一个Java类时没有使用extends关键字声明其父类,则其父类为 java.lang.Object 类。
Object类定义了“对象”的基本行为, 被子类默认继承。
常用的方法
案例题目:
编程实现Student类的封装,特征:学号(id)和姓名,要求提供打印所有特征的方法。
编程实现StudentTest类,在main方法中使用有参方式构造两个Student类型的对象并打印特征。
package com.lagou.task11; public class Student extends Object { private int id; // 用于描述学号的成员变量 private String name; //用于描述姓名的成员变量 public Student() { } public Student(int id, String name) { setId(id); setName(name); } public int getId() { return id; } public void setId(int id) { if(id > 0) { this.id = id; }else{ System.out.println("学号不合理哦!"); } } public String getName() { return name; } public void setName(String name) { this.name = name; } }
package com.lagou.task11; public class StudentTest { public static void main(String[] args) { //1.使用有参方式构造Student类型的两个对象并判断是否相等 Student s1 = new Student(01,"张飞"); Student s2 = new Student(02,"刘备"); // 下面调用从Object类中继承下来的equals方法,该方法默认比较两个对象的地址 Boolean b1 = s1.equals(s2); System.out.println("b1 = " + b1);
System.out.println(s2 == s1);
} }
如下图S1和S2指向不同的堆区地址,所以使用equals方法比较得到的结果是false
equals方法和==比较的结果是一样的,所以得到的结果是false
如果S2和S1都指向同一个对象,地址就相同了,得到的结果是true
题目扩展:
如何实现以姓名作为基准判断两个对象是否相等?以及以学号和姓名同时作为基准判断两个对象是
否相等?
package com.lagou.task11; public class Student extends Object { private int id; // 用于描述学号的成员变量 private String name; //用于描述姓名的成员变量 public Student() { } public Student(int id, String name) { setId(id); setName(name); } public int getId() { return id; } public void setId(int id) { if(id > 0) { this.id = id; }else{ System.out.println("学号不合理哦!"); } } public String getName() { return name; } public void setName(String name) { this.name = name; } /* * 为了比较两个对象的内容,也就是学号信息需要重写该方法 * Student this = s1 * Object obj = s2 * s2是父类类型的引用指向子类类型的对象,需要强制转换 * 前面我们讲过只要做强制类型转换的时候都要用到instanceof确认一下obj真正指向的对象是不是student */ @Override public boolean equals(Object obj) { if(obj instanceof Student) { Student ts = (Student) obj; return this.getId() == ts.getId(); } // 否则类型不一致没有可比性,则内容不一定相同 return false; } }
1、equals方法:
equals方法默认比较地址,我们改写以后的equals方法比较的是学号,而==比较的还是地址。所以最后得到的结果是true 、false。
Student s1相当于在栈区申请了一块内存,new Student相当于在堆区申请了一块内存,如下图所示:
执行我们改写的equals方法,this相当于s1,obj相当于s2,首先我们是不是又声明了一个变量叫做this,一个变量叫做obj,把S1的值赋值给this那么this指向s1的堆区,把s2的值指向obj那么obj指向s2的堆区。然后我们执行强制转换,又创建了一个临时变量TS,把obj的值给TS。
-
equals
方法在非null对象引用上实现等价关系:(为什么要在非空对象上实现?因为空引用会引发空指针异常)- 自反性 :对于任何非空的参考值
x
,x.equals(x)
应该返回true
。(自己和自己比肯定返回true,但是前提条件是非空) - 它是对称的 :对于任何非空引用值
x
和y
,x.equals(y)
应该返回true
当且仅当y.equals(x)
回报true
。(X和Y相当,那么Y和X相当) - 传递性 :对于任何非空引用值
x
,y
和z
,如果x.equals(y)
回报true
个y.equals(z)
回报true
,然后x.equals(z)
应该返回true
。(X和Y相当,Y和Z相当,那么X和Z相当) - 它是一致的 :对于任何非空引用值
x
和y
,多次调用x.equals(y)
始终返回true
或始终返回false
,前提是未修改对象上的equals
比较中使用的信息。(多次比较X和Y始终返回true或者false,前提是比较的对象没有改变) - 对于任何非空的参考值
x
,x.equals(null)
应该返回false
。(非空和空比,肯定是false)
@Override public boolean equals(Object obj) { // 当调用对象和参数对象指向同一个对象时,则内容一定相同 if(this == obj){return true;} // 当调用对象不为空而参数对象为空时,则内容一定不相同 if(obj == null){ return false;} if(obj instanceof Student) { Student ts = (Student) obj; return this.getId() == ts.getId(); } // 否则类型不一致没有可比性,则内容不一定相同 return false; } }
写道这个层面才算暂时性的写完,以后判断equals是否相等的时候首先判断是否是同一个对象,再判断obj是否为空,最后还要判断两个对象是否指向同一个类。
最后在重写equals方法以后还要去重写hashcode方法,维护hashcode方法的常规规定
2、hashcode方法:
hashcode方法:返回对象的哈希码值。哈希码值暂时把它理解成是一个内存地址的编号。
-
-
-
hashCode
的总合同是:- 只要在执行Java应用程序期间多次在同一对象上调用它,
hashCode
方法必须始终返回相同的整数,前提是不修改对象上的equals
比较中使用的信息。 从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。(它的意思是hashcode方法要和equals方法保持一个一致性) - 如果两个对象根据
equals(Object)
方法相等,则对两个对象中的每个对象调用hashCode
方法必须生成相同的整数结果。(如果两个对象比较得到的是内存地址相等,那么hashcode比较得到的内存地址是不是也就相同?) - 这不是必需的:如果两个对象根据不相等
equals(java.lang.Object)
方法,然后调用hashCode
在各两个对象的方法必须产生不同的整数结果。 但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。(如果两个对象调用equals方法不相等,那么调用hashcode得到的整数结果应该不同)
/* * 为了使得该方法的结果和equals方法的结果保持一致,从而满足java官方的常规协定,需要重写该方法 **/ @Override public int hashCode(){ //return getId(); // 耍流氓的写发,这样写就和getId区分不出来的了,可以这样写java官网只是要求要和equals的结果保持一致没有要求一定要和哈希码值保持一致。 //以下稍微耍流氓的写法,与getid的值区分开来,12和31是瞎掰的,这样写相对来说完美,12是固定死的,31也是固定死的,得到的结果也是固定死的我们得到的结果还是依赖于学号。 final int type = 12; return type*31 + getId(); };
重写hashcode方法使得他的结果与equals方法的结果保持一致,依赖于学号,这样得到的结果是固定死的。
注意:当student类中重写hashcode方法后,则调用重写以后的版本
3、toString()方法:
用于获取调用对象的字符串形式
该方法默认返回的字符串为:包名.类名@哈希码值的十六进制
为了返回更有意义的数据,需要重写该方法
使用print或println打印引用或字符串拼接引用都会自动调用该方法/* * 为了返回更有意义的字符串数据,则需要重写该方法 */ @Override public String toString() { return "Student[id = " + getId() + ", name = " + getName() + "]"; }
// 下面调用从object类中继承下来的toString()方法,获取调用对象的字符串形式:包名.类名@哈希码值的十六进制 // 实际上打印包名.类名实在 // 当student类中重写tostring方法后,则调用重写以后的版本:student[id = 01, name = 张飞] String str1 = s1.toString(); System.out.println("s1 = " + str1); System.out.println(str1); // 当打印一个引用变量时会自动调用tostring方法 System.out.println(s2); String str2 = "hello" +s2; System.out.println("str2 =" + str2); //print或者println打印或者字符串拼接引用都会自动调用tostring()方法
toString()方法返回哈希码值得十六进制,上述案例中我们可以重写toString()方法让它返回的数值更加有意义。
题目扩展:
如果实现以姓名作为基准判断两个对象是否相等?以及以学号和姓名同时作为基准判断两个对象是否相等?
package com.lagou.task11; public class Student extends Object { private int id; // 用于描述学号的成员变量 private String name; //用于描述姓名的成员变量 public Student() { } public Student(int id, String name) { setId(id); setName(name); } public int getId() { return id; } public void setId(int id) { if(id > 0) { this.id = id; }else{ System.out.println("学号不合理哦!"); } } public String getName() { return name; } public void setName(String name) { this.name = name; } /* * 为了比较两个对象的内容,也就是学号信息需要重写该方法 * Student this = s1 * Object obj = s2 * s2是父类类型的引用指向子类类型的对象,需要强制转换 * 前面我们讲过只要做强制类型转换的时候都要用到instanceof确认一下obj真正指向的对象是不是student */ @Override public boolean equals(Object obj) { // 当调用对象和参数对象指向同一个对象时,则内容一定相同 if(this == obj){return true;} // 当调用对象不为空而参数对象为空时,则内容一定不相同 if(obj == null){ return false;} if(obj instanceof Student) { Student ts = (Student) obj; // 以学号作为基准判断两个对象是否相等 int类型是基本数据类型,内存空间中放的就是数据本身,使用==可以判断数据是否相同 // return this.getId() == ts.getId(); // 以姓名作为基准判断两个对象是否相等 string是引用数据类型,内存空间中放的是内存地址,使用==判断地址是否相同 // 也就是判断两个对象中姓名字符串的地址是否相同,不够完美 // string类型是java官方提供的类,string继承Object,以为了equals、hashcode、tostring都继承了,如果string类的内部有equals方法那就意味着重写了equals方法,没有就证明没有重写equals方法 // this.getName()这个方法得到的结果最终得到的就是一个string类型的引用,所以可以使用这个引用调用string内内部的方法equals return this.getName().equals(ts.getName()); } // 否则类型不一致没有可比性,则内容不一定相同 return false; } /* * 为了使得该方法的结果和equals方法的结果保持一致,从而满足java官方的常规协定,需要重写该方法 **/ @Override public int hashCode(){ //return getId(); // 耍流氓的写发,这样写就和getId区分不出来的了,可以这样写java官网只是要求要和equals的结果保持一致没有要求一定要和哈希码值保持一致。 //以下稍微耍流氓的写法,与getid的值区分开来,12和31是瞎掰的,这样写相对来说完美,12是固定死的,31也是固定死的,得到的结果也是固定死的我们得到的结果还是依赖于学号。 final int type = 12; // return type*31 + getId(); // 如果调用getName()方法返回的是srting类型,报错因为我们需要的是int类型,因为string类型继承了object类型所以也继承了hashcode方法,所以我们可以直接使用string类型中hashcode方法调用即可 return type*31 + getName().hashCode(); } /* * 为了返回更有意义的字符串数据,则需要重写该方法 */ @Override public String toString() { return "Student[id = " + getId() + ", name = " + getName() + "]"; } }
在上述扩展题目中体现了string类型是继承object,所以只需要使用string类型内部的equals方0.
法和hashcode方法即可解决问题。
- 只要在执行Java应用程序期间多次在同一对象上调用它,
-
-
- 自反性 :对于任何非空的参考值