Java進階2 —— 使用Object的通用方法

原文連接:http://www.javacodegeeks.com/2015/09/using-methods-common-to-all-objects.htmlhtml

本文是Java進階課程的第二篇。java

本課程的目標是幫你更有效的使用Java。其中討論了一些高級主題,包括對象的建立、併發、序列化、反射以及其餘高級特性。本課程將爲你的精通Java的旅程提供幫助。git

內容提綱

  1. 引言程序員

  2. equals和hashCode方法apache

  3. toString方法編程

  4. clone方法segmentfault

  5. equals方法與"=="操做符api

  6. 有用的幫助類數組

  7. 源碼下載併發

  8. 下章概要

1. 引言

從前面一篇對象的建立與銷燬中,咱們知道Java是一種面向對象編程語言(儘管不是純粹的面向對象)。Java類層次結構的頂層是Object類,全部的其餘類都隱式的繼承於它。所以,全部的類也都從Object中繼承了方法,其中最重要的幾個方法以下表:

方法 描述
protected Object clone() 建立並返回當前對象的一份拷貝
protected void finalize() 當垃圾回收器判斷出該對象再也不被引用時,就會調用finalize()方法。在對象的建立與銷燬中有對finalizers的介紹。
boolean equals(Object obj) 判斷另一個對象是否與當前對象相等
int hasCode() 返回當前對象的哈希值
String toString() 返回一個表示當前對象的字符串
void notify() 喚醒一個等待當前對象的鎖監視器的線程。咱們將會在第9篇文章併發最佳實踐中詳細介紹此方法
void notifyAll() 喚醒全部等待當前對象的鎖監視器的線程。咱們將會在第9篇文章併發最佳實踐中詳細介紹此方法
void wait()
void wait(long timeout)
void wait(long timeout, int nanos)
使當前線程進入等待狀態直到其餘線程調用了當前對象的notify()notifyAll()方法。咱們將會在第9篇文章併發最佳實踐中詳細介紹此方法

表1

在本篇文章中咱們將重點介紹equalshashCodetoStringclone方法。經過本章節的學習,須要對這幾個方法的用法及重要的使用限制瞭然於胸。

2. equlas和hashCode方法

默認狀況下,Java 中任何兩個對象引用(或類實例引用)只有指向相同的內存地址時才認爲是相等的(引用相等)。可是Java容許經過重載Objectequals()方法給類自定義判等規則。聽起來這是個很強大的概念,然而在適當的equals()方法實現須要知足如下幾個規則限制:

  • 自反性:對象x必須與其自身相等,equals(x)返回true

  • 對稱性:若是equals(y)true,則y.equals(x)也要返回true

  • 傳遞性:若是equals(y)true,而且y.equals(z)也爲true,則x.equals(z)也要爲true

  • 一致性:屢次調用equals()方法應該返回相同值,除非對用於判等的任何一個屬性進行了修改

  • 與null判等equals(null)老是要返回false

不幸的是Java編譯器並不會在編譯時對以上規則進行檢查。然而,不遵照上述規則時可能會引入很是怪異並難以解決的問題。通用的建議是:若是須要重寫equals()方法,請至少思考兩次重寫的必要性。遵循以上規則,咱們爲Person類重寫一個簡單的equals()實現。

package com.javacodegeeks.advanced.objects;

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()方法返回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()方法重載恰當,能極大的簡化debug難度和分析解決問題的過程。

默認狀況下,toString()的結果僅僅返回以@符分隔的全類名與對象哈希值串,然而這個結果在大多場景下並沒什麼用途。以下:

com.javacodegeeks.advanced.objects.Person@6104e2ee

咱們來經過重寫PersontoString()方法以使其輸出更有用,下面是其中一種實例:

// 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的全部字段,而後執行下面的代碼片斷:

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()的實現。

package com.javacodegeeks.advanced.objects;

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實例的字符串值):

Office{persons=[Person[email=john.smith@domain.com, first name=John, last name=Smith]]}

Java社區實例了大量有用的類庫以簡化toString()的實現。其中普遍使用的有Google GuavaObjects.toStringHelperApache Commons LangToStringBuilder

4. clone方法

若是舉出Java中最聲名狼藉的方法,當屬clone()無疑。clone()方法的目的很簡單——返回對象實例的拷貝,然而有一堆理由可證實其使用並不像聽起來那麼垂手可得。

首先,實現自定義的clone()方法時須要遵照Java文檔)中列出的一系列約定。其次,在Object類中clone()方法被聲明爲protected,因此爲了提升方法的可見性,在重載時須要聲明爲public並把返回值類型調整爲重載類自身類型。再次,重載類須要實現Cloneable接口(儘管該接口做爲一種聲明,並未提供任何方法定義),不然將會拋出CloneNotSupportedException異常。最後,在實現clone()方法時要先調用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();
    }
}

上面的實現看起來簡單直接,然而卻隱藏着錯誤。當類實例的clone動做被執行時,未調用任何構造方法,後果將致使預料外的數據泄露。下面再看下Office類中的定義:

package com.javacodegeeks.advanced.objects;

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實例克隆出來的全部對象都將共享相同的person數組,然而這並非咱們預期的行爲。爲了讓clone()實現正確的行爲,咱們還要作一些額外的工做:

@Override
public Office clone() throws CloneNotSupportedException {
    final Office clone = ( Office )super.clone();
    clone.persons = persons.clone();
    return clone;
}

看起來是正確了,但若是對persons字段聲明爲final就將破壞這種正確性,所以final字段不能被從新賦值,從而致使數據再次被共享。

總之,當須要類實例的拷貝時,儘量避免使用clone() / Cloneable,相反能夠選擇其餘更簡單的替代方案(例如:C++程序員熟悉的複製構造方法,或者工廠方法——在對象的建立與銷燬中討論過的一種有用的構造模式)。

5. equals方法與"=="操做符

在Java中,==操做符與equals()方法有種奇怪的關係,卻會引入大量的問題與困惑。大多數狀況下(除比較基本數據類型),==操做符執行的是引用相等:只要兩個引用指向同一個對象時爲true,不然返回false。下面舉例說明兩者的區別:

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. 有用的幫助類

從Java 7發佈以來,一批有用的幫助類加入到了標準Java庫中,Objects即是其中之一。具體來講,如下三個方法能夠簡化你的equals()hashCode()方法實現。

方法 描述
static boolean equals(Object a, Object b) 當參數中的兩個對象相等時返回true,不然返回false
static int hash(Object...values) 爲參數列表生成哈希值
static int hashCode(Object o) 爲非null參數生成哈希值,若是參數爲null返回0

若是使用上面的方法來重寫Personequals()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 );
}

7. 源碼下載

能夠從這裏下載本文中的源碼:advanced-java-part-2

8. 下章概要

在本章中,咱們學習了做爲Java面向對象基礎的Object類,以及自定義的類如何經過本身的判等規則重載Object的相關方法。下一章中,咱們將會把視線暫時從代碼實現上收起,轉向去討論如何設計合適的類和接口。

相關文章
相關標籤/搜索