Java Object类
不想起床的小张 人气:0Java是一门天然的面向对象的语言。而所有我们手动创造出来的类,都继承于同一个类,即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方法
加载全部内容