一文带你了解Java万物之基之Object类
作者:不想起床的小张
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类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!