一文看懂Object类的12个方法

昨天博主整理发布了在秋招面试中常被问到的20道Java基础题目,有不少小伙伴私信问我答案,其实这些问题如果自己去网上搜的话,也可以搜个七七八八。

不过网上答案良莠不齐,有一些说法并不是很准确,而且有些题目不是简简单单几句话就能概括清楚的,这里就打算开一个专栏,每天选取一两个问题详细解答一下,今天就详细说一下Object类的12个方法。

直接奉上这12个方法,下面再展开介绍:

Object类有12个成员方法,分别是clone()、equals(Object)、finalize()、getClass() 、hashCode()、notify()、notifyAll()、toString()、wait()、wait(long)、wait(long,int)。

注意,由于Object类是所有类的父类,因此这些方法很多都被根据需要重写,我们在使用中一定要注意。

由于篇幅原因,下面主要讲解常用的五个方法,与多线程相关的我会在后面专题里面详细讲解。

1. clone()方法

被用来拷贝一个新对象。在Java中使用等号只是拷贝对象的引用并不是对象,需要拷贝对象的时候,可以借助clone方法。

要通过clone方法复制某一个对象,在该类中必须实现java.lang.Cloneable接口。

拷贝分为浅拷贝与深拷贝,我们先以代码的形式看一下浅拷贝的实现:

public class Province {
    private String name;
    Province(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

public class Student implements Cloneable {

    private String name;
    private int age;
    private Province province;

    public Student(int age, String name, Province province){
        this.age = age;
        this.name = name;
        this.province = province;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static void main(String[] args) {
        Province teacher = new Province("Shannxi");
        Student student1 = new Student(23, "mianjingxiangjie", teacher);
        Student student2 = (Student) student1.clone();
        student1.province.setName("Beijing");
        System.out.println(student1.province.getName());
        System.out.println(student2.province.getName());
    }
}

运行一下,上面代码输入为:

Beijing
Beijing

可见当实现Cloneable 接口的对象有其他对象的成员变量时,clone方法并不会复制一个新的成员变量。上面的student1和student2使用的是同一个province对象,当更改了student1的province名称,student2的province也相应的改变了。这就是浅拷贝。

那么怎么实现深拷贝呢?

以上面的例子为例只需要改变clone方法如下:

    @Override
    protected Object clone(){
        try {
            Student temp =  (Student) super.clone();
            temp.province = new Province(province.getName());
            return temp;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

在这段代码中需要手动创建一个新的province对象,也就是我们在clone方法里面需要将成员变量的对象进行手动拷贝就可以达到深拷贝的目的

2. toString()方法

toString提供对象的字符串表示形式。
Object类的默认toString()方法返回一个字符串,该字符串包括该对象的类名称,"@"字符以及该对象的哈希码的无符号十六进制表示形式。
当需要打印对象引用时,toString方法就会被调用。

public class Student {

    private String name;

    private int age;

    public Student(int age, String name){
        this.age = age;
        this.name = name;
    }

    public static void main(String[] args) {
        Student student = new Student(23, "mianjingxiangjie");
        System.out.println(student.toString());
    }
}

如上面代码所示,代码输入结果是:

Student@4554617c

也许你会有疑问,我平时用到的String类的toString()方法结果不是这样的啊,就是直接转换为了一个字符串,其实那都是被重写过的,直接返回字符串的表示。

3.equals()方法与hashCode()方法

这个方法相信大家都用过,没有重写过的equals()方法的作用跟 == 一样。
当我们比较两个字符串是不是完全相同的时候,就用的equals方法,注意,因为String类重写了equals()方法,因此我们在比较两个字符串是否一样的时候,不能用==。说到这里就必须要说一说hashCode()方法,这两个方法本来没有任何耦合关系,但是把它和hashCode()方法放在一起是因为Map集合的实现让这两个方法产生了某种程度上的耦合。

java散列容器的key都是复杂类型,比较两个key是否相等时,是先通过== 比较,如果返回false还要用equals()判断,为什么要这样比较呢? 在Java中,对于原始数据类型,== 比较的是他们的值,对于复合数据类型(对象),== 比较的是他们在内存中的存放地址。也就是说如果两个对象的引用指向的是堆上同一个对象时,这两者用== 比较才会返回true。但是我们在大多数情况下对于散列表中的key相等的条件没有严苛到地址都要一样的地步,只要属性一样就可以认为他们相等,所以==返回false不代表两个key不一样,还要用equals()进一步比较。

为什么要这样呢?首先从HashMap的源码我们知道,当从map中找一个指定的key时,我们首先是根据这个key的hashCode()的返回值经过处理后生成的hash值找到对应的桶,再遍历这个桶找到相等的key。从以上过程可以提炼出非常重要的一点,相等的对象hashCode()返回值一定要相等,因为如果hashCode()返回值不相等的话,计算出来的hash值很可能是不相等的,这会直接导致相同的key可能放入不同的桶中,这是不被允许的。所以如果我们要用自定义类对象做散列表的key时,在重写了equals()后,还要重写hashCode()来保证equals()返回为true的情况下两个对象hashCode()的返回值也相等。

finalize()方法:

finalize()方法我们在日常编程中基本不会用到,这个方法主要是虚拟机用于内存回收。

getClass() 方法:

这个方法的作用就是获取对象的运行时 class 对象,class 对象就是描述对象所属类的对象。这个方法通常是和 Java 反射机制搭配使用的。比如this.getClass().getName(),就会返回这个类的类名。

可以用下面的伪代码展示一下getClass()方法的使用

A a = new A();  
  
if(a.getClass()==A.class) {  
  
      System.out.println("equal");  
  
} else {  
  
      System.out.println("unequal");  
  
}  

上面代码会输出"equal"。

在获得类型类之后,你就可以调用其中的一些方法获得类型的信息了,主要的方法有:

getName():String:获得该类型的全称名称。

getSuperClass():Class:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。

getInterfaces():Class[]:获得该类型实现的所有接口。

isArray():boolean:判断该类型是否是数组。

isEnum():boolean:判断该类型是否是枚举类型。

isInterface():boolean:判断该类型是否是接口。

isPrimitive():boolean:判断该类型是否是基本类型,即是否是int,boolean,double等等。

isAssignableFrom(Classcls):boolean:判断这个类型是否是类型cls的父(祖先)类或父(祖先)接口。

getComponentType():Class:如果该类型是一个数组,那么返回该数组的组件类型。

剩下的几个主要用于多线程中,wait()系列用于线程等待,notify()系列用户唤醒线程,这类方法执行的前提就是一定要获取锁,这一部分我会在多线程部分详细讲解。

你可能感兴趣的:(一文看懂Object类的12个方法)