Java基础-Object类中的方法

下面这些是 Java 中的 Object 类中方法,共 11 个,9 种方法,wait() 方法被重载了。

方法

描述

protected native Object clone()

创建并返回当前对象的一份拷贝

public boolean equals(Object obj)

比较两个对象是否相等

protected void finalize()

当对象要被垃圾回收前,调用该方法

public final native Class<?> getClass()

返回该类的 Class 对象

public native int hashCode()

返回该对象的哈希码值

public final native void notify()

唤醒正在等待的对象监视器所在的单个线程

public final native void notifyAll()

唤醒正在等待的对象监视器所在的所有线程

public String toString()

返回对象的字符串表示

public final void wait()

线程等待

public final native void wait(long timeout)

在规定的时间内线程等待

public final void wait(long timeout, int nanos)

在规定的时间内线程等待

我们知道 Java 的继承是单继承的,也即继承树是单根继承,树的根就是 Object 类,Java 中的所有类都直接或间接继承自 Object,无论是否明确指明,无论类是否是抽象类。Object 类可以说是 Java 类的始祖类,其中有一些方法也是预留给了后代类,也即是上面表中没有 final 关键字修饰的方法,有 clone() 方法,equals() 方法,finalize() 方法,hashCode() 方法,toString() 方法,这些都是常见的被子类重写的方法,下面就针对这些方法做一个探讨。

1、clone() 方法

从字面意上看,这个方法被设计为克隆对象,返回一个和被克隆的对象一模一样的对象。这个方法被 protect 关键字修饰,说明只能被子类重写和调用。但是在子类重写该方法时,必须要加上一个 “克隆标记” 的接口 Cloneable,这个接口里面什么方法都没有,纯粹就是一个 “标记”。这个方法被 native 关键字修饰,所以可以看出这个是一个本地方法,最终调用的是外部的链接库(C语言或C++写成),非 Java 代码实现。

下面通过实验看看 clone() 方法的真相。

public class CloneTest implements Cloneable{
    public String name;

    public CloneTest(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest c1 = new CloneTest("小明");
        CloneTest c2 = (CloneTest) c1.clone();
        System.out.println(c1 == c2); // false
        System.out.println(c1.name == c2.name); // true
    }
}

代码运行的结果已经写在了代码注释中,该类没有实际重写父类中的 clone() 方法,只是简单的调用了父类的 clone() 方法。可以看到 c1 所引用的对象中 name 字段和 c2 所引用的对象的 name 字段地址相同,说明 c1.name 和 c2.name 都是对 “小明” 这一个字符串对象的引用,而并没有因克隆而产生一个新的 “小明” 字符串对象,也即是 clone() 方法本质上只是对 引用的复制(克隆),并没有真正复制对应对象中的内容,所以这只能算是一种 “浅克隆” 或者说是 “浅拷贝”。上面这段代码如果改变 c1.name 的值,c2.name 的值不会跟着改变,但如果 像下面代码这样,改变了 name 对应的值,就会对克隆对象中对应字段的值造成影响。

public class CloneTest {
    static class Clothes {
        public String name;

        public Clothes(String name) {
            this.name = name;
        }
                
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    static class People implements Cloneable{
        public Clothes clothes;

        public People(Clothes clothes) {
            this.clothes = clothes;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        People p1 = new People(new Clothes("测试"));
        People p2 = (People) p1.clone();
        System.out.println(p1 == p2); // false
        System.out.println(p1.clothes == p2.clothes); // true
        System.out.println(p1.clothes.name == p2.clothes.name); // true
        p1.clothes.name = "小明";
        System.out.println(p2.clothes.name); // 小明
    }
}

所以要想真正实现 “ 深拷贝”,必须要递归克隆到字段不再是一个类类型的字段,对类类型也要调用对应的 clone () 方法,这样就可以保证引用字段都被复制了一遍。

上面 People 类中的 clone() 方法可以写成:

@Override
protected Object clone() throws CloneNotSupportedException {
    People people = (People) super.clone();
    people.clothes = (Clothes) clothes.clone();
    return people;
}

2、equals() 方法

equals方法用来判断两个对象是否相等,Obejct 类中的 equals() 方法和直接使用 == 运算符是一样的,都是看引用是否相同,这点可以从源码中看出。子类可以根据自己的需要重写这个方法,并且重写该方法时遵循以下规则:

  • 自反性,对象必须与自身相等,即 x.equals(x) 为 true
  • 对称性,如果 x.equals(y) 为 true,那么 y.equals(x) 也为 true
  • 传递性,如果 x.equals(y) 为 true,y.equals(z) 为 true,那么 x.equals(z) 也为 true
  • 一致性,多次调用 equals() 方法,返回值应该始终是 true 或始终是 false
  • 对于非空的 null 值,x.equals(null) 为 false

在重写 equals() 方法时,也最好一并重写 hashCode() 方法,使得当 equals() 方法返回 true 时,两个对象的 hashCode 也是相同的。当然这并不是必须的,但为什么要这样做呢?因为如果对象要存储在散列结构(如 HashTable、HashSet、HashMap)中时,判断两个对象是否相等依据是 hashCode() 方法,如果只重写了 equals() 方法,而没有重写 HashCode() 方法,就可能会出现两个对象 equals 是 true,但 hashCode 不同,造成我们认为一样的对象被重复放入这些散列结构中。使用 hashCode 比较对象的另一好处是可以提高性能和搜索效率,从原来的全部查找对比,变为只查找一次,时间复杂度从原来的 O(n) 变为了 O(1)。

注意: equals 为 true 的对象,hashCode 也是相同的,但是反过来,hashCode 相同的两个对象,equals 不一定为 true。

equals() 就是一个普通的方法,返回 true 还是 false 的决定权在于我们。

常用的实现步骤:

  • 检查是否是同一个对象的引用,如果是,直接返回 true
  • 检查是否是同一类型,如果不是直接,返回 false,注意这里判断是否是同一类型,不能使用 instanceof 关键字,这个关键字判断的是前面对象是否是后面类的实例或者子类的实例额,不够准确。
  • 将 Object 对象转型
  • 判断每个关键域值是否相等(业务的实际需要)

实例代码如下(来自 GitHub):

public class EqualExample {
    private int x;
    private int y;
    private int z;

    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EqualExample that = (EqualExample) o;

        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
}

3、finalize() 方法

虽然这个方法也可以被子类重写,但是极少被使用,这个方法和 C++ 的析构函数功能是不一样的,C++ 中使用析构函数来清除一个对象,而 Java 中清除对象的工作是由 Java 虚拟机帮我们完成的。Java 中设计这个方法只是想在垃圾回收器将对象从内存中清除前做一些其他自定义的清理工作,在未来的 JDK 版本中,这个方法很有可能会被取消。在 Java 中,与 finalize() 方法相似的有 finally 语句块,用来在异常发生后关闭一些资源(如文件),常和 try .. catch 语句结合使用。final 关键字和 finalize() 看上去也很相似,但是一点关系都没有,final 关键词可以用来修饰变量、属性、方法和类,分别表示常量、属性不可变、方法不可被重写、类不可被继承。

4、hashCode() 方法

hashCode() 方法当然就是用来返回一个对象的哈希码(hashCode)啦,hashCode 是一个 int 类型的数字,如果我们没有重写一个类的 toString() 方法,而使用 System.out.println 打印这个类,调用的就是 Object 类中的 toString() 方法,输出格式为 类名@hashCode的十六进制数 ,其实 hashCode() 方法完全不用手写,强大的 IDE 工具一般都有自动生成的功能,可以帮我们写 hashCode() 方法和 equals() 方法,另外在 JDK7 中新增的 Objects 类,也有个静态方法 hashCode(obj) 可以生成对象的 hashCode 值。

5、toString() 方法

这个方法就不多说了,相信都懂,通俗的说就是对象的自我介绍方法,嘿嘿。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT可乐

JDK1.8源码(一)——java.lang.Object类

  本系列博客将对JDK1.8版本的相关类从源码层次进行介绍,JDK8的下载地址。   首先介绍JDK中所有类的基类——java.lang.Object。   ...

794150
来自专栏java一日一条

Java LinkedHashMap工作原理及实现

在理解了#7 介绍的HashMap后,我们来学习LinkedHashMap的工作原理及实现。首先还是类似的,我们写一个简单的LinkedHashMap的程序:

9520
来自专栏技术博客

编写高质量代码改善C#程序的157个建议[正确操作字符串、使用默认转型方法、却别对待强制转换与as和is]

  字符串应该是所有编程语言中使用最频繁的一种基础数据类型。如果使用不慎,我们就会为一次字符串的操作所带来的额外性能开销而付出代价。本条建议将从两个方面来探讨如...

11440
来自专栏步履前行

什么是面向对象

面向对象的特征有3个,封装、继承、多态。至于抽象的话,个人认为,应该是前面3大特征中都有抽象的思想,毕竟面向对象本身就是一种抽象。 比如 子类 extends ...

35760
来自专栏GreenLeaves

JavaScript之call()和apply()方法详解

简介:apply()和call()都是属于Function.prototype的一个方法属性,它是JavaScript引擎内在实现的方法,因为属于Functio...

24860
来自专栏云霄雨霁

Java--多态性之抽象类和接口

18940
来自专栏chenjx85的技术专栏

leetcode-482-License Key Formatting

15630
来自专栏源哥的专栏

BASE64编码

附录:BASE64编码的原理(节选自http://www.vbzx.net/ArticleView/vbzx_Article_View_1199.asp)

10640
来自专栏工科狗和生物喵

【计算机本科补全计划】Java学习笔记(四) 修饰符

正文之前 今天总算是把那个党员谈话给弄完了,三个学弟轮番跟我来聊天,讲自己的入党动机啥的,看到他们就仿佛看到了大一的自己,原来当时面对学长,面对这类事情,会紧张...

34690
来自专栏醒者呆

正则表达式——Java程序员懂你

正则表达式 关键字:正则表达式,Pattern,Matcher,字符串方法,split,replace 前文书立下了一个flag,这里要把它完成,就是正则...

39250

扫码关注云+社区

领取腾讯云代金券