1. 程式人生 > >Effective Java筆記(一)

Effective Java筆記(一)

Effective Java筆記(一)

Effective Java

1. 考慮用靜態方法代替構造器

例如

public static Boolean valueOf(boolean b){
    return b > Boolean.TRUE : Boolean.FALSE
}

優勢

  • 有名稱
  • 不必每次呼叫他們的的時候都建立一個新物件
  • 可以返回員返回型別的任何子型別的物件

缺點

  • 類如果不含共有的或者搜保護的構造器沒,就不能被子類化
  • 他們於其他的靜態方法實際上沒有任何區別

2.遇到多個構造器引數時要考慮用構建器

JavaBeans

呼叫一個無參構造器來建立物件,然後呼叫 setter方法來設定每個必要的引數以及各每個相關的可選引數
缺點:

  • 因為被分到幾個呼叫中,在構建過程中可能處於不一致的狀態。
  • 阻止了把類做成不可變的可能,需求程式設計師付出額外的努力來確保它的執行緒安全

Builder模式(建造者模式)

如果類的構造器或者靜態工廠中具有多個引數,設計這種類時,Builder模式是不錯的選擇。

// 例如
public class Course {
    private String courseName;
    private String coursePPT;
private String courseVideo; private String courseArticle; /** * question & answer */ private String courseQA; public Course(CourseBuilder courseBuilder) { this.courseName = courseBuilder.courseName; this.coursePPT = courseBuilder.coursePPT; this
.courseVideo = courseBuilder.courseVideo; this.courseArticle = courseBuilder.courseArticle; this.courseQA = courseBuilder.courseQA; } @Override public String toString() { return "Course{" + "courseName='" + courseName + '\'' + ", coursePPT='" + coursePPT + '\'' + ", courseVideo='" + courseVideo + '\'' + ", courseArticle='" + courseArticle + '\'' + ", courseQA='" + courseQA + '\'' + '}'; } public static class CourseBuilder { private String courseName; private String coursePPT; private String courseVideo; private String courseArticle; /** * question & answer */ private String courseQA; public CourseBuilder buildCoureseName(String courseName) { this.courseName = courseName; return this; } public CourseBuilder buildCoursePPT(String coursePPT) { this.coursePPT = coursePPT; return this; } public CourseBuilder buildCourseVideo(String courseVideo) { this.courseVideo = courseVideo; return this; } public CourseBuilder buildCourseArticle(String courseArticle) { this.courseArticle = courseArticle; return this; } public CourseBuilder buildCourseQA(String courseQA) { this.courseQA = courseQA; return this; } public Course build() { return new Course(this); } } } // 測試 Course course = new Course.CourseBuilder() .buildCoureseName("Java設計模式精講") .buildCoursePPT("Java設計模式PPT") .buildCourseVideo("Java設計模式視訊") .build(); System.out.println(course);

3.用私有構造器或者列舉型別強化Singleton屬性

Singleton是指僅僅被例項化一次的類。

注意:享有特權的客戶端可以藉助 AccessibleObject.setAccessible方法,通過反射機制呼叫私有構造器

4.通過私有構造器強化不可例項化的能力

企圖通過將類做成抽象類來強制該類不可被例項化,是行不通的

5.避免建立不必要的物件

要優先使用基本型別而不是裝箱基本型別,要當心無意思的自動裝箱

6.消除過期的物件引用

/**
 * @author stone
 * @des 記憶體洩露示例
 * @date 2018/12/5/005 8:40
 **/
public class Stack {

    private Object[] element;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {

    }

    public void push(Object e) {
        ensureCapacity();
        element[size++] = e;
    }

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        // 注意下面這行程式碼會引起記憶體洩露
        // 如果一個棧是先增長,然後再收縮,那麼,從棧中彈出來的物件將不會被當做垃圾回收,即使使用棧的程式不再引用這些物件,他們也不會被回收
        // 棧內部維護著這些物件的過期引用。過期引用:永遠也不會再被解除的引用。
        // 本例中 凡是element陣列中的活動部分之外的任何引用都是過期的。活動部分:element中下標小於size的那些元素
        // return element[--size];

        Object result = element[--size];
        // 解決方法如下 消除過期引用
        // Eliminate obsolete reference
        element[size] = null;
        return result;


    }

    /**
     * ensure space for at least one more element,roughly
     * doubling the capacity each time the array needs to grow
     */
    private void ensureCapacity() {
        if (element.length == size) {
            element = Arrays.copyOf(element, 2 * size + 1);
        }
    }
}

總結 : 過期的物件引用 可能會引起記憶體洩露
只要類是自己管理記憶體,程式設計師就應該警惕記憶體洩露的問題。

記憶體洩露常見來源

  • 只要類是自己管理記憶體,程式設計師就應該警惕記憶體洩露的問題。
  • 快取
  • 監聽器和其他回撥

7.避免使用終結方法

終結方法(finalizer)通常是不可預測的,很危險的,也一般是不必要的。

Java語言規範不僅不保證終結方法會被及時地執行,而且根本就不保證它們會被執行。
不應該依賴終結方法來更新重要的持久狀態。

System.gc System.runFinalization這兩個方法確實增加了終結方法被執行的機會,但它們並不保證終結方法一定會被執行。

終結方法有一個非常嚴重的(Severe)效能損失。
用終結方法建立和銷燬物件比正常要慢上百倍。

8.覆蓋equals時請遵守通用約定

equals方法實現了等價關係

  • 自反性 非null情況下 x.equals(s) return true
  • 對稱性 非null情況下 如果 y.equals(x) return true 則 x.equals(y) return true
  • 傳遞性 非null情況下 x,y,z 如果 x.equals(y) return true && y.euqals(z) return true 則 x.equals(z) return true
  • 一致性 非null情況下 x,y不被修改 x.equals(y) 結果不會變
  • 對於任何非null的引用值 x, x.equals(null) 一定 return false

一個對稱性衝突的例子 不建議如此書寫程式碼

public final class CaseInsensitiveString {

    private final String s;

    public CaseInsensitiveString(String s) {
        if (s == null) {
            throw new NullPointerException();
        }
        this.s = s;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString) {
            return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
        }
        // 為了解決對稱性的問題 建議將下面於String 進行操作的程式碼刪除
        if (o instanceof String) {
            return s.equalsIgnoreCase((String) o);
        }
        return false;
    }

    public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
        String s = "polish";

        // 注意 這裡違反了 equals的對稱性
        // 對稱性 非null情況下 如果  y.equals(x) return true 則  x.equals(y) return true
        // cis 的 equals 知道要忽略大小寫 但 s.equals 不知道
        boolean result1 = cis.equals(s);
        System.out.println(result1);
        boolean result2 = s.equals(cis);
        System.out.println(result2);
    }

}

我們無法在拓展可例項化的類的同時,即增加新的值元件,同時有保留 equals約定嗎

告誡

  • 覆蓋equals時總要覆蓋hashCode
  • 不要企圖讓equals方法過於智慧
  • 不要將equals宣告中的Object物件替換為其他的型別

9.覆蓋equals時總要覆蓋hashCode

在每個覆蓋了equals方法的類中,必須覆蓋hashCode。如果不這樣做就會違反Object,hashCode的通用約定,從而導致該類無法結合所有基於雜湊的集合一起正常工作,這些集合包括HashMap、HashSet和Hashtable。

  • 在應用程式的執行期間,只要物件的equals方法的比較操作所用到的資訊沒有被修改,那麼對這同一個物件呼叫多次,hashCode方法都必須始終如一地返回同一個整數。在同一個應用程式的多次執行過程中,每次執行所返回的整數可以不一致。
  • 如果兩個物件根據equals(Object)方法比較是相等的,那麼呼叫這兩個物件中任意一個物件的hashCode方法都必須產生同樣的整數結果。
  • 如果兩個物件根據equals(Object)方法比較是不相等的。那麼呼叫這兩個物件中任意一個物件的hashCode方法,則不一定要產生不同的整數結果。但是程式設計師應該知道,給不相等的物件產生截然不同的整數結果,有可能提高散列表(hash table)的效能。
public class PhoneNumber {

    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    private static void rangeCheck(int arg, int max, String name) {
        if (arg < 0 || arg > max) {
            throw new IllegalArgumentException(name + ":" + arg);
        }
    }

    @Override
    public boolean equals(Object o) {

        if (o == this) {
            return true;
        }
        if (!(o instanceof PhoneNumber)) {
            return false;
        }
        PhoneNumber pn = (PhoneNumber) o;

        return pn.lineNumber == lineNumber
                && pn.prefix == prefix
                && pn.areaCode == areaCode;

    }

    @Override
    public int hashCode() {
        int result = 17;

        // 31 * i == (i << 5) - i;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
        m.put(new PhoneNumber(707, 867, 5309), "jenny");

        // 新增 hashCode後 可以獲取到該 value
        // 如果不新增 hashCode 方法 這裡獲取到的 value是null
        String result = m.get(new PhoneNumber(707, 867, 5309));
        System.out.println(result);

    }

}

注:關於hashCode 的寫法有一整套的數學公式 1.6前還沒有支援函式化(雜湊函式) 需要自己寫
有空檢視一下 1.5之後有沒有支援

10.始終要覆蓋 toString

 PhoneNumber a = new PhoneNumber(707, 867, 5309);
        System.out.println(a.toString());

console

[email protected]

可以發現非常難以理解
建議儘量重寫 toString方法