一文带你了解Java万物之基之Object类

目录
  • native方法
  • getClass方法
  • hashCode方法
  • equals方法
    • ==和equals的区别
  • clone方法
    • 浅拷贝和深拷贝
  • toString方法
  • 线程方法
  • finalize方法

Java是一门天然的面向对象的语言。而所有我们手动创造出来的类,都继承于同一个类,即Object类。

可以看一下Object类的结构

native方法

首先,超类拥有一个native方法

private static native void registerNatives();
static {
    registerNatives();
}

Java中,用native关键字修饰的函数表明该方法的实现并不是在Java中去完成。而是被C/C++完成,并被编译成了.ddl文件,由Java去调用。registerNatives()方法本身,主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。同时,也定义了一个静态代码块,由此,每当我们创建Java对象时,都系统总是先调用静态代码块,即调用native方法。该方法被private修饰,表明了这个方法是私有的,不被外部调用

getClass方法

通过此方法,可获得类的Class实例,具体可见Java反射机制

hashCode方法

百度百科的定义如下:

哈希码(HashCode),并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构,把关键码值映射到表中一个位置来访问记录,以加快查找的速度.

由此可见,通过Java内部的散列函数,可以给每个实例化的对象分配一个内存地址,并记录在散列表中,便于在程序中查找、新建、对比对象时更加高效。

写一个实例打印看看:

public class base {

    public static void main(String[] args) {

        Apple apple = new Apple();
        System.out.println(apple.hashCode());
        System.out.println(apple);
        System.out.println(Integer.valueOf("74a14482",16));

    }

}

class Apple {
}

打印结果:

1956725890
p2.Apple@74a14482
1956725890

进程已结束,退出代码0

可见对象的哈希地址为10进制数,与打印的原生16进制地址相对应

equals方法

Object equals() 方法用于比较两个对象是否相等。

equals() 方法比较两个对象,是判断两个对象引用指向的是同一个对象,即比较 2 个对象的内存地址是否相等。

可见equals比较两个对象是否相等时,比较的是两个对象的hashcode是否相等。因此,若要重写equals方法,通常也要重写hashcode方法。

例如,String类型并不是一个原生的数据类型(例如int,char,double等),而是Java重新封装的对象。String、Integer等都重写了equals方法,改变为比较值是否相等,而不是引用类型(hashcode)

例如String对equals方法的重新封装:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

其中,instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。源码表示,会先匹配引用是否相同,相同则返回真,否则将String实例转化为字符数组,并逐个匹配是否相等,即匹配值是否相等。

String同时也重写了hashcode方法:

 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

其中,hash默认为0,所以重写hash计算公式为:hash=s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

==和equals的区别

  • == :当比较基本类型时,则比较两者的值是否相等;当比较引用类型时,则比较引用引用(hashcode)是否相等
  • equals:由源码,比较引用是否相等,部分类型如String、Integer等重写了equals方法,比较值是否相等

举几个例子:

public class base {

    public static void main(String[] args) {

        // new两个String对象,但内容相同
        String a = new String("xxx");
        String b = new String("xxx");
        System.out.println(a == b);    // 比较hash值,因为是两个不同的实例化对象,所以不同,返回false
        System.out.println(a.equals(b)); // 比较内容,均为“xxx”,返回true

        // 生成两个引用
        String c = "xxx";
        String d = "xxx";
        System.out.println(c == d); // 比较hash值,因为指向同一个引用,所以相同,返回true
        System.out.println(c.equals(d)); // 比较内容,均为“xxx”,返回true

    }

}

总结:equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较,所以一般情况下 可理解为equals 比较的是值是否相等。

clone方法

Object clone() 方法用于创建并返回一个对象的拷贝。

clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。

由源码文档,clone方法只能实现浅拷贝,且类需要重写clone方法,调用super.clone来获取返回的对象,因为不同包下,基类保护的实例方法子类无权访问。另外,object类本身没有实现Cloneable接口,但我们自己写的类需要继承Cloneable接口,否则会总会抛出CloneNotSupportedException异常。

写个例子:

public class base{

    public static void main(String[] args) throws CloneNotSupportedException {

        // 实例化一个Student对象
        Student student = new Student(18,"Tony");
        // 打印内容
        System.out.println(student);

        // 克隆student实例
        Student anotherStudent = (Student) student.clone();
        // 打印克隆内容
        System.out.println(anotherStudent);

    }

}

class Student implements Cloneable {
    int age;
    String name;

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

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

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

打印结果:

Student{age=18, name='Tony'}
Student{age=18, name='Tony'}

进程已结束,退出代码0

浅拷贝和深拷贝

浅拷贝例子

当拷贝的对象的成员有引用对象时,例如在Student类中包含了另一个Teacher对象时,被克隆的对象和克隆的对象指向同一个Teacher引用,所以当改变Teacher的数据时,克隆的对象也会随之改变

写个例子:

public class base {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 实例化一个Teacher对象
        Teacher teacher = new Teacher(25,"JayChou");
        // 实例化一个Student对象
        Student student = new Student(18, "Tony",teacher);
        // 打印内容
        System.out.println(student);
        // 克隆student实例
        Student anotherStudent = (Student) student.clone();
        System.out.println(anotherStudent);
        System.out.println("---------------------------------------");
        // 修改teacher数据,并更新student
        teacher.setAge(30);
        student.setTeacher(teacher);
        // 打印修改后的student实例和克隆对象实例
        System.out.println(student);
        System.out.println(anotherStudent);

    }

}

class Student implements Cloneable {
    int age;
    String name;
    Teacher teacher;

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

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

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

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

class Teacher implements Cloneable {
    int age;
    String name;

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

    public void setAge(int age) {
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Teacher{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

}

打印结果:

Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
---------------------------------------
Student{age=18, name='fuck', teacher=Teacher{age=30, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=30, name='JayChou'}}

进程已结束,退出代码0

这就是浅拷贝的结果,因指向同一个引用,当其中一个实例发生更新时,会发生连锁变化

所以相反,实现深拷贝,使得不会发生连锁反应,让克隆与被克隆对象彻底分离!

实现深拷贝

大致有一下思路:

不采用clone方法,重新new一个对象,将需要复制的对象所有属性成员放进去

 // 实例化一个Teacher对象
        Teacher teacher = new Teacher(25,"JayChou");
        // 实例化一个Student对象
        Student student = new Student(18, "Tony",teacher);
        // 打印内容
        System.out.println(student);
        // new一个一模一样的!
        Student anotherStudent = new Student(18,"Tony",new Teacher(25,"JayChou"));
        System.out.println(anotherStudent);
        System.out.println("---------------------------------------");
        // 修改teacher数据,并更新student
        teacher.setAge(30);
        student.setTeacher(teacher);
        // 打印修改后的student实例和克隆对象实例
        System.out.println(student);
        System.out.println(anotherStudent);

打印结果:

Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
---------------------------------------
Student{age=18, name='Tony', teacher=Teacher{age=30, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}

进程已结束,退出代码0

重写clone方法,将每个引用对象也实现克隆

@Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.setTeacher((Teacher) this.teacher.clone());
        return student;
    }

打印结果:

Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
---------------------------------------
Student{age=18, name='Tony', teacher=Teacher{age=30, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}

进程已结束,退出代码0

序列化

序列化的方式有很多,主要是工具比较多...这里我使用Apache Commons Lang序列化

首先,相关类都需要继承序列化接口(接口并没有实质的实现内容,仅仅作为一个标志)

public class base {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 实例化一个Teacher对象
        Teacher teacher = new Teacher(25,"JayChou");
        // 实例化一个Student对象
        Student student = new Student(18, "Tony",teacher);
        // 打印内容
        System.out.println(student);
        // 序列化深拷贝
        Student anotherStudent = (Student) SerializationUtils.clone(student);
        System.out.println(anotherStudent);
        System.out.println("---------------------------------------");
        // 打印序列化后内容 为字节流
        byte[] res = SerializationUtils.serialize(student);
        System.out.println(SerializationUtils.serialize(student));
        // 打印反序列化结果
        System.out.println(SerializationUtils.deserialize(res));
        System.out.println("---------------------------------------");
        // 修改teacher数据,并更新student
        teacher.setAge(30);
        student.setTeacher(teacher);
        // 打印修改后的student实例和克隆对象实例
        System.out.println(student);
        System.out.println(anotherStudent);

    }

}

class Student implements Serializable {
    int age;
    String name;
    Teacher teacher;

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

class Teacher implements Serializable {
    int age;
    String name;

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

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

}

打印结果:

Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
---------------------------------------
[B@50040f0c
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}
---------------------------------------
Student{age=18, name='Tony', teacher=Teacher{age=30, name='JayChou'}}
Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}

进程已结束,退出代码0

总结:第一种方式笨笨的哈哈,第二种方式需要手动重写clone方法,当对象复杂时,就不是一个明智的选择了。相比较之下,第三种当时显的十分方便帅气,可由于底层实现的复杂,存在一定的系统开销。

toString方法

当没有重写该方法时,当打印实例化对象时,则返回类名与hash地址的16进制拼接字符串。为便于人们阅读,建议所有子类重写该方法

例如我的Student类重写了该方法:

class Student implements Serializable {
    int age;
    String name;
    Teacher teacher;

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

则打印该对象时会返回人们便于阅读的内容:

Student{age=18, name='Tony', teacher=Teacher{age=25, name='JayChou'}}

线程方法

wait(),wait(long),wait(long,int),notify(),notifyAll()分别用于线程的休眠于唤醒,在多线程内容中再做详解

finalize方法

到此这篇关于一文带你了解Java万物之基之Object类的文章就介绍到这了,更多相关Java Object类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java中Object类4种方法详细介绍

    目录 Object(四大方法): hashCode()方法: equals()方法: getClass()方法: toString()方法: 总结 Object(四大方法): 文章干货满满,耐性看完~~何为Object?首先先来看看官方对Object的介绍:在这里附上Java官方的查阅工具:https://docs.oracle.com/en/java/javase/17/docs/api/index.html 由官方介绍可见,object属于Java.lang包内的一个类,而且提供了很多种方法

  • Java源码解析之object类

    在源码的阅读过程中,可以了解别人实现某个功能的涉及思路,看看他们是怎么想,怎么做的.接下来,我们看看这篇Java源码解析之object的详细内容. Java基类Object java.lang.Object,Java所有类的父类,在你编写一个类的时候,若无指定父类(没有显式extends一个父类)编译器(一般编译器完成该步骤)会默认的添加Object为该类的父类(可以将该类反编译看其字节码,不过貌似Java7自带的反编译javap现在看不到了). 再说的详细点:假如类A,没有显式继承其他类,编译

  • Java中的Object类详细介绍

    理论上Object类是所有类的父类,即直接或间接的继承java.lang.Object类.由于所有的类都继承在Object类,因此省略了extends Object关键字. 该类中主要有以下方法: toString(),getClass(),equals(),clone(),finalize(), 其中toString(),getClass(),equals是其中最重要的方法. 注意: Object类中的getClass(),notify(),notifyAll(),wait()等方法被定义为f

  • Java基础之Object类详解

    object类的介绍 object是所有类的直接父类或者是间接父类,为什么这么说呢? 可以查询java8的API帮助文档: 可见在这样的一个类树中,所有的类的根还是Object类 在IDEA中新建一个类,系统会默认继承Object类 public class Pet extends Object{ } 那么Dog继承了Pet类的属性和行为方法,还会继承Object类的属性和行为方法了吗?这一点是肯定的,Pet类作为Object类的子类,Dog类作为Pet类的子类,所以说Object是Dog类的间

  • Java中Object类常用的12个方法(小结)

    目录 前言 1. getClass 方法 2. hashCode 方法 3. equals 方法 4. clone 方法 5. toString 方法 6. notify 方法 7. notifyAll 方法 8. wait(long timeout) 方法 9. wait(long timeout, int nanos) 方法 10. wait 方法 11. finalize 方法 前言 Java 中的 Object 方法在面试中是一个非常高频的点,毕竟 Object 是所有类的"老祖宗&qu

  • 你了解Java中的Object类吗

    任何一个类默认继承Object类,就算没有直接继承,最终也会间接继承. Object类,有两种方式找到: 第一种:在源码当中找到 第二种:查阅java类库的帮助文档 Object类中两个重要的方法: boolean equals (Object obj) //判断两个对象是否相等 String toString () //将对象转换成字符串形式 equals方法: 源代码: public boolean equals(Object obj) { return (this == obj); } 作

  • 一文带你了解Java万物之基之Object类

    目录 native方法 getClass方法 hashCode方法 equals方法 ==和equals的区别 clone方法 浅拷贝和深拷贝 toString方法 线程方法 finalize方法 Java是一门天然的面向对象的语言.而所有我们手动创造出来的类,都继承于同一个类,即Object类. 可以看一下Object类的结构 native方法 首先,超类拥有一个native方法 private static native void registerNatives(); static { re

  • 一文带你了解Java中的ForkJoin

    目录 什么是ForkJoin? ForkJoinTask 任务 ForkJoinPool 线程池 工作窃取算法 构造方法 提交方法 创建工人(线程) 例:ForkJoinTask实现归并排序 ForkJoin计算流程 前言: ForkJoin是在Java7中新加入的特性,大家可能对其比较陌生,但是Java8中Stream的并行流parallelStream就是依赖于ForkJoin.在ForkJoin体系中最为关键的就是ForkJoinTask和ForkJoinPool,ForkJoin就是利用

  • 一文带你了解Java中的Object类及类中方法

    目录 1. Object类介绍 2. 重写toString方法打印对象 3. 对象比较equals方法 4. hashCode方法 1. Object类介绍 Object是Java默认提供的一个类.Java里面除了Object类,所有的类都是存在继承关系的.默认会继承Object父 类.即所有类的对象都可以使用Object的引用进行接收. 范例:使用Object接收所有类的对象 class Person{} class Student{} public class Test { public s

  • 一文带你了解Java排序算法

    目录 一.选择排序 二.冒泡排序 三.插入排序 一.选择排序 选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度.所以用到它的时候,数据规模越小越好.唯一的好处可能就是不占用额外的内存空间了吧 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾. 重复第二步,直到所有元素均排序完毕. public static void selectSort(int[] arr) { //选择排序

  • 一文带你了解Java设计模式之原型模式

    目录 定义 解决的问题 核心要点 类图 浅复制与深复制的区别 代码实现 未使用设计模式 实现Cloneable接口 深复制-重写clone 深复制-通过对象序列化实现(推荐) 拓展 定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 原型模式其实就是从一个对象在创建另外一个可定制的对象,不需要知道任何创建的细节 解决的问题 在运行期建立和删除原型. 经常用于: 类初始化消耗资源较多 构造函数比较复杂 核心要点 1.实现cloneable 接口,重写Object的clone方法

  • 一文带你了解Java中IO流与Guava的使用

    目录 Guava IO 分类 常用的流 示例 Guava中的IO 其他 结束语 Guava IO 日常系统交互中,文件的上传下载都是常见的,一般我们会通过jdk提供的IO操作库帮助我们实现.IO指的是数据相对当前操作程序的入与出,将数据通过 输出流从程序输出,或者通过输入流将数据(从文件.网络.数据等)写入到程序,这里的IO指的是基于流作为载体进行数据传输.如果把数据比作合理的水,河就是IO流,也是数据的载体. Java为我们提供了非常多的操作IO的接口与类,帮助开发者实现不同源间的数据传输,比

  • 一文带你了解Java中的SPI机制

    目录 1: SPI机制简介 2: SPI原理 3: 使用场景 4: 源码论证 5: 实战 6: 优缺点 6.1 优点 6.2 缺点 1: SPI机制简介 SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建.这个不是什么特别的技术,只是 一种设计理念. 2: SPI原理 Java SPI 实际上是基于接口的编程+策略模式+配置文件组合实现的动态加载机制. 系统设计的各个

  • 深入理解JAVA基础类库中对象Object类

    引言 Object类是所有类.数组的父类,位于java.lang 包下也就是说,Java允许把所有任何类型的对象赋给Object类型的变量.当定义一个类时没有使用extends关键字为它显式指定父类,则该类默认继承Object父类. 例如: public class Dog{ ...... } 等价于: public class Dog extends Object { ...... } Object常用方法 Object 类属于java.lang包,此包下的所有类在使用时无需手动导入,系统会在

  • Java Thread多线程开发中Object类详细讲解

    目录 方法概览 Thread wait  notify notifyAll方法详解 作用 阻塞阶段 唤醒阶段 遇到中断 代码展示 特点 通过wait notify方法实现生产者和消费者 sleep方法详解 sleep不会释放锁 sleep响应中断 总结 join方法详解 代码展示 yield方法 方法概览 Thread wait  notify notifyAll方法详解 作用 阻塞阶段 使用了wait方法之后,线程就会进入阻塞阶段,只有发生以下四种情况中的其中一个,线程才会被唤醒 另一个线程调

随机推荐