編寫高質量代碼:改善Java程序的151個建議(第1章:Java開發中通用的方法和準則___建議11~15)

做爲一個由影視圈轉行作Java的菜鳥來講,讀書是很關鍵的,本系列是用來記錄《編寫高質量代碼 改善java程序的151個建議》這本書的讀書筆記。方便本身查看,也方便你們查閱。java

建議11:養成良好習慣,顯示聲明UID程序員

    序列化Serializable是Java提供的通用數據保存和讀取的接口。任何類只要實現了Serializable接口,就能夠被保存到文件中,或者做爲數據流經過網絡發送到別的地方。服務器

package OSChina.Serializable;

import java.io.Serializable;

public class Man implements Serializable {
    private static final long serialVersionUID = 1L;

    private String username;
    private String password;

    public Man(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
package OSChina.Serializable;

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private Man man;
    private String username;
    private transient int age;

    public Person() {
        System.out.println("person constru");
    }

    public Person(Man man, String username, int age) {
        this.man = man;
        this.username = username;
        this.age = age;
    }

    public Man getMan() {
        return man;
    }
    public void setMan(Man man) {
        this.man = man;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
package OSChina.Serializable;

import java.io.*;

public class MainTest {
    private static final String FILE_NAME = "D:/data/rtdata.txt";
    public static void writeSerializableObject() {
        try {
            Man man = new Man("huhx", "123456");
            Person person = new Person(man, "劉力", 21);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
            objectOutputStream.writeObject("string");
            objectOutputStream.writeObject(person);
            objectOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Serializable:反序列化對象
    public static void readSerializableObject() {
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(FILE_NAME));
            String string = (String) objectInputStream.readObject();
            Person person = (Person) objectInputStream.readObject();
            objectInputStream.close();
            System.out.println(string + ", age: " + person.getAge() + ", man username: " + person.getMan().getUsername());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        writeSerializableObject();
        readSerializableObject();
    }
}

這是一個簡單的JavaBean,實現了Serializable接口,能夠在網絡上傳輸,也能夠在本地存儲而後讀取。網絡

序列化和反序列化的類在不一致的狀況下,反序列化時會報一個InalidClassException異常,緣由是序列化和反序列化所對應的類版本發生了變化,JVM不能把數據流轉換爲實例對象。刨根問底:JVM是根據什麼來判斷一個類的版本呢?app

經過SerializableUID,也叫作流標識符(Stream Unique Identifier),即類的版本定義的,它能夠顯示聲明也能夠隱式聲明。顯示聲明格式以下:分佈式

private static final long serialVersionUID = 1867341609628930239L;

serialVersionUID的做用:函數

JVM在反序列化時,會比較數據流中的serialVersionUID與類的serialVersionUID是否相同,若是相同,則認爲類沒有改變,能夠把數據load爲實例相同;若是不相同,拋個異常InviladClassException。性能

剛開始生產者和消費者持有的Person類一致,都是V1.0,某天生產者的Person類變動了,增長了一個「年齡」屬性,升級爲V2.0,因爲種種緣由(好比程序員疏忽,升級時間窗口不一樣等)消費端的Person類仍是V1.0版本,添加的代碼爲 priavte int age;以及對應的setter和getter方法。this

此時雖然生產這和消費者對應的類版本不一樣,可是顯示聲明的serialVersionUID相同,序列化也是能夠運行的,所帶來的業務問題就是消費端不能讀取到新增的業務屬性(age屬性而已)。spa

經過此例,咱們反序列化也實現了版本向上兼容的功能,使用V1.0版本的應用訪問了一個V2.0的對象,這無疑提升了代碼的健壯性。

顯示聲明serialVersionUID能夠避免對象的不一致,但儘可能不要以這種方式向JVM撒謊。

建議12:避免用序列化類在構造函數中爲不變量賦值

咱們知道帶有final標識的屬性是不變量,也就是隻能賦值一次,不能重複賦值,可是在序列化類中就有點複雜了,好比這個類:

在構造函數中不容許對final變量從新賦值。

去掉final以後呢?

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    public static String perName="程咬金";

    public Person() {
        System.out.println("person constru");
        perName = "秦叔寶";
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println(perName);
    }
}

序列化與反序列化的時候構造函數不會執行。

建議13:避免爲final變量複雜賦值

如今的Java好像已經命令禁止final的從新賦值了!

建議14:使用序列化類的私有方法巧妙解決部分屬性持久化問題

例如:一個計稅系統和一個HR系統,計稅系統須要從HR系統得到人員的姓名和基本工資,而HR系統的工資分爲兩部分:基本工資和績效工資,績效工資是保密的,不能泄露到外系統。

public class Salary implements Serializable {
    private static final long serialVersionUID = 2706085398747859680L;
    // 基本工資
    private int basePay;
    // 績效工資
    private int bonus;

    public Salary(int _basepay, int _bonus) {
        this.basePay = _basepay;
        this.bonus = _bonus;
    }
//Setter和Getter方法略

}
public class Person implements Serializable {

    private static final long serialVersionUID = 9146176880143026279L;

    private String name;

    private Salary salary;

    public Person(String _name, Salary _salary) {
        this.name = _name;
        this.salary = _salary;
    }

    //Setter和Getter方法略

}
public class Serialize {
    public static void main(String[] args) {
        // 基本工資1000元,績效工資2500元
        Salary salary = new Salary(1000, 2500);
        // 記錄人員信息
        Person person = new Person("張三", salary);
        // HR系統持久化,並傳遞到計稅系統
        SerializationUtils.writeObject(person);
    }
}
public class Deserialize {
    public static void main(String[] args) {
        Person p = (Person) SerializationUtils.readObject();
        StringBuffer buf = new StringBuffer();
        buf.append("姓名: "+p.getName());
        buf.append("\t基本工資: "+p.getSalary().getBasePay());
        buf.append("\t績效工資: "+p.getSalary().getBonus());
        System.out.println(buf);
    }
}

但這個不符合需求,你可能會想到一下四種解決方案:

一、java 的transient關鍵字爲咱們提供了便利,你只須要實現Serilizable接口,將不須要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。

static修飾的變量也不能序列化。

在bonus前加上關鍵字transient,使用transient關鍵字就標誌着salary失去了分佈式部署的功能,一旦出現性能問題,再想分佈式部署就不可能了,此方案否認。

注:分佈式部署是將數據分散的存儲於多臺獨立的機器設備上,採用可擴展的系統結構,利用多臺存儲服務器分擔存儲負擔,利用未知服務器定位存儲信息,提升了系統的可靠性、可用性和擴展性。

二、新增業務對象:增長一個Person4Tax類,徹底爲計稅系統服務,就是說它只有兩個屬性:姓名和基本工資。符合開閉原則,並且對原系統也沒有侵入性,只是增長了工做量而已。可是這個方法不是最優方法;

下面展現一個優秀的方案,其中實現了Serializable接口的類能夠實現兩個私有方法:writeObject和readObject,以影響和控制序列化和反序列化的過程。

public class Person implements Serializable {

    private static final long serialVersionUID = 9146176880143026279L;

    private String name;

    private transient Salary salary;

    public Person(String _name, Salary _salary) {
        this.name = _name;
        this.salary = _salary;
    }
    //序列化委託方法
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeInt(salary.getBasePay());
    }
    //反序列化委託方法
    private void readObject(ObjectInputStream input)throws ClassNotFoundException, IOException {
        input.defaultReadObject();
        salary = new Salary(input.readInt(), 0);
    }
}

其它代碼不作任何改動,運行以後結果爲:

這裏用到了序列化的獨有機制:序列化回調。

Java調用ObjectOutputStream類把一個對象轉換成數據流時,會經過反射(refection)檢查被序列化的類是否有writeObject方法,而且檢查其實否符合私有,無返回值的特性,如有,則會委託該方法進行對象序列化,若沒有,則由ObjectOutputStream按照默認規則繼續序列化。一樣,從流數據恢復成實例對象時,也會檢查是否有一個私有的readObject方法,若是有經過該方法讀取屬性值。

① oos.defaultWriteObject():告知JVM按照默認規則寫入對象

② ois.defaultWriteObject():告知JVM按照默認規則讀出對象

③ oos.writeXX和ois.readXX

分別是寫入和對出響應的值,相似一個隊列,先進先出,若是此處有複雜的數據邏輯,建議按封裝Collection對象處理。

上面的方式也是Person失去了分佈式部署的能了,確實是,可是HR系統的難點和重點是薪水的計算,特別是績效工資,它所依賴的參數很複雜,計算公式也不簡單(通常是引入腳本語言,個性化公式定製)而相對來講Person類基本上都是靜態屬性,計算的可能性不大,因此即便爲性能考慮,Person類爲分佈式部署的意義也不大。

既然這樣,爲什麼不直接使用transient???

建議15:break萬萬不可忘

 

江疏影讀書系列之編寫高質量代碼:改善Java程序的151個建議

相關文章
相關標籤/搜索