服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 一文带你了解Java万物之基之Object类

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

2022-09-06 11:00不想起床的小张 Java教程

Java是一门天然的面向对象的语言。而所有我们手动创造出来的类,都继承于同一个类,即Object类。本文将通过示例为大家详细介绍一下Java中的Object类,需要的可以参考一下

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类

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

原文链接:https://juejin.cn/post/7071860248520327198

延伸 · 阅读

精彩推荐
  • Java教程在zuulFilter中注入bean失败的解决方案

    在zuulFilter中注入bean失败的解决方案

    这篇文章主要介绍了在zuulFilter中注入bean失败的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    矿泉4332021-09-30
  • Java教程springboot集成mybatisplus的方法

    springboot集成mybatisplus的方法

    这篇文章主要为大家详细介绍了springboot集成mybatisplus的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    猪点点10592021-04-19
  • Java教程详解spring+springmvc+mybatis整合注解

    详解spring+springmvc+mybatis整合注解

    本篇文章主要介绍了详解spring+springmvc+mybatis整合注解,详细的介绍了ssm框架的使用,具有一定的参考价值,有兴趣的可以了解一下...

    White_Black0075982020-09-20
  • Java教程java网络编程基础知识介绍

    java网络编程基础知识介绍

    这篇文章主要介绍了java网络编程基础知识介绍,涉及OSI分层模型和TCP/IP分层模型的对应关系、IP地址、端口号、tcp、udp等相关内容,还是比较不错的,这里...

    冬瓜蔡10752021-02-01
  • Java教程JavaWeb 入门:Hello Servlet

    JavaWeb 入门:Hello Servlet

    这篇文章主要介绍了Servlet开发JavaWeb工程示例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下...

    宁在春3582021-10-14
  • Java教程Spring 中jdbcTemplate 实现执行多条sql语句示例

    Spring 中jdbcTemplate 实现执行多条sql语句示例

    本篇文章主要介绍了Spring 中jdbcTemplate 实现执行多条sql语句示例,可以对多个表执行多个sql语句,有兴趣的可以了解一下。...

    菜鸟宝宝3702020-07-26
  • Java教程浅谈Action+Service +Dao 功能

    浅谈Action+Service +Dao 功能

    下面小编就为大家带来一篇浅谈Action+Service +Dao 功能。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    Java教程网2232020-12-07
  • Java教程java程序员必须知道的4个书写代码技巧

    java程序员必须知道的4个书写代码技巧

    本篇文章主要给大家讲述了作为JAVA程序员如何能写出高效的代码以及运行效率更高的代码,一起学习分享下吧。...

    Java教程网4462021-03-05