Java Object 类方法解析

本文标题大纲:

前言

我们都知道 Java 语言是面向对象的编程语言,而面向对象编程以类作为基本单元。我们也都知道,在 Java 中,所有的类都将 Object 类作为父类,而 Object 类本身提供了一些基础但是很有用的方法,这些方法我们在日常工作中经常会用到,因此熟悉它们的原理和用法对我们的开发会有很大的帮助,下面我们来一起看一些这些方法:

getClass

这个方法用来动态的获取当前对象的类型信息,我们看看这个方法的源码声明:

/**
 * Returns the runtime class of this {@code Object}. The returned
 * {@code Class} object is the object that is locked by {@code
 * static synchronized} methods of the represented class.
 *
 * <p><b>The actual result type is {@code Class<? extends |X|>}
 * where {@code |X|} is the erasure of the static type of the
 * expression on which {@code getClass} is called.</b> For
 * example, no cast is required in this code fragment:</p>
 *
 * <p>
 * {@code Number n = 0;                             }<br>
 * {@code Class<? extends Number> c = n.getClass(); }
 * </p>
 *
 * @return The {@code Class} object that represents the runtime
 *         class of this object.
 * @jls 15.8.2 Class Literals
 */
public final native Class<?> getClass();

这是一个 final 类型的 native 方法,也就是说这个方法不能被子类重写,同时它的实现并不是通过 Java 语言实现的,而是用其他语言(C/C++)实现的,我们得到某个对象的类型信息(Class 类的对象)之后,我们就可以利用 Java 反射的机制做很多事情了,比如有以下代码:

public class Main {

    static class People {
        String name;

        public People(String name) {
            this.name = name;
        }

        public void work() {
            System.out.println(name + " is working!");
        }
    }

    private static void startTest() {
        People p1 = new People("指点");
        p1.work();
        // 获取 p1 对象的类型信息,在这里编译器只知道 p1 是 People 类型的对象的引用,
        // 但是编译器并不知道具体是 People 类型的对象还是 People 的子类对象(虽然在这里并没有 People 的子类),
        // 所以我们这里用泛型必须加类型边界限定符,
        // 当然也可以不加,这样就代表这是一个任意类型的 Class 对象
        Class<? extends People> cp = p1.getClass();
        People p2 = null;
        try {
            // 获取 cp 代表的 Class 对象的具有一个 String 类型的构造方法,
            // 再使用这个构造方法新建一个对象并将 p2 引用指向这个对象,
            // 即为通过反射的方式创建对象
            p2 = cp.getConstructor(String.class).newInstance("另一个人");
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (p2 != null) {
            p2.work();
        }
    }

    public static void main(String[] args) {
        startTest();
    }
}

在这里我们获取了 p1 的类型信息之后利用反射新建了一个 People 对象,并调用了它的 work() 方法,我们来看看结果:
这里写图片描述
Ok,其实得到了一个对象的 Class 类型对象之后我们能做的事情有很多(新建对象,调用方法,甚至访问类私有属性/方法…)。当然这些都是 Java 反射的内容,有兴趣的小伙伴可以查找相关资料。

hashCode

这个方法算是相对比较常见的一个方法了,我们看看它的源码声明:

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * <p>
 * The general contract of {@code hashCode} is:
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 * <li>If two objects are equal according to the {@code equals(Object)}
 *     method, then calling the {@code hashCode} method on each of
 *     the two objects must produce the same integer result.
 * <li>It is <em>not</em> required that if two objects are unequal
 *     according to the {@link java.lang.Object#equals(java.lang.Object)}
 *     method, then calling the {@code hashCode} method on each of the
 *     two objects must produce distinct integer results.  However, the
 *     programmer should be aware that producing distinct integer results
 *     for unequal objects may improve the performance of hash tables.
 * </ul>
 * <p>
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} does return distinct integers for distinct
 * objects. (This is typically implemented by converting the internal
 * address of the object into an integer, but this implementation
 * technique is not required by the
 * Java&trade; programming language.)
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */
public native int hashCode();

同样的,这个方法默认是利用 C/C++ 语言实现的,这个方法可以返回一个对象的哈希值,这个值在一定程度上可以标志一个对象。默认情况下,这个值会和当前对象所在内存中的地址有一定的关系(这取决于 JVM 的实现),当然,我们在子类中可以根据子类的特性选择重写这个方法。而提到 hashCode 方法就不得不提 Java 中的 Map 接口下的相关容器了,因为 Map 接口下的一些容器(HashMapIdentityHashMap)正是通过对象的 hashCode 方法进行工作的,在 HashMap 中,会有一个名为 table 的数组字段,这个数组字段用来储存 HashMap 中每一个键值对关系,即为映射表,每当储存一个新的键值对进入当前的 HashMap 对象的时候,都会调用这个键值对中的 “键” 对象的 hashCode 方法并将其返回的哈希值进行一定的处理,然后将这个结果作为数组的下标并且将要储存的键值对储存在 table 数组的这个下标元素中。当然这种做法可能会产生冲突,即多个键值对储存时得到的下标值相同,关于 HashMap 处理冲突细节,可以参考一下我的下一篇文章。
除此之外,我们还在利用 hashCode 方法时需要注意一些问题:

1、如果两个对象的 hashCode 返回值相同,我们不能直接说它们相等,原因很简单:这个方法本身可以被子类重写,我只需要定义一个类 A 并且重写这个方法然后让它返回一个固定值就行了,这样的话所有 A 类的对象的 hashCode 方法返回值都相同,但实际上它们并不是同一个对象。
2、但是反过来:如果两个对象的 hashCode 值不相等,那么我们就可以判断这两个对象一定不同(不是同一个对象)。这个道理很容易明白:如果两个引用指向同一个对象,那么这两个引用调用的 hashCode 方法一定是同一个对象的 hashCode 方法,同一个对象在同一时刻的 hashCode 方法返回值肯定相同,那么如果两个对象的 hashCode 值不同,那么我们也可以判断这两个对象不等(内存上不是同一个对象)。

那么在我们自定义的类中我们如何去重写这个方法呢,在《Java 编程思想》中提供了一个关于如何在自定义类中编写一个合理的 hashCode 方法的参考:
这里写图片描述
JDK 里面已经提供了工具方法来帮助我们计算一个复杂类对象的 hashCode 值,我们来看看这个方法(java.util.Objects#hash):

/**
    * Generates a hash code for a sequence of input values. The hash
    * code is generated as if all the input values were placed into an
    * array, and that array were hashed by calling {@link
    * Arrays#hashCode(Object[])}.
    *
    * <p>This method is useful for implementing {@link
    * Object#hashCode()} on objects containing multiple fields. For
    * example, if an object that has three fields, {@code x}, {@code
    * y}, and {@code z}, one could write:
    *
    * <blockquote><pre>
    * &#064;Override public int hashCode() {
    *     return Objects.hash(x, y, z);
    * }
    * </pre></blockquote>
    *
    * <b>Warning: When a single object reference is supplied, the returned
    * value does not equal the hash code of that object reference.</b> This
    * value can be computed by calling {@link #hashCode(Object)}.
    *
    * @param values the values to be hashed
    * @return a hash value of the sequence of input values
    * @see Arrays#hashCode(Object[])
    * @see List#hashCode
    */
    public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }

调用了 Arrays.hashCode 方法,我们继续跟进 java.util.Arrays#hashCode(java.lang.Object[])

/**
     * Returns a hash code based on the contents of the specified array.  If
     * the array contains other arrays as elements, the hash code is based on
     * their identities rather than their contents.  It is therefore
     * acceptable to invoke this method on an array that contains itself as an
     * element,  either directly or indirectly through one or more levels of
     * arrays.
     *
     * <p>For any two arrays <tt>a</tt> and <tt>b</tt> such that
     * <tt>Arrays.equals(a, b)</tt>, it is also the case that
     * <tt>Arrays.hashCode(a) == Arrays.hashCode(b)</tt>.
     *
     * <p>The value returned by this method is equal to the value that would
     * be returned by <tt>Arrays.asList(a).hashCode()</tt>, unless <tt>a</tt>
     * is <tt>null</tt>, in which case <tt>0</tt> is returned.
     *
     * @param a the array whose content-based hash code to compute
     * @return a content-based hash code for <tt>a</tt>
     * @see #deepHashCode(Object[])
     * @since 1.5
     */
    public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }

JDK 采用的方案是 (((((res * 31) + element1.hashCode()) * 31 + element2.hashCode()) * 31 + element3.hashCode()) * 31 + ....); 的方案。

当然,这个也仅供我们参考,想要保证决定的 hashCode 不冲突是不可能的,因为 hashCode 的返回值是 int 类型,那么最多有 2^32 个值,如果内存中的对象超过了 2^32 个 。那么必定会有两个或者两个以上对象的 hashCode 值是相同的。

equals

这个方法本意是用来判断两个对象在 “值” 上是否等价的,但是在 Object 类中的默认实现却是判断两个引用是否指向同一个对象,即比较对象的地址。我们来看看这个方法的源码:

/**
 * Indicates whether some other object is "equal to" this one.
 * <p>
 * The {@code equals} method implements an equivalence relation
 * on non-null object references:
 * <ul>
 * <li>It is <i>reflexive</i>: for any non-null reference value
 *     {@code x}, {@code x.equals(x)} should return
 *     {@code true}.
 * <li>It is <i>symmetric</i>: for any non-null reference values
 *     {@code x} and {@code y}, {@code x.equals(y)}
 *     should return {@code true} if and only if
 *     {@code y.equals(x)} returns {@code true}.
 * <li>It is <i>transitive</i>: for any non-null reference values
 *     {@code x}, {@code y}, and {@code z}, if
 *     {@code x.equals(y)} returns {@code true} and
 *     {@code y.equals(z)} returns {@code true}, then
 *     {@code x.equals(z)} should return {@code true}.
 * <li>It is <i>consistent</i>: for any non-null reference values
 *     {@code x} and {@code y}, multiple invocations of
 *     {@code x.equals(y)} consistently return {@code true}
 *     or consistently return {@code false}, provided no
 *     information used in {@code equals} comparisons on the
 *     objects is modified.
 * <li>For any non-null reference value {@code x},
 *     {@code x.equals(null)} should return {@code false}.
 * </ul>
 * <p>
 * The {@code equals} method for class {@code Object} implements
 * the most discriminating possible equivalence relation on objects;
 * that is, for any non-null reference values {@code x} and
 * {@code y}, this method returns {@code true} if and only
 * if {@code x} and {@code y} refer to the same object
 * ({@code x == y} has the value {@code true}).
 * <p>
 * Note that it is generally necessary to override the {@code hashCode}
 * method whenever this method is overridden, so as to maintain the
 * general contract for the {@code hashCode} method, which states
 * that equal objects must have equal hash codes.
 *
 * @param   obj   the reference object with which to compare.
 * @return  {@code true} if this object is the same as the obj
 *          argument; {@code false} otherwise.
 * @see     #hashCode()
 * @see     java.util.HashMap
 */
public boolean equals(Object obj) {
    return (this == obj);
}

如果我们在自定义的类中需要使用到这个方法,或者是我们使用的某些类中需要使用到相关类的 equals 方法(例如将自定义的类型作为 HashMap 对象的 “键”),我们就应该重写这个方法,一般情况下,我们只需要对两个对象中的每一个字段进行比较(如果是字段是引用的话再次调用该字段的 equals 方法)就可以了,例:

public class Main {
    // 车轮类
    static class Wheel {
        int radius;

        public Wheel(int radius) {
            this.radius = radius;
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof Wheel && radius == ((Wheel) obj).radius;
        }
    }

    static class Bike {
        // 车的前后轮
        Wheel frontWheel;
        Wheel backWheel;
        int weight;

        public Bike(Wheel frontWheel, Wheel backWheel, int weight) {
            this.frontWheel = frontWheel;
            this.backWheel = backWheel;
            this.weight = weight;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof Bike)) {
                return false;
            }
            Bike bike = (Bike) obj;
            return frontWheel.equals(bike.frontWheel) &&
                    backWheel.equals(bike.backWheel) &&
                    weight == bike.weight;
        }
    }

    private static void startTest2() {
        Bike bike1 = new Bike(new Wheel(1), new Wheel(2), 10);
        Bike bike2 = new Bike(new Wheel(1), new Wheel(2), 10);
        Bike bike3 = new Bike(new Wheel(2), new Wheel(2), 10);
        Bike bike4 = new Bike(new Wheel(2), new Wheel(2), 15);
        System.out.println("bike1 equals bike2: " + bike1.equals(bike2));
        System.out.println("bike1 equals bike3: " + bike1.equals(bike3));
        System.out.println("bike3 equals bike4: " + bike3.equals(bike4));
    }

    public static void main(String[] args) {
        startTest2();
    }
}

来看看结果:
这里写图片描述
在《Java 编程思想》中关于如何重写一个自定义类的 equals 方法也给出了几条建议供我们参考
这里写图片描述
其实这几条建议在上面方法的注释说明中已经体现出来了。值得一提的是,在 hashCode 方法的注释说明中,有提到关于 hashCode 方法和 equals 方法的关系:
如果一个对象和另一个对象通过 equals 方法判断等价时返回 true ,那么他们的 hashCode 方法应该返回相同的整型值,相反,虽然我们并不要求两个通过 equals 方法判断不相等的对象的 hashCode 方法一定要返回不同的整型值,但是我们要知道将它们的 hashCode 设计成返回不同的整型值在某些场景下会有较好的性能表现(在 HashMap 作为 “键” 的储存中可以减少冲突的次数)。简单点来说就是假设现在有两个同类型的对象 ab,如果 a.equals(b) == false ,那么我们最好使得 a.hashCode() != b.hashCode() 成立。

clone

这个方法用于获取一个当前对象的克隆对象(即对象的复制品)。我们来看一下它的源码:

/**
 * Creates and returns a copy of this object.  The precise meaning
 * of "copy" may depend on the class of the object. The general
 * intent is that, for any object {@code x}, the expression:
 * <blockquote>
 * <pre>
 * x.clone() != x</pre></blockquote>
 * will be true, and that the expression:
 * <blockquote>
 * <pre>
 * x.clone().getClass() == x.getClass()</pre></blockquote>
 * will be {@code true}, but these are not absolute requirements.
 * While it is typically the case that:
 * <blockquote>
 * <pre>
 * x.clone().equals(x)</pre></blockquote>
 * will be {@code true}, this is not an absolute requirement.
 * <p>
 * By convention, the returned object should be obtained by calling
 * {@code super.clone}.  If a class and all of its superclasses (except
 * {@code Object}) obey this convention, it will be the case that
 * {@code x.clone().getClass() == x.getClass()}.
 * <p>
 * By convention, the object returned by this method should be independent
 * of this object (which is being cloned).  To achieve this independence,
 * it may be necessary to modify one or more fields of the object returned
 * by {@code super.clone} before returning it.  Typically, this means
 * copying any mutable objects that comprise the internal "deep structure"
 * of the object being cloned and replacing the references to these
 * objects with references to the copies.  If a class contains only
 * primitive fields or references to immutable objects, then it is usually
 * the case that no fields in the object returned by {@code super.clone}
 * need to be modified.
 * <p>
 * The method {@code clone} for class {@code Object} performs a
 * specific cloning operation. First, if the class of this object does
 * not implement the interface {@code Cloneable}, then a
 * {@code CloneNotSupportedException} is thrown. Note that all arrays
 * are considered to implement the interface {@code Cloneable} and that
 * the return type of the {@code clone} method of an array type {@code T[]}
 * is {@code T[]} where T is any reference or primitive type.
 * Otherwise, this method creates a new instance of the class of this
 * object and initializes all its fields with exactly the contents of
 * the corresponding fields of this object, as if by assignment; the
 * contents of the fields are not themselves cloned. Thus, this method
 * performs a "shallow copy" of this object, not a "deep copy" operation.
 * <p>
 * The class {@code Object} does not itself implement the interface
 * {@code Cloneable}, so calling the {@code clone} method on an object
 * whose class is {@code Object} will result in throwing an
 * exception at run time.
 *
 * @return     a clone of this instance.
 * @throws  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
 */
protected native Object clone() throws CloneNotSupportedException;

值得注意的是如果一个自定义的对象要支持 clone() 方法,那么他必须实现 Cloneable 接口(虽然这个接口没有任何方法),但是实现这个接口的目的只是为了使得让这个类的对象同时也是 Cloneable 类型的对象。来看个例子:

public class Main {

    static class Clone1 {
        int number;

        public Clone1(int number) {
            this.number = number;
        }

        public static void startTest3() {
        Clone1 c1 = new Clone1(1);
            try {
                Clone1 c1Copy = (Clone1) c1.clone();
                System.out.println(c1Copy.number);
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Clone1.startTest3();
    }
}

结果:
这里写图片描述
正如 clone 方法的注释中所说的,对没有实现 Cloneable 接口的类调用 clone 方法是会抛出 CloneNotSupportedException 异常的。那么让我们修改一下,只需修改一行代码:

static class Clone1 implements Cloneable {

我们让 Clone1 类实现了 Cloneable 接口,这个接口的源码如下:

public interface Cloneable {
}

为了突出重点,这里我没有贴这个接口的注释说明,重点是什么呢?它是一个空接口。所以我们在实现这个接口的时候无需重写任何方法。Ok,再来运行一下:
这里写图片描述
这次就成功了!

深拷贝和浅拷贝

对于这个词,我想学过 C++ 的小伙伴肯定非常熟悉,因为 C++ 中有拷贝构造函数的概念。当然,这里是 Java ,我们来看一下深拷贝、浅拷贝和 clone 方法的关系:
浅拷贝:顾名思义,它是一个浅显的复制,我们可以理解成只复制值;
深拷贝:相对于浅拷贝来说,它是一个深入的复制,我们可以理解为它会复制整个对象(建立一个新的对象,新对象中的每一个属性值和旧对象中的每一个属性值都对应相同)。
我们来看一个例子:

Object o1 = new Object();
Object o1Copy = o1;

这是一个典型的浅拷贝的例子,我们只是简单的进行了引用的复制,实现上两个引用还是指向的同一个对象,再看一个深拷贝的例子:

class X {
	int num;
	
	public X(int num) {
		this.num = num;
	}
	
	public int getNum() {
		return num;
	}
}

X x1 = new X(2);
X x1Copy = new X(x1.getNum());

很明显,x1Copyx1 两个引用指向的是不同的 X 类型的对象,而同时两个对象中的 num 属性值又相同,这就是深拷贝,不仅仅拷贝字段的值,而且拷贝整个对象。用深拷贝生成的对象和原对象相互独立,互不影响,而这个也是我们推荐的做法,因为这更加符合 “克隆” 的含义,试想,如果一个修改一个克隆的对象还会对原对象产生影响,那怎么能叫克隆呢?

那么对于一个自定义的类我们如果编写具有深拷贝作用的 clone 方法呢?这里有 2 点参考:
1、对于类中的基本数据类型,直接复制;
2、对于类中的引用数据类型,引用赋值为被拷贝字段对象的 clone 方法的返回值。
看个例子:

public class Main {

    // 车轮类
    static class Wheel implements Cloneable {
        int radius;

        public Wheel(int radius) {
            this.radius = radius;
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof Wheel && radius == ((Wheel) obj).radius;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            Wheel wheelCopy = (Wheel)super.clone();
            wheelCopy.radius = radius;
            return wheelCopy;
        }
    }

    static class Bike implements Cloneable {
        // 车的前后轮
        Wheel frontWheel;
        Wheel backWheel;
        int weight;

        public Bike(Wheel frontWheel, Wheel backWheel, int weight) {
            this.frontWheel = frontWheel;
            this.backWheel = backWheel;
            this.weight = weight;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof Bike)) {
                return false;
            }
            Bike bike = (Bike) obj;
            return frontWheel.equals(bike.frontWheel) &&
                    backWheel.equals(bike.backWheel) &&
                    weight == bike.weight;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            // 先通过 super.clone() 方法获取对应的对象
            Bike bikeCopy = (Bike)super.clone();
            // 基本数据类型直接赋值
            bikeCopy.weight = weight;
            // 引用数据类型通过调用被拷贝属性对象的 clone 方法赋值
            bikeCopy.frontWheel = (Wheel) frontWheel.clone();
            bikeCopy.backWheel = (Wheel) backWheel.clone();
            return bikeCopy;
        }
    }

    private static void startTest() {
        Bike bike1 = new Bike(new Wheel(1), new Wheel(2), 5);
        try {
            Bike bike2 = (Bike) bike1.clone();
            System.out.println("bike1 == bike2: " + (bike1 == bike2));
            System.out.println("bike1.equals(bike2): " + (bike1.equals(bike2)));
            System.out.println("bike1.frontWheel == bike2.frontWheel: " + (bike1.frontWheel == bike2.frontWheel));
            System.out.println("bike1.frontWheel.equals(bike2.frontWheel): " + (bike1.frontWheel.equals(bike2.frontWheel)));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        startTest();
    }
}

还是使用上面出现过的 Wheel 类和 Bike 类,只不过对它们做了一些改动。我们来看看结果:
这里写图片描述
可以看到,直接使用 == 运算符判断两个 Bike 对象返回 false,证明在内存上不是同一个对象,而调用 equals 方法得到的结果却是 true,证明两个 Bike 对象在属性值上是等价的。对于它们的组件 frontWheel 字段也是如此。这样,我们就完成了对复杂对象的深拷贝。

toString

这个方法我想小伙伴们都不会陌生,这是我们最常用的对象方法之一了,其作用也很简单,就是返回对象的字符串内容表示,我们来看看这个方法的源码声明:

/**
 * Returns a string representation of the object. In general, the
 * {@code toString} method returns a string that
 * "textually represents" this object. The result should
 * be a concise but informative representation that is easy for a
 * person to read.
 * It is recommended that all subclasses override this method.
 * <p>
 * The {@code toString} method for class {@code Object}
 * returns a string consisting of the name of the class of which the
 * object is an instance, the at-sign character `{@code @}', and
 * the unsigned hexadecimal representation of the hash code of the
 * object. In other words, this method returns a string equal to the
 * value of:
 * <blockquote>
 * <pre>
 * getClass().getName() + '@' + Integer.toHexString(hashCode())
 * </pre></blockquote>
 *
 * @return  a string representation of the object.
 */
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

Object 类中这个方法的默认实现是 对象的类名@对象hashCode的16进制表示。当然,我们可以根据自己的需求重写。

wait / notify / notifyAll

先看一下方法声明:

/**
 * Causes the current thread to wait until either another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object, or a
 * specified amount of time has elapsed.
 * ...
 */
public final native void wait(long timeout) throws InterruptedException;

/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
 * ...
 */
public final void wait() throws InterruptedException {
    wait(0);
}

/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object, or
 * some other thread interrupts the current thread, or a certain
 * amount of real time has elapsed.
 * ...
 */
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);
}

/**
 * Wakes up a single thread that is waiting on this object's
 * monitor. If any threads are waiting on this object, one of them
 * is chosen to be awakened. The choice is arbitrary and occurs at
 * the discretion of the implementation. A thread waits on an object's
 * monitor by calling one of the {@code wait} methods.
 * ...
 */
public final native void notify();

/**
 * Wakes up all threads that are waiting on this object's monitor. A
 * thread waits on an object's monitor by calling one of the
 * {@code wait} methods.
 * ...
 */
public final native void notifyAll();

这几个方法其实都是和多线程有关,并且主要用于控制多个线程的同步问题,其实了解 Java 多线程机制的小伙伴对这几个方法应该不会陌生,这里还是简单介绍一下这三个方法:
1、wait:这个方法有三个重载的版本(其实终归调用的都是同一个方法),主要作用是使得当前线程让出所持有的对象锁并陷入阻塞状态,另外两个带有参数的版本可以控制线程的阻塞时间,阻塞时间达到了参数所指定的时间时就唤醒该线程。当然也可以在线程阻塞过程中通过这个对象的 notify / notifyAll 方法唤醒;
2、notify:随机唤醒一个因为调用了当前对象的 wait 方法而陷入阻塞的线程;
3、notifyAll:唤醒全部因为调用了当前对象的 wait 方法而陷入阻塞的线程。

这里大概介绍了一下这三个方法的作用,有兴趣深入了解的小伙伴可以参考一下我的多线程系列文章。当然也可以直接看 这篇文章 中对这几个方法的解释。

finalize

先看一下源码声明:

/**
 * Called by the garbage collector on an object when garbage collection
 * determines that there are no more references to the object.
 * A subclass overrides the {@code finalize} method to dispose of
 * system resources or to perform other cleanup.
 * ....
 */
protected void finalize() throws Throwable { }

这个方法是在 JVM 进行垃圾回收的时候可能会调用(注意我这里写的是可能)的方法。在 JVM 进行垃圾回收时,首先得获取到哪些对象是可以进行垃圾回收的,对于如何获取到可以回收的对象,有两种被提出的算法:
引用计数和可达性分析,简单看一下这两种方法:
1、引用计数:顾名思义,对每个对象设置一个引用计数器,记录当前对象被多少个引用所持有,如果某个对象的引用数为 0 ,那么证明该对象可以被回收;
这种算法咋一看挺不错的,实现简单,其实有很多问题,比如说不能解决对象之间互相引用的问题:假设有两个对象 A、B,A 中有一个 Object 类型的引用指向对象 B,B 中也有一个 Object 类型的引用指向对象 A,那么按照这种算法这两个对象永远不会被回收,但其实这两个对象都没有被外界用到。
2、可达性分析:这种算法基于深度优先搜索的思想,从 root 对象出发,找出 root 对象中的所有引用字段,再循环找出 root 对象中所有引用字段指向的对象中的引用字段,… 来看张图:
这里写图片描述
在这里面 B C D E F G H 对象都是不可回收的,因为他们都直接 / 间接的被 root 用到了。现在的关键问题是如何选择合适的 “root” 了。一般来说,JVM 选择的 root 都会是声明周期较长的,所以一般 JVM 会将类中的一些静态字段、常量字段和静态方法中的一些对象作为 “root” 。这种方法也是主流的虚拟机在进行垃圾回收时采用的寻找可以被回收的对象的算法。

简单介绍了一下 JVM 中的垃圾回收中如何找到需要被回收的对象,我们再来看看这个和 finalize 方法的关系,在 JVM 找到了当前要回收的对象之后(或者说标记了要回收的对象之后),**会开一个优先级较低的线程来执行这些对象的 finalize 方法(也有可能不开,这取决于 JVM 的状态)。**需要注意的是,即使开了线程去执行这些对象的 finalize 方法,由于线程优先级较低,所以可能在执行过程中由于系统资源不足就被回收了。所以在一开始我便标注了是可能被执行。另外,某个对象的 finalize 方法只会被 JVM 调用一次,即调用过之后这个对象的 finalize 方法就不会再被调用了。
我们还是举个例子来理解这个过程,先上代码:

public class Main {

    static Test obj;

    static class Test {

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            Main.obj = this;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t = null;
        // 启动 JVM 的垃圾回收动作
        System.gc();
        // 因为对象的 finalize 方法在一个优先级很低的线程中运行,
        // 所以主线程休眠 1 秒确保对象的 finalize 方法执行完成
        Thread.sleep(1000);
        if (Main.obj == null) {
            System.out.println("t 对象被回收了");
        } else {
            System.out.println("t 对象没有被回收");
        }
    }
}

我们在创建了一个新的 Test 对象之后将 t 设置为 null,之后手动调用垃圾回收动作,如果对象的 finalize 方法被调用了,那么就会有一个 static (静态)类型的 Test 引用指向这个对象,那么这个对象就不会被回收,否则的话这个对象就被回收了,我们来看看程序的结果:
这里写图片描述
结果中 t 对象并没有被回收,证明对象的 finalize 方法确实被执行了。那么如何反推它会被回收的情况呢?我们只需要修改一下 main 方法中的代码就行了:

public static void main(String[] args) throws InterruptedException {
    Test t = new Test();
    t = null;
    // 启动 JVM 的垃圾回收动作
    System.gc();
    // 因为对象的 finalize 方法在一个优先级很低的线程中运行,
    // 所以主线程休眠 1 秒确保对象的 finalize 方法执行完成
    Thread.sleep(1000);
    if (Main.obj == null) {
        System.out.println("t 对象被回收了");
    } else {
        System.out.println("t 对象没有被回收");
    }

    // 现将 obj 引用置为 null,排除干扰因素
    obj = null;
    // 启动 JVM 的垃圾回收动作
    System.gc();
    // 因为对象的 finalize 方法在一个优先级很低的线程中运行,
    // 所以主线程休眠 1 秒确保对象的 finalize 方法执行完成
    Thread.sleep(1000);
    if (Main.obj == null) {
        System.out.println("t 对象被回收了");
    } else {
        System.out.println("t 对象没有被回收");
    }
}

就是在原来的 main 方法后面加了一端几乎一样的代码(在前面多了一句 obj = null;)。我们再来看看结果:
这里写图片描述
可以看到,因为 finalize 方法的因素,第一次 t 对象没有被回收,而第二次 t 对象已经被回收了,证明 finalize 方法只被调用了一次,也证实了我们上面的结论。

由此我们也知道了:我们不应该将对象的相关资源回收的代码放在 finalize 方法中执行,因为 JVM 不保证这个方法每一次都会得到执行,也正因为如此,这个方法在平常开发并不常用,对于对象的资源回收,我们可以专门写一个方法处理都会得到比直接使用这个方法更好的表现。

好了。Java Object 类方法解析就到这里了,相信你对 Java Object 类中的方法啊有一个更深入的理解。如果博客中有什么不正确的地方,还请多多指点。如果这篇文章对您有帮助,请不要吝啬您的赞,欢迎继续关注我的其他文章。

谢谢观看。。。

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3o2apkp87k2sc

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页