標籤:msu 對象 reduce 工程 now() public 安全執行緒 final des
轉眼淘系應用升級JDK8已經幾個月過去了,Lambdas運算式和Streams APIs的確給同學們帶來了編程效率和代碼可讀性上的提升,代碼變得更加簡潔直接,更加符合人的思維(看來程式設計語言的發展也是本著“以人為本”的思路)。ATA上講這兩個新特性的文章已經很多了, 大魚大肉大家吃的差不多,得常常家常小菜吧,下面總結一下常用的Java 8中Lambdas和Streams之外的一些新特性。
1. Default Method
Java 單繼承的特性決定了一個類只能有1個父類,如果有通用的方法實現就只能寫在父類裡,如果是介面就不能有實現,偏偏Java 8中的stream方法就遇到了這個問題。原來沒有吧,在Collection介面裡加個非default的stream方法,必須在所有實現介面的類中都要實現這個方法(難道他們真的把XXXList、XXXSet都改了?)
肯(bu)定(yao)沒(sha)有(le),Oracle猿們開了個後門(自己的東西就是方便,自己想改就改),他們用default method解決了這個問題。在Collection介面中實現了一個default method - stream, 這樣所有其他的XXXList都不用改了,還可以直接使用這個方法。當然,只要實作類別中不重寫這個方法,執行個體調用的就是介面中的實現。
可以看到java.util.Collection中確實加了3個default方法。
default Spliterator<E> spliterator() { return Spliterators.spliterator(this, 0);}default Stream<E> stream() { return StreamSupport.stream(spliterator(), false);}default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true);}
使用上其實就是default關鍵字,在interface的方法上加上default就會有2個作用:a. 介面中必須實現這個方法;b. 子類中可以不實現這個方法了。
另外,還可以在interface中加入static方法,配合default使用,這樣直接靜態方式使用介面的default方法,減少了寫一些另外的util/helper類。
// 介面類public interface BaseJava { default void sayHi(){ System.out.println("Hi default method"); } public void goodbye();}// 實作類別public class ChildJava implements BaseJava{ @Override public void goodbye() { System.out.println("必須實現的方法"); } public static void main(String...args){ ChildJava cj = new ChildJava(); cj.sayHi(); // default method }}
2. Optional
業務應用的對象往往比較大,對象裡面有對象,對象裡面還有對象...在處理的時候,總是得判斷是否為null,這無形增加了程式的判斷深度(如果不加判斷,就是NullPointerException了,關鍵是一旦出了異常,排查起來也是時間)
如果是這種普通的非空判斷(用isPresent()代替!=null),其實和以前差不多,好像沒有怎麼方便(個人感覺甚至麻煩了,實際上相當於把非空判斷封裝了一個Helper靜態方法)。
Optional<String> str = Optional.ofNullable("some returned value"); // 可能是空System.out.println(str.isPresent()?str.get():"default value"); // Java 8System.out.println(str.orElse("default value")); // Java 8System.out.println(str!=null?str:"default value"); // Java 7// 看看Java原始碼public boolean isPresent() { return value != null;}public T orElse(T other) { return value != null ? value : other;}
但是,在對象比較複雜的情況下,就顯得比較高效了,尤其是配合stream (reduce max min)和 lambdas表達使用。看到傳回值是Optional的情況下,都會下意識去判斷非空,這和之前相比,可以減少程式因為NullPointerException而出現的功能缺陷,提高開發效率(我記得google的一個三方包裡早就有Optional這個東東了)。
3. 字串API
Scenario 1. 業務應用處理最多的東西莫過於List和String,常見的操作就是給你一個List,處理一下串成一個String,或者給你一個String(用delimiter分隔的),處理一下變成一個List(木辦法,前端要String ,後端要List,夾在中間只能不停地ForEach了)
這要擱以前(Java 7),某猿會這樣寫:
// 1 給前端拼一個逗號分隔的商品id列表的字串List<String> itemIdList = ...; // 從IC擷取的熱乎乎的ListStringBuffer sb = new StringBuffer(); // 也可以用StringBuilder...for(String itemId: itemIdList){ sb.append(itemId).append(","); // TODO BUG 末尾還有一個 ,}return sb.toString(); // 2 前端傳來的都好分隔的商品id列表字串,咱轉成List給後端String str ="1000,1001,1002,1003";String[] arr=str.split(",");List<String> list = Arrays.asList(arr);
這樣寫當然沒問題,但是在商務邏輯處理中,老是來這麼一下,一則影響代碼美觀(本來類像一棵直挺挺的樹,加了幾個這個,像是樹瘤一樣,100行代碼中有50行是for迴圈...),二則容易打斷了主要商務邏輯的思路(轉而處理for迴圈裡面的內容,當心最後一個 , )。
使用Java 8以後,情況會好很多,一兩行代碼搞定:
// 1 給前端拼一個逗號分隔的商品id列表的字串List<String> itemIdList = ...; return String.join(",", itemIdList);// 當然,如果擷取的是Object,就需要配合streams來完成join了List<ItemDO> itemDOs = ...String names = itemDOs.stream().map(ItemDO::getItemId).collect(Collectors.joining(","));// java 8 源碼 String.join的實現public static String join(CharSequence delimiter, CharSequence... elements) { Objects.requireNonNull(delimiter); Objects.requireNonNull(elements); // Number of elements not likely worth Arrays.stream overhead. StringJoiner joiner = new StringJoiner(delimiter); for (CharSequence cs: elements) { joiner.add(cs); } return joiner.toString();}
4. 日期和時間API
Scenario 2. 業務應用總是免不了和日期時間打交道,而且,與多個系統對接後,要在日期、時間、日期時間、字串等來回倒騰,前端要字串,資料庫存的datetime, 兄弟團隊的二方包居然只要日期部分的String等等,當然還有日期的操作,向前多少天,向後多少天
Java 7當中,我們最長使用的莫過於Calendar了,因為它簡單輕便,而且支援日期操作。此外,配合SimpleDateFormat, 就可以把任何String轉換成日期,然後利用Calendar操作,最後再使用另一個SimpleDateFormat寫成前端需要的格式。通常情況下,這樣寫:
SimpleDateFormat sdf =new SimpleDateFormat("yyyy/MM/dd");SimpleDateFormat sdf2 =new SimpleDateFormat("yyyy-MM-dd");Date date = sdf.parse("2016/09/10");Calendar cal = Calendar.getInstance();cal.setTime(date);cal.add(Calendar.DAY_OF_MONTH, 10);Date tenDaysLater = cal.getTime();System.out.println(sdf2.format(tenDaysLater));
Java 8中引入了 LocalDate, LocalTime, LocalDateTime 分別表示日期、時間、日期和時間,簡單直接,而且就地提供了安全執行緒的日期時間操作,總體來說比7要方便很多了。上面的代碼,在8裡可以這樣寫:
LocalDate today = LocalDate.now();System.out.println("today is " + today);LocalDate specifiedDay = LocalDate.of(2016, Month.JUNE, 1);System.out.println("specific date is " + specifiedDay);LocalDate fifthIn2016 = LocalDate.ofYearDay(2016, 5);System.out.println("5th day of 2016 is " + fifthIn2016);// 預設使用 DateTimeFormatter.ISO_LOCAL_DATELocalDate parsedDay = LocalDate.parse("2016-08-31"); System.out.println("parsed day is " + parsedDay);// 內建 yyyyMMddDateTimeFormatter basicIsoDate = DateTimeFormatter.BASIC_ISO_DATE; // 內建 yyyy-MM-ddDateTimeFormatter isoLocalDate = DateTimeFormatter.ISO_LOCAL_DATE; // 自訂 yyyy:MM:ddDateTimeFormatter customDate = DateTimeFormatter.ofPattern("yyyy:MM:dd"); // 轉換LocalDate formattedDay = LocalDate.parse("20160821", basicIsoDate);System.out.println("isoLocalDate day is " + formattedDay.format(isoLocalDate));System.out.println("customDate day is " + formattedDay.format(customDate));// 時間操作LocalTime now = LocalTime.now();LocalTime later = now.plus(5, HOURS);// 日期操作LocalDate today = LocalDate.now();LocalDate thirtyDaysLater = today.plusDays(30);LocalDate afterOneMonth = today.plusMonths(1);LocalDate beforeOneMonth = today.minusMonths(1);
相對於Calendar操作來說,LocalDate每次都返回一個全新的對象(LocalDate/LocalTime都是final class),這樣做在多線程環境下更安全。
5. 總結
Java 8從應用升級到全面使用,大概還有比較長的路要走,畢竟很多已有的代碼不會為了嘗試新文法和特性而進行更新。在新的工程和代碼中,嘗試新的文法和特性,會帶來更好的體驗和效率。另外,功能等價的代碼,除了文法上的不同,Java 7和Java 8在效能上有什麼不同麼,Java 8真的減少了資源提高了效率嗎?這個問題還得在今後的使用中進一步探討。
6. 參考資料
Joda-Time
Java 8 新特性
Java 8 基礎實踐
Java 8 不止是Lambdas和Streams