大可制作:QQ群:31564239(asp|jsp|php|mysql)

Java Gossip: Object 类

在Java中,所有的对象都隐含的继承了Object类,Object类是Java程序中所有类的父类,当您定义一个类时:
public class Foo {
    // 实现
}

这个程序码相当于:
public class Foo extends Object {
    // 实现
}

Object类定义了几个方法,包括"protected"的clone()、finalize()两个方法,以及几个"public"方法,像是equals()toString()getClass()hashCode()notify()notifyAll()等等的方法,这些方法您都可以加以覆盖(除了 getClass()、notify()、notifyAll()、wait()等方法之外,它们被声明为 "final",无法被子类继承,所以无法覆盖),以符合您所建立的类需求,我会在以后的适当主题中介绍这些方法的使用。

由于Object类是Java中所有类的父类,所以它可以指向任何的对象而不会发生任何错误,这是很有用,以后您会看到一些 Java程序中,有些对象可以加入一些衍生类对象,并可透过方法调用会直接传回Object对象,这些对象可以经由类型(接口)转换而指定给衍生类类 型引用。

下面这个程序中,您制作一个简单的 集合(Collection)类,并将一些自订类对象加入其中,这个程序示范了Object的一个应用:

  • Foo1.java
public class Foo1 { 
private String name;

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

public void showName() {
System.out.println("foo1 name: " + name);
}

// 覆盖toString()
public String toString() {
return "foo1 name: " + name;
}
}

  • Foo2.java
public class Foo2 { 
private String name;

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

public void showName() {
System.out.println("foo2 name: " + name);
}

// 覆盖toString()
public String toString() {
return "foo2 name: " + name;
}
}
  • SimpleCollection.java
public class SimpleCollection { 
private Object[] objArr;
private int index = 0;

public SimpleCollection() {
objArr = new Object[10]; // 默认10个对象空间
}

public SimpleCollection(int capacity) {
objArr = new Object[capacity];
}

public void add(Object o) {
objArr[index] = o;
index++;
}

public int getLength() {
return index;
}

public Object get(int i) {
return objArr[i];
}
}
  • Test.java
public class Test { 
public static void main(String[] args) {
SimpleCollection objs = new SimpleCollection();

objs.add(new Foo1("f1 number 1"));
objs.add(new Foo2("f2 number 1"));

Foo1 f1 = (Foo1) objs.get(0);
f1.showName();

Foo2 f2 = (Foo2) objs.get(1);
f2.showName();

System.out.println();
System.out.println("f1.toString(): " +
f1.toString());
System.out.println("f2.toString(): " +
f2.toString());
}
}

执行结果:
foo1 name: f1 number 1
foo2 name: f2 number 1
 
f1.toString(): foo1 name: f1 number 1
f2.toString(): foo2 name: f2 number 1 


在程序中,SimpleCollection对象可以加入任何类型的对象至其中,而传回对象时,您只要透过类型(接口)转换,就可以操作类型(接口)上的方法。

Object的toString()方法默认会传回以下的字符串:
getClass().getName() + '@' +
              Integer.toHexString(hashCode());

getClass()方法是Object中定义的方法,它会传回对象于运行时的Class实例,而hashCode()传回该对象的hash code,toString()方法用来传回对象的描述,通常是个文字性的描述,Object的toString()方法默认在某些场合是有用的,例如对象的自我检视时,但在这边,您将之覆盖为文字模式下使用者看得懂的文字描述。

上面这个程序范例虽然简单,但您以后一定会常常看到类似的应用,例如视窗程序容器、Vector类等等。

Object默认的equals()本身是比较对象的内存引用,如果您要有必要比较两个对象的内含数据是否相同(例如当对象被储存至Set时)您必须实现equals()与hashCode()

一个比较常被采用的方法是根据对象中包括的所有属性值来作比较,来看看下面的一个例子:
public class Cat {

    ...
    public boolean equals(Object other) {
        if (this == other)
            return true;

        if (!(other instanceof Cat))
            return false;

        final Cat cat = (Cat) other;

        if (!getName().equals(cat.getName()))
            return false;

        if (!getBirthday().equals(cat.getBirthday()))
            return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = getName().hashCode();
        result = 29 * result + getBirthday().hashCode();
        return result;
    }
}

这称之为by value equality,更严格的比对是根据商务键值(Business key)实现equals()与hashCode(),
商务键是一个属性或多个属性的结合,挑选那些可以从不为null、很少改变且组合后具有唯一性的属性

API中对于equals()的合约是必须具备反身性(Reflexive)、对称性(Symmetric)、传递性(Transitive)、一致性(Consistent)
  • 反身性(Reflexive):x.equals(x)的结果要是true。
  • 对称性(Symmetric):x.equals(y)与y.equals(x)的结果必须相同。
  • 传递性(Transitive):x.equals(y)、y.equals(z)的结果都是true,则x.equals(z)的结果也必须是true。
  • 一致性(Consistent):同一个执行期间,对x.equals(y)的多次调用,结果必须相同。

可以参考API文件中Object类的hashCode()之建议:
  • 在同一个应用程序执行期间,对同一对象调用 hashCode()方法,必须回传相同的整数结果。
  • 如果两个对象使用equals(Object)测试结果为相等, 则这两个对象调用hashCode()时,必须获得相同的整数结果。
  • 如果两个对象使用equals(Object)测试结果为不相等, 则这两个对象调用hashCode()时,可以获得不同的整数结果。

两个不同的对象,可以传回相同的hashCode()结果,这是合法甚至适当的,只是对象会被丢到同一个杂凑桶中。

至于clone()方法,它是有关于如何复制对象本身,您可以在当中定义您的复制方法,不过对象的复制要深入的话必须考虑很多细节,您可以从 这篇文章 开始稍微了解一下如何定义clone()方法。