《編寫高質量代碼-改善java程序的151個建議》php
--秦小波html
第一章、開發中通用的方法和準則java
一、不要在常量和變量中出現易混淆的字母python
long a=0l; --> long a=0L;
二、莫讓常量蛻變成變量c++
static final int t=new Random().nextInt();
三、三元操做符的類型無比一致web
int i=80; String s=String.valueOf(i<100?90:100); String s1=String.valueOf(i<100?90:100.0); System.out.print(s.equals(s1)); //false
編譯器會進行類型轉換,將90轉爲90.0。有必定的原則,細節不表正則表達式
四、避免帶有變長參數的方法重載算法
public class MainTest { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println(PriceTool.calPrice(12, 1)); // 1 } } class PriceTool { public static int calPrice(int price, int discount) { return 1; } public static int calPrice(int price, int... discount) { return 2; } }
編譯器會從最簡單的開始猜測,只要符合編譯條件的即採用spring
五、別讓null值和空值威脅到變長方法 數據庫
其中client.methodA("china")和client.methodA("china",null) 是編譯不經過的,由於編譯器不知道選擇哪一個方法
六、覆寫變長方法也循規蹈矩
重寫是正確的,由於父類的calprice編譯成字節碼後的形參是一個int類型的形參加上一個int數組類型的形參,子類的參數列表也是如此。
sub.fun(100,50) 編譯失敗,方法參數是數組,java要求嚴格類型匹配
七、警戒自增的陷阱
輸出:0
步驟1:jvm把count(此時是0)值拷貝到臨時變量區
步驟2:count值加1,這時候count的值是1
步驟3:返回臨時變量區的值,0
步驟4:返回值賦值給count,此時count值被重置爲0
八、少用靜態導入
java5開始引入 import static ,其目的是爲了減小字符輸入量,提升代碼的可閱讀性。
舉個例子:
濫用靜態導入會使程序更難閱讀,更難維護。靜態導入後,代碼中就不用再寫類名了,可是咱們知道類是「一類事物的描述」,缺乏了類名的修飾,靜態屬性和靜態方法的表象意義能夠被無限放大,這會讓閱讀者很難弄清楚所謂何意。
舉個糟糕的例子:
對於靜態導入,必定要遵循兩個規則:
1)、不使用*通配符,除非是導入靜態常量類(只包含常量的類或接口)
2)、方法名是具備明確、清晰表象意義的工具類
九、不要在本類中覆蓋靜態導入的變量和方法
本地的方法和屬性會被使用。由於編譯器有最短路徑原則,以確保本類中的屬性、方法優先
十、養成良好習慣,顯示聲明UID
十一、避免用序列化類在構造函數中爲不變量賦值
序列化1.0
序列化2.0
此時反序列化,name:混世魔王
由於飯序列化時構造函數不會執行。jvm從數據流中獲取一個object對象,而後根據數據流中的類文件描述信息查看,發現時final變量,須要從新計算,因而引用person類中的name值,而辭職jvm又發現name居然沒有賦值,不能引用,因而再也不初始化,保持原值狀態。
反序列化時final變量在如下狀況不會被從新賦值
1)經過構造函數爲final變量賦值
2)經過方法返回值爲final變量賦值
3)final修飾的屬性不是基本類型
原理:
保存在磁盤(網絡傳輸)的對象文件包括兩部分
1)類描述信息
包括路徑、繼承關係、訪問權限、變量描述、變量訪問權限、方法簽名、返回值,以及變量的關聯類信息。與class文件不一樣的是,它不記錄方法、構造函數、statis變量等的具體實現。
2)非瞬態(transient關鍵字)和非靜態實例變量值
這裏的值若是是一個基本類型,就保存下來;若是是複雜對象,就連該對象和關聯類信息一塊兒保存,而且持續遞歸下去,其實仍是基本數據類型的保存。
也正是由於這兩點,一個持久化後的對象文件會比一個class類文件大不少
舉個例子
一個服務像另外一個服務屏蔽類A的一個屬性x
class A{ int a; int b; int x; }
可能有幾種解決方案
1)在屬性x前加上transient關鍵字(失去了分佈式部署的能力?todo)
2)新增業務對象類A1,去掉x屬性(符合開閉原則,並且對原系統沒有侵入型,可是增長代碼冗餘,且增長了工做量)
class A{ int a; int b; int x; }
3)請求端過濾。得到A對象之後,過濾掉x屬性。(方案可行但不合規矩,本身服務的安全性須要外部服務承擔,不符合設計規範)
理想的解決方案:
用Serializable接口的兩個私有方法 writeObject和readObject,控制序列化和反序列化的過程
序列化回調:
java調用objectOutputStream類把一個對象轉換成流數據時,會經過反射檢查被序列化的類是否有writeObject方法,而且檢查其是否符合私有、無返回值的特性。如有,則會委託該方法進行對象序列化,若沒有,則由ObjectOutputStream按照默認規則繼續序列化。一樣,從流數據恢復成實例對象時,也會檢查是否有一個私有的readObject方法。
java世界一直在遭受異種語言的入侵,好比php,ruby,groovy,js等。這種入侵者都有一個共同特徵:腳本語言,他們都在運行期解釋執行。爲何java這種強編譯型語言會須要這些腳本語言呢?那是由於腳本語言的三大特性:
1)靈活。腳本語言通常都是動態類型,能夠不用聲明變量類型而直接使用,也在能夠在運行期改變類型
2)便捷。腳本語言是一種解釋性語言,不須要編譯成二進制代碼,也不須要像java同樣生成字節碼。它的執行是依靠解釋器解釋的,所以在運行期變動帶啊嗎很是容易,並且不用中止應用
3)簡單。
腳本語言的這些特性是java所缺乏的,引入腳本語言可使java更強大,因而java6開始正式支持腳本語言。可是由於腳本語言比較多,java的開發者也很難肯定該支持哪一種語言,因而jcp提出了jsr223規範,只要符合該規範的語言均可以在java平臺上運行(默認支持js)
動態編譯一直是java的夢想,從java6版本開始支持動態編譯,能夠在運行期直接編譯.java文件,執行.class等,只要符合java規範均可以在運行期動態家在。
在使用動態編譯時,須要注意如下幾點:
1)在框架中謹慎使用
好比在Spring中,寫一個動態類,要讓它動態注入到spring容器中,這是須要花費老大功夫的
2)不要在要求高性能的項目使用
動態編譯畢竟須要一個編譯過程,與靜態編譯相比多了一個執行環節,所以在高性能項目中不要使用動態編譯。不過,若是在工具類項目中它則能夠很好的發揮其優越性,好比在idea中寫一個插件,就能夠很好地使用動態編譯,不用重啓便可實現運行、調試,很是方便。
3)考慮安全問題
若是你在web界面上提供了一個功能,容許上傳一個java文件而後運行,那就等於說「個人機器沒有密碼,你們都來看個人隱私吧」,這是很是典型的注入漏洞,只要上傳一個惡意java程序就可讓你全部的安全工做毀於一旦。
4)記錄動態編譯過程
建議記錄源文件、目標文件、編譯過程、執行過程等日誌,不只僅是爲了診斷,仍是爲了安全和審計,對java項目來講,空中編譯和運行是很不讓人放心的,留下這些依據能夠更好地優化程序
instanceof是一個簡單的二元操做符,它是用來判斷一個對象是不是一個類實例的,兩側操做符須要有繼承或實現關係。
1)‘A’ instanceof Character :編譯不經過 ‘A’ 是一個char類型,也就是一個基本類型,不是一個對象,instanceof只能用於對象的判斷。
2)null instanceof String:編譯經過,返回false。這是instanceof特有的規則:若左操做符是null,結果直接返回false
3)(String)null instanceof String :編譯經過,返回false。null是一個萬用類型,也能夠說是沒類型,即便作類型轉換仍是個null
4)new Date()instanceof String:編譯不經過,date類和string沒有繼承或實現關係
5)new GenericClass<String>().isDateInstance("") :編譯經過,返回false。T是string,與date之間沒有繼承或實現關係,是由於java的泛型是爲編碼服務的,在編譯成字節碼時,T已是object類型了。傳遞的實參是string類型,也就是說T的表面類型是object,實際類型是string,這句話等價於object instance of date ,因此返回false。
在防護式編程中常常會用斷言對參數和環境作出判斷,避免程序因不當的輸入或錯誤的環境而產生邏輯異常,斷言在不少語言中都存在,c、c++、python都有不一樣的斷言表達形式。在java中斷言的使用是assert關鍵字,以下
assert <布爾表達式> :<錯誤信息>
在布爾表達式爲假時,拋出AssertionError錯誤,並附帶錯誤信息
1)assert默認是不開啓的
2)AssertionError是繼承自Error的。這是錯誤,不可恢復
1)在對外公開的方法中
2)在執行邏輯代碼的狀況下。由於生產環境是不開啓斷言的。避免由於環境的不一樣產生不一樣的業務邏輯
1)在私有方法中,私有方法的使用者是本身,能夠更好的預防本身犯錯
2)流程控制中不可能到達的區域。若是到達則拋異常
3)創建程序探針。咱們可能會在一段程序中定義兩個變量,分別代碼兩個不一樣的業務含義,可是二者有固定的關係。例如 var1=var2*2,那咱們就能夠在程序中處處設‘樁’,斷言這二者的關係,若是不知足即代表程序已經出現了異常,業務也就沒有必要運行下去了
舉個例子:
若是在一個運行中項目,直接替換constans.class ,其中 maxage改成180。client中的輸入依然是150
對於final修飾的基本類型和string類型,編譯器會認爲它是穩定態,因此在編譯時就直接把值編譯到字節碼中了,避免了在運行期引用,以提升代碼的執行效率。
對於final修飾的類,編譯器認爲它是不穩定態,在編譯時創建的則是引用關係(soft final),若是client類引入的常量是一個類或實例,即便不從新編譯也會輸出最新值
基本數據類型相關
i%2==1?奇數:偶數
這個邏輯是不對的,當i爲負數時計算錯誤。由於取餘的計算邏輯爲
int remainder(int a,int b){ return a-a/b*b; }
在計算機中浮點數有多是不許確的,它只能無限接近準確值,而不能徹底精確。這是因爲浮點數的存儲規則決定的(略過)。
舉個例子:system.out.print(10.00-9.06) :0.4000000000000036
有兩種解決方案:
1)BigDecimal
BigDecimal是專門爲彌補浮點數沒法精確計算的缺憾而設計的類,而且它自己也提供了加減乘除的經常使用數學算法。特別是與數據庫Decimal類型的字段映射時,BigDeciaml是最優的解決方案。
2)使用整型
把參與運算的值擴大100倍,並轉變爲整型,而後在展示時再縮小100倍。
舉個例子:
太陽逛照射到地球上須要8分鐘,計算太陽到地球的距離。
long result=light_speed * 60 * 8;
輸出的結果是 -202888064
緣由:java是先運算而後再進行類型轉換的,三者相乘,超過了int的最大值,因此其值是負值(溢出是負值的緣由看一下)
正確的處理是 long result=light_speed * 60L * 8;
舉個例子:
if(order+base<limit){...}
當order+base足夠大時,超過了int的最大值,其值是負值,因此業務邏輯會有問題
math.round(-10.5) 輸出 -10 這是math。round採用的舍入規則所決定的(採用的是正無窮方向舍入規則)
以上算法對於一個5000w存款的銀行來講,一年將損失10w。一個美國銀行家發現了此問題並提出了一個修正算法,叫作銀行家舍入的近似算法(規則不記錄了)。java5能夠直接用RoundingMode類提供的Round模式。與BigDecimal絕配。RoundingMode支持7種舍入模式:
遠離零方向舍入、趨向零方向舍入、向正無窮方向舍入、向負無窮方向舍入、最近數字舍入、銀行家算法
舉個例子。當list中有null元素,自動拆箱時調用intValue()會報空指針異常。
舉個例子。i==j false。Integer是引用類型
Integer緩存了-128-127的Integer對象。因此經過裝箱(Integer.valueOf())得到的對象能夠複用。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
自動裝箱有一個重要的原則:基本類型能夠先加寬,再轉變成寬類型的包裝類型,但不能直接轉變成寬類型的包裝類型。
舉個例子
兩次調用都是基本類型的方法
程序啓動後,生成的隨機數會不一樣。可是每次啓動程序,生成的都會是三個隨機數。產生隨機數和seed之間的關係以下:
1)種子不一樣,產生不一樣的隨機數
2)種子相同,即便實例不一樣也產生相同的隨機數
Random的默認種子(無參構造)是System.nanoTime()的返回值(jdk1.5之前是System.currentTimeMillis()),這個值是距離某一個固定時間點的納秒數,不一樣的操做系統和硬件有不一樣的固定時間點,隨機數天然也就不一樣了
其實是有這種可能的,可是千萬不要這樣寫
舉個例子
舉個例子:輸出1
靜態變量的初始化:先分配空間,再賦值
類初始化時會先先分配空間,再按照加載順序去賦值 :靜態的(變量、靜態塊)的加載順序是 從上到下
在子類中構建與父類相同的方法名、輸入參數、輸出參數、訪問權限,而且父類、子類都是靜態方法,此種行爲叫作隱藏,它與重寫有兩點不一樣:
1)表現形式不一樣。@override能夠用於重寫,不能用於隱藏
2)指責不一樣。隱藏的目的是爲了拋棄父類靜態方法。重寫則是將父類的行爲加強或者減弱,延續父類的指責
1)更符合面向對象編程
2)類與類關係複雜,容易形成棧溢出
什麼是構造代碼塊
構造代碼塊的特性:在每一個構造函數中都運行,且會首先運行
1)提供封裝性
2)提升代碼可讀性
舉個例子
List l1=new ArrayList(); List l2=new ArrayList(){}; List l3=new ArrayList(){{}}; System.out.println(l1.getClass()==l2.getClass());//false System.out.println(l1.getClass()==l3.getClass());//false System.out.println(l3.getClass()==l2.getClass());//false
l1:arraylist實例
l2:{}表示一個匿名內部類,可是沒有重寫任何方法,至關於匿名內部類的實例
l3:外層{}表示一個匿名內部類,可是沒有重寫任何方法,內層{}表示匿名內部類的初始化塊,能夠有多個。
通常類默認都是調用父類的無參構造函數的,而匿名類由於沒有名字,只能由構造代碼塊代替,也就無所謂的有參和無參構造函數類,它在初始化時直接調用類父類的同參構造函數,而後再調用本身的構造代碼塊
使用內部類實現多繼承
java項目中使用的工具類很是多,好比jdk本身的工具類java.lang.math java.util.collections等都是咱們常常用到的。工具類的方法和屬性都是靜態的,不須要生成實例便可訪問,並且jdk也作了很好的處理,因爲不但願被初始化,因而就設置構造函數爲private。也能夠在構造函數中拋一個error。
一個類實現類cloneable接口就表示它具有類被拷貝的能力,若是再重寫clone方法就會徹底具有拷貝能力。拷貝是在內存中進行的,因此在性能方面比直接經過new生成對象要快不少,特別是在大對象的生成上,這會使性能的提高很是顯著。可是object提供的默認對象拷貝是淺拷貝。
淺拷貝的規則:
1)基本類型
若是變量是基本類型,則拷貝其值
2)對象
拷貝地址引用
3)string字符串
這個比較特殊,拷貝的也是一個地址,是個引用。可是在修改時,它會從字符串池中從新生成新的字符串,原有的字符串對象保持不變,在此處咱們能夠認爲string是一個基本類型
實現serializable接口,使用序列化實現對象的深拷貝。或者其餘序列化方式json等
一句話總結,equals知足自反性,傳遞性,對稱性,一致性規則 ,參考:http://www.javashuo.com/article/p-guhzjvpv-mk.html
一句話總結,equals知足自反性,傳遞性,對稱性,一致性規則 ,參考:http://www.javashuo.com/article/p-guhzjvpv-mk.html
兩個不一樣的類,可能具有相同的屬性,致使equals相等
參考:http://www.javashuo.com/article/p-guhzjvpv-mk.html
java中有一個特殊的類:package-info類,它是專門爲本包服務的。package-info特性
1)它不能隨便被建立
不能經過new的形式建立。能夠在text建立,拷貝過來
2)它服務的對象很特殊
一個類是一類或一組事物的描述,但package-info是描述和記錄本包信息的
3)package-info類不能有代碼實現
package-info也會被編譯成package-info.class ,可是在package-info.java文件裏不能聲明package-info類。不能夠繼承,沒有接口...
package-info做用
1)聲明友好類和包內訪問常量
雖然它沒有編寫package-info的實現,可是package-info.class類文件仍是會生成。
2)爲在包上標註註解提供便利
好比咱們要寫一個註解,查看一個包下的全部對象,只要把註解標註到package-info文件中便可,並且不少開源項目也採用類此方法,好比struts2的@namespace、hibernate的@filterdef等
3)提供包的總體註釋說明
經過javadoc生成文檔時,會把這些說明做爲包文檔的首頁,讓讀者更容易對該包有一個總體的認識。固然在這點上它與package.htm的做用是相同的,不夠package-info能夠在代碼中維護文檔的完整性,而且能夠實現代賣與文檔的同步更新
常量池
舉個例子:
string.replaceAll("","") 要求第一個參數傳的是正則表達式。若是傳了一些$($在正則中表示字符串的結束位置)等,會有異常
java對加號的處理機制:在使用加號進行計算的表達式中,只要遇到string字符串,則全部的數據都會轉換爲string類型進行拼接,若是是對象,調用tostring方法的返回值拼接
string s=1+1+"a"; //2a
1)+ :編譯器對字符串的加號作了優化,它會使用tringbuilder的append方法進行追加,而後經過tostring方法轉換成字符串
2)concat():數組拷貝,可是會會建立string對象
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
3)stringbuffer、stringbuilder:數組拷貝
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
比較器通常是經過compareTo比較。該方法是先取得字符串的字符數組,而後一個個比較大小(減號操做符),也就是unicode碼值的比較。因此非英文排序會出現不許確的狀況。java推薦使用collator類進行排序
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; }
參考list擴容
arrays.copyof, clone都是淺拷貝
一句話總結:沒必要追求最快算法,仍是要結合業務,找準側重點
基本數據類型不能做爲aslist的輸入參數
輸出1
int類型不能泛型化。替換成Intger
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { public void add(int index, E element) { throw new UnsupportedOperationException(); } }
arraylist是arrays的靜態內部類,在父類聲明類add方法,拋出異常 。爲啥要設計成這樣?若是是不可變類推薦guava。immlist
兩種方式
1) foreach :shi iterator的變形用法。也就是須要先建立一個迭代器容器,而後屏蔽內部遍歷細節,對外提供hasnext等方法。
2) for(int i=0 ) 採用下標方式遍歷列表
arraylist 實現類RandomAccess接口(隨機存取接口),這也就標誌着arraylist是一個能夠隨機存取的列表 。適合採用下標方式來訪問
linkedlist,雙向鏈表,兩個元素原本就是有關聯的,用foreach會高效
s1.equals(s2) 。二者都是list,equals方法是在abstractlist中定義的
list接口提供來sublist方法,返回的子列表只是一個視圖,對子列表的操做至關於操做原列表
代碼比較簡潔
checkForconmodification方法是用於檢測併發修改的。modcount是從子列表的構造函數中賦值的,其值等於生成子列表時的修改次數。由於在生成子列表後再修改原始列表modcount的值就不相等了。
public void add(int index, E e) { rangeCheckForAdd(index); checkForComodification(); parent.add(parentOffset + index, e); this.modCount = parent.modCount; this.size++; }
private void checkForComodification() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); }
binarySearch基於二分算法。要求列表自己升序。推薦indexof
好比說 indexOf()依賴equals方法查找,binarySearch則依賴compareTo方法查找
1)並集:list1.addAll(list2)
2) 交集:list.retainAll(list2)
3) 差級:list1.removeAll(list2)
4)無重複的並集:list1.removeAll(list2); list1.addAll(list2)
哈?行吧。entry對象和2倍擴容 注意下內存使用就行
map key 衝突。下降效率
算了吧
原文是與treeset作對比的
8三、推薦使用枚舉定義常量
8四、使用構造函數協助描述枚舉項add code
8五、當心switch帶來的空值異常
8六、在switch的default代碼快中增長assertionerror錯誤
switch代碼與枚舉之間沒有強制的約束關係,只是在語義上創建了聯繫。在default後直接拋出AssertionError錯誤,其含義就是「不要跑到這裏來」
8七、使用valueof前必須校驗
valueof先經過反射從枚舉類的常量聲明中查找,若找到就直接返回,若找不到則拋出無效參數異常。valueof本意是保護編碼中的枚舉安全性,使其不產生空枚舉對象。
8八、用枚舉實現工廠方法模式更簡潔
1)避免錯誤調用
2)性能更好
3)下降類間耦合
8九、枚舉項的數量限制在64個之內
爲了更好的使用枚舉,java提供了兩個枚舉集合EnumSet和EnumMap。EnumSet很好用,可是它有一個隱藏的特色。
一句話總結:當枚舉項《64時,建立RegularenumSet實例,大於64時,建立JumboEnumSet實例對象。而JumboEnumSet內部分段處理。多了一次映射。因此小於64時效率比較高
90、當心使用註解
@inherited註解有利有弊,利的地方是一個註解只要標註到父類,全部的子類都會自動具備父類相同的註解,整齊、統一併且便於管理,弊的地方是單單
9一、枚舉和註解結合使用威力更大
9二、注意@override不一樣版本的區別
jdk1.5嚴格遵照重寫的定義。1.6之後開放了不少。好比說繼承接口的,在1.5不能用@override
9三、java的泛型是類型擦除的
之因此這樣處理:
1)避免jvm的大換血。c++的泛型生命期延續到了運行期,而java是在編譯器擦除掉的。避免jvm大量的重構工做
2)版本兼容。在編譯器擦除能夠更好的支持原生類型。在java1.5以上,即便聲明一個list這樣的原生類型也是能夠正常編譯經過的,只是會產生警告
9四、不能初始化泛型參數和數組
T[] tArray=new T[3]; 編譯失敗
List<T> list=new ArrayList<T>();編譯成功
爲何數據不能夠。可是集合能夠?由於arraylist表面是泛型,其實已經在編譯器轉型爲object了。在某些狀況下,咱們確實須要泛型數組,能夠以下實現:
9五、強制聲明泛型的實際類型
9六、不一樣的場景使用不一樣的泛型通配符
?:任意類型
extends:某一個類的子類型
super:某一個類的父類型
1)泛型結構只參與‘讀’操做則限定上界
2)泛型結構只參與‘寫’操做則限定下界
9七、警戒泛型是不能協變和逆變的
協變:用一個窄類型替換寬類型
舉個例子:
逆變:一個寬類型替換窄類型
舉個例子:
逆變不屬於重寫,只是重載而已。因爲此時的dostuff方法已經與父類沒有任何關係類,只是子類獨立擴展出的一個行爲,因此是否聲明爲dostuff方法名意義不大,逆變已經不具備特別的意義類。因此重點關注下協變。(其實也就是多態)
泛型不支持協變、逆變
9八、建議採用的順序是List<T> List<?> List<Object>
1)List<T>表示的是list集合中的元素都爲t類型,具體類型在運行期決定; List<?> 也是;List<Object>則表示集合中的全部元素
2)List<T>能夠進行讀寫操做,它的類型是固定的T類型,在編碼期不須要進行任何的轉型操做;List<?> 是隻讀類型的,由於編譯器不知道list中容納的是什麼類型的元素,並且讀出來的元素都是object類型的,須要主動轉型,因此它常常用於泛型方法的返回值。注意list<?>能夠remove,clear等,由於刪除動做與泛型類型無關 ; List<Object>也能夠讀寫操做,可是它執行寫入操做時須要轉型,而此時已經失去了泛型存在的意義了
9九、嚴格限定泛型類型採用多重界限
舉個例子
java語言是先把java源文件編譯成後綴爲class的字節碼文件,而後再經過classloader機制把這些類文件加載到內存中,最後生成實例執行的。java使用一個元類MetaClass來描述加載到內存中的類數據,這就是Class類,它是一個描述類的類對象。特殊性:
1)無構造函數。Class對象是在加載類時由java虛擬機經過調用類加載器中的defineClas方法自動構造的
2)能夠描述基本類型。雖然8個基本類型在jvm中並非一個對象,它們通常存在於棧內,可是class類仍然能夠描述它們,int.class
3)其對象都是單例模式。一個Class的實例對象描述一個類,而且只描述一個類。
Class類是java的反射入口,只有在得到類一個類的描述對象後才能動態地加載,調用。通常得到一個class對象有三種途徑
1) 類屬性方式 String.class
2) 對象的getClass方法 new String().getClass()
3) forName方法加載 Class.forName("java.lang.String")
getMethod:得到全部public訪問級別的方法,包括從父類繼承的方法
getDeclaredMethod:得到自身類的全部方法,包括public、private等
accessible屬性表示是否容易得到,是否須要進行安全檢查。咱們知道,動態修改一個類或方法都會受java安全體系的制約,而安全的處理是很是消耗資源的(性能很是低),所以對於運行期要執行的方法或屬性就提供類accessible可選項:由開發者決定是否要逃避安全體系的檢查
invoke的執行:
@CallerSensitive public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) {//保存了accessible的值 if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args); }
accessible屬性只是用來判斷是否須要進行安全檢查的,若是不須要則直接執行,這就能夠大幅度地提高系統性能(因爲取消了安全檢查,也能夠運行private方法,訪問private屬性)。通過大量測試,在大量的反射狀況下,設置accessible爲true能夠提高性能20倍以上
數組是一個很是特殊的類,雖然它是一個類,但沒有定義類路徑。編譯後會爲不一樣的數組類型生成不一樣的類
因此其實是能夠動態加載一個對象數組的
可是這沒有任何意思,由於它不能生成一個數組對象,也就是說以上代碼只是把一個string類型的數組類和long類型的數組類加載到類內存中,並不能經過newinstance方法生成一個實例對象,由於它沒有定義數組的長度,沒有長度的數組是不容許存在的。
可是!能夠用使用array數組反射類來動態加載:
由於數組比較特殊,要想動態建立和反問數組,基本的反射是沒法實現的。因而java就專門定義來一個array數組反射工具類來實現動態探知數組的功能
通常狀況下反射並非性能的終極殺手,而代碼結構混亂、可讀性差則極可能會埋下隱患
1)提升系統的友好性
2)提升系統的可維護性
3)解決java異常機制自身的缺陷,拋多個異常
public class IOException extends Exception { public IOException() { super(); } //記錄上一級異常 public IOException(String message, Throwable cause) { super(message, cause); } }
1)受檢異常使接口聲明脆弱
oop要求咱們儘可能多的面向接口編程,能夠提升代碼的擴展性、穩定性等。可是一旦設計異常問題就不同了。好比一個接口拋出了異常a,隨着業務的發展,該接口可能還會拋出異常b、異常c等。這會產生兩個問題:
a)異常是主邏輯的補充邏輯,修改一個補充邏輯,就會致使主邏輯也被修改,也就是出現了實現類「逆影響」接口的情景,咱們知道實現類是不穩定的,而接口是穩定的,一旦定義了異常,則增長了接口的不穩定性
b)實現的類變動最終會影響到調用者,破壞了封裝性,這也是迪米特法則所不能容忍的(設計模式6原則:一個對象應該對其餘對象保持最少的瞭解)
2)受檢異常使代碼的可讀性下降
3)受檢異常增長了開發工做量
咱們知道,異常須要封裝和傳遞,只有封裝才能讓異常更容易理解,上層模塊才能更好的處理,可這也會致使低層級的異常沒完沒了的封裝,無故加劇了開發的工做量。可是咱們也不能把全部的受檢異常轉化爲非受檢異常,緣由是在編碼期上層模塊不知道下層模塊會拋出何種非受檢異常,只有經過規則或文檔來約束,能夠這樣說:
受檢異常:法律下的自由
非受檢異常:協約性質的自由
受檢異常威脅到系統的安全性、穩定性、可靠性、正確性時,不能轉換爲非受檢異常
1)覆蓋了try代碼塊中的return返回值
在代碼中加上try代碼塊就標誌着運行時會有一個throwable線程監視着該方法的運行,若出現異常,則交由異常邏輯處理。
a)finally中修改基本數據類型返回值。返回值不會變化
方法在棧內存中運行,而且會按照‘先進後出’的原則執行,當dostuff方法執行完return a時,此方法的返回值已經肯定是int類型1,此後finally代碼塊再修改a的值已經於dostuff返回值沒有任何關係了
b)finally中修改基本引用類型返回值。返回值會變化
返回李四。person是一個引用對象,在try代碼塊中的返回值的person對象的地址。
2)屏蔽異常
異常線程在監視到有異常發生時,就會登記當前的異常類型爲dataformatexception,可是當執行finally代碼塊時,則會重新爲dostuff方法賦值,也就是告訴調用者‘該方法執行正確,沒有產生異常,返回值是1’
1)加劇了上層代碼編寫者的負擔
只能經過文檔約束來告知上層代碼有異常
2)致使子類代碼膨脹
子類的無參構造函數默認調用的是父類的構造函數,因此子類的無參構造也必須拋出該異常或父類
3)違背來里氏替換原則(父類能出現的地方子類就能夠出現,並且將父類替換爲子類也不會產生任何異常)
若是子類拋出的異常比父類拋出的異常範圍大,則沒法直接直接替換
4)子類構造函數擴展受限
子類存在的緣由就是指望實現並擴展父類的邏輯,可是父類構造函數拋出異常卻會讓子類構造函數的靈活性大大下降
aop編程能夠很輕鬆的控制一個方法調用哪些類,也能控制哪些方法容許被調用,通常來講切面編程只能控制到方法級別,不能實現代碼級別的植入,好比一個方法被類A調用時放回1,在類B調用時放回0,這就要求被調用者具備識別調用者的能力。在這種狀況下,可使用throwable得到棧信息,而後鑑別調用者並分別輸出
不要包含業務流轉
java的異常處理機制確實比較慢,單單從對象的建立來講,new一個ioexception會比string慢5倍,由於它要執行fillinstatcktrace方法,要記錄當前棧的快照,而string類則要直接申請一個內存建立對象。並且,異常類是不能緩存的,指望預先創建大量的異常對象是不可能的。(在jdk1.6,一個異常對象建立的時間1.4毫秒)
從多線程的設計思想來講。run方法是業務的處理邏輯,start是啓動一個線程,並執行run方法
stop():對於未啓動的線程(線程狀態爲new),會設置其標誌位爲不可啓動,而其餘的狀態則是直接中止
start():會先啓動線程,再判斷標誌位,若是標誌位是不可啓動,則中止線程
會有一個時間差
1)stop方法是過期的
2)stop方法會致使代碼邏輯不完整(好比說stop時還沒釋放io資源等等)
3)stop方法會破壞原子邏輯(會直接釋放全部鎖,致使原子邏輯受損)
線程的優先級(priority)決定了線程得到cpu運行的機會,優先級越高得到的運行機會越大,優先級越低得到的機會越小。但不保證順序執行。thread類中設置了三個優先級,建議使用優先常量,而不是1到10隨機的數字。
class thread{ /** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10; }
jdk1.5之後,在thread類中增長了setUncaughtExceptionHandler方法,實現了線程異常的捕捉和處理
其實比較雞肋
volatile關鍵字比較少用,緣由
1)java1.5以前該關鍵字在不一樣的操做系統上有不一樣的表現,所帶來的問題是移植性比較差;
2)只保證了可見性,不保證原子性
1)儘量多地佔用系統資源,提供快速運算??
2)能夠監控線程執行的狀況,好比是否執行完畢,是否有返回值,是否有異常等
3)能夠爲用戶提供更好的支持,好比計算進度
死鎖須要4個條件
1)互斥條件:一個資源每次只能被一個線程使用
2)資源獨佔條件:一個線程因請求資源而阻塞時,對已得到的資源保持不放
3)不剝奪條件:線程已得到的資源在未使用完以前,不能強行剝奪
4)循環等待條件:若干線程之間造成一種頭尾相接的循環等待資源關係
解決:
1)減小資源共享
2) 使用自旋鎖
lock.trylock(1,TimeUnit.SECONDS)必定時間內獲取不到鎖則放棄
功能與countdownlotch相似,增長了子線程結束後的處理線程
1)不要在循環條件中計算
2)儘量把變量、方法聲明爲fianl static類型
3)縮小變量的做用範圍(加快gc)
4)使用stringbuilder stringbuffer
5)使用非線性檢索
6)覆寫exception的fillinstacktrace方法
7)不創建冗餘對象
這個方法是用來記錄異常時的棧信息的,很是耗時,若是不關注能夠覆蓋之,會使性能提高10倍以上
經過clone方法生成一個對象時,就會再也不執行構造函數了,只是再內存中進行數據塊的拷貝,此方法看上去彷佛應該比new的性能好不少,可是java的締造者們也認識到二八原則,80%的對象是經過new關鍵字建立出來的,因此對new再生成對象時作了充分的性能優化,事實上,通常狀況下new生成的對象clone生成的性能方面要好不少
??????
??????
?????
13九、大膽採用開源工具
140、推薦使用guava擴展工具包
14二、apache擴展包
14三、推薦使用joda日期時間擴展包
14四、能夠選擇多種collections擴展
。。。。後面的就不說了 淡疼