终极基类Object

终极基类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()方法中检查这些状态,如果这些状态不正确,则说明有错误产生。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是Object和Box的定义: ```c++ class Object { protected: double weight; public: Object(double w = 0) : weight(w) { cout << "Object constructor called." << endl; } virtual ~Object() { cout << "Object destructor called." << endl; } void setWeight(double w) { weight = w; } double getWeight() const { return weight; } }; class Box : public Object { private: double height; double width; public: Box(double h = 0, double w = 0, double weight = 0) : Object(weight), height(h), width(w) { cout << "Box constructor called." << endl; } ~Box() { cout << "Box destructor called." << endl; } void setHeight(double h) { height = h; } void setWidth(double w) { width = w; } double getVolume() const { return height * width * getWeight(); } }; ``` 以下是主函数中的代码: ```c++ int main() { Box b(2, 3, 4); cout << "Box weight: " << b.getWeight() << endl; cout << "Box volume: " << b.getVolume() << endl; return 0; } ``` 构造函数和析构函数的调用顺序如下: ``` Object constructor called. Box constructor called. Box weight: 4 Box volume: 24 Box destructor called. Object destructor called. ``` 可以看到,先调用Object的构造函数,再调用Box的构造函数,然后执行主函数中的代码,最后先调用Box的析构函数,再调用Object的析构函数。这与派生继承基类的构造函数和析构函数的执行顺序相关。在创建派生对象时,先调用基类的构造函数,再调用派生的构造函数;在销毁派生对象时,先调用派生的析构函数,再调用基类的析构函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值