編寫高質量代碼:改善Java程序的151個建議(第3章:類、對象及方法___建議31~51)

書讀的多而不思考,你會以爲本身知道的不少。java

書讀的多而思考,你會以爲本身不懂的愈來愈多。  -----江疏影程序員

在面向對象編程(Object-Oriented Programming, OOP)的世界裏,類和對象是真實世界的描述工具,方法是行爲和動做的展現形式,封裝、繼承、多態則是其多姿多彩的主要實現方式,本章主要講述關於Java對象,方法的種種規則,限制和建議。算法

建議31:在接口中不要存在實現代碼編程

建議32:靜態變量必定要先聲明後賦值session

建議33:不要重寫靜態方法多線程

實例對象訪問靜態方法或靜態屬性不是好習慣,直接類名調用就好了。模塊化

建議34:構造函數儘可能簡化函數

建議35:避免在構造函數中初始化其餘類工具

建議36:使用構造函代碼塊精簡程序性能

一、代碼塊基本概念:

什麼叫作代碼塊(Code Block)?用大括號把多行代碼封裝在一塊兒,造成一個獨立的數據體,實現特定算法的代碼集合即爲代碼塊,通常來講代碼快不能單獨運行的,必需要有運行主體。在Java中一共有四種類型的代碼塊:

普通代碼塊:就是在方法後面使用"{}"括起來的代碼片斷,它不能單獨運行,必須經過方法名調用執行;

靜態代碼塊:在類中使用static修飾,並用"{}"括起來的代碼片斷,用於靜態變量初始化或對象建立前的環境初始化。

同步代碼塊:使用synchronized關鍵字修飾,並使用"{}"括起來的代碼片斷,它表示同一時間只能有一個線程進入到該方法塊中,是一種多線程保護機制。

構造代碼塊:在類中沒有任何前綴和後綴,並使用"{}"括起來的代碼片斷;

二、代碼實例展現:

package OSChina.Client;

public class Base {
    static {
        System.out.println("我是父類靜態代碼塊1");
    }

    {
        System.out.println("我是父類構造代碼塊2");
    }

    Base() {
        System.out.println("我是父類無參構造3");
    }

    public static void doSomething(){
        System.out.println("我是父類靜態函數");
    }
}
package OSChina.Client;

public class Sub extends Base{
    static {
        System.out.println("我是子類靜態代碼塊1");
    }

    {
        System.out.println("我是子類構造代碼塊2");
    }

    Sub(){
        System.out.println("我是子類無參構造3");
    }
}

當實例化時:

Base base = new Sub();

0f2068267775be66c89ba467366ccf039f3.jpg

當實例化時,執行順序:靜態代碼塊>>構造代碼塊>>構造函數>>普通方法。

當類名調用靜態方法,不實例化時:

f9e5eb570a5dfd6538a85698242cfc01bc0.jpg

只執行靜態代碼塊和對應的靜態函數,構造函數不執行!

三、構造代碼塊應用場景:

① 初始化實例變量

若是每一個構造函數都要初始化變量,能夠經過構造代碼塊來實現。

注:不是每一個構造函數都要加載的,而構造代碼塊必定加載。

② 初始化實例變量

一個對象必須在適當的場景下才能存在,若是沒有適當的場景,就須要在建立該對象的時候建立場景。例如HTTP request必須首先創建HTTP session,此時就能夠經過構造代碼塊來檢查HTTP session是否已經存在,不存在則建立。

注:構造函數要實現複雜邏輯的時候,能夠經過編寫多個構造代碼塊來實現,每一個代碼塊完成不一樣的業務邏輯(構造函數儘可能簡單的原則),按照業務順序依次存放,這樣在建立實例對象時JVM會按照順序依次執行,實現複雜對象的模塊化建立。

建議37:構造代碼塊會想你所想

建議38:使用靜態內部類提升封裝性

一、Java中的嵌套類分爲兩種:靜態內部類和內部類。

public class Person {
    // 姓名
    private String name;
    // 家庭
    private Home home;

    public Person(String _name) {
        name = _name;
    }

    /* home、name的setter和getter方法略 */

    public static class Home {
        // 家庭地址
        private String address;
        // 家庭電話
        private String tel;

        public Home(String _address, String _tel) {
            address = _address;
            tel = _tel;
        }
        /* address、tel的setter和getter方法略 */
    }
}

其中,Person類中定義了一個靜態內部類Home,它表示的意思是「人的家庭信息」,因爲Home類封裝了家庭信息,不用在Person類中定義homeAddr,homeTel等屬性,這就提升了封裝性,可讀性也提升了。

public static void main(String[] args) {
    // 定義張三這我的
    Person p = new Person("張三");
    // 設置張三的家庭信息
    p.setHome(new Home("北京", "010"));
 }

二、靜態內部類的優點:

提升封裝性

提升代碼的可讀性

形似內部,神似外部

靜態內部類雖然存在於外部類中,並且編譯後的類文件也包含外部類(格式是:外部類+$+內部類),可是它能夠擺脫外部類存在,也就是說能夠經過new Home()聲明一個home對象,只是須要導入Person.Home而已。

三、靜態內部類和普通內部類的區別:

① 靜態內部類不持有外部類的引用:

普通內部類能夠訪問外部類的全部屬性和方法。

靜態內部類只能訪問外部類的靜態屬性和靜態方法。

② 靜態內部類不依賴外部類:

普通內部類與外部類同生共死,一塊兒被垃圾回收。

靜態內部類能夠獨立存在。

③ 普通內部類不能聲明static的方法和變量,final static修飾的常量除外。

建議39:使用匿名類的構造函數?

建議40:匿名類的構造函數很特殊?

建議39和建議40暫時沒看出有啥用,再說吧!若有須要請閱讀原著《編寫高質量代碼:改善Java程序的151個建議》!

建議41:讓多繼承成爲現實

在Java中一個類能夠多重實現,但不能多重繼承,也就是說一個類可以同時實現多個接口,但不能同時繼承多個類。

Java中提供的內部類能夠曲折的解決此問題。

建議42:讓工具類不可實例化

建議43:避免對象的淺拷貝

咱們知道一個類實現了cloneable接口就表示它具有了被拷貝的能力,若是重寫clone()方法就會徹底具有拷貝能力。拷貝是在內存中進行的,因此性能方面比直接new生成的對象要快不少,特別在大對象的生成上,性能提高很是顯著。可是淺拷貝存在對象屬性拷貝不完全的問題。

淺拷貝:

一、概念

建立一個新對象,而後將當前對象的非靜態字段複製到新對象,若是是值類型,對該字段進行復制;若是是引用類型,複製引用但不復制引用的對象。所以,原始對象及其副本引用同一個對象。

二、實現方法

調用對象的 clone 方法,必需要讓類實現 Cloneable 接口,而且覆寫 clone 方法。

建議44:推薦使用序列化對象的拷貝

深拷貝:

一、概念

建立一個新對象,而後將當前對象的非靜態字段複製到該新對象,不管該字段是值類型的仍是引用類型,都複製獨立的一份。當你修改其中一個對象的任何內容時,都不會影響另外一個對象的內容。

二、實現方法

① 讓每一個引用類型屬性內部都重寫clone()方法

② 利用序列化

序列化是將對象寫到流中便於傳輸,而反序列化則是把對象從流中讀取出來。這裏寫到流中的對象是原始對象的一個拷貝,由於原始對象還存在在JVM中,因此咱們能夠利用對象的序列化產生克隆對象,而後經過反序列化獲取這個對象。

注意每一個須要序列化的類都要實現serializable接口,若是有某個屬性不須要序列化,能夠將其聲明爲transient,即將其排除在克隆屬性以外。

三、利用序列化完成深拷貝的代碼實例

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public final class CloneUtils {
    private CloneUtils() {
        throw new Error(CloneUtils.class + " cannot instance ");
    }

    // 拷貝一個對象
    public static <T extends Serializable> T clone(T obj) {
        // 拷貝產生的對象
        T cloneObj = null;
        try {
            // 讀取對象字節數據
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(cloneObj);
            oos.close();
            // 分配內存空間,寫入原始對象,生成新對象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            // 返回新對象, 並作類型轉換
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return cloneObj;

    }
}

此工具類要求被拷貝的對象實現serializable接口。

注:Apache下的Commons工具包中SerializationUtils類,直接使用更加簡潔。

建議45:重寫equals方法時不要識別不出本身

重寫的equals方法作了多個校驗,考慮到Web上傳遞過來的對象有可能輸入了先後空格,因此用trim方法剪切了一下。

建議46:equals應該考慮null值情景

建議47:在equals中使用getClass進行類型判斷

建議48:重寫equals方法必須重寫hashcode方法

爲啥?官方解釋沒看懂。

建議49:推薦重寫toString方法

Java提供的默認toString方法不友好,打印出來看不懂,不重寫不行!

建議50:使用package-info類爲包服務

package-info簡稱包文檔,爲包級文檔和包級別註釋提供一個地方,並且該文件惟一必須包含的是包的聲明。

一、package-info的建立

bb3a6dd8846e71134c98ecb33fe5d6e9ff9.jpg

c4a67fef7eba63d25f73730d84d584e6fd8.jpg

二、package-info類不能繼承,沒有接口,沒有類間關係等。

但package-info有啥用呢?只是對這個包的註釋說明?

① 聲明友好類和包內訪問常量:

這個比較簡單,並且很實用,好比一個包中有不少內部訪問的類或常量,就能夠統一放到package-info類中,這樣很方便,便於集中管理,能夠減小友好類處處遊走的狀況,代碼以下:

class PkgClazz {
    public void test() {
    }
}
    
class PkgConstant {
    static final String PACKAGE_CONST = "ABC";
}

注意以上代碼是放在package-info.java中的,雖然它沒有編寫package-info的實現,可是package-info.class類文件仍是會生成。經過這樣的定義,咱們把一個包須要的常量和類都放置在本包下,在語義上和習慣上都能讓程序員更適應。

② 爲在包上提供註解提供便利:

好比咱們要寫一個註解(Annotation),查一下包下的對象,只要把註解標註到package-info文件中便可,並且不少開源項目中也採用了此方法,好比Struts2的@namespace、hibernate的@filterDef等。

③ 提供包的總體註釋說明:

若是是分包開發,也就是說一個包實現了一個業務邏輯或功能點或模塊或組件,則該包須要一個很好的說明文檔,說明這個包是作什麼用的,版本變遷歷史,與其餘包的邏輯關係等,package-info文件的做用在此就發揮出來了,這些均可以直接定義到此文件中,經過javadoc生成文檔時,會吧這些說明做爲包文檔的首頁,讓讀者更容易對該包有一個總體的認識。

建議51:不要主動進行垃圾回收

System.gc要中止全部的響應,才能檢查內存中是否存在能夠回收的對象,這對一個應用系統來講風險極大。

 

編寫高質量代碼:改善Java程序的151個建議@目錄

相關文章
相關標籤/搜索