Object方法的秘密

为什么要有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()

唤醒在此监视器上等待的所有对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值