Ví dụ: “Cách bình thường: Map m=new HashMap; *Với hàm khởi tạo: Map m=HashMap.newlnstance; * Quy udc: — lớp chứa hàm kiểu này không nên được khởi tạo trực tiếp nữa, vì đã có hàm khởi
Trang 1
Tac gia: Joshua Bloch Trình bày: Lê Quốc Anh
30-05-2015
Trang 2
VN-Có nhận xét gì về đoạn code này ?
List<Integer> lst = new ArrayList<Integer>() ;
Iterator<Integer> iter = lst.iterator();
Trang 3Có gì khác nhau?
List<Integer> lst = new List<Integer> lst = new
ArrayList<Integer>() ; LinkedList<Integer>() ;
Iterator<Integer> iter = Iterator<Integer> iter =
1st.iterator(); Let., iterator ();
iter.remove () ; iter remove () ;
Trang 4So sánh thời gian thực hiện remove
size=1000: time =166 ms size=1000: time =57 ms
size=10000: time =8167 ms size=10000: time =60 ms
Trang 6LinkedList<E> ArrayList<E>
Get (idx) Add(e)
‘Add(idx, e)
Remove(idx)
Trang 8Effective Java
Second Edition
Trang 9
PHAN 1: TAO VA HUY ĐỐI
TUONG
Trang 10Quy tắc 1: Dùng hàm gọi đối tượng
» _ Định nghĩa: Là hàm trả lại một đối tượng của lớp
public static Boolean valueOf(boolean b) {return b ? Boolean.TRUE : Boolean.FALSE;
* Loi ich:
— Không cần phải khởi tạo lớp mỗi lần gọi
— Gọi đến cùng một đối tượng (ví dụ Singleton ở phần sau)
— Dé doc (ham cé tén gọi, rút gọn tham số, etc ) Ví dụ:
“Cách bình thường: Map<String, List<String>> m=new HashMap<String, List<String>>();
*Với hàm khởi tạo: Map<String, List<String>> m=HashMap.newlnstance();
* Quy udc:
— lớp chứa hàm kiểu này không nên được khởi tạo trực tiếp nữa, vì đã có hàm khởi tạo va tra
lại đối tượng của lớp rồi Do vậy lớp hoặc constructor của lớp được khai báo private
— _ Nếu đối tượng trả về có tên gọi là Type thì lớp chứa hàm factory sé có tên la Types Ví dụ
public abstract class Books() { public static Book createBook(){ return new MyBook(); }
private class MyBook implements Book{ public void cover(){} } }
10
Trang 11Quy tắc 1: Dùng hàm gọi đối tượng
° Điểm hạn chế:
— Lớp chứa các static factory methods không cho phép
kế thừa Ví dụ không thể kế thừa lớp
java.util.Collections (theo quy ước lớp này có giao
diện tên là java.util.Collection không có s) Nguyên nhân là để các lập trình viên nên dùng composition thay vì inheritance mà ta sẽ xem trong Quy tắc 16
— Không có sự khác biệt rõ ràng giữa các hàm static
factory methods và các static factory khác.
Trang 12Quy tắc 2: Dùng builder cho lớp có
nhiều tham số
* _ Xem xét lỚp:
public class NutritionFacts (
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional private final int sodium; // (mg) optional private final int carbohydrate; // (g) optional
}
NutritionFacts cocaCola = new NutritionFacts(size, serving, calos, fat, sodium, carbo);
* Hạn chế: Nhiều tham số tùy chọn nhưng vẫn phải khai báo hết khi khởi tạo
* Giải pháp 1: Khởi tạo đối tượng và dùng hàm setter
— Vidu public void setFat(int val) { fat = val; }
— _ Không khả thi cho lớp bất biến
* _ Giải pháp 2: Dùng lớp trung gian builder để tùy chọn khai báo tham số
public class NutritionFacts {
public static class Builder {
}
Trang 13Quy tắc 2: Dùng builder cho lớp có
nhiều tham số
public class NutritionFacts {
public static class Builder {
}
private final int servingSize; // bat budc
private int calories = 0; // tùy chọn, khởi tạo với giá trị mặc định private int fat = 0; // tùy chọn, khởi tạo với giá trị mặc định
public Builder(int servingSize) {this.servingSize = servingSize; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; }
public NutritionFacts build() {return new NutritionFacts(this); }
Trang 14Quy tắc 3:Xây dựng lớp Singleton với kiểu Enum
* - Câu hỏi: Singleton khác với lớp bất biến???
*- Định nghĩa: Lớp chỉ được khởi tạo 1 lần duy nhất Ví dụ:
// Singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { }
* - Câu hỏi: Khi lớp Singleton thực thi Serializable?
Trang 15Quy tắc 3:Xây dựng lớp Singleton với kiểu Enum
// Singleton with static factory
public class Elvis implements Serializable {
private transient String[] songs= {« a», « b »};
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { }
// readResolve method to preserve singleton property
// Return the one true Elvis and let the garbage collector // take care of the Elvis impersonator
return INSTANCE;
}
*Deserialization sẽ gọi hàm constructor để tạo ra đối tượng mới ngay cả khi nó được khai báo private
*readResolve() bình thường sẽ trả lại đối tượng mới được tạo mới sau khi deserialized => viết lại
* Các thuộc tính (eg., version) nên được khai báo với từ khóa transient nếu giá trị của chúng không đổi trước và sau khi serialization hoặc chúng có kiểu khác primitives.
Trang 16Quy tắc 3:Xây dựng lớp Singleton với kiểu Enum
+ Giải pháp ngắn gọn và an toàn hơn là dùng Enum
public enum Elvis {
INSTANCE;
private transient String[] songs= {« a », « b »};
public void leaveTheBuilding() { }
}
* Enum mac dinh đã thu’ thi serializable
Trang 17Quy tắc 4: Cấm khởi tạo lớp với
private constructor
*_ Đôi khi ta muốn xây dựng lớp chỉ chứa các
static methods hay static fields
* Giải pháp khai báo lớp kiểu trừu tượng
Abstract không hiệu quả vì lớp con của nó
vẫn có thể được khởi tạo
* Giải pháp triệt để là khai báo lớp với private
constructor
Trang 18Quy tắc 5: Tránh tạo đối tượng thừa
* Vi du: String s = new String("stringette");
— Có đến 2 đối tượng được tạo ra
— Tương đương: String s = "stringette”;
° Giải pháp chung:
— Str dung static factory methods (Quy tac 1)
— Sử dụng phương phap Jazy initialization
— Sử dụng kiểu biến primitives (eg., long, int,
double) thay vì boxed primitives (eg., Long,
Integer, Double)
Trang 19Quy tắc 6: Loại bỏ các tham chiếu
đến đối tượng không dùng nữa
° Xét vi du sau:
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[ size];
return result;
Trang 20Quy tắc 6: Loại bỏ các tham chiếu
đến đối tượng không dùng nữa
° Xét ví dụ sau:
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements|[ size];
elements[size] = null; // Eliminate obsolete reference return result;
}
* Giải pháp: chú ý giải pháp bộ nhớ khi xây
dựng caches, listeners hoặc callbacks
Trang 21Quy tắc 7: Tránh sử dụng
finalizers
° Định nghĩa: Garbage collector (GC) trong Java phụ trách giải phóng các đối tượng không còn có thể được sử dụng (unreacheable) Trước khi giải phóng đối tượng thì hàm finalize() của đối tượng đó sẽ được gọi
»- Sử dụng finalizers nguy hiểm? Vì sao?
— Không dự đoán được kết quả: thời gian từ lúc một đối tượng thành unreachable cho tới lúc được GC xử lý là không xác định
— Ngay cả ép chạy GC bằng lệnh System.gc vì nó cũng không
đảm bảo để finalize() của đối tượng được thực thi tức thời
— Sử dụng finalize() làm tăng thời gian thực hiện lên 430 lần (cần
kiểm chứng lại)
Trang 22Quy tac 7: Tránh sử dụng
finalizers
* Giải pháp
— Thay thế bằng try finally
— Hoặc gọi lệnh System.runFinalizersOnexit hoặc
Runtime.runFinalizersOnExit Tuy nhiên cả 2 hàm
này không còn được sử dụng (deprecated) bởi lý
do nó có thể được gọi ngay cả với đối tượng
reacheable => gây ra các hành vi bất thường
không kiểm soát được
Trang 23Quy tắc 7: Tránh sử dụng
finalizers
*_ Giải pháp
E Nếu buộc phải sử dụng, phải đảm bảo finalize() luôn được gọi
cả ở lớp cha (superclass) và lớp con (subclass) Ví dụ
@Override protected void finalize() throws Throwable { try{
// Finalize subclass state } finally {
super finalize(); } }
— Hoặc sử dụng phương pháp finalizer guardian để đảm bảo
finalize() luôn được gọi Ví dụ:
public class Foo {
Foo object private final Object finalizerGuardian = new
Object() {
@Override protected void finalize() throws Throwable { }
}
Trang 24
PHẦN 2: PHƯƠNG THỨC
CHUNG CHO CÁC ĐÔI TƯƠNG
Trang 25Quy tắc 8: Điều kiện khi viết lại hàm
Khong can quan tam lieu ham equals hien co co hop ly khong
Vi du nhu viet lai ham equals cho lop Random de bat buoc 2 the hien cua lop Random duoc cho la bang nhau neu va chi neu chung san xuat mot chuoi cac gia tri giong nhau Cai nay phai xem lai nhu cau co can thiet ko
Cac lop cua Set: HashSet, TreeSet, hay cac lop cua List:
ArrayList, LinkedList, da viet de ham equals thich hop cho chung roi thi cung ko nen viet lai
Cac lop private hay package-private thi tuyet doi ko can viet lai ham equals vi chung ko the duoc truy cap toi :)
Trang 26Quy tắc 8: Điều kiện khi viết lại hàm
equals()
+ Nếu cần viết lại hàm equals thì phải tuân thủ 5 quy định sau:
1 Phan chieu: No phai bang no, x.equals(x) luon dung
2 Doi xung: x.equals(y) dung khi va chi khi y.equals(x) cung dung
3 Bac cau: Neu x.equals(y) va y.equals(z) dung thi x.equals(z) cung
dung
4 Vung chac: Neu x va y la cac gia tri khong null thi x.equals(y) se
phai luon tra lai gia tri nhu nhau moi khi duoc goi den cho du cac gia tri khong thuoc equals cua lop x va y co the bi thay doi Loi khuyen la khong neu dung cac resources khong tin cay de xay dung ham equals
5 Loi goi x.equals(null) luon tra lai false.
Trang 27Quy tắc 8: Điều kiện khi viết lại hàm
equals()
* - Cách xây dựng hàm equals hiệu quả:
— Dau tien kiem tra bang toan tu == xem lieu chung co reference
— Kiem tra xem chung co cung kieu khong bang toan tu
instanceof (vd: if (!(o instanceof PhoneNumber)) return false;)
— Ep kieu de chung ve cung kieu (vd: PhoneNumber pn =
(PhoneNumber)o;)
— So sanh tung doi so (argument) xem chung co tuong ung voi
— Sau khi viet xong ham equals, kiem tra lai xem no co tuan thu 5 quy dinh khong
— Luon viet lai ham hashCode mot khi da viet lai ham equals (quy tac 9)
Trang 28Quy tắc 9: Luôn viết lại hashCode()
sau khi thay đổi hàm equals()
1 Trong một phiên làm việc nếu hàm hashCode được gọi
lại nhiều lần thì kết quả trả lại đều phải như nhau Tuy nhién, néu đó là 2 phiên làm việc khác nhau thì có thể cho kết quả hashCode khác nhau
2 Nếu hai đối tượng x, y được tính là bằng nhau theo hàm
3 Ngược lại, nếu x equals(y) == false, thi ham hashCode()
cUa x va y không nhất thiễt phải khác nhau
4 Nên biết rằng khi hàm hashCode khác nhau trên mỗi đối
tượng khác nhau thì mới tăng hiệu suất của hệ thống.
Trang 29Quy tắc 9: Luôn viết lại hashCode()
sau khi thay đổi hàm equals()
Điều 2 « đối tượng bằng nhau theo equals thì hashCode phải
bằng nhau» hay bị vi phạm khi ta viết lại hàm equals Ví dụ:
@Override public boolean equals(Object o) {
if (o == this) return true;
PhoneNumber pn = (PhoneNumber)o;
}
HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
Chúng ta hi vọng lời gọi m.get(new PhoneNumber(707 , 867,
5309)) sé tra lai gia tri "Jenny", nhuing no tra lai null => Ly do là vì hàm hashCode cho 2 đối tượng không bằng nhau
Trang 30Quy tắc 9: Luôn viết lại hashCode()
sau khi thay đổi hàm equals()
* Cach giai quyet don gian nhat la viet lai ham hashCode nhu sau:
@Override public int hashCode() {
return 42;
}
* Cach lam nhu tren co on thoa khong?
Trang 31Quy tắc 9: Luôn viết lại hashCode()
sau khi thay đổi hàm equals()
Cach giai quyet don gian nhat la viet lai ham hashCode nhu
sau:
@Override public int hashCode() { return 42; }
Cach lam nhu tren co on thoa khong?
Khong, vi nhu vay tat ca cac doi tuong se co cung gia tri
hashCode Khi do bang bam se phai su dung danh sach lien
ket (linkedList) de tim kiem doi tuong va do do lam giam hieu
suat tim kiem
Giai phap hay nhat la xay dung hashCode sao cho hai doi
tuong khong bang nhau se ko co chung gia tri hashCode => Dieu nay kho, phai lam vai cai luan van TS
Trang 32Quy tắc 9: Luôn viết lại hashCode()
sau khi thay đổi hàm equals()
* Nguyen tac xay dung an toan ham hashCode:
— la chi su dung cac gia tri dung trong ham equals
— Can doi giua running time tong the he thong so voi running time cua ham hashCode Loi khuyen la khong nen ngai su dung tỉnh toan phục tap hashCode
— Neu viec tinh toan hashCode phuc tap, khong nen tinh lai no nhieu lan cho mot doi tuong, cung nhu chi tinh toan no khi doi tuong duoc tao ra Vi du:
private volatile int hashCode; // (Xem quy tac 71)
@Override public int hashCode() {
int result = hashCode;
if (result == 0) { result = 17; result = 31 * result + areaCode;
result = 31 * result + prefix; result = 31 * result + lineNumber;
}
return result; }
Trang 33Quy tắc 10: Nên viết lại toString()
* ham toString nen tra lai toan bo thong tin ve doi tuong
* viet tai lieu ro rang ve dinh dang du lieu tra ve cua
toString- cung cap ham truy cap den tat ca cac thong tin
tra ve boi toString
— LTV khong can phai xu ly chuoi String ket xuat thong tin nao do
— Kho kiem soat khi dinh dang du lieu tra ve cua toString thay doi
— Vi du, binh thuong lop PhoneNumber.toString se tra lai
"PhoneNumber@14845" tuong ung "ten lop@gia tri hashCode viet duoi dang unsigned hexadecimal" Chung ta co the viet lai ham toString ro rang hon de tra lai gia tri vi du "(0084) 043 -
8738242"