本系列文章是總結(jié)Effective Java文章中我認(rèn)為最重點的內(nèi)容,給很多沒時間看書的朋友以最短的時間看到這本書的精華。
第一篇《Effective Java——創(chuàng)建和銷毀對象》
第二篇《Effective Java——對于所有對象都通用的方法》
第四章 類和接口
第13條:使類和成員的可訪問性最小化
該條規(guī)則盡可能使每個類或者成員不被外界訪問,只對外暴露有用的API接口且永遠(yuǎn)支持它。
開發(fā)系統(tǒng)每個模塊之前的實現(xiàn)細(xì)節(jié)全部隱藏,只是通過API去掉用,那么會大大增加這個系統(tǒng)的穩(wěn)定性和并行開發(fā)能力,每個模塊都可以單獨的運行、調(diào)試、測試。
四種訪問級別按照訪問性的遞增順序如下:
- 私有的(private)——只要在聲明該成員的頂層類內(nèi)部才可以訪問。
- 包級私有(package-private)——聲明該成員的包內(nèi)部任何類都可以訪問這個成員。又被成為“缺省(default)訪問級別”,如果沒有為成員指定訪問修飾符,就采用這個訪問級別。
- 受保護(hù)(protected)——聲明該成員的包內(nèi)部任何類和該類的子類都可以訪問這個成員。
- 共有的(public)——任何地方都可以訪問。
protected和public都屬于導(dǎo)出API的一部分,需要永久維護(hù)。
實例域決不能是共有的
如果域是非final得,或者是一個指向可變對象final引用,那么一旦這個域成為共有的,就放棄了對存儲在這個域中的值進(jìn)行限制的能力。
用代碼說話:
public static final class ClassA{
//非final
public String string = "A";
//可變對象final
public final StringBuilder stringBuilder = new StringBuilder();
}
//在外部都可以修改這個域,所以就放棄了對存儲在這個域中的值進(jìn)行限制的能力。
ClassA classA = new ClassA();
classA.string = "B";
classA.stringBuilder.append("BBBB");
長度非零的數(shù)組總是可變的
用代碼說話:
//類暴露這個字段,客戶端程序員可以任意修改數(shù)組中的值
public static final Integer[] VALUES = {0,1,2,3,4,5,6,7,8,9};
//解決方法1
public static final Integer[] PRIVATE_VALUES = {0,1,2,3,4,5,6,7,8,9};
public static final List<Integer> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
//解決方法2
public static final Integer[] PRIVATE_VALUES = {0,1,2,3,4,5,6,7,8,9};
public static final Integer[] values(){
return PRIVATE_VALUES.clone();
}
第14條:在公有類中使用訪問方法而非公有域
這條說的很簡單:
如果類可以在它所在的包的外部進(jìn)行訪問,就提供訪問方法。
如果類是包級私有的,或者是私有嵌套類,直接暴露他的數(shù)據(jù)域并沒有本質(zhì)的錯誤。
用代碼說話:
//如下類是不符合規(guī)范的
public class Point{
public double x;
public double y;
}
//需要提供getter和setter方法
public class Point{
public double x;
public double y;
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
第15條:使可變性最小化
不可變類只是其實例不能被修改的類。每個實例中包含的所有信息都必須在創(chuàng)建該實例的時候就提供,并在對象的整個生命周期內(nèi)固定不變。例如String
,BigInteger
,BigDecimal
,基本類型的包裝類。
使類不可變的五條原則:
1.不要提供任何會修改對象狀態(tài)的方法。
2.保證類不會被擴(kuò)展。防止子類化:final修飾類,private構(gòu)造方法
3.使所有的域都成為私有的。
4.確保對于任何可變組件的互斥訪問。如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用。并且永遠(yuǎn)不要用客戶端提供的對象引用來初始化這樣的域,也不要從任何訪問方法(accessor)中返回該對象對的引用。在構(gòu)造器、訪問方法和readObject方法中請使用保護(hù)性拷貝(defensive copy)技術(shù)。
函數(shù)的:類的方法進(jìn)行運算之后返回一個新創(chuàng)建的實例,而不是修改這個實例。
String s = "string test";
String rep = s.replace("str","");
System.out.println(s);//打印結(jié)果string test
System.out.println(rep); //打印結(jié)果ing test
對比發(fā)現(xiàn)字符串實例`s`調(diào)用`replace`之后打印結(jié)果還是原來的沒變,表示沒有修改當(dāng)前實例內(nèi)部狀態(tài),而是重新創(chuàng)建一個實例,查看字符串實例`rep`的打印結(jié)果可以證明這點。
過程的或者命令式的:類的方法進(jìn)行運算之后不產(chǎn)生新的實例,導(dǎo)致當(dāng)前實例內(nèi)部狀態(tài)發(fā)生了改變。
函數(shù)的
優(yōu)點:
- 不可變對象比較簡單,只有一種狀態(tài)創(chuàng)建時期的狀態(tài),在整個生命周期內(nèi)永遠(yuǎn)不再發(fā)生變化。
- 不可變對象本質(zhì)上是線程安全的,不需要同步。
- 不可變對象可以被自由的共享。無需提供
clone
方法或者拷貝構(gòu)造器。但是String
是個反例它仍然具有拷貝構(gòu)造器。 - 不僅可以共享不可變對象,還可以共享它們的內(nèi)部信息。例如:BigInteger用int類型表示符號,用int數(shù)組表示數(shù)值。negate方法方法產(chǎn)生一個新的實例數(shù)值一樣但是符號相反,在內(nèi)部它并不需要拷貝數(shù)組,新建的實例也指向原來的數(shù)組來優(yōu)化內(nèi)存。
缺點:
每個不同的值都會創(chuàng)建一個單獨的對象。如果頻繁調(diào)用會造成內(nèi)存緊張或者頻繁GC影響系統(tǒng)的性能。
解決方法: - 將頻繁用到的值提供靜態(tài)
final
常量。 - 提供靜態(tài)工廠方法,把頻繁被請求的實例緩存起來。從而降低內(nèi)存占用和垃圾回收的成本。
- 提供可變配套類,例如;String的可變配套類為StringBuilder
不允許被子類化方案
1. 用final關(guān)鍵字修飾類
2. 將類的所有構(gòu)造器都聲明為private
用代碼說話:
public class Complex{
private final double re;
private final double im;
//構(gòu)造器聲明為private
private Complex(double re,double im){
this.re = re;
this.im = im;
}
public static final Complex valueOf(double re,double im){
return new Complex(re,im);
}
}
這種方式相對于第一種方式更為靈活,優(yōu)點:
- 它允許使用多個實現(xiàn)類。
- 使用靜態(tài)工廠方式創(chuàng)建對象有非常多的好處,可以增加緩存對象的能力,避免重載構(gòu)造方法造成的功能不清晰。(可以會看本本書第一條規(guī)則)
用代碼說話:
public static class Complex{
private final double re;
private final double im;
private static SubComplex subComplex = null;
private Complex(double re,double im){
this.re = re;
this.im = im;
}
//每個不同的功能用不同的靜態(tài)方法,避免構(gòu)造方法重載
public static final Complex valueOf(double re,double im){
return new Complex(re,im);
}
//可以對頻繁創(chuàng)建的對象進(jìn)行緩存
public static final Complex valueOfSub(double re,double im){
if(null == subComplex){
subComplex = new SubComplex(re,im);
}
return subComplex;
}
//允許子類化
private static class SubComplex extends Complex{
private SubComplex(double re, double im) {
super(re, im);
}
}
}
不可變類實現(xiàn)Serializable接口
如果選擇讓不可變類實現(xiàn)Serializable接口,那么會涉及到一下幾個方法,如下代碼解釋
public static class CustomSerializable implements Serializable{
public static final CustomSerializable INSTANCE = new CustomSerializable();
private String name;
private String age;
public String getName() { return name;}
public void setName(String name) { this.name = name;}
public String getAge() {return age; }
public void setAge(String age) {this.age = age;}
//如下兩個方法可以自定義序列化對象那些字段可以序列化,那些對象不可以序列化
private void writeObject(ObjectOutputStream out) throws IOException {
// out.defaultWriteObject();
out.writeObject(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// in.defaultReadObject();
age = (String) in.readObject();
}
//實際上就是用readResolve()中返回的對象直接替換在反序列化過程中創(chuàng)建的對象。
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
@Override
public String toString() {
return "CustomSerializable{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
try{
INSTANCE.setName("aaaaaaaa");
INSTANCE.setAge("30");
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/person.out");
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
oout.writeObject(INSTANCE); // 保存單例對象
oout.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson = oin.readObject();
oin.close();
Log.e("TAG",newPerson.toString());
Log.e("TAG",String.valueOf(INSTANCE == newPerson));
}catch (Exception e){
e.printStackTrace();
}
//打印結(jié)果
04-19 22:09:49.644 22007-22007/? E/TAG: CustomSerializable{name='aaaaaaaa', age='30'}
04-19 22:09:49.644 22007-22007/? E/TAG: true
這篇文章講解的非常詳細(xì):http://developer.51cto.com/art/201202/317181.htm
第16條:復(fù)合優(yōu)先于繼承
在包的內(nèi)部使用繼承是非常安全的,子類和超類的實現(xiàn)都處在同一個程序員的控制下。對于專門為了繼承而設(shè)計、并且具有很好文檔說明的類來說繼承也是非常安全的。然而對于普通類進(jìn)行跨域包邊界的繼承則是非常危險的。
繼承打破了封裝性,子類依賴其超類中特定功能的實現(xiàn)。如果超類的實現(xiàn)隨著發(fā)布的新版本發(fā)生了變化,那么子類有可能會遭到破壞,即使他的代碼完全沒有改變。
如下例子:
檢測一個Set從創(chuàng)建依賴一共增加了多少個元素:如下代碼:
public static class InstrumentedHashSet<E> extends HashSet<E>{
private int addCount = 0;
public InstrumentedHashSet(){}
public InstrumentedHashSet(int intCap,float loadFactor){
super(intCap,loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
InstrumentedHashSet hashSet = new InstrumentedHashSet();
hashSet.addAll(Arrays.asList("aaa","bbb","ccc"));
Log.e("TAG","count : " + hashSet.getAddCount());
如上我們期望返回3,但實際上返回6。出現(xiàn)這種情況就是因為HashSet.addAll
內(nèi)部調(diào)用了add()
方法,所以總共增加了6。
子類的功能需要依賴于父類的內(nèi)部實現(xiàn)。由于父類的內(nèi)部實現(xiàn)是不對外承諾的,不能保證java的每個版本內(nèi)部實現(xiàn)都一樣,所以不能保證子類的功能一定是正確的。
使用“復(fù)合”來解決這個問題,也就是包裝器模式。
如下代碼:
public static class InstrumentedHashSet<E> extends ForwardingSet<E>{
private int addCount = 0;
public InstrumentedHashSet(Set<E> set) {
super(set);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
//包裝器類非常穩(wěn)固它不依賴于任何類的實現(xiàn)細(xì)節(jié),包裝器中的方法稱為轉(zhuǎn)發(fā)方法
public static class ForwardingSet<E> implements Set<E>{
private Set<E> mSet;
public ForwardingSet(Set<E> set){
mSet = set;
}
@Override
public int size() {
return mSet.size();
}
@Override
public boolean add(E e) {
return mSet.add(e);
}
@Override
public boolean addAll(@NonNull Collection<? extends E> c) {
return mSet.addAll(c);
}
//此處省略好多轉(zhuǎn)發(fā)方法,因為篇幅有限
}
//而且所有繼承`Set`接口的對象都可以用這個包裝器類來實現(xiàn)增加對象計數(shù)功能
InstrumentedHashSet hashSet = new InstrumentedHashSet(new HashSet());
InstrumentedHashSet treeSet = new InstrumentedHashSet(new TreeSet());
如上代碼包裝類不依賴任何類的實現(xiàn)細(xì)節(jié),只是通過轉(zhuǎn)發(fā)方法來實現(xiàn)類的功能,非常完美的解決了這個問題,而且所有繼承Set
接口的對象都可以用這個包裝器類來實現(xiàn)增加對象計數(shù)功能。
包裝器模式也叫裝飾模式動態(tài)地給一個對象添加一些額外的職責(zé)。就增加功能來說,裝飾模式相比生成子類更為靈活。
第17條:要么為繼承而設(shè)計,并提供文檔說明,要么就禁止繼承
專門為繼承設(shè)計的類,該類的文檔必須精確地描述每個方法所帶來的影響,換句話說,該類必須有文檔說明它的可覆蓋的方法的自用性。對于每個共有的或受保護(hù)的方法或者構(gòu)造器,它的文檔必須指明該方法或者構(gòu)造器調(diào)用了那些可覆蓋的方法,是以什么順序調(diào)用的,每個調(diào)用的結(jié)果又是如何響應(yīng)后續(xù)的處理過程的。類必須在文檔中說明,在哪些情況下它會調(diào)用可覆蓋方法。
例如:java.util.AbstractCollection
中public boolean remove(Object o)
這個方法的注釋,非常清楚。
源碼注釋片段:該實現(xiàn)遍歷整個集合來查找制定元素,如果找到該元素,將會利用迭代器的remove
方法將之從集合中刪除,注意,如果該集合的iterator
方法返回的迭代器沒有實現(xiàn)remove
方法,改實現(xiàn)就會拋出UnsupportOperationException
。
該文檔清楚地說明了,覆蓋iterator
方法將會影響remove
方法的行為。而且,它確切的描述了iterator
返回的Iterator
的行為將會怎樣影響remove
方法的行為。
- 對于為了繼承而設(shè)計的類,唯一的測試方法就是編寫子類。
- 為了繼承而設(shè)計的類的構(gòu)造器決不允許調(diào)用可被覆蓋的方法,如果實現(xiàn)了
Cloneable
和Serializable
接口,就應(yīng)該意識到clone
和readObject
,方法在行為上非常類似構(gòu)造器,所以無論是clone
還是readObject
都不可以調(diào)用可覆蓋方法,不管是直接還是間接。 - 不可變類禁止繼承
- 繼承一個類消除可覆蓋方法的自用性,而不改變它的行為。將每個可覆蓋方法的代碼體移到一個私有的“輔助方法”中,并且讓每個可覆蓋的方法調(diào)用它的私有輔助方法。然后,用“直接調(diào)用可覆蓋方法的私有輔助方法”來代替“可覆蓋方法的每個自用調(diào)用”。
第18條:接口優(yōu)于抽象類
- 現(xiàn)有類可以很容易被更新,以實現(xiàn)新的接口。例如,當(dāng)Comparable接口引入Java平臺時,會更新許多現(xiàn)有類,以實現(xiàn)Comparable接口。一般來說無法更新現(xiàn)有類實現(xiàn)新的抽象類,由于Java只允許單繼承,所以會破壞類的層級關(guān)系,這樣做非常危險。
- 接口是定義mixin(混合類型)的理想選擇。mixin是指這樣的類型:類除了實現(xiàn)他的“基本類型”之外,還可以實現(xiàn)這個mixin類型,以表示它提供了某些可供選擇的行為。例如,實現(xiàn)Comparable這個接口類的實例,除了表示他本身的類型外,還可以表示,它的實例可以和任何其他實現(xiàn)這個接口的類型的實例相比較。
- 接口允許我們構(gòu)造非層次結(jié)構(gòu)的的類的框架。主要說的就是接口可以多重繼承,一個類可以同時實現(xiàn)多個接口,實現(xiàn)多種功能。
- 包裝類模式,接口使得安全地增強(qiáng)類的功能成為可能。如果使用抽象類,那么只能使用繼承手段來增加功能。
-
骨架類。接口定義類型,骨架實現(xiàn)類接管了所有與接口實現(xiàn)相關(guān)的工作。例如,
AbstractList、AbstractMap
等。可以自行查看源碼。 - 模擬多重繼承,實現(xiàn)了這個接口的類可以把對于接口方法的調(diào)用,轉(zhuǎn)發(fā)到一個內(nèi)部類私有類的實力上,這個內(nèi)部私有類擴(kuò)展了骨架實現(xiàn)類。
- 抽象類的演變比接口的演變要容易得多。如果在后續(xù)版本中,希望在抽象類中增加新的方法,始終可以增加具體的方法,并且可以包含具體的默認(rèn)實現(xiàn)。然后,該抽象類的所有現(xiàn)有實現(xiàn)都將提供這個新的方法。對于接口是行不通的。
- 接口一旦被公開發(fā)行,并且已被廣泛實現(xiàn),在想改變這個接口幾乎是不可能的。它會影響所有實現(xiàn)這個接口的類。但是實現(xiàn)這個接口骨架類的類不會受到影響,因為可以直接在骨架類中增加新的方法。最佳途徑,為每個借口都實現(xiàn)一個骨架類。
- 如果演變比靈活性更加重要的情況下,應(yīng)該使用抽象類而非接口。因為抽象類在后期非常容易添加方法。
第19條:接口只用于定義類型
- 避免常量接口。接口沒有任何方法,只包含靜態(tài)的final域。
- 如果這些常量最好被看做枚舉類型,就應(yīng)該使用枚舉常量。
- 如果這些常量與某個現(xiàn)有的類或者接口機(jī)密相關(guān),就應(yīng)該把這些常量添加到這個接口或者類中。例如,Java平臺中的
Integer.MIN_VALUE
和Integer.MAX_VALUE
。 - 使用不可實例化工具類來導(dǎo)出這些常量。例如,
final
類,或者private
構(gòu)造方法類
第20條:層次優(yōu)先于標(biāo)簽類
標(biāo)簽類
public static class Figure{
enum Shape{ RECTANGLE, CIRCLE};
final Shape shape;
double lenght;
double width;
double radius;
Figure(double radius){
shape = Shape.CIRCLE;
this.radius = radius;
}
Figure( double lenght, double width){
this.shape = Shape.RECTANGLE;
this.lenght = lenght;
this.width = width;
}
double area(){
switch (shape){
case RECTANGLE: {
return lenght * width;
}
case CIRCLE:{
return Math.PI*(radius*radius);
}
default:{return -1;}
}
}
}
如上代碼被稱為標(biāo)簽類。他有很多缺點:
- 充斥著樣本代碼,包括枚舉聲明、標(biāo)簽域(
final Shape shape;
)以及條件語句。 - 單個類中存在了多個實現(xiàn),通過標(biāo)簽域進(jìn)行區(qū)分,破壞了可讀性。
- 內(nèi)存占用增加,因為實例承擔(dān)著屬于其他風(fēng)格的不相關(guān)的域。
- 無法給標(biāo)簽類增加風(fēng)格,除非修改源代碼。如果一定要添加風(fēng)格,就必須記得給每個條件語句都添加一個條件,否則會運行失敗。
一句話,標(biāo)簽類過于冗長,容易出錯,并且效率低下。
類層次
public static abstract class Figure{
abstract double area();
}
public static class Circle extends Figure{
final double radius;
public Circle(double radius){
this.radius = radius;
}
@Override
double area() {
return Math.PI*(radius*radius);
}
}
public static class Rectangle extends Figure {
final double length;
final double width;
public Rectangle(double length, double width){
this.length = length;
this.width = width;
}
@Override
double area() {
return lenght * width;
}
}
如上代碼被稱為類層次,他糾正了標(biāo)簽類的所有缺點:
- 代碼簡單清除,沒有樣板代碼。
- 每個類型都有自己的類,這些類都沒有收到不想管數(shù)據(jù)域的拖累。
- 所有域都是
final
的 - 杜絕了
switch case
這種形式的語句,防止擴(kuò)展時忘記寫case
造成的運行失敗。 - 多個程序員可以獨立的擴(kuò)展層次結(jié)構(gòu),并且不用訪問根類的遠(yuǎn)代碼就能相互操作。6. 在不修改源文件的情況下增加類型。
標(biāo)簽類盡量少用,需要被類層次來代替。
第21條:用函數(shù)對象表示策略
用函數(shù)對象表示策略模式
- 函數(shù)對象:如果一個類只導(dǎo)出一個方法,那么他的實例就相當(dāng)于指向該方法的一個指針,這樣的實例被稱為函數(shù)對象。
- 策略模式(Strategy Pattern):一個類的行為或其算法可以在運行時更改。這種類型的設(shè)計模式屬于行為型模式。
策略模式的典型應(yīng)用就是java.util.Collections.sort(List<T> list, Comparator<? super T> c)
查看源碼:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
// BEGIN Android-changed: Compat behavior for apps targeting APIs <= 25.
// list.sort(c);
int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion();
if (targetSdkVersion > 25) {
list.sort(c);
} else {
// Compatibility behavior for API <= 25. http://b/33482884
if (list.getClass() == ArrayList.class) {
Arrays.sort((T[]) ((ArrayList) list).elementData, 0, list.size(), c);
return;
}
Object[] a = list.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<T> i = list.listIterator();
for (int j = 0; j < a.length; j++) {
i.next();
i.set((T) a[j]);
}
}
// END Android-changed: Compat behavior for apps targeting APIs <= 25.
}
由源碼可見這是一個比較方法,具體的比較策略是由函數(shù)的參數(shù)決定的,也就是Comparator<? super T> c
這個參數(shù)。所有實現(xiàn)Comparator
接口的對象都可以作為這個比較方法的策略。
查看Comparator
源碼:
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
如上代碼,這就是策略接口,實現(xiàn)這個接口,實現(xiàn)int compare(T o1, T o2);
方法,就可以定義自己的策略了。實現(xiàn)這個接口需要使用函數(shù)對象的形式。
第22條:優(yōu)先考慮靜態(tài)成員類
嵌套類:被定義在另一個類內(nèi)部的類。嵌套類存在的目的應(yīng)該只是為了他的外圍類提供服務(wù)。
嵌套類四種:靜態(tài)成員類、非靜態(tài)成員類、匿名類、局部類。
1. 靜態(tài)成員類,靜態(tài)成員類使用static
定義的內(nèi)部類,由于靜態(tài)成員類內(nèi)部沒有保存外圍類的實例,所以它只能訪問外圍類的靜態(tài)成員變量或者方法。靜態(tài)成員類是外圍類的一個靜態(tài)成員,與其他的靜態(tài)成員一樣,也遵守同樣的可訪問性。靜態(tài)成員的一個典型應(yīng)用——建造者模式,如下代碼:
public class TestBuilder {
private final String mName;
private TestBuilder(Builder builder){
this.mName = builder.mName;
}
public static final class Builder{
private String mName;
public Builder setName(String name){
this.mName = name;
return this;
}
public TestBuilder builder(){
return new TestBuilder(this);
}
}
public static final void main(){
TestBuilder testBuilder = new TestBuilder
.Builder()
.setName("heiheihei")
.builder();
}
}
2. 非靜態(tài)成員類,和靜態(tài)類唯一區(qū)別是它的定義去掉static
關(guān)鍵字。非靜態(tài)成員類的每個實例都隱含著外圍類的一個外圍實例。
優(yōu)點:它可以訪問外圍類的所有成員變量和方法。
缺點:
- 創(chuàng)建非靜態(tài)成員類浪費內(nèi)存,并且增加了時間開銷(由于隱含著外圍類的實例)。
- 會導(dǎo)致外圍類實例在符合垃圾回收時仍然得以保留,出現(xiàn)內(nèi)存泄漏(例如Android中Handler的內(nèi)存泄漏)。
典型應(yīng)用:ArrayList.class
中的Iterator
非靜態(tài)內(nèi)部類,可以自行查看源碼。
- 想要創(chuàng)建非靜態(tài)成員類實例必須先創(chuàng)建外圍類的實例,
enclosingInstance.new MembnerClass()
。 - 如果嵌套類的實例可以在它外圍類的實例之外獨立存在,這個嵌套類必須是靜態(tài)成員類。
- 如果聲明成員類不需要訪問外圍實例,就要始終定義成靜態(tài)成員類。
3. 匿名類,沒有名字,不是外圍類的一個成員。
- 它并不與其他成員一起被聲明,而是在使用的同時被聲明和實例化。
- 匿名類可以出現(xiàn)在代碼中任何允許存在表達(dá)式的地方。
- 匿名類出現(xiàn)在非靜態(tài)環(huán)境中,才有外圍實例。
- 匿名類中不能有任何靜態(tài)成員變量,無論是否在靜態(tài)環(huán)境中。
- 匿名類除了在它們被聲明的時候之外,是無法將他們實例化的。
- 不能執(zhí)行
instanceof
測試,或者做任何需要命名類的其他事情。 - 匿名類無法實現(xiàn)接口、無法繼承類。
典型應(yīng)用
- 動態(tài)的創(chuàng)建函數(shù)對象,例如:
java.util.Collections.sort(List<T> list, Comparator<? super T> c)
方法中的函數(shù)對象。 - 創(chuàng)建過程對象例如:
Runnable
、Thread
、TimerTask
- 靜態(tài)工廠內(nèi)部例如:18條中創(chuàng)建的匿名骨架類。
3. 局部類,在任何“可以聲明局部變量”的地方,都可以聲明局部類。
局部類與其他三種嵌套類一樣有一些共同屬性,只是它聲明的位置不太一樣。
總結(jié):
- 嵌套類需要在單個方法之外仍然可見,或者太長,就應(yīng)該定義成成員類。
- 如果成員類每個實例都需要指向一個外圍類的引用,就應(yīng)該定義成非靜態(tài)成員類,否則就應(yīng)該定義成靜態(tài)成員類。
- 假設(shè)嵌套類屬于一個方法內(nèi)部,如果你只需要在一個地方創(chuàng)建實例,并且已經(jīng)有一個預(yù)置的類型可以說明這個類的特征,就要把它做成匿名類,否則,就做成局部類。