Object有趣的几个方法

有趣但引人深入思考的Java知识点之Object几个方法

学Java有一个多月了,但是还没有好好的系统的去看API,都是需要什么方法就去学一下。在阿里的直播上看到孤尽老师的直播,感觉这个问题提出的很有趣。之前在学习之中,没有在乎Object类的几个方法。在后面的集合和String的学习中,突然发现hashcode()方法很是重要,而且在封装数据类型Integer中发现比较范围只在-128-127之间,愈发感觉源码的重要性,系统的学习要从底层来,那我就先回去一下下,从顶级父类Object的几个方法看看,感觉在后面很是重要,就我目前学到的序列来说,感觉equals()、hashcode()和toString()方法很重要。

从Java中最基本的类java.lang.Object开始谈起。

Object 是类层次结构的根类。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。

构造方法:

Object()

方法

  • clone() 创建并返回此对象的一个副本

  • equals(Object obj) 指示其他某个对象是否与此对象相等

  • finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

  • getClass() 返回此 Object 的运行时类。

  • hashCode() 返回该对象的哈希码值。

  • notify() 唤醒在此对象监视器上等待的单个线程。

  • notifyAll() 唤醒在此对象监视器上等待的所有线程。

  • toString() 返回该对象的字符串表示。

  • wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。

  • wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。

  • wait(long timeout, int nanos) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。


源码:

Java.long.Object



/*

 * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.

 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

 */



package java.lang;



/**

 * Class {@code Object} is the root of the class hierarchy.

 * Every class has {@code Object} as a superclass. All objects,

 * including arrays, implement the methods of this class.

 *

 * @author  unascribed

 * @see java.lang.Class

 * @since   JDK1.0

 */

public class Object {



    private static native void registerNatives();

    static {

        registerNatives();

    }

    public final native Class<?> getClass();

    public native int hashCode();

    public boolean equals(Object obj) {

    return (this == obj);

    }

    protected native Object clone() throws CloneNotSupportedException;

    public String toString() {

        return getClass().getName() + "@" + Integer.toHexString(hashCode());

    }

    public final native void notify();

    public final native void notifyAll();

    public final native void wait(long timeout) throws InterruptedException;

    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 >= 500000 || (nanos != 0 && timeout == 0)) {

            timeout++;

        }



        wait(timeout);

    }

    public final void wait() throws InterruptedException {

        wait(0);

    }

    protected void finalize() throws Throwable { }

}

首先,我们必须要了解的是代码块、成员变量和构造方法之间的先后执行顺序。

Code类:

public class Code {

    String str = "我是成员变量。。。";



    {

        System.out.println("我是代码块。。。");

    }



    public Code(){

        System.out.println("我是构造方法,并执行了" + str);

    }



}

TestCode类:

public class Test {



    public static void main(String[] args) {



        new Code();



    }



}

结果:

我是代码块。。。



我是构造方法,并执行了我是成员变量。。。

显而易见,代码块和成员变量在构造方法之前就执行了。


`然后开始说说Object的有趣的几个方法

1. static代码块和native方法

在Java中代码块是在构造器初始化之前进行的,恰在这里也进行了验证。在对象初始化时,自动调用了Object中的代码块,注册了本地方法,这样在后面的native方法执行中,系统就可以调用本地方法了。

static{ 

    registerNatives();

}

其本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用。

private static native void registerNatives();

public final native Class<?> getClass();

public native int hashCode();

protected native Object clone() throws CloneNotSupportedException;

public final native void notify();

public final native void notifyAll();

public final native void wait(long timeout) throws InterruptedException;

2. 类构造器Object()

大部分情况中,Java中都是new一个对象,这种途径都是通过类中的构造器完成的。为了体现这一特性,Java中规定:在类定义过程中,对于未定义构造函数的类,默认会有一个无参数的构造函数,作为所有类的基类,Object类自然要反映出此特性,在源码中,未给出Object类构造函数定义,但实际上,此构造函数是存在的。

当然,并不是所有的类都是通过此种方式去构建,也自然的,并不是所有的类构造函数都是public。

3. registerNatives()

private static native void registerNatives();

static {

    registerNatives();

}

其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。函数的执行是在静态代码块中执行的,在类首次进行加载的时候执行。

4. getClass()

public final native Class<?> getClass();

getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class<>。效果与Object.class相同。该方法被final修饰,不能被重写。

5. clone()

protected native Object clone() throws CloneNotSupportedException;

其是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法。clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。

【顺便我们来理解一下protect权限修饰符】

Object a = new Object();    

Object b = a.clone();

当写下这样的代码时,在编译时期eclipse就已经提示错误,运行系统给的提示是:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 

    The method clone() from the type Object is not visible

clone()方法不存在。回到Object类中clone()方法的定义,可以看到其被声明为protected,估计问题就在这上面了,protected修饰的属性或方法表示:在同一个包内或者不同包的子类可以访问。显然,Object类与ObjectTest类在不同的包中,但是ObjectTest继承自Object,是Object类的子类,于是,现在却出现子类中通过Object引用不能访问protected方法,原因在于对”不同包中的子类可以访问”没有正确理解。

看这里:

 * @return a clone of this instance.

 * @exception  CloneNotSupportedException  if the object's class does not

 *   support the {@code Cloneable} interface. Subclasses

 *   that override the {@code clone} method can also

 *   throw this exception to indicate that an instance cannot

 *   be cloned.

 * @see java.lang.Cloneable

源码中给的注释写到,必须实现Cloneable接口

public class Test implements Cloneable{



    public static void main(String[] args) throws CloneNotSupportedException {



        Test a = new Test();



        Test b = (Test)a.clone();



    }



}

6. equals()

public boolean equals(Object obj) {

    return (this == obj);

}

==和equals在Java中经常被使用,区别也是挺大的。前者(==)是变量内容的比较,值应该更为恰当,值相同,就为true。后者(equals)则不同,返回this==obj的值,在对象的初始化中,会在堆和栈中进行开辟空间和存储变量或对象,如下图中:

equals在比较的过程中,实际上比较的是对象所指向的地址,若不是一个对象,则其实不相等的,返回false。

正好用前面的clone(),举个栗子。

public class Test implements Cloneable{



    public static void main(String[] args) throws CloneNotSupportedException {



        Test a = new Test();



        Test b = (Test)a.clone();



        System.out.println(a);//默认调用了toString方法打印其hashCode码,不是真实的内存地址

        System.out.println(b);//同上



        System.out.println("a==b?------>"+a.equals(b));



    }



}

结果:

demo1.Test@1774b9b

demo1.Test@104c575

a==b?------>false

其clone()之后,只是复制了对象,而不是重新让对象名换了指向,其是两个完全不同地址的内容相同对象。

在后面的学习中,也有很多地方都是需要重写equals方法,例如我们想创建了两个Student对象,分别为

Student A = new Student("KangKang",12); 

Student B = new Student("Kangkang",12);

在调用默认的equlas()方法的时候,系统返回的A指向的堆地址和B指向的堆地址值,显然他们是不用的,但是实际生活中,我们认为二者是相同的。这也就需要我们重写equals()方法,我们需要比较姓名和年龄是不是一样,如果一样,我们认为是同一个对象,返回true。

public boolean equals(Object obj) {

    return (this.name == obj.name && this.age == obj.age);

}

这样,就符合了实际生活中的需要了。接下来说说toString,这个方法也是在很多地方需要重写。

7. toString()

public String toString() {

    return getClass().getName() + "@" + Integer.toHexString(hashCode());

}

在实际的开发中,这个toString()大多都被重写了。例如在一个类中,我们需要查看类的属性,我们希望toString()可以打印出其属性,已方便我们来进行调试错误,查看属性。

8. hashCode()

public native int hashCode();

hashCode()方法返回一个整形数值,表示该对象的哈希码值。

以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。

在此需要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。

所以,大多的时候,在重写equals()方法后,需要重写hashCode()方法,在孤尽老师的阿里巴巴Java规范书中也是写到了,真的是敲细心。

放出书籍中的截图(建议大家买一本看,真的很好,书籍不大不厚,一本规范,夜晚睡觉看一看,然后再去阿里云大学上考一个阿里巴巴Java规范证书,去年一出来考试,我就考了,还是挺难的,考了三次才过,而且那时候才大二,整本书就只学了基础Java到线程,数据库什么根本不知道,疯狂网上看博客,看了好几天,眼睛都快瞎了。孤尽老师说如果深刻理解了这本书,最低可以到阿里的二面,不过这些我也是可望不可及,悄悄的看着老师说话,哈哈哈哈哈哈,很满足,现在就是慢慢修炼吧,等待老师的下一本书)

敲黑板!在集合处理中,只要重写了equals,就必须重写hashCode,至于原因,老师说的很清楚。


9. finalize()

protected void finalize() throws Throwable { }

Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为,其具体调用时机在:JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。由此可以看出,此方法并不是由我们主动去调用的(虽然可以主动去调用,此时与其他自定义方法无异)。

finalize方法主要与Java垃圾回收机制有关。

emmmmmmmmm…..深入理解JVM第一遍还没有看完,虽然已经看了垃圾回收机制,但是吧。。。完全没看懂!!!等我看第二遍的时候再回来补上 >_<


10. wait(…) / notify() / notifyAll()

wait():调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。

wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量。

notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程。

wait(…) / notify() | notifyAll()一般情况下都是配套使用。

emmmmmmmmm…..我还没学到线程,等我学到了线程再回来补上 >_<

猜你喜欢

转载自blog.csdn.net/xiaheshun/article/details/79843070