在我看來 lambda 表達式就是簡化了之前的一大堆繁瑣的操做,讓咱們代碼看起來更加簡潔,讓之前五六行甚至更多行的代碼只須要兩三行就能解決,可是對於 Java 初學者可能不是特別友好,可能一會兒理解不過來該代碼想表達什麼。dom
lambda 表達式是一段能夠傳遞的代碼,所以它能夠被執行一次或屢次ide
咱們先來看看老版本的排序字符串的辦法,這裏咱們不按照字典序,而按照字符串的大小來排序函數
Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(),o2.length()); } }; List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,comparator);// [a, aa, aaa, aaaa, aaaaa]
老版本的排序咱們會先建立一個自定義比較器,而後按照比較器的規則進行排序。
如今咱們來看看 lambda 表達式如何來實現的
List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,(String o1,String o2)->{ return Integer.compare(o1.length(),o2.length()); });//[a, aa, aaa, aaaa, aaaaa]
能夠看到,代碼濃縮了很多,可是可讀性沒有原來好,原來須要先建立一個比較器而後將比較器傳到 Collections 工具類進行排序,典型的面向對象編程,可是 lambda 表達式確是將代碼傳進去而後直接進行比較。若是你覺得這樣就是最簡便的,那你就錯了,有更簡便的。
List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,(String o1,String o2) ->Integer.compare(o1.length(),o2.length()));
若是返回值只有一行,能夠省略大括號和 return 關鍵字。
List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa"); Collections.sort(list,(o1,o2)->Integer.compare(o1.length(),o2.length()));
若是是帶泛型的容器,參數的類型能夠省略,JVM 會本身進行上下文判斷出類型。
咱們能夠看到從原來的那麼多行代碼濃縮成了一行,看着清爽了不少,可是可讀性卻沒有原來那麼友好了。
訪問局部變量
能夠在 lambda 表達式中訪問外部的局部變量
int number = 10; Converter<String,Integer> converter = num->Integer.valueOf(num + number); System.out.println(converter.convert("123"));//12310
在匿名內部類中外部的局部變量必須聲明爲 final。而咱們這裏不須要。
可是要注意的是這的 number 不能被後面的代碼修改,不然編譯不經過,也就是具備隱性的 final 語義。
訪問 字段和靜態變量
咱們對 lambda 表達式中的實例字段和靜態字段變量都有讀寫訪問權限。
class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
沒法在 lambda 表達式中訪問默認接口方法。
在 Java 中有許多已有的接口都選哦封裝成代碼塊,好比 Runnable 或者 Comparator 。 lambda 表達式與這些接口是像後兼容的。
對於只包含一個抽象方法的接口,可是能夠有多個非抽象方法,(非抽象方法也是 java 8 新特性,咱們後面會講到),咱們能夠經過 lambda 表達式來建立該接口的對象。這種接口被稱爲函數式接口。
Java 8 新增長了一種特殊的註解 @FunctionalInterface,該接口會自動判斷你的接口中是否只有一個抽象方法,若是多於一個抽象方法就會報錯。
如今咱們來自定義一個函數式接口:
@FunctionalInterface interface Converter<F,T>{ T convert(F num); }
// 將數字形式的字符串轉化成整型 Converter<String,Integer> converter = (num -> Integer.valueOf(num)); System.out.println(converter.convert("123").getClass());//class java.lang.Integer
如今來解釋下該代碼,在該代碼中咱們的函數式接口中定義了一個方法,該方法可以實現傳入一個 F 類型的參數,咱們能夠對這個類型的參數進行各類處理,最後返回一個 T 類型的結果。在這裏我只是簡單的將傳進來的 string 轉成了 integer。這裏的 F 與 T 都是泛型類型,能夠爲任何實體類。
java 8 幫咱們實現了不少函數式接口,大部分都不須要咱們本身寫,這些接口在 java.util.function 包 裏,能夠自行進行查閱。
上面的代碼能夠寫的更加簡單:
Converter<String,Integer> converter = Integer::valueOf; System.out.println(converter.convert("123").getClass());//class java.lang.Integer
java 8 能夠經過 ** : : **來傳遞方法或者構造函數的引用。上面的演示了若是引用靜態方法,引用對象方法也相差不大,只是須要聲明一個對象:
class Demo{ public Integer demo(String num){ return Integer.valueOf(num); } } public class Main { public static void main(String[] args) { Demo demo = new Demo(); Converter<String,Integer> converter = demo::demo; System.out.println(converter.convert("123").getClass()); //class java.lang.Integer } }
Predicate 接口是隻有一個參數的返回布爾類型值的 斷言型 接口。該接口包含多種默認方法來將 Predicate 組合成其餘複雜的邏輯(好比:與,或,非)
@FunctionalInterface public interface Predicate<T> { // 該方法是接受一個傳入類型,返回一個布爾值.此方法應用於判斷. boolean test(T t); .... }
Function 接口接受一個參數並生成結果。默認方法可用於將多個函數連接在一塊兒(compose, andThen)
@FunctionalInterface public interface Function<T, R> { //將Function對象應用到輸入的參數上,而後返回計算結果。 R apply(T t); ... }
Supplier 接口產生給定泛型類型的結果。 與 Function 接口不一樣,Supplier 接口不接受參數。
Consumer 接口表示要對單個輸入參數執行的操做。
Comparator 是老Java中的經典接口, Java 8在此之上添加了多種默認方法
前面已經有寫地方提到了接口的默認方法,這裏對其作下介紹。接口的默認方法也是 java 8 新出的功能。可以經過使用 default
關鍵字向接口添加非抽象方法實現。
interface Formula{ double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
Formula 接口中除了抽象方法計算接口公式還定義了默認方法 sqrt
。 實現該接口的類只須要實現抽象方法 calculate
。 默認方法sqrt
能夠直接使用。固然你也能夠直接經過接口建立對象,而後實現接口中的默認方法就能夠了,咱們經過代碼演示一下這種方式。
public class Main { public static void main(String[] args) { // TODO 經過匿名內部類方式訪問接口 Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; System.out.println(formula.calculate(100)); // 100.0 System.out.println(formula.sqrt(16)); // 4.0 } }
formula 是做爲匿名對象實現的。該代碼很是容易理解,6行代碼實現了計算 sqrt(a * 100)
。
不論是抽象類仍是接口,均可以經過匿名內部類的方式訪問。不能經過抽象類或者接口直接建立對象。對於上面經過匿名內部類方式訪問接口,咱們能夠這樣理解:一個內部類實現了接口裏的抽象方法而且返回一個內部類對象,以後咱們讓接口的引用來指向這個對象。
Stream 是在 java.util 下的。Stream 表示能應用在一組元素上一次執行的操做序列。Stream 操做分爲中間操做或者最終操做兩種,最終操做返回一特定類型的計算結果,而中間操做返回 Stream 自己,這樣咱們就能夠將多個操做依次串起來。Stream 的建立須要指定一個數據源,好比java.util.Collection
的子類:List 或者 Set。Map 不支持。Stream 的操做能夠串行執行或者並行執行。
當咱們使用 Stream 時,咱們將經過三個階段來創建一個操做流水線。
在這以後 stream 就不會再被使用了。
經過 Java 8 在 Collection 接口中新提娜佳的 stram 方法,能夠將任何集合轉化爲一個 Stream。若是咱們面對的是一個數組,也能夠用靜態的 Stream.of 方法將其轉化爲一個 Stream。
@Test public void test1(){ List<String> stringList = new ArrayList<>(); stringList.add("ddd2"); stringList.add("aaa2"); stringList.add("bbb1"); stringList.add("aaa1"); stringList.add("bbb3"); stringList.add("ccc"); stringList.add("bbb2"); stringList.add("ddd1"); Stream<String> stream = stringList.stream(); //Stream<String> stringStream = stringList.parallelStream(); }
咱們能夠經過 Collection.stream() 或者 Collection.parallelStream() 來建立一個Stream。
過濾經過一個predicate接口來過濾並只保留符合條件的元素,該操做屬於中間操做,因此咱們能夠在過濾後的結果來應用其餘Stream操做。(好比forEach)。forEach須要一個函數來對過濾後的元素依次執行。forEach是一個最終操做,因此咱們不能在forEach以後來執行其餘Stream操做。
stringList .stream() .filter(s->s.startsWith("a")) .forEach(System.out::println);
forEach 是爲 Lambda 而設計的,保持了最緊湊的風格。並且 Lambda 表達式自己是能夠重用的,很是方便。
排序是一箇中間操做,返回的是排序好的 Stream 。若是咱們不指定一個自定義的 Comparator 則會使用默認排序。
stringList .stream() .sorted((o1,o2)->Integer.compare(o1.length(),o2.length())) .forEach(System.out::println);
須要注意的是,排序只建立了一個排列好後的Stream,而不會影響原有的數據源,排序以後原數據stringCollection是不會被修改的。
中間操做 map 會將元素根據指定的 Function 接口來依次將元素轉成另外的對象。
map返回的 Stream 類型是根據咱們 map 傳遞進去的函數的返回值決定的。
stringList .stream() .map(String::toUpperCase) .sorted((o1,o2)->Integer.compare(o1.length(),o2.length())) .forEach(System.out::println);
Stream提供了多種匹配操做,容許檢測指定的Predicate是否匹配整個Stream。全部的匹配操做都是 最終操做 ,並返回一個 boolean 類型的值。
boolean anyStartsWithA = stringList .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringList .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringList .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
計數是一個 最終操做,返回Stream中元素的個數,返回值類型是 long。
long count = stringList .stream() .map(String::toUpperCase) .sorted((o1, o2) -> Integer.compare(o1.length(), o2.length())) .count(); System.out.println(count);
Stream有串行和並行兩種,串行Stream上的操做是在一個線程中依次完成,而並行Stream則是在多個線程上同時執行。
下面使用串行流和並行流爲一個大容器進行排序,比較二者性能。
串行排序
@Test public void test1(){ int max = 1000000; List<String> list = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); list.add(uuid.toString()); } long startTime = System.nanoTime(); long count = list.stream().sorted().count(); System.out.println(count); long endTime = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(endTime-startTime); System.out.println(millis); //1000000 //877 }
並行排序
@Test public void test2(){ int max = 1000000; List<String> list = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); list.add(uuid.toString()); } long startTime = System.nanoTime(); long count = list.parallelStream().sorted().count(); System.out.println(count); long endTime = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(endTime-startTime); System.out.println(millis); } //1000000 //512
能夠明顯看出在大數據量的狀況下並行排序比串行來的快。可是小數據量的話倒是串行排序比較快,緣由是並行須要涉及到上下文切換。
Collector 是專門用來做爲 Stream 的 collect 方法的參數的。而 Collectors 是做爲生產具體 Collector 的工具類。
toList():將流構形成 list
List<String> collect = list.stream().collect(Collectors.toList());
toSet():將流構形成set
Set<String> set = list.stream().collect(Collectors.toSet()); Set<String> treeSet = list.stream().collect(Collectors.toCollection(TreeSet::new));
joining():拼接流中全部字符串
String collect = list.stream().collect(Collectors.joining()); String collect = list.stream().collect(Collectors.joining(";"));
toMap():將流轉成 map
Map<String, String> collect = list .stream() .collect(Collectors .toMap(e -> "key" + e, e -> "v" + e,(a,b)->b,HashMap::new));
上面的 e -> "key" + e 定義了 map 的 key 的生成規則,e -> "v" + e 定義了 map 的 value 的生成規則,(a,b)->b 表示衝突的解決方案,若是鍵 a 和 鍵 b 衝突了則該鍵鍵值取 b 的,HashMap::new 定義了生成的 map 爲 hashmap。
Map 雖然不支持 Stream 可是咱們能夠經過 map.keySet().stream()
,map.values().stream()
和map.entrySet().stream()
來經過過去鍵、值的集合再轉換成流進行處理。
Java 8 中 map 新方法:
putIfAbsent(key, value)//有則不加,無則加
map.forEach((key, value) -> System.out.println(value));//循環打印
map.computeIfPresent(3, (num, val) -> val + num);//當key 存在則執行後面方法
map.computeIfAbsent(23, num -> "val" + num);//當key 不存在時執行後面方法
map.getOrDefault(42, 1);//有則獲取,無則置 1
map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); //若是鍵名不存在則插入,不然則對原鍵對應的值作合併操做並從新插入到map中。
LocalTime(本地時間)
LocalTime 定義了一個沒有時區信息的時間
方法 | 描述 |
---|---|
now,of | 這些靜態方法能夠根據當前時間或指定的年、月、日來建立一個 LocalTime 對象 |
getHour,getMinute,getSecond,getNano | 得到當前 LocalTime 的小時、分鐘、秒鐘及微妙值 |
isBefore,isAfter | 比較兩個LocalTime |
LocalDate(本地日期)
LocalDate 表示了一個確切的日期,好比 2014-03-11。該對象值是不可變的,用起來和LocalTime基本一致。
方法 | 描述 |
---|---|
now,of | 這些靜態方法能夠根據當前時間或指定的年、月、日來建立一個LocalDate對象 |
getDayOfMonth | 獲取月份天數(在 1~ 31 之間) |
getDayOfYear | 獲取年份天數(在1~366之間) |
getMonth,getMonthValue | 得到月份,或者爲一個 Month 枚舉的值,或者是 1 ~ 12 之間的一個數字 |
getYear | 獲取年份 |
isBefore,isAfter | 比較兩個LocalDate |
上面這些方法是比較經常使用的,其他的能夠自行查閱。