在java中,Object 处在类的最顶层,其他类都继承它,于是所有类都继承了 Object类的方法, 最重要的几个如下:
| Method | Description |
protected Object clone() |
Creates and returns a copy of this object. |
protected void finalize() |
Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. |
boolean equals(Object obj) |
Indicates whether some other object is “equal to” this one. |
int hashCode() |
Returns a hash code value for the object. |
String toString() |
Returns a string representation of the object. |
void notify() |
Wakes up a single thread that is waiting on this object’s monitor. |
void notifyAll() |
Wakes up all threads that are waiting on this object’s monitor. |
void wait()void wait(long timeout)void wait(long timeout, int nanos) |
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. |
Table 1
在这部分教程,我们将看看 equals,hashCode,toString 和clone 方法, 记住它们的用法和重要的约束。
1. 方法的 equals 和 hashCode
默认情况下,在Java中任意两个对象引用(或类实例的引用)只有当他们指向相同的内存位置(引用相等)是相等的。但是Java允许类通过覆盖Object类的equals()方法来定义自己的相等规则。这听起来像一个强大的概念,然而正确的equals()方法的实现应遵循一套规则,满足以下约束条件:
- Reflexive. 对象x必须和自己相等,equals(x) 必须返回 true.
- Symmetric. 如果equals(y) 返回 true 那么 y.equals(x) 必须返回 true.
- Transitive. 如果equals(y) 返回 true 并且 y.equals(z) 返回 true, 那么 x.equals(z) 必须返回 true.
- Consistent. 多个调用equals()方法的结果必须是相同的值
- Equals To Null. equals(null) 返回 false.
不幸的是,Java编译器在编译过程中无法执行这些约束。然而,不遵守这些规则可能会导致非常奇怪和难以解决的问题。一般建议是这样的:如果你要自己编写equals()方法实现,三思而后行,如果你真的需要它。现在带着所有这些规则,让我们编写一个简单的实现Person类的equals()方法。
public class Person {
private final String firstName;
private final String lastName;
private final String email;
public Person( final String firstName, final String lastName, final String email ) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public String getEmail() {
return email;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
// Step 0: Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public boolean equals( Object obj ) {
// Step 1: Check if the 'obj' is null
if ( obj == null ) {
return false;
}
// Step 2: Check if the 'obj' is pointing to the this instance
if ( this == obj ) {
return true;
}
// Step 3: Check classes equality. Note of caution here: please do not use the
// 'instanceof' operator unless class is declared as final. It may cause
// an issues within class hierarchies.
if ( getClass() != obj.getClass() ) {
return false;
}
// Step 4: Check individual fields equality
final Person other = (Person) obj;
if ( email == null ) {
if ( other.email != null ) {
return false;
}
} else if( !email.equals( other.email ) ) {
return false;
}
if ( firstName == null ) {
if ( other.firstName != null ) {
return false;
}
} else if ( !firstName.equals( other.firstName ) ) {
return false;
}
if ( lastName == null ) {
if ( other.lastName != null ) {
return false;
}
} else if ( !lastName.equals( other.lastName ) ) {
return false;
}
return true;
}
}
这一部分在其标题也包括hashcode()方法,这不是偶然的。至少要记住:当你重写equals()方法的时候记得总是要重写hashcode()方法。如果任何两个对象的equals()方法返回true,那么在这些两个对象的hashcode()方法必须返回相同的整数值(但是相反的说法是不严格的:如果任何两个对象的equals()方法returns false,每两对象 的hashcode()方法可能会或可能不会返回相同的整数值)。让我们来看看person类的hashcode()方法。
// Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( email == null ) ? 0 : email.hashCode() );
result = prime * result + ( ( firstName == null ) ? 0 : firstName.hashCode() );
result = prime * result + ( ( lastName == null ) ? 0 : lastName.hashCode() );
return result;
}
保护自己免受意外,尽可能尝试在实现equals()和hashcode()的时候使用final域。它将保证这些方法的行为不会受到域的变化(然而,在现实的项目中,这是不可能的)。
最后,始终确保实现equals()和hashcode()方法使用相同的域。这将在任何时候域改变的情况下,保证这两种方法的一致性。
3. toString方法
toString()可以说是最有趣的方法,更频繁地被覆盖。其目的是提供对象的字符串表示(类实例)。正确写toString()方法可以极大地简化现实系统的调试和故障排除问题。
在大多数情况下,默认的 toString() 实现不是很有用,只是返回类名和对象的hashcode,让我们使用实现和覆盖 toString()方法来改进上面的Person类,这是一个让toString()更有用的方法。
// Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public String toString() {
return String.format( "%s[email=%s, first name=%s, last name=%s]",
getClass().getSimpleName(), email, firstName, lastName );
}
现在,toString() 方法提供了包含 Person 类实例所有域的string版本. 例如, 执行下面的代码段:
final Person person = new Person( "John", "Smith", "john.smith@domain.com" ); System.out.println( person.toString() );
结果如下:
Person[email=john.smith@domain.com, first name=John, last name=Smith]
不幸的是, 标准的java库对简化 toString() 方法的实现的支持有限, 值得注意的是, 最有用的方法是 Objects.toString(), Arrays.toString() / Arrays.deepToString(). 让我们看看Office 类及其可能的 toString() 实现.
import java.util.Arrays;
public class Office {
private Person[] persons;
public Office( Person ... persons ) {
this.persons = Arrays.copyOf( persons, persons.length );
}
@Override
public String toString() {
return String.format( "%s{persons=%s}",
getClass().getSimpleName(), Arrays.toString( persons ) );
}
public Person[] getPersons() {
return persons;
}
}
下面的结果将输出到 控制台 (可以看到 Person 类实例转化为string):
Office{persons=[Person[email=john.smith@domain.com, first name=John, last name=Smith]]}
在Java社区已经开发了几个有很大的帮助的比较全面的库,使tostring()实现不再痛苦并且变得轻松。在这些中有比如 Google Guava's Objects.toStringHelper 和Apache Commons LangToStringBuilder.
4. clone方法
首先,如果你已经决定要实现自己的clone()方法,在Java documentation中有很多规范。其次,该方法在 Object 中声明为protected,为了使其可见,它应该被覆盖为public。再次,覆盖类应该实现Cloneable接口,否则将引发CloneNotSupportedException异常。最后,实现应首先调用super.clone() ,然后如果需要,执行其他操作。让我们看看如何为我们的Person类实现。
public class Person implements Cloneable {
// Please add the @Override annotation, it will ensure that your
// intention is to change the default implementation.
@Override
public Person clone() throws CloneNotSupportedException {
return ( Person )super.clone();
}
}
实现看起来很简单,于是这里会出什么问题?相当多问题,其实。当正在执行的类实例的克隆,没有类构造函数被调用。这样的行为的后果是,意外的数据共享可能会出现。让我们考虑下面的Office类的例子
import java.util.Arrays;
public class Office implements Cloneable {
private Person[] persons;
public Office( Person ... persons ) {
this.persons = Arrays.copyOf( persons, persons.length );
}
@Override
public Office clone() throws CloneNotSupportedException {
return ( Office )super.clone();
}
public Person[] getPersons() {
return persons;
}
}
在这个实现中,所有的 Office 类实例将共享相同的persons数组,这不是所希望的行为。为了使clone() 方法做正确的事,还需要做一点工作。
@Override
public Office clone() throws CloneNotSupportedException {
final Office clone = ( Office )super.clone();
clone.persons = persons.clone();
return clone;
}
这样的实现看起来好些,但是依然非常脆弱,把persons的域设置为final会导致数据共享问题。
总的来说,如果你想让你的类的精确副本,可能最好是避免 clone() / Cloneable ,使用更简单的替代品(例如,拷贝构造函数,或工厂方法,一个有用的建设模式)。
5. equals方法和== 操作
java中的==运算符和equals()方法有一个有趣的关系并造成许多问题和困惑。在大多数情况下(除了比较原始类型),==操作符执行引用的相等:如果两个引用指向同一个对象,则返回真。让我们来看看一个简单的例子,它说明差异:
final String str1 = new String( "bbb" ); System.out.println( "Using == operator: " + ( str1 == "bbb" ) ); System.out.println( "Using equals() method: " + str1.equals( "bbb" ) );
预见一下, str1==”bbb” 和 str1.equals(“bbb”)应该没有差异:就好像str1指向”bbb”的引用,在这两种情况下的结果应该是一样的。但在Java中是不正确的:
Using == operator: false
Using equals() method: true
即使两个字符串看起来完全一样,在这个特定的例子中他们作为两种不同的string实例存在。作为一个经验法则,如果你处理的对象引用,总是用 equals() 或 Objects.equals()来比较相等,除非你真的有意向比较对象引用指向同一个实例。
6. 有用的辅助类
自从发布了java7,在java标准库中有几个非常有用的辅助类。其中一个是Objects类。特别,以下三种方法可以大大简化你的equals()和hashcode()方法实现。
| Method | Description |
static boolean equals(Object a, Object b) |
Returns true if the arguments are equal to each other and false otherwise. |
static int hash(Object... values) |
Generates a hash code for a sequence of input values. |
static int hashCode(Object o) |
Returns the hash code of a non-null argument and 0 for a null argument. |
Table 2
如果你使用这些辅助类来为 Person 类写 equals() 和 hashCode() 方法 ,代码的量将明显变小,代码可读性增强。
@Override
public boolean equals( Object obj ) {
if ( obj == null ) {
return false;
}
if ( this == obj ) {
return true;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final PersonObjects other = (PersonObjects) obj;
if( !Objects.equals( email, other.email ) ) {
return false;
} else if( !Objects.equals( firstName, other.firstName ) ) {
return false;
} else if( !Objects.equals( lastName, other.lastName ) ) {
return false;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash( email, firstName, lastName );
}

Comments