首页 > 编程笔记

Java Object类(超级详细)

Java 是通过类来构建代码结构的,类分为两种:
我们写的 Java 程序,其实就是由 Java 提供的类和自定义的类组成的,打开 Eclipse,在 JRE System Library 中存放的就是 Java 提供的类,如下图所示:


图 1 Java提供的类

开发者自定义的类存放在 src 目录下,如下图所示。


图 2 自定义的类

JRE System Library 中的类全部是编译之后的字节码文件,即 class 格式的文件,我们可以看到源码,但是不能修改,如下图所示。


图 3 class格式的文件

Object 就是 Java 提供的一个类,位于 java.lang 包中,该类是所有类的直接父类或间接父类。无论是 Java 提供的类,还是开发者自定义的类,都是 Object 的直接子类或间接子类。或者说 Java 中的每一个类都是 Object 的后代,Object 是所有类的祖先。

一个类在定义时如果不通过 extends 指定其直接父类,系统就会自动为该类继承 Object 类,Object 类的源码如下所示:
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 > 0) {
            timeout++;
        }
        wait(timeout);
    }
    public final void wait() throws InterruptedException {
        wait(0);
    }
    protected void finalize() throws Throwable { }
}
可以看到 Object 类中提供了很多用 public 和 protected 修饰的方法,子类是可以直接继承这些方法的,即 Java 中的任何一个类,都可以调用 Object 类中的 public 和 protected 方法,当然 private 是不能调用的,如下图所示。


图 4 继承Object类的方法

重写Object类的方法

Object 是所有类的父类,每一个类都可以直接继承 Object 类的非私有方法,实例化对象可以直接调用这些方法。但是通常情况下不会直接调用这些方法,而是需要对它们进行重写,因为父类中统一的方法并不能适用于所有的子类。就像老爹房子的装修风格是老爹喜欢的,儿子们审美各有不同,老爹的房子并不能满足他们的需求,所以儿子们会把房子的旧装修覆盖掉,重新装修以适应他们的需求。这种方式是多态的一种体现,父类信息通过不同的子类呈现出不同的形态。

接下来我们就一起看看 Object 类经常被子类所重写的那些方法,如下表所示。

表 1 Object类经常被子类重写的方法
方 法 描 述
public String toString() 以字符串的形式返回该类的实例化对象信息。
public boolean equals(Objeect obj) 判断两个对象是否相等。
public native int hashCode() 返回对象的散列码。

toString()方法的实现如下所示:
*@return  a string representation of the object.
*/
public String toString(){
      return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
原生的 toString() 方法会返回对象的类名以及散列值,直接打印对象默认调用 toString() 方法,例如:
public class Test {
    public static void main(String[] args) {
        People people = new People();
        people.setId(1);
        people.setName("张三");
        people.setAge(22);
        people.setGender('男');
        System.out.println(people);
    }
}
程序的运行结果为:

com.southwind.entity.People@61064425

但是在实际开发中返回这样的信息意义不大,我们更希望看到的是对象的属性值,而非它的内存地址,所以我们需要对 toString() 方法进行重写,例如:
public class People {
     ……
    @Override
    public String toString() {
        return "People [id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender + "]";
    }
}

public class Test {
    public static void main(String[] args) {
        People people = new People();
        people.setId(1);
        people.setName("张三");
        people.setAge(22);
        people.setGender('男');
        System.out.println(people);
    }
}
程序的运行结果为:

People [id=1, name=张三, age=22, gender=男]


equals() 方法的实现是:
* @see     Java.util.HashMap
*/
public boolean equals(Object obj){
      return (this == obj);
}
通过内存地址对两个对象进行判断,即两个对象的引用必须指向同一块内存程序才会认为它们相等,但是在不同的场景下,这种方式不见得都适用。

比如说,两个字符串“String str1 = new String(“Hello”);”和“String str2 = new String( “Hello”);”,虽然 str1 和 str2 是两个完全不同的对象,但是它们的值是相等的,就可以认为这两个字符串相等。

我们需要对 equals() 方法进行重写,String 类已经完成了重写的工作,直接使用即可,重写的代码为:
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
}
你可以看到 String 类中对 equals() 方法的重写,是将两个字符串中的每一个字符依次取出进行比对,如果所有字符完全相等,则认为两个对象相等,否则不相等,字符串比较的过程如下:
public class Test {
    public static void main(String[] args) {
        String str1 = new String("Hello");
        String str2 = new String("Hello");
        System.out.println(str1.equals(str2));
    }
}
程序的运行结果为:

true

自定义类也可以根据需求对 equals() 方法进行重写,如我们定义一个 People 类,创建该类的实例化对象,认为只要成员变量的值都相等就是同一个人,用程序的语言来表述就是两个对象相等,但是如果直接调用 equals() 方法进行比较,结果却并不是我们所预期的,代码如下:
public class People {
    private int id;
    private String name;
    private int age;
    private char gender;
    //getter、setter方法
}

public class Test {
    public static void main(String[] args) {
        People people = new People();
        people.setId(1);
        people.setName("张三");
        people.setAge(22);
        people.setGender('男');
        People people2 = new People();
        people2.setId(1);
        people2.setName("张三");
        people2.setAge(22);
        people2.setGender('男');
        System.out.println(people.equals(people2));
    }
}
程序的运行结果为:

true

现在对 People 类继承自 Object 类的 equals() 方法进行重写,如果两个对象的成员变量值都相等,则它们就是同一个对象,具体实现如下:
public class People {
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        People people = (People) obj;
        if(people.getId() == this.id && people.getName().equals(this.name) && people.getGender() == this.gender && people.getAge() == this.age){
            return true;
        }
        return false;
    }
}
再次运行程序,结果为:

true


hashCode() 方法如下所示:
* @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();
该方法返回一个对象的散列值,这个值是由对象的内存地址结合对象内部信息得出的,任何两个对象的内存地址肯定是不一样的。

但是上面例子中我们认为,如果两个 People 对象的成员变量值都相等,就是同一个对象,那么它们的散列值也应该相等,如果直接调用父类的 hashCode() 方法,两个对象的散列值是不相等的,例如:
public class Test {
    public static void main(String[] args) {
        People people = new People();
        people.setId(1);
        people.setName("张三");
        people.setAge(22);
        people.setGender('男');
        People people2 = new People();
        people2.setId(1);
        people2.setName("张三");
        people2.setAge(22);
        people2.setGender('男');
        System.out.println(people.hashCode());
        System.out.println(people2.hashCode());
    }
}
程序的运行结果为:

1627800613
2065530879

现在对 People 的 hashCode() 方法进行重写,将 idnameage*gender 的值作为结果返回,name 是字符串类型,值相等散列值就相等,具体实现如下:
public class People {
    //......
    @Override
    public int hashCode() {
        return this.id*this.name.hashCode()*this.age*this.gender;
    }
}
再次运行程序,结果为:

444964682
444964682

如此一来,成员变量值都相等的两个 People 对象,散列值也是相等的。

推荐阅读