终极基类Object
在Java中Object是所有类的基类,因此熟悉这个类的方法和属性当然是十分重要的。
before learning:
native关键字:在Object类中,有很多方法都使用了native关键字,用这个关键字修饰的方法表示该方法的函数体是由其他语言实现的,实现的位置被内置到JVM中了,更加详细的说明参见:
http://blog.csdn.net/jiakw_1981/article/details/3073613
1、protected Object clone() :创建和返回该类的一个拷贝;
这个函数是一个native函数,自己创建的类必须实现cloneable接口,否则调用clone()时会出现CloneNotSupportedException();
拷贝到底是一个怎样的拷贝?
一般来说对象object的拷贝满足:
a、object.clone()!=object; // 拷贝的地址(姑且叫地址吧,更准确的可以叫对象号)和对象不一样
b、object.clone().getClass()==object.getClass(); // 拷贝的类型和对象的类型完全一致
c、object.clone().equal(object) is true; // equal()函数的返回值为true
更一般的来说,拷贝有浅拷贝和深拷贝之分。
首先,什么样的拷贝才是深拷贝?
深拷贝使得拷贝和原始对象完全不相关,即不能通过拷贝来修改原始对象的任何属性。
一个对象可能会有多层的引用结构,当对某一层的引用所指向的对象复制后,拷贝对象则至少在该层和原始对象相互独立,如果上层类实现了clone()函数,则深度拷贝在下一层的实现将会十分方便(通俗的说,如果想对某个类进行深度拷贝,而该类中的类成员变量都实现了clone()方法,则该深度拷贝是容易的)。
上图中,body1的head对象和body2的head对象是相互独立的,也就是说可以修改body1中的head对象的成员变量(不包括类成员引用所指向的对象)而不对body2造成影响。
一般化的实现代码:
Eye.java
public class Eye implements Cloneable{
String color;
protected Object clone() throws CloneNotSupportedException {
Eye newEye = (Eye) super.clone();
newEye.color = this.color;
return newEye;
}
}
Head.java
public class Head implements Cloneable{
Eye eye;
public Head(Eye eye) {
this.eye = eye;
}
protected Object clone() throws CloneNotSupportedException {
Head head = (Head) super.clone();
head.eye = (Eye) eye.clone();
return head;
}
}
Body.java
public class Body implements Cloneable{
Head head;
public Body(Head head) {
this.head = head;
}
protected Object clone() throws CloneNotSupportedException {
Body body = (Body) super.clone();
body.head = (Head) head.clone();
return body;
}
public static void main(String[] args) {
Body b1 = new Body(new Head(new Eye()));
Body b2 = null;
try {
b2 = (Body) b1.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(b1==b2);
System.out.println(b1.head==b2.head);
System.out.println(b1.head.eye==b2.head.eye);
}
false
false
false
Eye、Head、Body均实现了clone()方法,所以其深度拷贝的实现很容易,如果Eye和Head均没有实现clone()函数,那么在实现Body的深度拷贝时,则必须对每一层的对象(Head和Eye,注意此时,这两个对象都必须初始化)都进行拷贝。
public class Body2 {
Head head;
public Body2(Head head) {
this.head = head;
}
protected Object clone() {
Body2 body = new Body2(new Head(new Eye()));
return body;
}
public static void main(String[] args) {
Body2 b1 = new Body2(new Head(new Eye()));
Body2 b2 = null;
b2 = (Body2) b1.clone();
System.out.println(b1==b2);
System.out.println(b1.head==b2.head);
System.out.println(b1.head.eye==b2.head.eye);
}
}
false
false
false
注意数组对象实现的拷贝是浅拷贝,也就是只拷贝了引用,而没有拷贝引用所指向的内容。
2、public boolean equal(Object obj)和public native int hashcode()
equal()方法的意义很明显,就是判断两个类是否相等:Object类中该方法的返回值和“==”的返回值相同,也就是判断两个引用是否指向同一块内存
一般equal()方法的返回值需要满足一下5个条件:
a, 自反性:x.equal(x) is true;
b, 对称性:当仅当y.equal(x) is true时,x.equal(y) is true;
c, 传递性:x.equal(y) is true, y.equal(z) is true, 则 x.equal(z) is true;
d, 稳定性:无论何时调用equal()方法,其值都应该保持不变;
e, 非空性:x.equal(null) is false;
Object类的hashcode()方法的返回值就是对象地址的一个hash值,如果没有特殊的要求,这个并不是由java语言实现的方法并不需要修改,修改hashcode()方法时需要满足下面三个条件:
a, 稳定性:
b, equal()方法返回true的两个对象,则hashcode()方法返回值必须相等;
c, hashcode()返回值相等时,并不要求equal()的返回值也相等;
条件b和条件c其实是为了在使用hashtable或者hashmap时,为了快速的判断两个键值是否相同时,会直接调用类的hashcode()方法,然后在hashcode()方法返回值相等的情况下调用equal()方法来作为返回值。也就是说,如果equal()方法的返回值为true,而hashcode()的返回值却不相等,则键值判断为不相等。为了避免这种情况,故而要求条件b和c。
3、public String toString()
返回一个类的描述,一般推荐每个子类都override这个方法。
Object默认的返回值是 getClass().getName()+’@’+Integer.toHexString(hashcode())
4、public final native Class<> getClass()
a, 该函数是一个final方法,所以其子类无法override这个方法;
b, 该方法返回的是一个Class对象,表示该对象的运行时类型;
有个尚未弄明白的地方,见如下代码
Base.java
public class Base {
public Base() {}
public void pri() {
// TODO Auto-generated method stub
System.out.println(“Base”);
}
}
Inherit.java
public class Inherit extends Base{
public Inherit() {
System.out.println(super.getClass().toString());
super.pri();
}
public void pri() {
System.out.println(“Inherit”);
}
public static void main(String[] args) {
Inherit in = new Inherit();
}
}
输出:
class 继承.Inherit
Base
对于getClass()方法来说,super关键字的使用似乎不起作用。
5、public final native wait()、public final native notify()、public native final notifyAll()
这三个函数都和多线程相关:
wait(): 当前线程进入等待状态,直到其它线程调用该对象的notify()或者notifyAll()方法;
notify(): 唤醒等待该对象锁的一个线程,具体是哪个线程由具体实现决定(程序员不可控);
notifyAll(): 唤醒等待该对象锁的所有线程;
下面给出两个简单的例子:
例一:2个线程循环打印
两个线程循环打印比较简单,只需要创建两个相同对象锁的线程,并相互交替唤醒和等待即可:
public class MyThread implements Runnable{
Object lock;
String pri;
public MyThread(Object lock, String pri) {
this.lock = lock;
this.pri = pri;
}
@Override
public void run() {
// TODO Auto-generated method stub
int t = 0;
boolean hungry = true;
while (hungry) {
hungry = t<10;
synchronized (lock) {
System.out.println(pri); // 本狗已吃
try {
lock.notify(); // 通知另一条狗吃
if (hungry) lock.wait(); // 如果还没吃饱,等待着
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t++;
}
}
}
public static void main(String[] args) {
Object chaint = new Object();
Thread dog1 = new Thread(new MyThread(chaint, "Dog1 eat meat"));
Thread dog2 = new Thread(new MyThread(chaint, "Dog2 eat meat"));
dog1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
dog2.start();
}
}
例二:3线程循环打印
三个线程交替打印相比于两个线程来说要复杂些,一个对象锁已经不能够满足条件,需要使用三个对象锁,对象锁和线程的关系如下图:
可以看出,线程A、线程B和线程C是交替上锁的,线程A会首先获得锁1和锁2,此时,线程B和线程C均处在等待状态,接着线程A首先释放锁2,线程B获得该锁,接着获得锁3,同时线程A释放锁1,唤醒等待锁1的所有线程并同时等待锁1,线程C获得锁1,并等待锁3,然后线程B释放锁3,线程C获得锁3,紧接着获得锁1,线程B释放锁2,并唤醒等待锁2的所有线程,线程C释放锁3和锁1,并唤醒等待锁1的所有线程,线程A重新开始。
实际上这和两个线程的情况类似,也是一种交替唤醒的方法:线程1唤醒线程2->线程2唤醒线程3->线程3唤醒线程1,循环不断。
public class MyThread implements Runnable{
Object lock1;
Object lock2;
String pri;
public MyThread(Object lock1, Object lock2, String pri) {
this.lock1 = lock1;
this.lock2 = lock2;
this.pri = pri;
}
@Override
public void run() {
// TODO Auto-generated method stub
int t = 0;
boolean hungry = true;
while(hungry) {
hungry = t<10;
try {
synchronized (lock1) {
synchronized (lock2) {
System.out.println(pri);
lock2.notifyAll();
//lock2.wait();
}
lock1.notifyAll();
if (hungry) lock1.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t++;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Object lock1 = new Object();
Object lock2 = new Object();
Object lock3 = new Object();
Thread t1 = new Thread(new MyThread(lock1, lock2, "T1"));
Thread t2 = new Thread(new MyThread(lock2, lock3, "T2"));
Thread t3 = new Thread(new MyThread(lock3, lock1, "T3"));
try {
t1.start();
Thread.sleep(10);
t2.start();
Thread.sleep(10);
t3.start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
对于4个线程的交替打印,线程锁的设置应该如下:
对于n个线程的交替打印,线程锁的设置递推。
6、protected void finalize()
a, Object类的这个方法为直接返回,没有进行任何操作;
b, 当垃圾回收器发现没有引用指向该对象(所有的线程均无法访问该对象)时,可能会调用该方法。该方法也可以使得该对象重新获得引用,但一般来说子类overrride这个方法来清理一些本地方法分配的内存或者资源(如内嵌的C/C++代码);
c, Java保证当某一个线程调用finalize()方法时,该方法不会获得任何用户可见的同步锁;
d, 如果该方法抛出异常,则该异常忽略,finalize()方法终止;
e, 该方法最多调用一次;
finalize()方法的小妙用:若一个对象在彻底回收前,某些状态是确定的(若是其他状态则说明产生了错误),那么可以在finalize()方法中检查这些状态,如果这些状态不正确,则说明有错误产生。