北京尚學堂提供Java是目前最流行的編程語言之一——它可以用來編寫Windows程式或者是Web應用,移動應用,網路程式,消費電子產品,機頂盒設備,它無處不在。有超過30億的設備是運行在Java之上的。根據Oracle的統計數據,光是使用中的Java Card就有有50億。超過900萬程式員選擇使用...
Java是目前最流行的編程語言之一——它可以用來編寫Windows程式或者是Web應用,移動應用,網路程式,消費電子產品,機頂盒設備,它無處不在。
有超過30億的設備是運行在Java之上的。根據Oracle的統計數據,光是使用中的Java Card就有有50億。
超過900萬程式員選擇使用Java進行開發,它是最受開發人員歡迎的語言,同時也是最流行的開發平臺。
本文為那些準Java程式員們準備了一系列廣為流傳的Java最佳編程實踐
- 優先返回空集合而非null
如果程式要返回一個不包含任何值的集合,確保返回的是空集合而不是null。這能節省大量的”if else”檢查。
1 2 3 |
public class getLocationName { return (null==cityName ? "": cityName); } |
- 謹慎操作字元串
如果兩個字元串在for迴圈中使用+操作符進行拼接,那麼每次迴圈都會產生一個新的字元串對象。這不僅浪費記憶體空間同時還會影響性能。類似的,如果初始化字元串對象,儘量不要使用構造方法,而應該直接初始化。比方說:
1 2 3 4 5 |
//Slower Instantiation String bad = new String("Yet another string object");
//Faster Instantiation String good = "Yet another string object" |
- 避免無用對象
創建對象是Java中最昂貴的操作之一。因此最好在有需要的時候再進行對象的創建/初始化。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import java.util.ArrayList; import java.util.List;
public class Employees {
private List Employees;
public List getEmployees() {
//initialize only when required if(null == Employees) { Employees = new ArrayList(); } return Employees; } } |
- 數組與ArrayList之爭
開發人員經常會發現很難在數組和ArrayList間做選擇。它們二者互有優劣。如何選擇應該視情況而定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import java.util.ArrayList;
public class arrayVsArrayList {
public static void main(String[] args) { int[] myArray = new int[6]; myArray[7]= 10; // ArraysOutOfBoundException
//Declaration of ArrayList. Add and Remove of elements is easy. ArrayList<Integer> myArrayList = new ArrayList<>(); myArrayList.add(1); myArrayList.add(2); myArrayList.add(3); myArrayList.add(4); myArrayList.add(5); myArrayList.remove(0);
for(int i = 0; i < myArrayList.size(); i++) { System.out.println("Element: " + myArrayList.get(i)); }
//Multi-dimensional Array int[][][] multiArray = new int [3][3][3]; } } |
- 數組是定長的,而ArrayList是變長的。由於數組長度是固定的,因此在聲明數組時就已經分配好記憶體了。而數組的操作則會更快一些。另一方面,如果我們不知道數據的大小,那麼過多的數據便會導致ArrayOutOfBoundException,而少了又會浪費存儲空間。
- ArrayList在增刪元素方面要比數組簡單。
- 數組可以是多維的,但ArrayList只能是一維的。
- try塊的finally塊沒有被執行
看下下麵這段代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class shutDownHooksDemo { public static void main(String[] args) { for(int i=0;i<5;i++) { try { if(i==4) { System.out.println("Inside Try Block.Exiting without executing Finally block."); System.exit(0); } } finally { System.out.println("Inside Finally Block."); } } } } |
從代碼來看,貌似finally塊中的println語句應該會被執行5次。但當程式運行後,你會發現finally塊只執行了4次。第5次迭代的時候會觸發exit函數的調用,於是這第5次的finally便永遠也觸發不到了。原因便是——System.exit會掛起所有線程的執行,包括當前線程。即便是try語句後的finally塊,只要是執行了exit,便也無力回天了。
在調用System.exit時,JVM會在關閉前執行兩個結束任務:
首先,它會執行完所有通過Runtime.addShutdownHook註冊進來的終止的鉤子程式。這一點很關鍵,因為它會釋放JVM外部的資源。
接下來的便是Finalizer了。可能是System.runFinalizersOnExit也可能是Runtime.runFinalizersOnExit。finalizer的使用已經被廢棄有很長一段時間了。finalizer可以在存活對象上進行調用,即便是這些對象仍在被其它線程所使用。而這會導致不可預期的結果甚至是死鎖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class shutDownHooksDemo {
public static void main(String[] args) { for(int i=0;i<5;i++) { final int final_i = i; try { Runtime.getRuntime().addShutdownHook( new Thread() { public void run() { if(final_i==4) { System.out.println("Inside Try Block.Exiting without executing Finally block."); System.exit(0); } } }); } finally { System.out.println("Inside Finally Block."); }
} } } |
- 判斷奇數
看下這幾行代碼,看看它們是否能用來準確地判斷一個數是奇數?
1 2 3 |
public boolean oddOrNot(int num) { return num % 2 == 1; } |
看似是對的,但是每執行四便會有一個錯誤的結果(用數據說話)。考慮到負奇數的情況,它除以2的結果就不會是1。因此,返回值是false,而這樣是不對的。
代碼可以修改成這樣:
1 2 3 |
public boolean oddOrNot(int num) { return (num & 1) != 0; } |
這麼寫不光是負奇數的問題解決了,並且還是經過充分優化過的。因為算術運算和邏輯運行要比乘除運算更高效,計算的結果也會更快。
- 單引號與雙引號的區別
1 2 3 4 5 6 |
public class Haha { public static void main(String args[]) { System.out.print("H" + "a"); System.out.print('H' + 'a'); } } |
看起來這段代碼會返回”Haha”,但實際返回的是Ha169。原因就是用了雙引號的時候,字元會被當作字元串處理,而如果是單引號的話,字元值會通過一個叫做基礎類型拓寬的操作來轉換成整型值。然後再將值相加得到169。
- 一些防止記憶體泄露的小技巧
記憶體泄露會導致軟體的性能降級。由於Java是自動管理記憶體的,因此開發人員並沒有太多辦法介入。不過還是有一些方法能夠用來防止記憶體泄露的。
- 查詢完數據後立即釋放資料庫連接
- 儘可能使用finally塊
- 釋放靜態變數中的實例
- 避免死鎖
死鎖出現的原因有很多。避免死鎖不是一句話就能解決的。通常來說,當某個同步對象在等待另一個同步對象所擁有的資源上的鎖時,便會產生死鎖。
試著運行下下麵的程式。它會告訴你什麼是死鎖。這個死鎖是由於兩個線程都在等待對方所擁有的資源,因此會產生死鎖。它們會一直等待,沒有誰會先放手。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public class DeadlockDemo { public static Object addLock = new Object(); public static Object subLock = new Object();
public static void main(String args[]) {
MyAdditionThread add = new MyAdditionThread(); MySubtractionThread sub = new MySubtractionThread(); add.start(); sub.start(); } private static class MyAdditionThread extends Thread { public void run() { synchronized (addLock) { int a = 10, b = 3; int c = a + b; System.out.println("Addition Thread: " + c); System.out.println("Holding First Lock..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Addition Thread: Waiting for AddLock..."); synchronized (subLock) { System.out.println("Threads: Holding Add and Sub Locks..."); } } } } private static class MySubtractionThread extends Thread { public void run() { synchronized (subLock) { int a = 10, b = 3; int c = a - b; System.out.println("Subtraction Thread: " + c); System.out.println("Holding Second Lock..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Subtraction Thread: Waiting for SubLock..."); synchronized (addLock) { System.out.println("Threads: Holding Add and Sub Locks..."); } } } } } |
輸出:
1 2 3 4 5 6 |
Addition Thread: 13 Subtraction Thread: 7 Holding First Lock... Holding Second Lock... Addition Thread: Waiting for AddLock... Subtraction Thread: Waiting for SubLock... |
但如果調用的順序變一下的話,死鎖的問題就解決了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public class DeadlockSolutionDemo { public static Object addLock = new Object(); public static Object subLock = new Object();
public static void main(String args[]) {
MyAdditionThread add = new MyAdditionThread(); MySubtractionThread sub = new MySubtractionThread(); add.start(); sub.start(); }
private static class MyAdditionThread extends Thread { public void run() { synchronized (addLock) { int a = 10, b = 3; int c = a + b; System.out.println("Addition Thread: " + c); System.out.println("Holding First Lock..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Addition Thread: Waiting for AddLock..."); synchronized (subLock) { System.out.println("Threads: Holding Add and Sub Locks..."); } } } }
private static class MySubtractionThread extends Thread { public void run() { synchronized (addLock) { int a = 10, b = 3; int c = a - b; System.out.println("Subtraction Thread: " + c); System.out.println("Holding Second Lock..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Subtraction Thread: Waiting for SubLock..."); synchronized (subLock) { System.out.println("Threads: Holding Add and Sub Locks..."); } } } } } |
輸出:
1 2 3 4 5 6 7 8 |
Addition Thread: 13 Holding First Lock... Addition Thread: Waiting for AddLock... Threads: Holding Add and Sub Locks... Subtraction Thread: 7 Holding Second Lock... Subtraction Thread: Waiting for SubLock... Threads: Holding Add and Sub Locks... |
- 替Java省點記憶體
某些Java程式是CPU密集型的,但它們會需要大量的記憶體。這類程式通常運行得很緩慢,因為它們對記憶體的需求很大。為了能提升這類應用的性能,可得給它們多留點記憶體。因此,假設我們有一臺擁有10G記憶體的Tomcat伺服器。在這台機器上,我們可以用如下的這條命令來分配記憶體:
1 |
export JAVA_OPTS="$JAVA_OPTS -Xms5000m -Xmx6000m -XX:PermSize=1024m -XX:MaxPermSize=2048m" |
- Xms = 最小記憶體分配
- Xmx = 最大記憶體分配
- XX:PermSize = JVM啟動時的初始大小
- XX:MaxPermSize = JVM啟動後可分配的最大空間
- 如何計算Java中操作的耗時
在Java中進行操作計時有兩個標準的方法:System.currentTimeMillis()和System.nanoTime()。問題就在於,什麼情況下該用哪個。從本質上來講,他們的作用都是一樣的,但有以下幾點不同:
- System.currentTimeMillis()的精度在千分之一秒到千分之15秒之間(取決於系統)而System.nanoTime()則能到納秒級。
- System.currentTimeMillis讀操作耗時在數個CPU時鐘左右。而System.nanoTime()則需要上百個。
- System.currentTimeMillis對應的是絕對時間(1970年1 月1日所經歷的毫秒數),而System.nanoTime()則不與任何時間點相關。
- Float還是double
數據類型 |
所用位元組 |
有效位數 |
float |
4 |
7 |
double |
8 |
15 |
在對精度要求高的場景下,double類型相對float要更流行一些,理由如下:
大多數處理器在處理float和double上所需的時間都是差不多的。而計算時間一樣的前提下,double類型卻能提供更高的精度。
- 冪運算
Java是通過異或操作來進行冪運算的。Java對於冪運算有兩種處理方式:
- 乘積:
1 2 3 4 5 |
double square = double a * double a; // Optimized double cube = double a * double a * double a; // Non-optimized double cube = double a * double square; // Optimized double quad = double a * double a * double a * double a; // Non-optimized double quad = double square * double square; // Optimized |
- pow方法:在無法使用乘積的情況下可以使用pow方法。
1 |
double cube = Math.pow(base, exponent); |
不到萬不得已不要使用Math.pow。比方說,當指數是小數的時候。因為Math.pow要比乘積慢300-600倍左右。
- 如何處理空指針異常
空指針異常是Java中很常見的異常。當你嘗試調用一個null對象上的方法時便會拋出這個異常。比如:
1 |
int noOfStudents = school.listStudents().count; |
在上述例子中,school為空或者listStudents()為空都可能會拋出了NullPointerException。因此最好檢查下對象是否為空以避免類似情況。
1 2 3 4 |
private int getListOfStudents(File[] files) { if (files == null) throw new NullPointerException("File list cannot be null"); } |
- JSON編碼
JSON是數據存儲及傳輸的一種協議。與XML相比,它更易於使用。由於它非常輕量級以及自身的一些特性,現在JSON在網路上已經是越來越流行了。常見的數據結構都可以編碼成JSON然後在各個網頁間自由地傳輸。不過在開始編碼前,你得先安裝一個JSON解析器。在下麵的例子中,我們將使用json.simple庫來完成這項工作 (https://code.google.com/p/json-simple/)。
下麵是編碼成JSON串的一個簡單的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import org.json.simple.JSONObject; import org.json.simple.JSONArray;
public class JsonEncodeDemo {
public static void main(String[] args) {
JSONObject obj = new JSONObject(); obj.put("Novel Name", "Godaan"); obj.put("Author", "Munshi Premchand");
JSONArray novelDetails = new JSONArray(); novelDetails.add("Language: Hindi"); novelDetails.add("Year of Publication: 1936"); novelDetails.add("Publisher: Lokmanya Press");
obj.put("Novel Details", novelDetails);
System.out.print(obj); } } |
輸出:
1 |
{"Novel Name":"Godaan","Novel Details":["Language: Hindi","Year of Publication: 1936","Publisher: Lokmanya Press"],"Author":"Munshi Premchand"} |
- JSON解析
開發人員要想解析JSON串,首先你得知道它的格式。下麵例子有助於你來理解這一點:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Iterator;
import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException;
public class JsonParseTest {
private static final String filePath = "//home//user//Documents//jsonDemoFile.json";
public static void main(String[] args) {
try { // read the json file FileReader reader = new FileReader(filePath); JSONParser jsonParser = new JSONParser(); JSONObject jsonObject = (JSONObject)jsonParser.parse(reader);
// get a number from the JSON object Long id = (Long) jsonObject.get("id"); System.out.println("The id is: " + id);
// get a String from the JSON object String type = (String) jsonObject.get("type"); System.out.println("The type is: " + type);
// get a String from the JSON object String name = (String) jsonObject.get("name"); System.out.println("The name is: " + name);
// get a number from the JSON object Double ppu = (Double) jsonObject.get("ppu"); System.out.println("The PPU is: " + ppu);
// get an array from the JSON object System.out.println("Batters:"); JSONArray batterArray= (JSONArray) jsonObject.get("batters"); Iterator i = batterArray.iterator(); // take each value from the json array separately while (i.hasNext()) { JSONObject innerObj = (JSONObject) i.next(); System.out.println("ID "+ innerObj.get("id") + " type " + innerObj.get("type")); } // get an array from the JSON object System.out.println("Topping:"); JSONArray toppingArray= (JSONArray) jsonObject.get("topping"); Iterator j = toppingArray.iterator(); // take each value from the json array separately while (j.hasNext()) { JSONObject innerObj = (JSONObject) j.next(); System.out.println("ID "+ innerObj.get("id") + " type " + innerObj.get("type")); }
} catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } catch (ParseException ex) { ex.printStackTrace(); } catch (NullPointerException ex) { ex.printStackTrace(); } } } |
jsonDemoFile.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "id": 0001, "type": "donut", "name": "Cake", "ppu": 0.55, "batters": [ { "id": 1001, "type": "Regular" }, { "id": 1002, "type": "Chocolate" }, { "id": 1003, "type": "Blueberry" }, { "id": 1004, "type": "Devil's Food" } ], "topping": [ { "id": 5001, "type": "None" }, { "id": 5002, "type": "Glazed" }, { "id": 5005, "type": "Sugar" }, { "id": 5007, "type": "Powdered Sugar" }, { "id": 5006, "type": "Chocolate with Sprinkles" }, { "id": 5003, "type": "Chocolate" }, { "id": 5004, "type": "Maple" } ] } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
The id is: 1 The type is: donut The name is: Cake The PPU is: 0.55 Batters: ID 1001 type Regular ID 1002 type Chocolate ID 1003 type Blueberry ID 1004 type Devil's Food Topping: ID 5001 type None ID 5002 type Glazed ID 5005 type Sugar ID 5007 type Powdered Sugar ID 5006 type Chocolate with Sprinkles ID 5003 type Chocolate ID 5004 type Maple |
- 簡單字元串查找
Java提供了一個庫函數叫做indexOf()。這個方法可以用在String對象上,它返回的是要查找的字元串所在的位置序號。如果查找不到則會返回-1。
- 列出目錄下的文件
你可以用下麵的代碼來列出目錄下的文件。這個程式會遍歷某個目錄下的所有子目錄及文件,並存儲到一個數組裡,然後通過遍曆數組來列出所有文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import java.io.*;
public class ListContents { public static void main(String[] args) { File file = new File("//home//user//Documents/"); String[] files = file.list();
System.out.println("Listing contents of " + file.getPath()); for(int i=0 ; i < files.length ; i++) { System.out.println(files[i]); } } } |
- 一個簡單的IO程式
Java提供了FileInputStream以及FileOutputStream類來進行文件的讀寫操作。FileInputStream的構造方法會接收輸入文件的路徑作為入參然後創建出一個文件的輸入流。同樣的,FileOutputStream的構造方法也會接收一個文件路徑作為入參然後創建出文件的輸出流。在處理完文件之後,一個很重要的操作就是要記得”close”掉這些流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import java.io.*;
public class myIODemo { public static void main(String args[]) throws IOException { FileInputStream in = null; FileOutputStream out = null;
try { in = new FileInputStream("//home//user//Documents//InputFile.txt"); out = new FileOutputStream("//home//user//Documents//OutputFile.txt");
int c; while((c = in.read()) != -1) { out.write(c); } } finally { if(in != null) { in.close(); } if(out != null) { out.close(); } } } } |
- 在Java中執行某個shell命令
Java提供了Runtime類來執行shell命令。由於這些是外部的命令,因此異常處理就顯得異常重要。在下麵的例子中,我們將通過一個簡單的例子來演示一下。我們會在shell命令行中打開一個pdf文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader;
public class ShellCommandExec {
public static void main(String[] args) { String gnomeOpenCommand = "gnome-open //home//user//Documents//MyDoc.pdf";
try { Runtime rt = Runtime.getRuntime(); Process processObj = rt.exec(gnomeOpenCommand);
InputStream stdin = processObj.getErrorStream(); InputStreamReader isr = new InputStreamReader(stdin); BufferedReader br = new BufferedReader(isr);
String myoutput = "";
while ((myoutput=br.readLine()) != null) { myoutput = myoutput+"\n"; } System.out.println(myoutput); } catch (Exception e) { e.printStackTrace(); } } } |
- 使用正則
正則表達式的結構摘錄如下(來源: Oracle官網)
字元
x |
字元x |
\ |
反斜杠 |
\0n |
8進位值為0n的字元(0<=n<=7) |
\0nn |
|
\0mnn |
8進位值為0mnn的字元(0 <= m <= 3, 0<=n<=7) |
\xhh |
16進位值為0xhh的字元 |
\uhhhh |
16進位值為0xhhhh的字元 |
\x{h…h} |
16進位值為0xh…h的字元(Character.MINCODEPOINT <= 0xh…h <= Character.MAXCODEPOINT) |
\t |
製表符(‘\u0009′) |
\n |
換行符(‘\u000A’) |
\r |
回車(‘\u000D’) |
\f |
分頁符(‘\u000C’) |
\a |
警告符(‘\u0007′) |
\e |
ESC(‘\u001B’) |
\cx |
ctrl+x |
字元分類