为什么要有Object父类
Object是所有类的父类,里面定义了13个常用的方法,为什么要让它成为所有类的父类呢,个人感觉主要包括一下两点:
1.一种规范,方便类的转换,如果不知道一个对象的类型,可以向上转型为Object
2.提供了常用和基本的方法,代码可以得到复用,比如equals,hashCode等,能够极大的减少开发工作。
Object的13个方法
1.Object()
public Object() {}
是一个默认无参构造方法。
2.hashCode()
public native int hashCode();
一个很重要的本地方法,返回散列值(通过内部地址转换而来)
3.equals()
public boolean equals(Object obj) {
return (this == obj);
}
判断两个对象是否等价,从源码可知,比较的是地址。
3.1equals和==的区别
- 对于基本数据类型,没有equals的说法,==比较的是数据的值。
- 对于引用类型,==比较的是它们的地址,equals分是否重写该方法,如果没有重写,和 == 一样比较的是他们的地址,如果重写了equals,则根据重写内容比较。
3.2 equals和hashcode重写规范
1.equals相同,hashcode必须相同。也就是重写了equals必须重写hashcode,Why?
假如一个类重写了equals,但是没有重写hashcode,我们在使用map和set类型的容器时就会出现不应该出现的情况。以hashmap为例,如果两个对象相等(equals判定为true),但是却会把这两个本该相等的对象都put进去造成错误的结果。例如:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//创建两个equals相等的对象
Student student1=new Student("Alice");
Student student2=new Student("Alice");
System.out.println(student1.equals(student2));
HashMap<Student,String> studentStringHashMap= new HashMap<>();
//把两个本该相等的对象put进去
studentStringHashMap.put(student1,"student1");
studentStringHashMap.put(student2,"student2");
Set<Map.Entry<Student, String>> entrySet = studentStringHashMap.entrySet();
Iterator<Map.Entry<Student, String>> entryIterator = entrySet.iterator();
//输出hashmap里面的对象,本应该只存在一个对象,但是却输出了两
while (entryIterator.hasNext())
{
Map.Entry<Student, String> next = entryIterator.next();
System.out.println(next.getKey().getName()+"----"+ next.getValue());
}
}
}
class Student
{
private String name;
Student(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student student = (Student) obj;
return student.name.equals(this.name);
}
return false;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
输出结果为
true
Alice----student1
Alice----student2
由上面例子可知把两个相同的对象put进去了,原因就是没有重写hashcode,我们知道map、set类型的容器,put操作判断两个对象是否相同是通过先判断对象hash值是否相同,然后判断两个对象是否等价去实现的,所以,java规范重写了equals就需要重写hashcode。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
2.equals不相同,hashcode可以相同。也就是说,可以只重写hashcode,不重写equals。同样对hashmap容器分析,如果两个对象equals判定为不相同,但是hashcode判定为相同,如上述put规则是能够put进去的,但是hashcode相同,也就意味着他们会放入同一个链上,所以会出现hash冲突的情况。所以,这种做法没有问题,但是不推荐使用。
想了解hashmap原理的可以查看这篇文章
hashMap源码分析----彻底搞透
4.toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString方法可以给开发人员提供一些类的信息,如源码可知,默认是输出类名+@+hashcode的十六进制数。
5.getClass()
public final native Class<?> getClass();
getClass返回一个运行时类,可用于反射。
6.finalize()
protected void finalize() throws Throwable { }
1.finalize是对象死亡前调用的,它是对象逃离GC的最后机会,如果在finalize方法里面,对象重新获得引用,那么这个对象不会被这次GC收集,但是需要注意,finalize方法只能被调用一次。
2.finalize方法块也不是必须执行的,比如守护线程,当没有非守护线程存活时,守护线程会直接退出而不会执行finalize方法块。
7.clone()
克隆的作用就是返回对象的副本,实现需要包括两部分
- 1.实现Cloneable空接口。
- 2.重写clone()方法,并且声明为public
克隆分为浅克隆和深克隆
- 浅克隆:返回一个对象副本,但是对象内部的引用类型成员变量还是指向相同的地址。
- 深克隆:返回一个对象副本,对象内部的引用类型成员变量指向新的对象,需要保证引用类型成员变量也是可克隆的。
总的来说,克隆并不推荐使用,因为克隆实现麻烦有风险,需要实现接口,重写方法,还需要捕获异常,我们可以直接通过拷贝构造函数去实现克隆的功能。例如:
public class Main {
public static void main(String[] args) {
SchoolBag schoolBag=new SchoolBag("schoolBag");
Student student=new Student("student",schoolBag);
Student cloneStudent= new Student(student);
System.out.println(student.equals(cloneStudent));
System.out.println(student.getSchoolBag().equals(cloneStudent.getSchoolBag()));
System.out.println(student.getName().equals(cloneStudent.getName()));
System.out.println(student.getSchoolBag().getName().equals(cloneStudent.getSchoolBag().getName()));
}
}
class Student
{
private String name;
private SchoolBag schoolBag;
public Student(String name, SchoolBag schoolBag) {
this.name = name;
this.schoolBag = schoolBag;
}
//克隆构造函数
public Student(Student student)
{
this.name=new String(student.getName());
this.schoolBag=new SchoolBag(student.getSchoolBag());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SchoolBag getSchoolBag() {
return schoolBag;
}
public void setSchoolBag(SchoolBag schoolBag) {
this.schoolBag = schoolBag;
}
}
class SchoolBag
{
private String name;
SchoolBag(String name) {
this.name = name;
}
SchoolBag(SchoolBag schoolBag)
{
this.name=new String(schoolBag.getName());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
输出为:
false
false
true
true
后两个String类型的名字输出true是因为String重写了equals方法,与拷贝构造函数无关。
8.registerNatives()
private static native void registerNatives();
static {
registerNatives();
}
用的比较少,笔者不是很了解。
9.10.11 wait()
public final void wait() throws InterruptedException {
wait(0L);
}
public final native void wait(long timeoutMillis) throws InterruptedException;
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
if (timeoutMillis < 0) {
throw new IllegalArgumentException("timeoutMillis value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
timeoutMillis++;
}
wait(timeoutMillis);
}
wait方法有3个,主要是让线程从运行状态进入等待和超时等待状态,很重要的一点是线程调用wait方法后,会释放锁,而Thread.sleep、Thread.join()、LockSupport.park()等方法是不会释放锁的。
12.notify()
唤醒在此监视器上等待的对象,选择是任意的。
13. notifyAll()
唤醒在此监视器上等待的所有对象。