關於Java的十個編碼小技巧

2020-09-22 12:00:43

相信每個人都喜歡看簡潔易懂的程式碼,程式碼可讀性也是衡量程式碼品質的重要標準之一,而本文則通過十個具體的程式設計小技巧(部分技巧並不侷限於Java),希望能夠幫助到你。

1. 使用三元表示式

考慮以下程式碼:

public boolean isOdd(int num) {
    if (num % 2 == 1) {
        return true;
    } else {
        return false;
    }
}

我們可能會經常見到類似上面的程式碼,只需要一次簡單的if ... else ...判斷,而判斷所得到的內容也很簡單,在這種時候,我們就可以使用三元表示式來簡化我們的程式碼,就像下面這樣:

public boolean isOdd(int num) {
	return num % 2 == 1 ? true : false;
}

可以發現,通過使用... ? ... : ...三元表示式,我們能夠寫出更加簡單的程式碼,當然這個程式碼仍然不夠簡潔,在接下來第二點會進行討論,這裡先討論三元表示式。雖然三元表示式可以簡化我們的程式碼,在很多時候也能夠簡化我們的程式碼,但是當判斷的情況過多並且語句較長的時候,我們就不應該使用三元表示式了,就像下面這種情況,我們就不應該採用三元表示式:

public int getMaxDays(int year, int month) {
    // 當條件過多時, 使用三元表示就無法體現發揮簡潔的優勢了,我們應該考慮其它的方法
    return month == 2 ? (isLeapYear(year) ? 29 : 28) :
            (month == 4 || month == 6 || month == 9 || month == 11) ? 30 : 31;
}

private boolean isLeapYear(int year) {
    return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
}

2. 簡化布林條件表示式

再次考慮1中的程式碼:

public boolean isOdd(int num) {
	return num % 2 == 1 ? true : false;
}

由於我們返回的結果是布林值,所以在這種情況下,我們就可以直接返回布林表示式即可,而不需要再進行條件判斷,讓程式碼不夠簡潔:

public boolean isOdd(int num) {
	return num % 2 == 1;
}

3. 使用衛語句

在1中我們以一個反例講訴了三元表示不用改被亂用,這裡我們就先通過if語句,來進行改寫,寫出第一版程式碼:

public int getMaxDays(int year, int month) {
    int result;
    if (month == 2) {
        result = isLeapYear(year) ? 29 : 28;
    } else if (month == 4 || month == 6 || month == 9 || month == 11) {
        result = 30;
    } else {
        result = 31;
    }
    return result;
}

private boolean isLeapYear(int year) {
    return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) ;
}

我們為了實現單入口單出口原則,而讓多次判斷都有依賴,如果每個判斷條件記憶體在大量的語句的時候,更會顯示出這種編寫方式的弊端,因此對於這種情況,我們通常會編寫單入口多出口的程式碼,讓簡便情況儘快得到判斷,每個判斷語句之間不必耦合:

public int getMaxDays(int year, int month) {
    if (month == 2) {
        return isLeapYear(year) ? 29 : 28;
    } 
    if (month == 4 || month == 6 || month == 9 || month == 11) {
        return  30;
    }
    return 31;
}

private boolean isLeapYear(int year) {
    return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
}

4. 使用arraycopy拷貝陣列

我們有時可能因為不能在原陣列上進行修改,需要對原陣列進行一份拷貝,然後寫出類似下面這樣的程式碼:

int[] arr = {1, 2, 3, 4, 5};
int[] temp = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
    temp[i] = arr[i];
}

不過我們可以通過使用系統自帶的arraycopy函數,讓程式碼更易讀,效率也更高:

int[] arr = {1, 2, 3, 4, 5};
int[] temp = new int[arr.length];
System.arraycopy(arr, 0, temp, 0, arr.length);

5. 使用try ... with ...進行資源管理

當使用io流時,我們由於需要對資源進行管理,需要寫出類似下面的程式碼,當多個資源需要管理的時候,不僅難以管理,我們還可能經常會忘記對資源的關閉:

FileInputStream fis = null;
try {
	fis = new FileInputStream(new File(""));
	fis.read();
} catch (Exception e) {
	e.printStackTrace();
} finally {
	try {
		fis.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

但是通過使用try ... with ...就可以寫出下面這樣簡便的程式碼,我們不需要再擔心資源的管理問題:

try (FileInputStream fis = new FileInputStream(new File(""))) {
	fis.read();
} catch (Exception e) {
	e.printStackTrace();
}

6. 位運算小技巧

相信很多人都知道,位運算在很多情況下都能表現出更好地效能,在某些情況下也能夠簡化程式碼,這裡就講兩個小例子:

  1. 使用位移代替乘除法:

    a << n // <=> a * 2^n
    a >> n // <=> a / 2^n
    
  2. 使用位運算避免二分法中的加法溢位:

    相信很多人都知道在二分查詢中,以下程式碼可能因為加法運算的溢位,導致無法得到正確的結果:

    int mid = (left + right) / 2;
    

    如果使用下面這種方法就可以避免:

    int mid = left + (right - left) / 2;
    

    但是通過使用無符號位移,我們就可以寫出下面這樣簡潔的程式碼,而且也可以避免溢位:

    int mid = (left + right) >>> 1;
    
  3. 判斷兩號是否同號

    如果不使用位運算,我們需要寫出下面這樣的程式碼:

    (a >= 0 && b >= 0) || (a < 0 && b < 0)
    

    但是如果使用^的特性,我們就可以寫出下面這樣簡潔的程式碼:

    (a ^ b) >= 0
    

7. 分割字串

很多時候,我們為了進行列印偵錯,都需要對陣列的資料或者字串進行分割列印,例如將[1, 2, 3]或者"123"列印為1, 2, 3的格式,按照傳統的方式,我們需要寫出下面這樣的程式碼:

int[] arr = {1, 2, 3};
StringBuilder sb = new StringBuilder(String.valueOf(arr[0]));
for (int i = 1; i < arr.length; i++) {
    sb.append(", ").append(arr[i]);
}
// output: 1, 2, 3
System.out.println(sb);

String str = "123";
String[] split = Pattern.compile("").split(str);
StringBuilder sb = new StringBuilder(split[0]);
for (int i = 1; i < split.length; i++) {
    sb.append(", ").append(split[i]);
}
// output: 1, 2, 3
System.out.println(sb);

但是通過使用Stream的特性,我們就能寫出下面這樣更加方便易讀的程式碼(為了程式碼的可讀性,我們通常會將鏈式呼叫拆成多行):

int[] arr = {1, 2, 3};
// output: 1, 2, 3
System.out.println(
        IntStream.of(arr)
                 .mapToObj(String::valueOf)
                 .collect(Collectors.joining(", "))
);

String str = "123";
// output: 1, 2, 3
System.out.println(
        Pattern.compile("")
               .splitAsStream(str)
               .collect(Collectors.joining(", "))
);

8. 使用系統方法列印陣列

我們通常需要列印陣列進行偵錯,正如7中所說的那樣,但是我們通常只要能夠得出陣列的資料即可,而不在乎其形式,但是如果直接列印陣列,只會得到一個記憶體地址,不過我們其實可以通過呼叫Arrays.toString(arr),很容易就實現我們的需求:

int[] arr = {1, 2, 3};
// output: [1, 2, 3]
System.out.println(Arrays.toString(arr));

9. Stream實現計數器

有時,我們需要對陣列或者集合中的資料進行統計其次數,我們會寫出如下所示的程式碼:

int[] arr = {1, 2, 3, 4, 5, 6};
Map<Integer, Integer> map = new HashMap<>();
for (int val : arr) {
    map.put(val, map.getOrDefault(val, 0) + 1);
}

如果利用Stream,我們就能夠更加專注於我們的業務邏輯,也可加易讀:

int[] arr = {1, 2, 3, 4, 5, 6};
Map<Integer, Integer> map = IntStream.of(arr)
                                     .boxed()
                                     .collect(Collectors.toMap(k -> k, k -> 1, Integer::sum));

10. 使用Arrays.asList(arr)將物件陣列轉換為集合

有時我們需要為了將陣列轉換為集合,然後寫出類似下面的程式碼:

String[] strs = new String[10];
List<String> list = new ArrayList<>();
for (String str : strs) {
    list.add(str);
}

但是利用Array.asList(arr)就可以寫出下面這樣簡潔的程式碼:

String[] strs = new String[10];
// Array.asList(arr)生成的結果集合無法進行資料的修改,因此需要使用 new ArrayList<>();
List<String> list = new ArrayList<>(Arrays.asList(strs));

或者使用Stream這樣進行轉換:

String[] strs = new String[10];
List<String> list = Stream.of(strs).collect(Collectors.toList());

以上就是關於Java的一些程式設計小技巧,希望能夠對你有些幫助。