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

作者:不想起床的小张 时间:2023-10-09 01:58:28 

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

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

可以看一下Object类的结构

一文带你了解Java万物之基之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方法

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

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

hashCode方法

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

百度百科的定义如下:

哈希码(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 个对象的内存地址是否相等。

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

可见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 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。

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

由源码文档,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方法

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

当没有重写该方法时,当打印实例化对象时,则返回类名与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类

来源:https://juejin.cn/post/7071860248520327198

标签:Java,Object类
0
投稿

猜你喜欢

  • 利用C#实现在Word中更改字体颜色

    2021-12-25 12:14:09
  • Android 帧动画的实例详解

    2023-02-05 16:09:42
  • Gradle的安装和环境变量的配置详解

    2023-07-11 04:56:37
  • Android仿QQ首页ListView左滑置顶、删除功能

    2022-02-16 05:15:48
  • java 算法之快速排序实现代码

    2023-01-30 01:44:59
  • C#分布式事务的超时处理实例分析

    2022-06-16 03:11:28
  • C#多线程之Thread中Thread.IsAlive属性用法分析

    2023-07-11 11:44:12
  • java异步编程之一文看完其异步函数表

    2021-12-25 10:57:39
  • java统计文件中每个字符出现的个数

    2021-12-22 06:43:42
  • C#关联自定义文件类型到应用程序并实现自动导入功能

    2023-06-22 20:11:11
  • Java里的static import使用小结

    2023-08-18 18:36:36
  • Java操作IO对象流进行数据的读写

    2021-08-25 03:33:49
  • Java删除二叉搜索树的任意元素的方法详解

    2021-10-04 12:27:26
  • Java经典算法汇总之选择排序(SelectionSort)

    2021-12-23 03:59:52
  • 浅谈Silverlight 跨线程的使用详解

    2021-10-16 01:23:05
  • Android应用开发中CardView的初步使用指南

    2022-12-30 02:04:47
  • Android App开发中自定义View和ViewGroup的实例教程

    2021-06-11 08:58:03
  • Android Studio配置(Android Studio4.1为例)

    2022-04-18 04:19:18
  • 详解如何在SpringBoot项目中使用统一返回结果

    2023-10-30 16:41:21
  • C++定义和初始化string对象实例详解

    2022-08-05 06:03:00
  • asp之家 软件编程 m.aspxhome.com