Java 8 - Map new method application

Java 8 Map新增的一些method可以取代我們以前在操作map時多餘的code,使用到對應的

method及Lambda語法可以使冗長的判斷程式碼縮短至一行就可以了!

在此將提到的是
這些方法所對應到的行為,裡面的實作已包含我們平常會用到的判斷式!

而官方的API說明還特別列出相關的pseudo code來解說這些method

若您需要使用到的話可以先從這裡來了解!  

PS. 在帶入相關的參數中,還可能會利用到Lambda語法! 請先了解一下這個部分囉!

這裡會從一個範例承上啟下的逐步說明下去,講解到這些method被使用到的話,會間接造

成什麼樣的結果,以此就可以判斷哪些method該用到實際應用的什麼地方上!



#putIfAbsent - 初始化map時可使用,所有key皆會被走訪

若key → value == null 才會被set value,因此您不需要再做map.containsKey是否成立或

map.get(...)是否等於null的判斷,這些判斷本身是避免造成已有value的key其值被覆寫調!

官方解說如下:
V v = map.get(key);
if (v == null)
    v = map.put(key, value);

return v;

範例程式碼如下:
static Map<String, Integer> idMapSalary;
public static void main(String[] args) {
   int basic_salary = 130;
   idMapSalary = new HashMap<String, Integer>();
   idMapSalary.put("1", 140);
   idMapSalary.put("2", 150);

   IntStream.range(1, 5)
            .mapToObj(i -> String.valueOf(i))
            .forEach(i -> { 
               idMapSalary.putIfAbsent(i, addHourSalaryForOddId(i, basic_salary));
               printResult(i, idMapSalary.get(i));
            });
}

public static void printResult(String id, Integer value){
    System.out.println("No."+id+" hour sallary="+value);
}
 
public static Integer addHourSalaryForOddId(String id, Integer salary){
    System.out.println(id);
    if(salary != null)
       return (Integer.parseInt(id) % 2 == 1) ? salary + 10 : salary;
    return salary;
 }

idMapSalary map利用IntStream進行走訪來put value,若是使用一般的put method,id 1, 2的

value將會被覆蓋掉,而利用putIfAbsent,第一個參數為key, 第二個參數為value,這個value

值可自行put至key id 3,4裡面

DEMO 如下
1
No.1 hour sallary=140
2
No.2 hour sallary=150
3
No.3 hour sallary=140
4
No.4 hour sallary=130

從執行結果可以得知,所有的key皆會被走訪,但是已存在有效value的key,其值並不會有

所變動! 而請注意! putIfAbsent的回傳值可能為value or null,null的例子為id 3,4在第一

次put後! 之後皆會像id 1, 2一樣回傳其value值!


#computeIfAbsent- 初始化map時可使用,value → null的key被走訪

類似於putIfAbsent的功用,只不過在參數2會建立lambda運算式,此時滿足的key其

value → null才成立,帶入這個key之後您可以進行相關的運算,如可能帶入這個key從別的

map取得對應的newValue等等!

官方解說如下:
if (map.get(key) == null) {
     V newValue = mappingFunction.apply(key);
     if (newValue != null)
         map.put(key, newValue);
}

如上所述,假設求得的newValue本身若是為null,那也沒有必要再做put的動作了

範例程式碼如下:
IntStream.range(1, 7)
         .mapToObj(i -> String.valueOf(i))
         .forEach(i -> {
             printResult(i, idMapSalary.computeIfAbsent(i,  key -> 
                addHourSalaryForOddId(key, basic_salary)));
          });

同樣利用IntStream來進行走訪,此時再增加兩個id 5,6做初始化給值動作

DEMO如下
No.1 hour sallary=140
No.2 hour sallary=150
No.3 hour sallary=140
No.4 hour sallary=130
5
No.5 hour sallary=140
6
No.6 hour sallary=130

從執行結果可以得知,key → value != null的id皆不會進入computeIfAbsent的第二個參數內

,因此執行addHourSalaryForOddId method的key只有5, 6。

請注意! computeIfAbsent的回傳值,不像putIfAbsent,id 5, 6在初始化其值之後,回傳的值

也就是這個newValue,而不是null


#computeIfPresent - 針對map value != null的key進行走訪

相對於computeIfAbsent,這個method所執行的是value != null的key來進行走訪,相對地value

 → null的key則不會有任何動作! 但是若經過走訪後的key得到的value = null的話,則這個key

將會被remove掉!(很合理!)

官方解說如下:
if (map.get(key) != null) {
     V oldValue = map.get(key);
     V newValue = remappingFunction.apply(key, oldValue);
     if (newValue != null)
         map.put(key, newValue);
     else
         map.remove(key);
}

如上所述,這個method需注意的是第二個參數的lambda expression,將會帶入

key and value(OldValue),由此看來這個method比較像是在執行map的運算!

範例程式碼如下:
IntStream.range(1, 9)
         .mapToObj(i -> String.valueOf(i))
         .forEach(i -> {
             printResult(i, idMapSalary.computeIfPresent(i, (key, val) -> 
                addHourSalaryForOddId(key, val)));
         });

同樣利用IntStream來進行走訪,此時再增加兩個id 7,8,只不過這兩個id將無法進行初始化!

DEMO如下
1
No.1 hour sallary=150
2
No.2 hour sallary=150
3
No.3 hour sallary=150
4
No.4 hour sallary=130
5
No.5 hour sallary=150
6
No.6 hour sallary=130
No.7 hour sallary=null
No.8 hour sallary=null

從執行結果可以得知,id 1 ~ 6將進行computeIfPresent內第二個參數lambda expression的運算!


#compute - 所有的key皆會進行走訪

不管您的map key → value是否為null,皆進行lambda expression處理,取得的newValue

只有是null時才不進行put動作,但key是存在的將會remove掉(很合理)

官方解說如下:
V oldValue = map.get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (oldValue != null ) {
    if (newValue != null)
       map.put(key, newValue);
    else
       map.remove(key);
} else {
    if (newValue != null)
       map.put(key, newValue);
    else
       return null;
}

看起來蠻複雜的,不過只要了解computeIfAbsent and computeIfPresent怎麼使用後,compute

就蠻好理解的!

範例程式碼如下:
IntStream.range(1, 9)
         .mapToObj(i -> String.valueOf(i))
         .forEach(i -> {
            printResult(i, idMapSalary.compute(i, (key, val) -> 
             addHourSalaryForOddId(key, val)));
         });

與computeIfPresent帶入的參數形式是一樣的!

DEMO如下
1
No.1 hour sallary=160
2
No.2 hour sallary=150
3
No.3 hour sallary=160
4
No.4 hour sallary=130
5
No.5 hour sallary=160
6
No.6 hour sallary=130
7
No.7 hour sallary=null
8
No.8 hour sallary=null

從執行結果可以得知,所有的id皆會進行走訪,由於method addHourSalaryForOddId無法針

對oldValue → null的值進行處理,因此使得id 7,8的newValue還是null


#merge - 初始化map時可使用,所有key皆會被走訪

這個method比較特別的地方是確保key → value = null也可以帶入預設值,但是只有針對

key → value != null的value會進入lambda expression的走訪!

官方解說如下:
V oldValue = map.get(key);
V newValue = (oldValue == null) ? value :
              remappingFunction.apply(oldValue, value);
if (newValue == null)
     map.remove(key);
else
     map.put(key, newValue);

範例程式碼如下:
IntStream.range(1, 9)
         .mapToObj(i -> String.valueOf(i))
         .forEach(i -> {
            printResult(i, idMapSalary.merge(i, basic_salary, (oldValue, Value) -> 
                            (oldValue <= Value) ? oldValue + 10 : Value));
         });

merge method的參數新增為三個,帶入了key, value, lambda expression

特別的是這個value的作用因為兩種情況而有不一樣的走向

1. map.get的值(oldValue)為null時,直接map.put(key, value)
2. map.get的值(oldValue)不等於null時,就會進入lambda expression的運算

DEMO如下
No.1 hour sallary=130
No.2 hour sallary=130
No.3 hour sallary=130
No.4 hour sallary=140
No.5 hour sallary=130
No.6 hour sallary=140
No.7 hour sallary=130
No.8 hour sallary=130

從執行結果可以得知,id 7,8只會帶入預設值130

總結

Map提供的新方法雖然可以讓您少了很多邏輯判斷上對於map的處理,不過請注意Map內

對應的value若是Integer的型態時,有可能會是null的情況,連帶的在lambda expression呼叫

的method參數可能需要宣告為Integer而並非是int型態!

以compute來看,因為key → value = null也會進入lambda expression

此時將帶入的value = null,若呼叫前沒先過濾掉value == null不處理,帶入後value又進行

運算會有NullPointerException問題發生!

留言