Object:
a.概述:Object是类层次结构的根类,也就是所有类的父类。所有对象(包括数组)都实现这个类的方法。
b.成员变量:没有成员变量。
c.构造方法:
Object() 只有一个无参构造方法
d.成员方法:
protected Obeject() clone() 获取拷贝对象,默认是浅拷贝。
boolean equals(Object obj) 判断两个对象是否相等,默认比较的是地址。
protected void finalize() 当GC确定不存在对该对象的更多引用时,由对象的GC调用此方法。
Class> getClass() 获取字节码文件对象(反射),或者说返回此Object的运行时类。
int hashCode() 返回对象的哈希值,是一个十进制整数,引入此方法的目的是为了提高哈希表的性能。默认是将对象的内存地址转换成一个整数(哈希值)来实现的。
String toString() 默认返回:运行时类名字 + @ + 对象的hashCode的十六进制的字符串形式,建议所有类重写此方法。
public class ObjectDemo1 {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
System.out.println(obj1); // java.lang.Object@1b6d3586,调用toString()方法
System.out.println(obj2); // java.lang.Object@4554617c,调用toString()方法
System.out.println(obj1.toString()); // java.lang.Object@1b6d3586
System.out.println(obj2.toString()); // java.lang.Object@4554617c
}
}
1.public final Class> getClass()
被final修饰,所以子类不能重写此方法,即任何一个对象,它的getClass()行为是一致的。
返回此Object的运行时类,或者说返回字节码文件对象(反射)。
Class:类类型
概述:Class类的实例表示正在运行的Java应用程序中的类或接口。枚举是一种类,注解是一种接口。
public String getName():返回Class对象的运行时类的全限定名。
Student:
package com.cskaoyan.a_object;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/* getter & setter 省略 */
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
ObjectDemo2:
public class ObjectDemo2 {
public static void main(String[] args) {
Object obj1 = new Object();
Class<?> cl1 = obj1.getClass();
System.out.println(cl1); // class java.lang.Object // Class类中重写了toString()方法
String name1 = cl1.getName();
System.out.println("name1 = " + name1); // name1 = java.lang.Object
System.out.println("--------------------------");
Object obj2 = new Student(); // 多态
Class<?> cl2 = obj2.getClass();
System.out.println(cl2); // class com.cskaoyan.a_object.Student // Class类中重写了toString()方法
String name2 = cl2.getName();
System.out.println("name2 = " + name2); // name2 = com.cskaoyan.a_object.Student
// 正在运行的类是Student类(子类),Caution!!!
}
}
2.public int hashCode()
返回对象的哈希值,是一个十进制整数,引入此方法的目的是为了提高哈希表的性能。默认是将对象的内存地址转换成一个整数(哈希值)来实现的。
hashCode()的常规协定:
a.一致性: 如果一个对象进行比较的信息没有发生修改,那么在一次程序运行期间,它们的hash值要一致。
b.相等性: 如果两个对象通过 equals 比较是相等的,那么它们的 hashcode 也要相等。
c.哈希碰撞概率低: 如果两个对象不相等,那么它们的 hashcode 最好不相等,这个可以提高哈希表的性能。
public class ObjectDemo3 {
public static void main(String[] args) {
Object obj1 = new Object();
System.out.println(obj1.hashCode()); // 460141958
Object obj2 = new Object();
System.out.println(obj2.hashCode()); // 1163157884
}
}
补充:哈希函数(算法)
(1)哈希函数
哈希函数是把任意长度的输入数据映射成固定长度(字长)的输出数据的一种函数。哈希函数是不可逆的,也就是不能通过结果值,来反推输入值。
哈希冲突:输入值不同,但是它们的哈希值相同。
好的哈希函数应该具备的特征:
a.很难通过哈希值,反推出输入值。
b.对输入数据非常敏感,输入数据哪怕只有一个bit不同,哈希值也要表现出不相关性(或者表现出无关性)。
c.对于任意两个输入数据,哈希值相同的概率很小。(哈希冲突概率很小)
d.运算效率要比较高,对于很长的数据(如大文本),也要很快地计算出哈希值。
总的来说,好的哈希函数是在模拟随机映射。
经典的哈希函数包括:MD4, MD5, SHA家族。
(2)如何自己设计一个哈希函数(算法)
常见的哈希函数有:加法哈希、乘法哈希、位运算哈希、混合哈希(将前面三个结合在一起)。见下图。
**注意:**对于乘法哈希,一般选择乘以31,即hash * 31,乘法是比较耗时的,所以底层会转化为hash << 5 - hash,不会选择乘以诸如28,25等,因为这样仍然需要乘法运算。
3.public String toString():
返回该对象的字符串形式,结果应该是一个简明且易于读懂的信息表达式。建议所有子类都重写此方法。
默认实现:return getClass().getName() + "@" + Integer.toHexString(hashCode());
注意事项:
直接打印对象,默认会调用该对象的toString()方法。
Student:
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/* getter & setter */
@Override // 重写toString()方法
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
ObjectDemo4:
public class ObjectDemo4 {
public static void main(String[] args) {
Object obj = new Object();
String s = obj.toString();
System.out.println(s); // 主动调用toString()方法 java.lang.Object@1b6d3586
System.out.println(obj.getClass().getName() + "@" + Integer.toHexString(obj.hashCode())); // toString()方法的底层实现 java.lang.Object@1b6d3586
System.out.println(obj); // 直接打印对象,默认会调用toString()方法 java.lang.Object@1b6d3586
System.out.println("----------------------");
Student stu = new Student("Heson_z", 18);
System.out.println(stu); // Student类重写了toString()方法 Student{name='Heson_z', age=18}
}
}
4.public boolean equals(Object obj):
判断两个对象是否相等。
默认实现:return this == obj; 即默认比较的是两个对象的内存地址。
实体类:
一个对象对应一个实体,不需要重写equanl()方法。
值类:
如String、Point(x, y)、之前写的Rectangle(矩形) 都属于值类,需要重写equals()方法。
对于Rectangle来说:
关键域:x, y
衍生域: area, perimeter
无关域: color
应该根据关键域来重写equals()方法。
equals()方法的常规协定:
equals()方法在非空对象引用上实现相等关系:
自反性:对于任何非空引用值 x,x.equals(x) 都应返回true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回true时,x.equals(y)才应返回true。
传递性:对于任何非空引用值 x、y 和 z,如果x.equals(y)返回 true,并且y.equals(z)返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。
注意事项:
当equals()方法被重写时,通常有必要重写hashCode()方法。比如在Set集合中,添加的元素重写了equals()方法,但是没有重写hashCode()方法,可能会得到错误的结果。
instanceof:
null instanceof 类名 --> false
obj instanceof Object --> true
Point:
import java.util.Objects;
public class Point {
int x;
int y;
public Point() {
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
/* getter & setter */
/* toString() */
// 这不是重写方法,因为重写要求方法名和形参相同,这里形参的类型是Point,而equals()方法的形参是Object类型。Caution!!!
/*@Override
public boolean equals(Point point) {
return false;
}*/
@Override // 使用IDEA自动生成的代码也是这样的
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || this.getClass() != obj.getClass()) return false;
Point point = (Point) obj;
return (this.x == point.x) && (this.y == point.y);
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
ColorPoint:
import java.util.Objects;
public class ColorPoint extends Point {
String color;
public ColorPoint() {
}
public ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
/* getter & setter & toString() */
@Override // 与IDEA自动生成的equal()方法一致。
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || this.getClass() != obj.getClass()) return false;
ColorPoint cp = (ColorPoint) obj;
return super.equals(obj) && Objects.equals(this.color, cp.color);
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = hash * 31 + Objects.hash(color);
return hash;
}
}
ShapePoint:
import java.util.Objects;
public class ShapePoint extends Point {
String shape;
public ShapePoint() {
}
public ShapePoint(int x, int y, String shape) {
super(x, y);
this.shape = shape;
}
/* getter & setter & toString() */
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || this.getClass() != obj.getClass()) return false;
ShapePoint sp = (ShapePoint) obj;
return super.equals(obj) && Objects.equals(this.shape, sp.shape);
}
@Override
public int hashCode() {
// hash * 一个数,这个数通常选择质数31,因为:hash * 31 = hash << 5 - hash,效率高。Caution!!!
int hash = super.hashCode();
hash = hash * 31 + Objects.hashCode(shape);
return hash;
}
}
ObjectDemo5:
public class ObjectDemo5 {
public static void main(String[] args) {
/*Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = obj1;
System.out.println(obj1 == obj2); // false
System.out.println(obj1.equals(obj2)); // false
System.out.println(obj1 == obj3); // true
System.out.println(obj1.equals(obj3)); // true*/
/*Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
Student student = new Student();
System.out.println(p1.equals(p2)); // true
System.out.println(p1.equals(student)); // false*/
/*System.out.println(null instanceof Object); // false
Point p = new Point(3, 4);
System.out.println(p instanceof Object); // true*/
/*Point p1 = new Point(3, 4);
ColorPoint cp = new ColorPoint(3, 4, "red");
System.out.println(p1.equals(cp)); // false
System.out.println(cp.equals(p1)); // false*/
/*ShapePoint sp = new ShapePoint(3, 4, "dot");
Point p1 = new Point(3, 4);
ColorPoint cp = new ColorPoint(3, 4, "red");
System.out.println(sp.equals(p1)); // false
System.out.println(p1.equals(cp)); // false
System.out.println(sp.equals(cp)); // false*/
// 使用IDEA自动生成的equals()方法,对于字父类对象的判断都会返回false,因为它们的运行时类不同,返回false;
// 这违反了里氏替换原则:父类出现的地方都可以用子类来代替。
Point p = new Point(3, 4);
Point p1 = new Point(3, 4);
System.out.println(p.equals(p1)); // true
System.out.println(p.hashCode()); // 1058 重写了Point的hashCode()方法,所有p和p1的hashCode()结果相同。
System.out.println(p1.hashCode()); // 1058
}
}
5.protected void finalize() thorws Throwable
概述:当GC回收此对象时,会自动调用此方法。子类重写finalize()方法,以释放资源。
默认实现:方法体为空,什么都没做。
finalize():
1.可以手动调用,该对象没有被回收。
2.当GC回收这个对象时,会自动调用此方法。
3.释放资源最好不要放在finalize()里面。
因为GC线程是一个优先级低的线程,当一个对象变成垃圾后,并不会马上被回收,因此资源不会得到立即释放。
那么应该如何释放资源呢? --> try ... catch ... finally 异常处理语句来释放资源。
public class ObjectDemo6 {
public static void main(String[] args) {
Student s = new Student("Heson_z", 18);
s.finalize(); // clear resources
System.out.println(s); // Student{name='Heson_z', age=18} 自己调用finalize()方法,该对象没有被回收,仍然能够打印出来。
s = null; // GC线程优先级低,对象不会立即被回收。
System.gc(); // clear resources 强制执行gc线程,自动调用finalize()方法,进行垃圾回收。
}
}
6.protected Object clone() throws CloneNotSupportedException:
创建并返回该对象的一个"副本",默认实现的是浅拷贝。
异常:CloneNotSupportedException
解决方法:对于需要克隆的对象,其对应类必须实现Cloneable接口,否则抛出上述异常。
Cloneable接口(空接口,标记接口,表示具有克隆能力):
如果某个类(比如Student类)实现了Cloneable接口,则student.clone()方法可以合法地对该类(Student类)实例进行按字段拷贝。
如果某个类(比如Student类)没有实现Cloneable接口,则Student类的实例student调用clone()方法会抛出CloneNotSupportedException。
Cloneable接口没有定义任何内容,通常这样的接口叫做空接口,在Java中往往起标记作用。
Java默认实现的是深拷贝还是浅拷贝?
浅拷贝
如何实现多级深拷贝呢?
在FirstLevel、SecondLevel等例子中演示。
Student:
import java.util.Objects;
public class Student implements Cloneable { // 必须要实现Cloneable接口,对象才能进行拷贝。
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/* getter & setter & toString() */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override // 返回值类型被父类返回值类型兼容,属于方法重写。
protected Student clone() throws CloneNotSupportedException {
Student s = (Student) super.clone(); // 调用Object的clone()方法,默认是浅拷贝。
s.name = new String(name); // 进行深拷贝(一级深拷贝)
return s; // 返回的克隆对象实现了深拷贝。
}
}
ObjectDemo7:
public class ObjectDemo7 {
public static void main(String[] args) throws CloneNotSupportedException {
// Object obj = new Object();
// obj.clone(); // clone()使用protected修饰,且Object在java.lang包下,子类在当前包下,只能通过子类对象访问。Caution!!!
Student s1 = new Student("Henson_z", 18);
Student s2 = s1.clone(); // 必须先确保Student类实现了Cloneable接口,否则会抛出CloneNotSupportedException。s2是深拷贝
System.out.println(s1.equals(s2)); // true
System.out.println(s2); // Student{name='Henson_z', age=18}
System.out.println(s1 == s2); // false
System.out.println(s1.getName().equals(s2.getName())); // ture;
System.out.println(s1.getName() == s2.getName()); // false 因为是深拷贝(一级深拷贝)
}
}
(1)什么是浅拷贝和深拷贝?
以上面的Student类为例,浅拷贝和深拷贝分别如下所示:
(2)实现FirstLevel深拷贝(两级深拷贝)
FirstLevel:
/*
对FirstLevel进行深拷贝:
分析:
1.实现Cloneable接口;
2.重写clone()方法。
*/
public class FirstLevel implements Cloneable {
int a;
SecondLevel secondLevel;
public FirstLevel() {
}
public FirstLevel(int a, SecondLevel secondLevel) {
this.a = a;
this.secondLevel = secondLevel;
}
@Override
protected FirstLevel clone() throws CloneNotSupportedException {
// return ((FirstLevel) super.clone()); // a处
FirstLevel firstLevel = (FirstLevel) super.clone(); // 调用父类的clone()方法
// 对SecondLevel进行深拷贝不是FirstLevel的职责,所以不应该写在这里,将其放到SecondLevel中。
/*SecondLevel secondLevel = new SecondLevel();
secondLevel.b = firstLevel.secondLevel.b;
secondLevel.s = new String(firstLevel.secondLevel.s);
firstLevel.secondLevel = secondLevel;
return firstLevel;*/
// 正确写法
firstLevel.secondLevel = secondLevel.clone(); // 进行深拷贝
return firstLevel;
}
public static void main(String[] args) throws CloneNotSupportedException {
SecondLevel secondLevel = new SecondLevel(20, "java");
FirstLevel firstLevel = new FirstLevel(10, secondLevel);
FirstLevel clone = firstLevel.clone();
System.out.println(clone.secondLevel == firstLevel.secondLevel); // false 深拷贝
System.out.println(clone.secondLevel.s == firstLevel.secondLevel.s); // false 深拷贝
// 如果clone()方法只有a处一行代码,则上面两行都打印true。
}
}
SecondLevel:
public class SecondLevel implements Cloneable {
int b;
String s;
public SecondLevel() {
}
public SecondLevel(int b, String s) {
this.b = b;
this.s = s;
}
@Override
protected SecondLevel clone() throws CloneNotSupportedException {
SecondLevel secondLevel = (SecondLevel) super.clone(); // 调用父类clone()方法
secondLevel.s = new String(s); // 进行深拷贝
return secondLevel;
}
}
总结: 浅拷贝直接调用父类的clone()方法即可,即return super.clone()
,深拷贝需要对每一级的引用都要重新创建对象,但是最开始仍然是先调用父类的clone方法。
思考: 为什么最开始都要使用super.clone()
?
Object中的clone()方法是native修饰的,底层是C/C++代码实现,执行的时候使用了RTTI(run-time type identification)机制,动态地找到目前正在调用clone()方法地那个引用,根据它的大小申请内存空间,然后进行bitwise复制,将该对象的内存空间完全复制到新的空间中去,从而达到浅拷贝的目的。所以调用super.clone()
得到的是当前调用类的副本,而不是父类的副本。
// 实现FirstLevel深拷贝(三级深拷贝)
public class FirstLevel {
int i;
int j;
SecondLevel sec;
public FirstLevel(int i, int j, SecondLevel sec) {
this.i = i;
this.j = j;
this.sec = sec;
}
}
class SecondLevel {
double j;
ThirdLevel third;
public SecondLevel(double j, ThirdLevel third) {
this.j = j;
this.third = third;
}
}
class ThirdLevel implements Cloneable{
int k;
public ThirdLevel(int k) {
this.k = k;
}
}
FirstLevel:
public class FirstLevel implements Cloneable {
int i;
int j;
SecondLevel sec;
public FirstLevel(int i, int j, SecondLevel sec) {
this.i = i;
this.j = j;
this.sec = sec;
}
@Override
protected FirstLevel clone() throws CloneNotSupportedException {
FirstLevel clone = (FirstLevel) super.clone();
clone.sec = sec.clone();
return clone;
}
}
SecondLevel:
public class SecondLevel implements Cloneable {
double j;
ThirdLevel third;
public SecondLevel(double j, ThirdLevel third) {
this.j = j;
this.third = third;
}
@Override
protected SecondLevel clone() throws CloneNotSupportedException {
SecondLevel clone = (SecondLevel) super.clone(); // 最开始都调用父类的clone()
clone.third = third.clone();
return clone;
}
}
ThirdLevel:
class ThirdLevel implements Cloneable {
int k;
public ThirdLevel(int k) {
this.k = k;
}
@Override
protected ThirdLevel clone() throws CloneNotSupportedException {
// 没有引用数据类型,直接返回父类的clone()方法。
return ((ThirdLevel) super.clone());
}
}
Proj2(测试):
public class Proj2 {
public static void main(String[] args) throws CloneNotSupportedException {
// 测试结果
ThirdLevel thirdLevel = new ThirdLevel(30);
SecondLevel secondLevel = new SecondLevel(20, thirdLevel);
FirstLevel firstLevel = new FirstLevel(10, 11, secondLevel); // 原对象
FirstLevel clone = firstLevel.clone(); // 拷贝对象
System.out.println(firstLevel.sec == clone.sec); // false
System.out.println(firstLevel.sec.third == clone.sec.third); // false
}
}