1.使類和成員的可訪問性最小化
訪問修飾符:
- private
- protected
- public
頂層的(非嵌套)類和接口描孟,兩種訪問級(jí)別:
- 包級(jí)私有(package-private)
- public
成員(域脱拼、方法、嵌套類和嵌套接口)
- private:只有在聲明該成員的頂層類內(nèi)部才可以訪問
- package-private:聲明該成員的包內(nèi)部的任何類都可以訪問,是默認(rèn)訪問級(jí)別
- protected:在聲明類和子類中可以訪問
- public:任何地方可以訪問
規(guī)則一:盡可能使每個(gè)類或者成員不被外界訪問
如果一個(gè)包級(jí)私有的頂層類只是在某一個(gè)類的內(nèi)部使用,就應(yīng)該考慮使它成為唯一使用它的那個(gè)類的私有嵌套類。
規(guī)則二:如果方法覆蓋了超類中第一個(gè)方法撩穿,子類中的訪問級(jí)別就不允許低于超類的訪問級(jí)別,確保任何可使用超類的地方都可以使用子類
規(guī)則三:接口中的所有方法都必須是public
規(guī)則四:實(shí)例域不能是公有的
如果域時(shí)非final的谒撼,或者是一個(gè)指向可變對(duì)象的final引用食寡,那么一旦使這個(gè)域稱為公有,就放棄了在這個(gè)域中的值進(jìn)行限制的能力廓潜,也就放棄了這個(gè)域的不可變能力
包含公有可變域的類并不是線程安全的抵皱。
規(guī)則四:靜態(tài)域不要是公有的(除了暴露靜態(tài)常量)
要對(duì)外暴露靜態(tài)域,必須是基本類型的值辩蛋,或者是不可變對(duì)象呻畸。
長(zhǎng)度非零的數(shù)組總是可變的,所以悼院,類具有公有的靜態(tài)final數(shù)組域伤为,或者返回這種域的方法,總是不正確的据途。
以下錯(cuò)誤:
public static final Tings[] VALUES = {...};
解決方案:公有數(shù)組私有化绞愚,并增加一個(gè)公有的不可變列表
private static final Tings[] VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(VALUES));
或者:添加公有方法,返回私有數(shù)組的拷貝
private static final Tings[] VALUES = {...};
public static final Tings[] values() {
return VALUES.clone;
}
2.在公有類中使用訪問方法而非公有域
公有類永遠(yuǎn)不要暴露可變的類颖医。
3.使可變性最小化
不可變類:其實(shí)例不能被修改的類位衩。具體來說,每個(gè)實(shí)例中包含的所有信息都必須在創(chuàng)建該實(shí)例的時(shí)候就提供熔萧,并在對(duì)象的整個(gè)生命周期內(nèi)固定不變糖驴。
Java 平臺(tái)類庫(kù)中的不可變類:String、基本類型的包裝類哪痰、BigInteger遂赠、BigDecimal。不可變類對(duì)應(yīng)配套的可變類:StringBuilder晌杰、BitSet跷睦。本應(yīng)該是不可變,但卻是可變的類:Date肋演、Point抑诸。
不可變類的優(yōu)點(diǎn):
- 易于設(shè)計(jì)、實(shí)現(xiàn)和使用
- 不可變對(duì)象很簡(jiǎn)單爹殊、只有一種狀態(tài)蜕乡,即被創(chuàng)建時(shí)的狀態(tài)
- 不容易出錯(cuò)、更加安全
- 線程安全梗夸,不要求同步层玲,可以被自由的共享
不可變類的缺點(diǎn):
- 在特定的情況下,存在潛在的性能問題,比如執(zhí)行一個(gè)多步驟操作辛块,每個(gè)步驟都會(huì)產(chǎn)生一個(gè)新的對(duì)畔派,但除了最后的結(jié)果之外其他的對(duì)象最終都會(huì)被丟棄,就會(huì)有性能問題
- 所以應(yīng)該使一些小的值對(duì)象成為不可變的
- 如果發(fā)生了性能問題润绵,才應(yīng)該為不可變的類提供公有的可變配套版
String對(duì)象不可變性的優(yōu)缺點(diǎn)
- 字符串常量池的需要.
- 線程安全考慮
- 類加載器要用到字符串线椰,不可變性提供了安全性,以便正確的類被加載
- 支持hash映射和緩存
使類成為不可變尘盼,遵循的規(guī)則:
- 不要提供任何會(huì)修改對(duì)象狀態(tài)(屬性)的方法
- 保證類不會(huì)被擴(kuò)展憨愉,不會(huì)有子類,破壞該類的不可變行為
- 如果類可以被繼承會(huì)破壞類的不可變性機(jī)制卿捎,只要繼承類覆蓋父類的方法并且繼承類可以改變成員變量值配紫,那么一旦子類以父類的形式出現(xiàn)時(shí),不能保證當(dāng)前類是否可變娇澎。使所有的域都是final的笨蚁。
- 使所有的域(屬性)都是final
- 使所有的域都是private,防止客戶端獲得訪問被域引用的可變對(duì)象的權(quán)限趟庄,并防止客戶端直接修改這些對(duì)象
- 確保對(duì)于任何可變組件的互斥訪問
- 如果類具有指向可變對(duì)象的域,必須確保該類的客戶端無法獲得執(zhí)行這些對(duì)象的引用
- 在構(gòu)造器中伪很,永遠(yuǎn)不要用客戶端提供的對(duì)象引用來初始化這樣的域
- 在訪問方法中戚啥,也不要返回該對(duì)象引用
- 普通對(duì)象,直接new 一個(gè)新的對(duì)象
- 數(shù)組這類復(fù)雜對(duì)象锉试,可使用 clone方法
- 在構(gòu)造器猫十、訪問方法和 readObject 方法中請(qǐng)使用保護(hù)性拷貝技術(shù)
通過構(gòu)造器初始化所有成員,構(gòu)造器初始化成員時(shí)呆盖,需要進(jìn)行深淺拷貝拖云,如果構(gòu)造器傳入的對(duì)象直接賦值給成員變量,還是可以通過對(duì)傳入對(duì)象的修改進(jìn)而導(dǎo)致改變內(nèi)部變量的值
public final class ImmutableDemo {
private final int[] myArray;
public ImmutableDemo(int[] array) {
this.myArray = array; // wrong
}
}
這種方式不能保證不可變性应又,myArray和array指向同一塊內(nèi)存地址宙项,用戶可以在ImmutableDemo之外通過修改array對(duì)象的值來改變myArray內(nèi)部的值。
為了保證內(nèi)部的值不被修改株扛,可以采用深度copy來創(chuàng)建一個(gè)新內(nèi)存保存?zhèn)魅氲闹涤瓤稹U_做法:
public final class MyImmutableDemo {
private final int[] myArray;
public MyImmutableDemo(int[] array) {
this.myArray = array.clone();
}
}
不可變類的設(shè)計(jì)
對(duì)于訪問方法,一般會(huì)返回一個(gè)新的實(shí)例洞就,而不是修改這個(gè)實(shí)例盆繁,大多數(shù)不可變類使用這種模式,稱為函數(shù)的做法旬蟋。
不可變的類一般會(huì)提供一些靜態(tài)工廠油昂,它們把頻繁被請(qǐng)求的實(shí)例緩存起來,使客戶端之間可以共享這些實(shí)例,而不用創(chuàng)建新的實(shí)例冕碟,降低內(nèi)存占用和垃圾回收成本拦惋。所有基本類型的包裝類和BigInteger都有這樣的靜態(tài)工廠。
不可變對(duì)象可以被自由共享鸣哀,所以根本不需要做任何拷貝架忌,因?yàn)榭截愂冀K等于原始的對(duì)象,所以不需要為不可變的類提供clone
方法或者拷貝構(gòu)造器我衬。
不僅可以共享不可變對(duì)象叹放,也可以共享它們的內(nèi)部信息。
不可變對(duì)象為其他對(duì)象提供了大量的構(gòu)件挠羔。
有關(guān)序列化井仰,如果讓自己的不可變類實(shí)現(xiàn)序列化,就必須顯式提供 readObject 或者 readResolve破加,否則反序列化可能會(huì)產(chǎn)生新的實(shí)例俱恶。
盡量使用不可變類,不要為每個(gè)get
方法編寫一個(gè)相應(yīng)的set
方法
舉例
說明:這個(gè)類表示一個(gè)復(fù)數(shù)范舀,加減運(yùn)算都是返回一個(gè)新的實(shí)例合是,而不是在原來的實(shí)例上修改,稱為函數(shù)的做法锭环。
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public double realPart() {
return re;
}
public double imaginaryPart() {
return im;
}
public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex sub(Complex c) {
return new Complex(re - c.re, im - c.im);
}
}
將構(gòu)造函數(shù)改為私有的聪全,并添加靜態(tài)工廠來替代公有構(gòu)造器
public final class Complex {
private final double re;
private final double im;
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
...
}
4.復(fù)合優(yōu)先于繼承
與方法調(diào)用不同的是,繼承打破了封裝性辅辩。子類依賴于其超類中特定功能的實(shí)現(xiàn)細(xì)節(jié)难礼。所以子類必須要跟著其超類的更新而演變,導(dǎo)致子類很脆弱玫锋。
繼承
- 繼承打破了封裝性
- 父類內(nèi)部細(xì)節(jié)對(duì)于子類是可見的蛾茉,繼承的代碼復(fù)用是一種白盒式代碼復(fù)用,如果基類的實(shí)現(xiàn)發(fā)生改變撩鹿,那么派生類也將隨之改變谦炬,導(dǎo)致子類的行為不可預(yù)知
- 只有兩者存在is-a的關(guān)系,才使用繼承三痰,如果不是吧寺,則使用組合
組合
- 在新的類中增加一個(gè)私有域,它引用現(xiàn)有類的一個(gè)實(shí)例
- 繼承必須在編譯器確定繼承哪個(gè)類散劫,組合可以采用面向接口編程稚机,類的組合關(guān)系可以在運(yùn)行期確定
5.要么為繼承而設(shè)計(jì),并提供文檔說明获搏,要么就禁止繼承
類的文檔必須精確描述覆蓋每個(gè)方法所帶來影響赖条,也就是說失乾,覆蓋的方法必須說明其自用性
類必須通過某種形式提供適當(dāng)?shù)你^子(hook),以便能夠進(jìn)入它的內(nèi)部工作流程中纬乍,這種形式可以是精心選擇的受保護(hù)的方法
6.接口優(yōu)于抽象類
接口優(yōu)點(diǎn)
- 現(xiàn)有的類可以很容易被更新碱茁,以實(shí)現(xiàn)新的接口
- 接口是定義mixin(混合類型)的理想選擇
- 類不可能有一個(gè)以上的父類,類層次結(jié)構(gòu)中也沒有適當(dāng)?shù)牡胤讲迦雖ixin
- 接口允許我們構(gòu)造非層次接口的類型框架
接口和抽象類區(qū)別
- 接口里不能定義靜態(tài)方法仿贬;抽象類里可以定義靜態(tài)方法纽竣。
- 接口里不包含構(gòu)造器,抽象類可以包含構(gòu)造器茧泪。抽象類里的構(gòu)造器并不是用于創(chuàng)建對(duì)象蜓氨,而是讓其子類調(diào)用這些構(gòu)造器來完成屬于抽象類的初始化操作。
- 接口里不能包含初始化塊队伟,但抽象類可以包含初始化塊穴吹。
- 接口里不包含已經(jīng)提供實(shí)現(xiàn)的方法,只能包含抽象方法嗜侮,港令;抽象類則完全可以包含普通方法。
- 接口里只能定義靜態(tài)常量锈颗,不能定義其他變量顷霹。抽象類既可以定義普通變量,也可以定義靜態(tài)常量击吱。
- 注意:在接口里定義的接口泼返、枚舉類、變量默認(rèn)都采用public static兩個(gè)修飾符姨拥,不管定義時(shí)是否指定這兩個(gè)修飾符,系統(tǒng)都會(huì)自動(dòng)使用public static對(duì)他們進(jìn)行修飾渠鸽,同理叫乌,在抽象類里,會(huì)默認(rèn)使用public abstract修飾方法徽缚。
骨架實(shí)現(xiàn)類
雖然接口不允許包含默認(rèn)實(shí)現(xiàn)憨奸,但是,可通過對(duì)你導(dǎo)出的每個(gè)重要接口都提供一個(gè)抽象的骨架實(shí)現(xiàn)類凿试,把接口和抽象類的優(yōu)點(diǎn)結(jié)合起來排宰。接口的作用仍然是定義類型,但是骨架實(shí)現(xiàn)類接管了所有與接口實(shí)現(xiàn)相關(guān)的工作
在選擇抽象類和接口時(shí)那婉,并不是二選一的答案板甘,或干脆槍斃掉抽象類。其實(shí)详炬,你可以把接口和抽象類的優(yōu)點(diǎn)結(jié)合起來盐类,對(duì)于你希望導(dǎo)出(對(duì)外提供)的每一個(gè)重要接口都提供一個(gè)抽象類(骨架實(shí)現(xiàn)類)。接口的作用仍然是定義類型,骨架實(shí)現(xiàn)類負(fù)責(zé)所有與接口實(shí)現(xiàn)相關(guān)的工作
7.接口只用于定義類型
當(dāng)類實(shí)現(xiàn)接口時(shí)在跳,接口就充當(dāng)可以引用這個(gè)類的實(shí)例的類型枪萄。類實(shí)現(xiàn)了接口,就表明客戶端對(duì)這個(gè)類的實(shí)例實(shí)施某些動(dòng)作猫妙。
接口應(yīng)該只被用來定義類型瓷翻,不應(yīng)該被用來導(dǎo)出常量
接口用于導(dǎo)出常量
常量接口:只包含靜態(tài)的final域。導(dǎo)出常量的一種形式割坠。常量接口模式是對(duì)接口的不良使用齐帚。
缺點(diǎn):
- 實(shí)現(xiàn)常量接口,會(huì)導(dǎo)致把這樣的實(shí)現(xiàn)細(xì)節(jié)泄漏到該類導(dǎo)出的API中
- 如果非final類實(shí)現(xiàn)了常量接口韭脊,它的所有子類的命名空間也會(huì)被接口中的常量所”污染“童谒。
常量接口的例子
public interface PhysicalConstants {
static final double AAA = 0.1;
static final double BBB = 0.1;
static final double CCC = 0.1;
}
導(dǎo)出常量的合理方案:
- 使用枚舉類型導(dǎo)出
- 使用不可實(shí)例化的工具類導(dǎo)出
工具類的方式:
public class PhysicalConstants {
private PhysicalConstants() {};
static final double AAA = 0.1;
static final double BBB = 0.1;
static final double CCC = 0.1;
}
8.類層次優(yōu)于標(biāo)簽類
有時(shí),可能遇到帶有兩種甚至更多風(fēng)格的實(shí)例的類沪羔,并包含表示實(shí)例風(fēng)格的標(biāo)簽域饥伊。
下面例子,此類表示圓形或者矩形
標(biāo)簽類缺點(diǎn):
- 充斥樣板代碼蔫饰,包括枚舉聲明琅豆、標(biāo)簽域以及條件語句
- 破壞了可讀性
- 內(nèi)存占用也增加
- 實(shí)例承擔(dān)著其他風(fēng)格不相關(guān)的域
解決方案:子類化
定義一個(gè)包含抽象方法的抽象類,公共方法定義在抽象類
9.用函數(shù)對(duì)象表示策略(策略模式)
函數(shù)指針(引用)的主要用途是實(shí)現(xiàn)策略模式篓吁,在Java中實(shí)現(xiàn)策略模式茫因,要聲明一個(gè)接口來表示該策略,并且為每個(gè)具體策略聲明一個(gè)實(shí)現(xiàn)了該接口的類杖剪。當(dāng)一個(gè)具體策略只被使用一次時(shí)冻押,通常使用匿名類來聲明和實(shí)例化這個(gè)具體策略類,當(dāng)一個(gè)具體策略是設(shè)計(jì)用來重復(fù)使用的時(shí)候盛嘿,它的類通常就要被實(shí)現(xiàn)為私有的靜態(tài)成員類洛巢,并且通過公有的靜態(tài)final域被導(dǎo)出,其類型為該策略接口次兆。
舉例:比較器函數(shù)代表一種為元素排序的策略稿茉。
Java沒有提供函數(shù)指針,可以用對(duì)象引用實(shí)現(xiàn)此功能芥炭。
比較器實(shí)例
StringLengthComparator 實(shí)例就是用于字符串長(zhǎng)度比較的具體策略漓库。
StringLengthComparator 是無狀態(tài)的(沒有域),所以單例比較合適园蝠。
class StringLengthComparator implements Comparator<String> {
private StringLengthComparator() {};
private static final StringLengthComparator INSTANCE = new StringLengthComparator();
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
定義一個(gè)策略接口
public interface Comparator<T> {
public int compare(T t1, T t2);
}
使用比較策略
Arrays.sort(stringArray, new Comparator(String)(){
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
})
10.優(yōu)先考慮靜態(tài)成員類
嵌套類:被定義在另一個(gè)類的內(nèi)部的類渺蒿。嵌套類存在的目的只是為了它的外圍類提供服務(wù)。
嵌套類包括:
- 靜態(tài)成員類(內(nèi)部類)
- 非靜態(tài)成員類(內(nèi)部類)
- 匿名類
- 局部類
靜態(tài)成員類
最簡(jiǎn)單的一種嵌套類砰琢,可看作是普通的類蘸嘶,可以訪問外圍類的所有成員良瞧,包括私有成員。
公有靜態(tài)成員類常見用法训唱,是作為公有的輔助類褥蚯,僅當(dāng)與它的外部類一起使用時(shí)才有意義。
私有靜態(tài)成員類常見用法况增,用來代表外圍類所代表的對(duì)象的組件赞庶。例如,Map實(shí)例澳骤,Map的內(nèi)部都有一個(gè)Entry對(duì)象歧强,對(duì)應(yīng)于Map的key和value。
非靜態(tài)成員類(內(nèi)部類)
必須和外圍類的一個(gè)實(shí)例相關(guān)聯(lián)为肮,可以用this來訪問
常見用法:Adapter
如果成員類不要求訪問外圍實(shí)例摊册,就要聲明成靜態(tài)成員類,不然每個(gè)實(shí)例都會(huì)包含一個(gè)額外的指向外圍對(duì)象的引用