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方法