Java中存在最底层的类,所有类都直接或者间接的继承java.lang.Object
。如果在代码中,没有明确声明该类有继承的父类,那这个类的父类就是Object。
Object可以作为任何类型的引用变量
Object obj = new Employee();
Object[] obj = new Object[10]; obj[0] = 1; obj[1] = 'a'; obj[2] = null; obj[3] = true;
当然Object也提供了许多方法供子类去继承使用,如
protected native Object clone()throws CloneNotSupportedException;
顾名思义就是复制,查阅API,clone就是复制对象,分配一个与源对象空间大小一样的对象。
clone创建对象与new操作符不同,使用new,根据类型分配内存,调用构造,填充对象的各个域,再返回构造,对外发布引用,然后外部就可以操作这个对象。clone的第一步也是分配内存,但它是根据源对象分配内存,分配的内存与源对象相同,再根据源对象的各个域填充新对象的域,clone返回这个新对象,把新对象的引用发布到外部。
Employee alice = new Employee(); Employee alice2 = alice;
在程序中分别打印这两个对象的地址值会发现他们的输出是一模一样。
既然地址都是相同的,那两个对象是同一个对象,无论修改alice或alice2中的属性,最后获取的属性值都是一样的,这就是引用复制,任何一方的操作都会影响到另一方。他们指向的都是同一个地址,只是引用名不同,但对象是同一个。
Employee alice = new Employee(); Employee bob = alice.clone();
从程序的打印结果可以看出,这两个不是同一个对象,因为他们打印的地址值不同,也就是clone是重新创建了一个与alice一模一样的内存空间,引用之间互不影响。指向不同的地址,对象也不同。
当一个对象中含有基本数据类型和引用数据类型属性时,如果要复制这个对象,基本数据类型会直接拷贝过去,但引用数据类型会有两种情况。一种是将源对象中引用类型的引用值也就是地址值拷贝到新对象中的引用类型里;而另一种则是根据这个引用创建一个新的相同内容的对象,将这个新对象的地址值赋值给新的引用类型。
前者是浅克隆,后者是深克隆。而默认实现的clone()
是属于前者
浅克隆就是把源对象的基本数据都拷贝过来,但对源对象中的引用类型则是拷贝它所指向的地址值;而深克隆则是把源对象中的引用类型指向的对象也拷贝一份,将拷贝后对象的地址值赋给克隆对象中的引用类型
如果要实现深克隆,可以重写clone()
,让对象中的每个引用类型都clone()
出一个新的对象,如果每个引用类型的对象中还有引用类型,也要对其进行clone()
,不然就是不彻㡳深克隆。要覆盖并实现clone()
就要实现Cloneable接口
public Body implements Cloneable{ public Head head; public Body(){} public Body(Head head){ this.head = head; } @Override protected Object clone() throws CloneNotSupportedException(){ Body body = (Body) super.clone(); body.head = (Head) head.clone(); return body; } } public class Head implements Cloneable{ public Face face; public Head(){} public Head(Face face){ this.face = face; } @Override protected Object clone() throws CloneNotSupportedException(){ Head head = (Head) super.clone(); head.face = (Face) face.clone(); return body; } } public class Face implements Cloneable{ @Override protected Object clone() throws CloneNotSupportedException(){ return super.clone(); } } main: Body body01 = new Body(new Head(new Face())); Body body02 = body.clone(); body01 == body02 //false body01.head == body02.head //false body01.head.face == body02.head.face //false
如果Face中还有引用类型还要继续在clone中对这些引用类型拷贝直到引用链的最后一个,这才算是深克隆。如果在引用的对象没有实现
clone()
那么在这个对象之后的所有引用的对象都是被共享的。
cloneable是标记接口,实现这个接口后才能重写clone()
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
Object的toSting方法返回的是一个代表对象值的字符串。格式是:类名,一个@符号,一串代表这个对象的哈希码由无符号十六进制组成。
如果子类去重写这个方法,会将这个格式改成:类名,一对方括号[]
,方括号里是域值
public class Employee{ public String name; public int salary; public Date hireDay; //coed @Override public String toString(){ return getClass().getName() +"[name="+name +",salary="+salary +",hireDay="+hireDay +"]"; } }
main: Employee emp = new Employee(); System.out.println(""+emp);//Employee[name=null,salary=0,hireDay=null]
只要对象与字符串通过
+
操作符连接起来,编译器就会自动调用它的toString()
方法。
public final native Class<?> getClass();
返回些对象的运行时类。
main: Fase sons = new Sons(); System.out.println(sons.getClass);//class com.base.Sons Fase fase = new Fase(); System.out.println(fase.getClass);//class com.base.Fase System.out.println(sons.getClass() == Face.class);//false System.out.println(sons.getClass() == Sons.class);//true
对象可能被声明为超类型,而实际上它是所声明的子类型的实例,运行时类代表的是被实例(new)的类。
getClass
方法一般配合getName
方法使用obj.getClass().getName()
,用于获取全类名(类名包含包的名字:com.base.Fase
)
protected void finalize() throws Throwable { }
java有垃圾回收机制,也叫GC(Garbage Collection)。当一个堆空间中的对象没有被栈空间变量指向的时候,这个对象会等待被java回收。在GC回收对象前,会先调用其finalize方法,并在下一次回收动作发生时,回收上一个对象。
当对象不再被程序所使用,GC会把他回收;GC是在后台运行的,我们无法主动去叫它回收对象,只有告诉他尽可能早点回收对象;当GC准备回收对象时,会先调用其finalize
方法;GC只与内存有关联,使用GC为的是回收程序不再使用的内存
注,在jdk9以后这个方法就过时了,被其他的方法代替
public boolean equals(Object obj) { return (this == obj); }
equals方法用于检测对象与另一个对象是否相等。如果相等那么他们引用的是同一块内存,内容、属性相等不代表他们会是同一块内存
对于比较两个对象引用是相等完全没有意义。在开发中,经常需要检测两对象状态的相等性,如果两对象状态相等,那就认为两个对象是相等的。
public class Employee{ //code @Override public boolean equals(Object obj){ if(this == obj) return true; if(null == this) return false; if(getClass() != obj.getClass()) return false; Employee emp = (Employee) obj; return Object.equals(this.name, emp.name) && this.salary == emp.salary && Object.equals(this.hireDay, emp.hireDay); } }
因为name和hireDay是引用类型,有可能为null。所以需要用
Object.equals()
做比较
子类在定义equals时,首先调用父类的equals检测,如果检测失败,则对象不相等。如果检测成功,再与子类的域比较
public class Employee extends Parson{ public double salary; public LocalDate hireDay; @Override public boolean equals(Object o) { if (this == o) return true; if (getClass() != o.gtClass()) return false; if (!super.equals(o)) return false;//调用父类的equals检测 Employee employee = (Employee) o; return Double.compare(employee.salary, salary) == 0 && hireDay.equals(employee.hireDay); } }
obj.equals(obj)
应该返回true
x.equals(y)
返回true时,y.equals(x)
也应该返回true
x.equals(y)
返回true,y.equals(z)
也返回true
,x.equals(z)也应该返回true
x.equals(y)
应该返回同样的结果x.equals(null)
应该返回false
如果用instanceof对子类对象检测,则违反了对称性。
显性参数命名为otherObject
比较this与参数otherObject,是否引用同一对象。if(this == otherObject) return true;
检测参数otherObject是否为null。if(null == otherObject) return false;
比较是否为同一个类getClass()
。if(getClass() != otherObject.getClass()) return false;
把参数转换为当前类类型变量,再对所有要比较的域做比较
public boolean equals(Object otherObject) { if(this == otherObject) return true; if(null == otherObject) return false; if(getClass() != otherObject.getClass()) return false; Classname other = (Classname) otherObject; return field1 == other.field1 && Object.equals(field2, other.field2) && ...; }
在最后比较的域时,如果是引用类型,则用
Object.equals()
比较;如果是数组类型的,用Arrays.equals()
比较如果是子类定义equals,就要再加上
super(otherObject)
public native int hashCode();
hash code散列码,是由对象导出的一个整型值。散列码没有规律,如果两个不同引用的对象分别调用hashCoed,返回的散列码原则上不会相同
一般对两对象检测equals返回的是true,那么可以推出他们的hashCode是相等(==)的,反之则不行
如果重写了equals,hashCode一般也会重新定义
public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }
public final void wait() throws InterruptedException { wait(0); }
public final native void wait(long timeout) throws InterruptedException;
wait方法有三种重载,调用方法使当前线程等待,直到另一个线程调用此对象的notify()
方法或notifAll()
方法,或其他一些线程中断当前线程,或一定量的实时时间(timeout)
IllegalArgumentException
-- 如果超时值timeout为负数,或毫微秒值nanos不在0-999999范围内IllegalMonitorStateException
-- 如果当前线程不是此对象的监视器的所有者InterruptedException
-- 如果任何线程在当前线程等待通知之前或当前线程中断当前线程。 当抛出此异常时,当前线程的中断状态将被清除。public final native void notify();
用于唤醒正在等待对象监视器的单个线程,如果该对象上有多个线程在等待,随机唤醒单个。
public final native void notifyAll();
唤醒该对象上等待的所有线程