【04】Effective Java - 类和接口
為什么80%的碼農都做不了架構師?>>> ??
1、使類和成員的可訪問性最小化
? ? ? 封裝是軟件設計的基本原則之一,它的好處就是解除組成系統的各個模塊之間的耦合關系,使得這些模塊可以獨立地開發、測試、優化、使用、理解 修改,同時可以有效地進行性能調節。
(1)可見性
A、私有的
B、包級私有的
C、受保護的
D、公有的
(2)實例域不能是公有的
? ?包含公有可變域的類并不是線程安全的
(3)公有靜態final域
A、常量通常用公有靜態域來暴露,大寫加下劃線
B、如果final域包含可變對象的引用,便具有非final域的所有缺點,雖然引用自身不能被修改,但是所引用對象的內容卻可以被修改,這會導致災難性的后果
C、類具有公有的靜態final數據域,或者返回這種域的訪問方法,幾乎總是錯誤的,這是安全漏洞的一個常見根源。
//potential?security?hole public?static?final?Thing[]?VALUES?=?{...};改進方法一
private?static?final?Thing[]?PRIVATE_VALUES?=?{...}; public?static?final?List<Thing>?VALUES?=Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));改進方法二
private?static?final?Thing[]?PRIVATE_VALUES?=?{...}; public?static?final?Thing[]?values(){return?PRIVATE_VALUES.clone(); }2、在公有類中使用訪問方法而非公有域
(1)杜絕退化類
數據域是public,沒有提供訪問方法
public?class?Point?{public?double?x;public?double?y; }? 如果類可以在它所在的包外進行訪問,或提供訪問方法,以保留將來改變該類的內部表示法的靈活性。如果共有類暴露了它的數據域,要想在將來改變其內部表示法是不可能的,因為公有類的客戶端代碼已經遍布各處了。
public?class?Point?{private?double?x;private?double?y;public?Point(double?x,?double?y)?{this.x?=?x;this.y?=?y;}public?double?getX(){return?x;}?public?double?getY(){return?y;}public?void?setX(double?x){this.x?=?x;}public?void?setY(double?y){this.y?=?y;} }(2)原則
? 公有類永遠都不應該暴露可變的域。
3、使可變量最小化
(1)使類變為不可變的規則
A、不要提供任何會修改對象狀態的方法
B、保證類不會被擴展
C、使所有域都是final的
D、使所有的域都稱為私有的
E、確保對任何可變組件的互斥訪問
? ? 永遠不要用客戶端提供的對象引用來初始化可變對象的域
(2)函數風格
該函數返回一個運算結果,該運算并不會改變它的狀態。一般不可變類都返回一個新的運算實例,而非修改原來的實例。
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);}public?static?Complex?valueOfPolar(double?r,?double?theta)?{return?new?Complex(r?*?Math.cos(theta),r?*?Math.sin(theta));}public?static?final?Complex?ZERO?=?new?Complex(0,?0);public?static?final?Complex?ONE??=?new?Complex(1,?0);public?static?final?Complex?I????=?new?Complex(0,?1);//?Accessors?with?no?corresponding?mutatorspublic?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?subtract(Complex?c)?{return?new?Complex(re?-?c.re,?im?-?c.im);}public?Complex?multiply(Complex?c)?{return?new?Complex(re?*?c.re?-?im?*?c.im,re?*?c.im?+?im?*?c.re);}public?Complex?divide(Complex?c)?{double?tmp?=?c.re?*?c.re?+?c.im?*?c.im;return?new?Complex((re?*?c.re?+?im?*?c.im)?/?tmp,(im?*?c.re?-?re?*?c.im)?/?tmp);}@Override?public?boolean?equals(Object?o)?{if?(o?==?this)return?true;if?(!(o?instanceof?Complex))return?false;Complex?c?=?(Complex)?o;//?See?page?43?to?find?out?why?we?use?compare?instead?of?==return?Double.compare(re,?c.re)?==?0?&&Double.compare(im,?c.im)?==?0;}@Override?public?int?hashCode()?{int?result?=?17?+?hashDouble(re);result?=?31?*?result?+?hashDouble(im);return?result;}private?int?hashDouble(double?val)?{long?longBits?=?Double.doubleToLongBits(re);return?(int)?(longBits?^?(longBits?>>>?32));}@Override?public?String?toString()?{return?"("?+?re?+?"?+?"?+?im?+?"i)";} }(3)優缺點
A、線程安全,可以被自由共享
B、可以提供靜態工廠方法,緩存實例,提升性能
C、無需clone方法,也不該有clone方法
缺點就是:
對于每個不同的值都需要一個單獨的對象
(4)堅決不要為每個get方法提供set方法
除非有很好的理由讓類成為可變的類,否則就應該是不可變的
4、組合優先于繼承
(1)繼承破壞封裝
public?class?InstrumentedHashSet<E>?extends?HashSet<E>?{//?The?number?of?attempted?element?insertionsprivate?int?addCount?=?0;public?InstrumentedHashSet()?{}public?InstrumentedHashSet(int?initCap,?float?loadFactor)?{super(initCap,?loadFactor);}@Override?public?boolean?add(E?e)?{addCount++;return?super.add(e);}//超類方法可能是自引用的,super.addAll引用了add方法,因為子類覆蓋了add方法,將改為調用子類的add方法,因而count被重復計數了@Override?public?boolean?addAll(Collection<??extends?E>?c)?{addCount?+=?c.size();return?super.addAll(c);}public?int?getAddCount()?{return?addCount;}public?static?void?main(String[]?args)?{InstrumentedHashSet<String>?s?=new?InstrumentedHashSet<String>();s.addAll(Arrays.asList("Snap",?"Crackle",?"Pop"));????System.out.println(s.getAddCount());} }(2)組合
public?class?ForwardingSet<E>?implements?Set<E>?{private?final?Set<E>?s;public?ForwardingSet(Set<E>?s)?{?this.s?=?s;?}public?void?clear()???????????????{?s.clear();????????????}public?boolean?contains(Object?o)?{?return?s.contains(o);?}public?boolean?isEmpty()??????????{?return?s.isEmpty();???}public?int?size()?????????????????{?return?s.size();??????}public?Iterator<E>?iterator()?????{?return?s.iterator();??}public?boolean?add(E?e)???????????{?return?s.add(e);??????}public?boolean?remove(Object?o)???{?return?s.remove(o);???}public?boolean?containsAll(Collection<?>?c){?return?s.containsAll(c);?}public?boolean?addAll(Collection<??extends?E>?c){?return?s.addAll(c);??????}public?boolean?removeAll(Collection<?>?c){?return?s.removeAll(c);???}public?boolean?retainAll(Collection<?>?c){?return?s.retainAll(c);???}public?Object[]?toArray()??????????{?return?s.toArray();??}public?<T>?T[]?toArray(T[]?a)??????{?return?s.toArray(a);?}@Override?public?boolean?equals(Object?o){?return?s.equals(o);??}@Override?public?int?hashCode()????{?return?s.hashCode();?}@Override?public?String?toString()?{?return?s.toString();?} }(2)組合不是委托
InstrumentedHashSet把HashSet包裝了起來,成為包裝類,屬于組合的方式,但不是委托,委托要求包裝對象把自身傳遞給被包裝對象。
同時包裝類不適合在回調框架里頭用。
5、要么為繼承而設計,要么禁止
(1)構造器決不能調用可被覆蓋的方法
? ?無論是直接調用,還是間接調用
public?class?Super?{//?Broken?-?constructor?invokes?an?overridable?methodpublic?Super()?{overrideMe();}public?void?overrideMe()?{} }public?final?class?Sub?extends?Super?{private?final?Date?date;?//?Blank?final,?set?by?constructorSub()?{date?=?new?Date();}//?Overriding?method?invoked?by?superclass?constructor@Override?public?void?overrideMe()?{System.out.println(date);}public?static?void?main(String[]?args)?{Sub?sub?=?new?Sub();sub.overrideMe();} }? 打印兩次,第一次為null,因為overrideMe被super構造器調用的時候,Sub構造器還沒有機會初始化date域。
(2)clone還是readObject方法都不可以調用可覆蓋的方法
實現Cloneable或者Serializable接口時。
對于readObject方法,覆蓋版本的方法將在子類狀態被反序列化之前被運行
對于clone方法,覆蓋版本的方法則是在子類的clone方法有機會修正被克隆對象的狀態之前被運行
6、接口優于抽象類
(1)接口允許我們構造非層次結構的類型框架
? ? 接口是定義mixin的理想選擇,避免組合爆炸,類層次臃腫,因為Java是單繼承,因而接口優于抽象類
(2)骨架實現
public?abstract?class?AbstractMapEntry<K,V>implements?Map.Entry<K,V>?{//?Primitive?operationspublic?abstract?K?getKey();public?abstract?V?getValue();//?Entries?in?modifiable?maps?must?override?this?methodpublic?V?setValue(V?value)?{throw?new?UnsupportedOperationException();}//?Implements?the?general?contract?of?Map.Entry.equals@Override?public?boolean?equals(Object?o)?{if?(o?==?this)return?true;if?(!?(o?instanceof?Map.Entry))return?false;Map.Entry<?,?>?arg?=?(Map.Entry)?o;return?equals(getKey(),???arg.getKey())?&&equals(getValue(),?arg.getValue());}private?static?boolean?equals(Object?o1,?Object?o2)?{return?o1?==?null???o2?==?null?:?o1.equals(o2);}//?Implements?the?general?contract?of?Map.Entry.hashCode@Override?public?int?hashCode()?{return?hashCode(getKey())?^?hashCode(getValue());}private?static?int?hashCode(Object?obj)?{return?obj?==?null???0?:?obj.hashCode();} }7、接口只用于定義類型
(1)常量接口是對接口的不良使用
public?interface?ObjectStreamConstants?{/***?Magic?number?that?is?written?to?the?stream?header.*/final?static?short?STREAM_MAGIC?=?(short)0xaced;/***?Version?number?that?is?written?to?the?stream?header.*/final?static?short?STREAM_VERSION?=?5;/*?Each?item?in?the?stream?is?preceded?by?a?tag*//***?First?tag?value.*/final?static?byte?TC_BASE?=?0x70;... }(2)工具類版本
public?class?PhysicalConstants?{private?PhysicalConstants()?{?}??//?Prevents?instantiation//?Avogadro's?number?(1/mol)public?static?final?double?AVOGADROS_NUMBER???=?6.02214199e23;//?Boltzmann?constant?(J/K)public?static?final?double?BOLTZMANN_CONSTANT?=?1.3806503e-23;//?Mass?of?the?electron?(kg)public?static?final?double?ELECTRON_MASS??????=?9.10938188e-31; }? ?現在一般建議用枚舉。
8、類層次優于標簽類
(1)標簽類太過臃腫
class?Figure?{enum?Shape?{?RECTANGLE,?CIRCLE?};//?Tag?field?-?the?shape?of?this?figurefinal?Shape?shape;//?These?fields?are?used?only?if?shape?is?RECTANGLEdouble?length;double?width;//?This?field?is?used?only?if?shape?is?CIRCLEdouble?radius;//?Constructor?for?circleFigure(double?radius)?{shape?=?Shape.CIRCLE;this.radius?=?radius;}//?Constructor?for?rectangleFigure(double?length,?double?width)?{shape?=?Shape.RECTANGLE;this.length?=?length;this.width?=?width;}double?area()?{switch(shape)?{case?RECTANGLE:return?length?*?width;case?CIRCLE:return?Math.PI?*?(radius?*?radius);default:throw?new?AssertionError();}} }(2)類層次簡潔
abstract?class?Figure?{abstract?double?area(); }? 類層次
class?Circle?extends?Figure?{final?double?radius;Circle(double?radius)?{?this.radius?=?radius;?}double?area()?{?return?Math.PI?*?(radius?*?radius);?} }class?Rectangle?extends?Figure?{final?double?length;final?double?width;Rectangle(double?length,?double?width)?{this.length?=?length;this.width??=?width;}double?area()?{?return?length?*?width;?} }class?Square?extends?Rectangle?{Square(double?side)?{super(side,?side);} }? 杜絕了switch case遺漏的可能,同時也可以便于擴展。
9、用函數對象表示策略
(1)只使用一次
Arrays.sort(stringArray,new?Comparator<String>(){public?int?compare(String?s1,String?s2){return?s1.length()?-?s2.length();} });(2)使用多次
class?Export{private?static?class?StrLenCmp?implements?Comparator<String>,Serializable{public?int?compare(String?s1,String?s2){return?s1.length()?-?s2.length();}}public?static?final?Comparator<String>?STRING_LENGTH_COMPARATOR?=?new?StrLenCmp(); }? ?使用匿名類的方式,將會在每次執行調用的時候創建一個新的實例,如果它被重復執行,則考慮將函數對象存儲到一個私有的靜態final域中,并重用它。
10、優先考慮靜態成員類
(1)嵌套類
? ? ? 嵌套類指被定義在另一個類的內部的類,其存在的目的是只為其外圍類提供服務(外圍類的輔助類)。除A外,其他都可以稱為內部類(inner class)。
A、靜態成員類(static member class)
? ?常見用法是作為公有的輔助類。
? ?JDK實例
public?class?LinkedList<E>?extends?AbstractSequentialList<E>?…;private?static?class?Entry<E>?{E?element;Entry<E>?next;Entry<E>?previous;Entry(E?element,?Entry<E>?next,?Entry<E>?previous)?{this.element?=?element;this.next?=?next;this.previous?=?previous;}}…; }B、非靜態成員類(nonstatic member class)
其每一個實例都隱含著與外圍類的一個外圍實例相關聯,常見的用法就是定義一個Adapter,允許外部類的實例被看做是另外一個不相關的類的實例。
成員類的顯著特性就是成員類能訪問它的外部類實例的任意字段與方法。方便一個類對外提供一個公共接口的實現是成員類的典型應用。
public?abstract?class?AbstractList<E>?extends?AbstractCollection<E>?implements?List<E>?{private?class?Itr?implements?Iterator<E>?{………;}public?Iterator<E>?iterator()?{return?new?Itr();} }以JDK Collection類庫為例,每種Collection類必須提供一個與其對應的Iterator實現以便客戶端能以統一的方式遍歷任一Collection實例。每種Collection類的Iterator實現就被定義為該Collection類的成員類。
注意,如果聲明成員類不要求訪問外圍實例,則用static修飾,否則每一個實例都將包含一個額外的指向外圍對象的引用,保存這引用要消耗時間和空間,并且會導致外圍實例在符合垃圾回收時卻仍然得以保留。
C、匿名類(annoymous class)
沒有類名的局部類就是匿名類。用一條語句完成匿名類的定義與實例創建。在使用的同時被聲明和實例化。
可以出現在代碼中任何允許出現表達式的地方,當且僅當匿名類出現在非靜態環境中時,才有外圍實例。
常見的用法就是用來動態創建函數對象和過程對象(Runnable,Thread,TimerTask)。
queryBtn.addActionListener(new?ActionListener()?{@Overridepublic?void?actionPerformed(ActionEvent?e)?{String?day?=?dateBtn.getCurrentSimpleDateFormat().format(dateBtn.getDate());Integer?col?=?Integer.valueOf(box.getSelectedItem().toString());ShifenTableGUI?gui?=?new?ShifenTableGUI(day,?col);gui.show();} });Arrays.sort(stringArray,new?Comparator<String>(){public?int?compare(String?s1,String?s2){return?s1.length()?-?s2.length();} });? 創建過程對象
?public?static?void?main(String[]?args){Thread[]?threads?=?new?Thread[THREADS_COUNTS];for(int?i=0;i<THREADS_COUNTS;i++){threads[i]?=?new?Thread(new?Runnable(){@Overridepublic?void?run()?{System.out.println(Thread.currentThread().getName()+",start");for(int?j=0;j<10000;j++){increase();}latch.countDown();}});threads[i].start();}//等待所有累加線程都結束try?{latch.await();}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(race);}D、局部類(local class)
四種嵌套類中使用最少的類,在任何可以聲明局部變量的地方,都可以使用。
對一個靜態成員類,去掉其聲明中的“static”關鍵字,將其定義移入其外部類的靜態方法或靜態初始化代碼段中就成為了局部靜態成員類。對一個成員類,將其定義移入其外部類的實例方法或實例初始化代碼中就成為了局部成員類。
內部類只在定義它的代碼段中可見,不能在它所屬代碼段之外的代碼中使用;因此也就沒有public/private/default權限修飾符(無意義);不能以局部類形式定義一個接口。局部類只在其所屬代碼段中可見,定義這樣的接口無意義;局部類類名不能與其外部類類名重復。
public?class?Outer?{private?int?instanceField;?private?static?int?staticField;?//define?a?local?member?class?in?instance?code?block{int?localVirable1?=?0;final?int?localVirable2?=?1;class?Inner1?{public?Inner1()?{//can?access?its?outer?class'?field?and?method?directlyinstanceField?=?1;//use?OuterClass.this?to?get?its?corresponding?outer?class?instanceOuter.this.instanceField?=?1;//can?not?access?the?not?final?local?virable?in?its?containing?code?block//System.out.print(localVirable1);//can?access?the?final?local?virable?in?its?containing?code?blockSystem.out.print(localVirable2);}}????????//local?class?can?not?have?privilege?modifier?/*public?class?inner2?{????????????}*/}//?define?a?local?static?member?class?in?static?code?blockstatic?{class?Inner2?{public?Inner2()?{staticField?=?1;//can?not?access?instance?field?and?method?in?a?local?static?member?class?//intanceField?=?2;}}}public?void?intanceMethod()?{//define?a?local?class?in?its?out?class'?instance?methodclass?Inner3?{}//local?class?is?visible?only?in?its?containning?code?block//Outer.Inner2?inner2;}private?static?void?staticMethod()?{//define?a?local?static?member?class?in?its?out?class'?static?methodclass?Inner4?{????public?Inner4()?{staticField?=?2;}}//can?not?define?a?interface?as?a?local?class/*interface?I?{}*/} }轉載于:https://my.oschina.net/scipio/blog/288142
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的【04】Effective Java - 类和接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nagios添加主机监控失败-故障小结
- 下一篇: 免费社交类图标集