java方法定義在類中方法外面 java方法定義和調用



文章插圖
java方法定義在類中方法外面 java方法定義和調用

文章插圖
前言
代碼優化  , 一個很重要的課題 。可能有些人覺得沒用 , 一些細小的地方有什么好修改的 , 改與不改對于代碼的運行效率有什么影響呢?這個問題我是這么考慮的 , 就像大海里面的鯨魚一樣 , 它吃一條小蝦米有用嗎?沒用 , 但是 , 吃的小蝦米一多之后 , 鯨魚就被喂飽了 。
代碼優化也是一樣 , 如果項目著眼于盡快無 BUG 上線 , 那么此時可以抓大放小 , 代碼的細節可以不精打細磨;但是如果有足夠的時間開發、維護代碼 , 這時候就必須考慮每個可以優化的細節了 , 一個一個細小的優化點累積起來 , 對于代碼的運行效率絕對是有提升的 。
代碼優化的目標是:
減小代碼的體積提高代碼運行的效率代碼優化細節1、盡量指定類、方法的final修飾符
帶有 final 修飾符的類是不可派生的 。在 Java 核心 API 中 , 有許多應用 final 的例子 , 例如 java.lang.String , 整個類都是 final 的 。為類指定 final 修飾符可以讓類不可以被繼承 , 為方法指定 final 修飾符可以讓方法不可以被重寫 。如果指定了一個類為 final , 則該類所有的方法都是 final 的 。Java 編譯器會尋找機會內聯所有的 final 方法 , 內聯對于提升 Java 運行效率作用重大 , 具體參見 Java 運行期優化 。此舉能夠使性能平均提高50%。
2、盡量重用對象
特別是 String 對象的使用 , 出現字符串連接時應該使用StringBuilder/StringBuffer 代替 。由于 Java 虛擬機不僅要花時間生成對象 , 以后可能還需要花時間對這些對象進行垃圾回收和處理 , 因此 , 生成過多的對象將會給程序的性能帶來很大的影響 。
3、盡可能使用局部變量
調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧中速度較快 , 其他變量 , 如靜態變量、實例變量等 , 都在堆中創建 , 速度較慢 。另外 , 棧中創建的變量 , 隨著方法的運行結束 , 這些內容就沒了 , 不需要額外的垃圾回收 。
4、及時關閉流
Java 編程過程中 , 進行數據庫連接、I/O 流操作時務必小心 , 在使用完畢后 , 及時關閉以釋放資源 。因為對這些大對象的操作會造成系統大的開銷 , 稍有不慎 , 將會導致嚴重的后果 。
5、盡量減少對變量的重復計算
明確一個概念 , 對方法的調用 , 即使方法中只有一句語句 , 也是有消耗的 , 包括創建棧幀、調用方法時保護現場、調用方法完畢時恢復現場等 。所以例如下面的操作:
java
for (int i = 0; i < list.size(); i++) {...}建議替換為:
java
for (int i = 0, int length = list.size(); i < length; i++) {...}這樣 , 在 list.size() 很大的時候 , 就減少了很多的消耗 。
6、盡量采用懶加載的策略 , 即在需要的時候才創建
例如:
java
String str = "aaa";if (i == 1) {list.add(str);}建議替換為:
java
if (i == 1) {String str = "aaa";list.add(str);}7、慎用異常
異常對性能不利 。拋出異常首先要創建一個新的對象 , Throwable 接口的構造函數調用名為 fillInStackTrace() 的本地同步方法 , fillInStackTrace() 方法檢查堆棧 , 收集調用跟蹤信息 。只要有異常被拋出 , Java 虛擬機就必須調整調用堆棧 , 因為在處理過程中創建了一個新的對象 。異常只能用于錯誤處理 , 不應該用來控制程序流程 。
8、不要在循環中使用try…catch… , 應該把其放在最外層
除非不得已 。如果毫無理由地這么寫了 , 只要你的領導資深一點、有強迫癥一點 , 八成就要罵你為什么寫出這種垃圾代碼來了 。
9、如果能估計到待添加的內容長度 , 為底層以數組方式實現的集合、工具類指定初始長度
比如 ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等 , 以 StringBuilder 為例:
StringBuilder() // 默認分配16個字符的空間StringBuilder(int size) // 默認分配size個字符的空間StringBuilder(String str) // 默認分配16個字符+str.length()個字符空間
可以通過類(這里指的不僅僅是上面的 StringBuilder)的來設定它的初始化容量 , 這樣可以明顯地提升性能 。比如 StringBuilder吧 , length 表示當前的 StringBuilder 能保持的字符數量 。因為當 StringBuilder 達到最大容量的時候 , 它會將自身容量增加到當前的2倍再加2 , 無論何時只要 StringBuilder 達到它的最大容量 , 它就不得不創建一個新的字符數組然后將舊的字符數組內容拷貝到新字符數組中—-這是十分耗費性能的一個操作 。試想 , 如果能預估到字符數組中大概要存放5000個字符而不指定長度 , 最接近5000的2次冪是4096 , 每次擴容加的2不管 , 那么:
在4096 的基礎上 , 再申請8194個大小的字符數組 , 加起來相當于一次申請了12290個大小的字符數組 , 如果一開始能指定5000個大小的字符數組 , 就節省了一倍以上的空間;把原來的4096個字符拷貝到新的的字符數組中去 。
這樣 , 既浪費內存空間又降低代碼運行效率 。所以 , 給底層以數組實現的集合、工具類設置一個合理的初始化容量是錯不了的 , 這會帶來立竿見影的效果 。但是 , 注意 , 像 HashMap 這種是以數組+鏈表實現的集合 , 別把初始大小和你估計的大小設置得一樣 , 因為一個 table 上只連接一個對象的可能性幾乎為0 。初始大小建議設置為2的N次冪 , 如果能估計到有2000個元素 , 設置成 new HashMap(128)、new HashMap(256) 都可以 。
10、當復制大量數據時 , 使用System.arraycopy()命令11、乘法和除法使用移位操作
例如:
java
for (val = 0; val < 100000; val += 5) {a = val * 8;b = val / 2;}用移位操作可以極大地提高性能 , 因為在計算機底層 , 對位的操作是最方便、最快的 , 因此建議修改為:
java
for (val = 0; val < 100000; val += 5) {a = val << 3;b = val >> 1;}移位操作雖然快 , 但是可能會使代碼不太好理解 , 因此最好加上相應的注釋 。
12、循環內不要不斷創建對象引用
例如:
java
for (int i = 1; i <= count; i++) {Object obj = new Object();}這種做法會導致內存中有count份Object對象引用存在 , count很大的話 , 就耗費內存了 , 建議為改為:
java
Object obj = null;for (int i = 0; i <= count; i++) {obj = new Object();}這樣的話 , 內存中只有一份 Object 對象引用 , 每次 new Object() 的時候 , Object 對象引用指向不同的 Object 罷了 , 但是內存中只有一份 , 這樣就大大節省了內存空間了 。
13、基于效率和類型檢查的考慮 , 應該盡可能使用array , 無法確定數組大小時才使用ArrayList14、盡量使用HashMap、ArrayList、StringBuilder , 除非線程安全需要 , 否則不推薦使用Hashtable、Vector、StringBuffer , 后三者由于使用同步機制而導致了性能開銷15、不要將數組聲明為public static final
因為這毫無意義 , 這樣只是定義了引用為 static final , 數組的內容還是可以隨意改變的 , 將數組聲明為 public 更是一個安全漏洞 , 這意味著這個數組可以被外部類所改變 。
16、盡量在合適的場合使用單例
使用單例可以減輕加載的負擔、縮短加載的時間、提高加載的效率 , 但并不是所有地方都適用于單例 , 簡單來說 , 單例主要適用于以下三個方面:
控制資源的使用 , 通過線程同步來控制資源的并發訪問控制實例的產生 , 以達到節約資源的目的控制數據的共享 , 在不建立直接關聯的條件下 , 讓多個不相關的進程或線程之間實現通信17、盡量避免隨意使用靜態變量
要知道 , 當某個對象被定義為 static 的變量所引用 , 那么 GC 通常是不會回收這個對象所占有的堆內存的 , 如:
java
public class A {private static B b = new B();}此時靜態變量 b 的生命周期與 A 類相同 , 如果 A 類不被卸載 , 那么引用 B 指向的 B 對象會常駐內存 , 直到程序終止 。
18、及時清除不再需要的會話
為了清除不再活動的會話 , 許多應用服務器都有默認的會話超時時間 , 一般為30分鐘 。當應用服務器需要保存更多的會話時 , 如果內存不足 , 那么操作系統會把部分數據轉移到磁盤 , 應用服務器也可能根據 MRU(最近最頻繁使用)算法把部分不活躍的會話轉儲到磁盤 , 甚至可能拋出內存不足的異常 。如果會話要被轉儲到磁盤 , 那么必須要先被序列化 , 在大規模集群中 , 對對象進行序列化的代價是很昂貴的 。因此 , 當會話不再需要時 , 應當及時調用 HttpSession 的 invalidate() 方法清除會話 。
19、實現RandomAccess接口的集合比如ArrayList , 應當使用最普通的for循環而不是foreach循環來遍歷
這是 JDK 推薦給用戶的 。JDK API 對于 RandomAccess 接口的解釋是:實現 RandomAccess 接口用來表明其支持快速隨機訪問 , 此接口的主要目的是允許一般的算法更改其行為 , 從而將其應用到隨機或連續訪問列表時能提供良好的性能 。實際經驗表明 , 實現 RandomAccess 接口的類實例 , 假如是隨機訪問的 , 使用普通 for 循環效率將高于使用 foreach 循環;反過來 , 如果是順序訪問的 , 則使用 Iterator 會效率更高 。可以使用類似如下的代碼作判斷:
java
if (list instanceof RandomAccess) {for (int i = 0; i < list.size(); i++) {}} else {Iterator<?> iterator = list.iterable();while (iterator.hasNext()) {iterator.next();}}循環的底層實現原理就是迭代器 Iterator , 參見 Java 語法糖1:可變長度參數以及 foreach 循環原理 。所以后半句“反過來 , 如果是順序訪問的 , 則使用 Iterator 會效率更高”的意思就是順序訪問的那些類實例 , 使用 foreach 循環去遍歷 。
20、使用同步代碼塊替代同步方法
這點在多線程模塊中的 synchronized 鎖方法塊一文中已經講得很清楚了 , 除非能確定一整個方法都是需要進行同步的 , 否則盡量使用同步代碼塊 , 避免對那些不需要進行同步的代碼也進行了同步 , 影響了代碼執行效率 。
21、將常量聲明為static final , 并以大寫命名
這樣在編譯期間就可以把這些內容放入常量池中 , 避免運行期間計算生成常量的值 。另外 , 將常量的名字以大寫命名也可以方便區分出常量與變量
22、不要創建一些不使用的對象 , 不要導入一些不使用的類
這毫無意義 , 如果代碼中出現“The value of the local variable i is not used”、“The import java.util is never used” , 那么請刪除這些無用的內容 。
23、程序運行過程中避免使用反射
關于 , 請參見反射 。反射是 Java 提供給用戶一個很強大的功能 , 功能強大往往意味著效率不高 。不建議在程序運行過程中使用尤其是頻繁使用反射機制 , 特別是 Method 的 invoke() 方法 , 如果確實有必要 , 一種建議性的做法是將那些需要通過反射加載的類在項目啟動的時候通過反射實例化出一個對象并放入內存—-用戶只關心和對端交互的時候獲取最快的響應速度 , 并不關心對端的項目啟動花多久時間 。
24、使用數據庫連接池和線程池
這兩個池都是用于重用對象的 , 前者可以避免頻繁地打開和關閉連接 , 后者可以避免頻繁地創建和銷毀線程 。
25、使用帶緩沖的輸入輸出流進行IO操作
帶緩沖的輸入輸出流 , 即 BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream , 這可以極大地提升 IO 效率 。
26、順序插入和隨機訪問比較多的場景使用ArrayList , 元素刪除和中間插入比較多的場景使用LinkedList這個 , 理解ArrayList和LinkedList的原理就知道了27、不要讓public方法中有太多的形參
public 方法即對外提供的方法 , 如果給這些方法太多形參的話主要有兩點壞處:
違反了面向對象的編程思想 , Java 講求一切都是對象 , 太多的形參 , 和面向對象的編程思想并不契合參數太多勢必導致方法調用的出錯概率增加
至于這個“太多”指的是多少個 , 3、4個吧 。比如我們用 JDBC 寫一個 insertStudentInfo() 方法 , 有10個學生信息字段要插如 Student 表中 , 可以把這10個參數封裝在一個實體類中 , 作為 insert() 方法的形參 。
28、字符串變量和字符串常量equals的時候將字符串常量寫在前面
這是一個比較常見的小技巧了 , 如果有以下代碼:
java
String str = "123";if (str.equals("123")) { ... }建議修改為:
java
String str = "123";if ("123".equals(str)){ ... }這么做主要是可以避免空指針異常 。
29、請知道 , 在java中if (i == 1)和if (1 == i)是沒有區別的 , 但從閱讀習慣上講 , 建議使用前者
平時有人問 , if (i == 1) 和 if (1== i) 有沒有區別 , 這就要從 C/C++ 講起 。在C/C++中 , if (i == 1) 判斷條件成立 , 是以0與非0為基準的 , 0表示 false , 非0表示 true , 如果有這么一段代碼:
java
int i = 2;if (i == 1) { ... } else { ... }C/C++ 判斷 i==1 不成立 , 所以以0表示 , 即 false 。但是如果:
java
int i = 2;if (i = 1) { ... } else { ... }萬一程序員一個不小心 , 把 if (i == 1) 寫成 if (i = 1) , 這樣就有問題了 。在 if 之內將i賦值為1 , if 判斷里面的內容非0 , 返回的就是 true 了 , 但是明明 i 為2 , 比較的值是1 , 應該返回的 false 。這種情況在 C/C++ 的開發中是很可能發生的并且會導致一些難以理解的錯誤產生 , 所以 , 為了避免開發者在if語句中不正確的賦值操作 , 建議將 if 語句寫為:
java
int i = 2;if (1 == i) { ... } else { ... }這樣 , 即使開發者不小心寫成了 1 = i , C/C++ 編譯器也可以第一時間檢查出來 , 因為我們可以對一個變量賦值 i 為1 , 但是不能對一個常量賦值1為 i 。
但是 , 在 Java 中 , C/C++ 這種 if (i = 1) 的語法是不可能出現的 , 因為一旦寫了這種語法 , Java 就會編譯報錯 “Type mismatch: cannot convert from int to boolean” 。但是 , 盡管 Java 的 if (i == 1) 和 if (1 == i) 在語義上沒有任何區別 , 但是從閱讀習慣上講 , 建議使用前者會更好些 。
30、不要對數組使用toString()方法
看一下對數組使用toString()打印出來的是什么:
java
public static void main(String[] args) {int[] is = new int[]{1, 2, 3};System.out.println(is.toString());}結果是:
[[email protected]本意是想打印出數組內容 , 卻有可能因為數組引用 is 為空而導致空指針異常 。不過雖然對數組 toString() 沒有意義 , 但是對集合 toString() 是可以打印出集合里面的內容的 , 因為集合的父類 AbstractCollections 重寫了 Object 的 toString() 方法 。
31、不要對超出范圍的基本數據類型做向下強制轉型
這絕不會得到想要的結果:
java
public static void main(String[] args) {long l = 12345678901234L;int i = (int) l;System.out.println(i);}我們可能期望得到其中的某幾位 , 但是結果卻是:
1942892530解釋一下 。Java 中 long 是8個字節64位的 , 所以 12345678901234 在計算機中的表示應該是:
0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010一個 int 型數據是4個字節32位的 , 從低位取出上面這串二進制數據的前32位是:
0111 0011 1100 1110 0010 1111 1111 0010這串二進制表示為十進制 1942892530 , 所以就是我們上面的控制臺上輸出的內容 。從這個例子上還能順便得到兩個結論:
整型默認的數據類型是 int , long l = 12345678901234L , 這個數字已經超出了 int 的范圍了 , 所以最后有一個 L , 表示這是一個 long 型數 。順便 , 浮點型的默認類型是 double , 所以定義 float 的時候要寫成 float f = 3.5f接下來再寫一句 int ii = l + i; 會報錯 , 因為 long + int 是一個 long , 不能賦值給 int32、公用的集合類中不使用的數據一定要及時remove掉
如果一個集合類是公用的(也就是說不是方法里面的屬性) , 那么這個集合里面的元素是不會自動釋放的 , 因為始終有引用指向它們 。所以 , 如果公用集合里面的某些數據不使用而不去 remove 掉它們 , 那么將會造成這個公用集合不斷增大 , 使得系統有內存泄露的隱患 。
33、把一個基本數據類型轉為字符串 , “基本數據類型.toString()是最快的方式、String.valueOf(數據)次之、數據+”最慢
把一個基本數據類型轉為一般有三種方式 , 我有一個 Integer 型數據i , 可以使用 i.toString()、String.valueOf(i)、i+””三種方式 , 三種方式的效率如何 , 看一個測試:
java
public static void main(String[] args) {int loopTime = 50000;Integer i = 0;long startTime = System.currentTimeMillis();for (int j = 0; j < loopTime; j++) {String str = String.valueOf(i);}System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms");startTime = System.currentTimeMillis();for (int j = 0; j < loopTime; j++) {String str = i.toString();}System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms");startTime = System.currentTimeMillis();for (int j = 0; j < loopTime; j++) {String str = i + "";}System.out.println("i + "":" + (System.currentTimeMillis() - startTime) + "ms");}運行結果為:
String.valueOf():11ms Integer.toString():5ms i + "":25ms所以以后遇到把一個基本數據類型轉為 String 的時候 , 優先考慮使用 toString() 方法 。至于為什么 , 很簡單:
String.valueOf() 方法底層調用了 Integer.toString() 方法 , 但是會在調用前做空判斷Integer.toString() 方法就不說了 , 直接調用了i + “”底層使用了 StringBuilder 實現 , 先用 append() 方法拼接 , 再用 toString() 方法獲取字符串
三者對比下來 , 明顯是2最快、1次之、3最慢
34、使用最有效率的方式去遍歷Map
遍歷 Map 的方式有很多 , 通常場景下我們需要的是遍歷 Map 中的 Key 和 Value , 那么推薦使用的、效率最高的方式是:
java
public static void main(String[] args) {HashMap<String, String> map = new HashMap<String, String>();map.put("111", "222");Set<Map.Entry<String, String>> entrySet = map.entrySet();Iterator<Map.Entry<String, String>> iter = entrySet.iterator();while (iter.hasNext()) {Map.Entry<String, String> entry = iter.next();System.out.println(entry.getKey() + "" + entry.getValue());}}如果你只是想遍歷一下這個 Map的 key 值 , 那用 Set keySet = map.keySet(); 會比較合適一些
35、對資源的close()建議分開操作
意思是 , 比如我有這么一段代碼:
java
try {XXX.close();YYY.close();} catch (Exception e) { ...}建議修改為:
java
try {XXX.close();} catch (Exception e) { ...}try {YYY.close();} catch (Exception e) { ...}【java方法定義在類中方法外面 java方法定義和調用】雖然有些麻煩 , 卻能避免資源泄露 。我想 , 如果沒有修改過的代碼 , 萬一 XXX.close() 拋異常了 , 那么就進入了 catch 塊中了 , YYY.close() 不會執行 , YYY 這塊資源就不會回收了 , 一直占用著 , 這樣的代碼一多 , 是可能引起資源句柄泄露的 。而改為上面的寫法之后 , 就保證了無論如何 XXX 和 YYY 都會被 close 掉 。