JAVA8基礎與實戰(上)
學習網址:https://www.bilibili.com/video/BV1w4411e7T8?p=3
https://gitee.com/LiuDaiHua/jdk8
JAVAEE8在2017年9月正式釋出,這是自2013年6月,Java企業版的首次更新。JAVAEE8提供了一些新的API,提供了對HTTP/2的新支援。
在JDK1.8以前的版本中,定義一個介面時,所有的方法必須是抽象方法,這是Java語法規定的。但是在JDK1.8中定義一個介面時,在滿足特定的前提下,可以有方法的具體實現。這樣一個介面中可以有抽象方法,也可以有具體方法,這跟JDK1.8以前的介面比,明顯介面的功能變得強大了。
介面中定義具體的方法實現是有限制的,它不能像我們在一個普通類那樣隨便定義方法實現,它只能定義default和static型別的方法。在呼叫的時候,被default修飾的預設方法需要通過實現了該介面的類去呼叫,被static修飾的靜態方法可以通過介面名直接呼叫。
1 package interfact; 2 3 /** 4 * 介面中方法預設是public 5 * 介面中的方法只能使用:default/static/abstract 三者之一修飾,其中使用abstract修飾時可以省略abstract關鍵字 6 */ 7 public interface MyInterface {8 String HELLO = "hello"; 9 10 /** 11 * default是jdk8引入的關鍵字、只能用於介面中方法的修飾 12 * 被default修飾的方法必須有方法體 13 */ 14 default void canDoAny() { 15 System.out.println("I am default method! " + HELLO); 16 } 17 18 /** 19 * default是jdk8引入的關鍵字、只能用於介面中方法的修飾 20 * 在介面中被static修飾的方法必須有方法體 21 */ 22 static void canUseByInterface() { 23 System.out.println("I am static method! " + HELLO); 24 } 25 26 /** 27 * 抽象方法可以省略abstract關鍵字 28 */ 29 void abs(); 30 31 32 }
package interfact; public class MyInterfaceImpl implements MyInterface { @Override public void abs() { System.out.println("I implements MyInterface and overrid abs method"); } @Override public void canDoAny() { System.out.println("I implements MyInterface and overrid canDoAny method"); } }
1 package interfact; 2 3 public class Main { 4 public static void main(String[] args) { 5 6 // 介面中的default/abstract方法只能由介面的實現類呼叫 7 new MyInterfaceImpl().abs(); 8 new MyInterfaceImpl().canDoAny(); 9 10 // 介面可以直接呼叫其內靜態方法 11 MyInterface.canUseByInterface(); 12 } 13 }
I implements MyInterface and overrid abs method I implements MyInterface and overrid canDoAny method I am static method! hello
default是非常巧妙的設計,預設方法的出現一方面保證的java8新特性(lambda表示式、函式式介面)的加入,同時它又確保了jdk8與老版本程式碼的完全相容。如果你又想在介面的層面上增加一個實現,又想老版本介面相容,jdk的設計者也是思考了很長時間,最後才提出了在接口裡面採用default方式去滿足這種需求。
舉個例子:例如在jdk1.8中,對Iterable介面又添加了forEach方法,他就是一個default方法,只要實現了Iterable介面的類,可以直接呼叫接口裡的forEach進行元素的遍歷,而不需要自己在手寫實現。
1 public interface Iterable<T> { 2 ... // 略 3 4 /** 5 * Performs the given action for each element of the {@code Iterable} 6 * until all elements have been processed or the action throws an 7 * exception. Unless otherwise specified by the implementing class, 8 * actions are performed in the order of iteration (if an iteration order 9 * is specified). Exceptions thrown by the action are relayed to the 10 * caller. 11 * 12 * @implSpec 13 * <p>The default implementation behaves as if: 14 * <pre>{@code 15 * for (T t : this) 16 * action.accept(t); 17 * }</pre> 18 * 19 * @param action The action to be performed for each element 20 * @throws NullPointerException if the specified action is null 21 * @since 1.8 22 */ 23 default void forEach(Consumer<? super T> action) { 24 Objects.requireNonNull(action); 25 for (T t : this) { 26 action.accept(t); 27 } 28 } 29 30 ... // 略 31 }
public static void main(String[] args) { // List實現了Iterable介面 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); System.out.println("---------jdk1.8---------"); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); }
Java8引入預設方法,避免了對原有的類或介面造成破壞,主要是為了保證向後相容。
當某兩個介面A和B具有相同的default方法時,一個實現類C同時實現了這兩個介面時,需要指明到底實現的是這兩個接口裡的哪一個default方法:
package interfact; public interface MyInterface1 { default void myMethod() { System.out.println("MyInterface2"); } }
package interfact; public interface MyInterface2 { default void myMethod() { System.out.println("MyInterface2"); } }
1 package interfact; 2 3 public class InterfaceMain implements MyInterface1, MyInterface2 { 4 5 /** 6 * 宣告實現了哪個介面的方法 7 */ 8 @Override 9 public void myMethod() { 10 // 我們直接可以把需要實現某個介面的具體default方法拷貝進來,也可以 11 MyInterface1.super.myMethod(); 12 } 13 14 public static void main(String[] args) { 15 InterfaceMain interfaceMain = new InterfaceMain(); 16 interfaceMain.myMethod(); 17 } 18 19 }
當某兩個介面A和B具有相同的default方法時,一個實現類C實現了介面A的default,現在某個實現類D繼承了實現類C實現了介面B:
package interfact; public class MyInterface1Impl implements MyInterface1 { @Override public void myMethod() { System.out.println("MyInterface1Impl"); } }
package interfact; public class InterfaceMain extends MyInterface1Impl implements MyInterface2 { public static void main(String[] args) { InterfaceMain interfaceMain = new InterfaceMain(); interfaceMain.myMethod(); } }
列印結果為MyInterface1Impl。因為介面只是一種抽象的表述,它表示一種規範協議,而實現類更加具體的描述了介面的行為,在java語言中,認為實現類比介面更加高階,所以最終列印的結果是以實現類優先順序高列印的。
1 public interface Iterable<T> { 2 /** 3 * Returns an iterator over elements of type {@code T}. 4 * 5 * @return an Iterator. 6 */ 7 Iterator<T> iterator(); 8 9 /** 10 * 對迭代器的每一個原生執行給定的操作,直到所有元素都已處理完畢 11 * 或該操作引發異常。如果實現類沒有重寫,操作將按迭代順序執行 12 * 。操作引發的異常將傳達給呼叫者。 13 * @param action The action to be performed for each element 14 * @throws NullPointerException if the specified action is null 15 * @since 1.8 16 */ 17 default void forEach(Consumer<? super T> action) { 18 Objects.requireNonNull(action); 19 for (T t : this) { 20 action.accept(t); 21 } 22 }
public interface Collection<E> extends Iterable<E> { ...// 略 /** * Returns a sequential {@code Stream} with this collection as its source. * * <p>This method should be overridden when the {@link #spliterator()} * method cannot return a spliterator that is {@code IMMUTABLE}, * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()} * for details.) * * @implSpec * The default implementation creates a sequential {@code Stream} from the * collection's {@code Spliterator}. * * @return a sequential {@code Stream} over the elements in this collection * @since 1.8 */ default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } ...// 略 }
Stream提供支援一個元素序列的序列和並行聚合操作,以下示例說明使用Stream和IntStream:
int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight()) .sum();
在此示例中,程式碼裡widgets是一個Collection<Widget>。我們通過Collection#stream建立一個Widget物件流,它過濾以產生只有紅色widgets的流,然後將其轉變成int值流,這些值代表每個紅色小部件的權重,然後該流求和以產生一個總權重。
此外,除了物件引用的流Stream,還存在一些原始型別的流,例如IntStream、LongStream、DoubleStream,所有的這些都稱為流,它們都符合此處描述的特徵和限制:
要執行計算,流業務被組合成一個流的管道,一個流的管道由一個源頭(源頭可以是一個數組,集合,generator方法,I/O通道等),零個或多箇中間操作(將一個流轉換成另外一個流,例如:filter(Predicate),一個終端操作(產生結果或副作用(例如count 或 forEach),【終端操作指的是截至操作,即流結束】)),流是惰性的,只有在終端操作啟動時才會對源資料進行計算,源資料只在需要時才被使用。
集合和流雖然表面上有一些相似之處,但是有著不同的目標。集合主要涉及對其元素的有效管理和訪問。相比之下,流不提供直接訪問或操作元素的方法,而是關注於宣告性地描述其源和將在該源上聚合執行的計算操作。但是,如果提供的流操作沒有提供所需的功能(指僅建立stream,不進行其他filter等操作),則可以使用流的iterator()和spliterator()操作獲取遍歷器。
流管道pipeline,類似上面“widgets”示例,可以看作是對流資料來源的查詢。除非流的資料來源物件明確指明可以併發修改(例如ConcurrentHashMap),否則在查詢流源時修改流源可能會導致不可預測或錯誤的行為【遍歷過程中,內部或外部隨意修改流源可能會引發錯誤】。
大多數流操作都接受 描述使用者指定行為 的引數,例如上例中傳遞給mapToInt的lambda表示式w->w.getWeight()。為了保持正確的行為,這些行為引數:
大多數情況下必須是無狀態的(它們的結果不應該依賴於在執行流水線過程中可能改變的任何狀態,即結果不可變)
這些引數總是函式式介面的例項,通常是lambda表示式或方法引用。除非另有規定,否則這些引數必須非空。
一個流只能被操作一次(呼叫中間流或終端流操作)。例如,這就排除了“分叉”流,即同一個源為多個管道提供資料,或者同一個流的多次遍歷。如果流實現檢測到正在被重複使用,它可能會丟擲illegalStateException。然而,在某些情況下,可能不是所有的操作都返回一個新的流,有些操作會重新使用它們的流。
Stream有一個close()方法並實現類AutoCloseable,但幾乎所有的流例項在使用後實際上並不需要關閉。通常只有源為IO通道的流(例如檔案.lines(Path,Charset))將需要關閉。大多數流由集合、陣列或generater函式支援,這些資料來源不需要特殊的資源管理(即try catch close一套操作)。(如果流確實需要關閉,則可以在try with resources語句中將其宣告為資源。)
流管道可以順序執行,也可以並行執行。此執行模式是流的特性。流的初始建立可以選擇是並行的還是序列的(例如,集合.stream()建立一個序列流,集合.parallelStream建立一個並行流)。此執行模式的選擇可以通過sequential()或parallel()方法進行修改,也可以使用isParallel()方法進行查詢。
Stream的出現有兩大概念,第一流,第二管道。流是一個抽象的概念,可以表示成移動的資料單元,管道的作用就是對流裡的每個單元起著什麼樣的操作。流又分為中間流和終端流,中間流也稱為節點流,它指的是輸入是一個流返回的還是一個流的一種流,而終端流指的是輸入是一個流,輸入完後流就被中斷了,不會傳遞到下一個管道里了。
public static void main(String[] args) { List<Person> persons = new ArrayList<Person>() { { add(new Person(20, "maomao")); add(new Person(30, "mayun")); add(new Person(26, "keke")); add(new Person(40, "angular")); } }; Stream<Person> originStream = persons.stream(); Stream<Person> filterStream = originStream.filter(person -> person.getAge() > 20); System.out.println(originStream); // java.util.stream.ReferencePipeline$Head@7ba4f24f System.out.println(filterStream); // java.util.stream.ReferencePipeline$2@3b9a45b3
從列印結果上看originStream經過filter之後不在是之前的流物件了,也就是說產生了一個新的中間流物件。既然中間流物件是一個新的流物件那mapToInt等這些操作把originStream轉化成其他中間流肯定也不在是原始流了
Stream<Person> originStream = persons.stream(); IntStream intStream = originStream.mapToInt(person -> person.getAge()); System.out.println(originStream); // java.util.stream.ReferencePipeline$Head@7ba4f24f System.out.println(intStream); // java.util.stream.ReferencePipeline$4@3b9a45b3
Stream<Person> originStream = persons.stream(); Stream<Integer> integerStream = originStream.map(person -> person.getAge()); Stream<Person> sortStream = originStream.sorted(); // 再次使用originStream中間流 System.out.println(originStream); System.out.println(integerStream); System.out.println(sortStream);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203) at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94) at java.util.stream.ReferencePipeline$StatefulOp.<init>(ReferencePipeline.java:647) at java.util.stream.SortedOps$OfRef.<init>(SortedOps.java:111) at java.util.stream.SortedOps.makeRef(SortedOps.java:51) at java.util.stream.ReferencePipeline.sorted(ReferencePipeline.java:389) at newApi.StreamTest.main(StreamTest.java:48)
// 流操作過程中引數(即forEach裡lambda表示式)的執行結果必須不可變 // 即下面的lambda表示式不可變 originStream.forEach(person -> { // lambda表示式內的引數person狀態更改不影響節點流 person.setAge(person.getAge() + 1); System.out.println(person.getAge()); }); // 流操作過程中引數(即forEach裡lambda表示式),必須時無干擾的(不可以改變資料來源) originStream.forEach(person -> persons.remove(0)); // originStream.forEach(person -> persons.add(new Person(43, "aobama")));
執行結果:上面列印能正常,但是下面修改就報錯了,不允許修改資料來源
21 31 27 41 Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at newApi.StreamTest.main(StreamTest.java:56)
Stream<Person> originStream = persons.stream(); originStream.forEach(person -> persons.remove(person));
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at newApi.StreamTest.main(StreamTest.java:48)
public static void main(String[] args) { /** * 流是存在短路運算的。 * 它裡面相當於存在這樣一個容器,容器裡存的是對流裡面每一個元素 * 的操作,當對流進行處理的時候,它會拿著流裡面的 * 每操作逐個的應用到流裡面的當前元素,而且流是存在短路運算的。 * 如下列印結果為: * hello * 5 */ List<String> strings = Arrays.asList("hello", "world", "hello world"); strings.stream().mapToInt(item -> { int length = item.length(); System.out.println(item); return length; }).filter(item -> item == 5).findFirst().ifPresent(item -> System.out.println(item)); }
// 流的常用建立方式 Stream stream1 = Stream.of("beijing", "tianjing", "shanghai"); stream1.forEach(System.out::println); String[] myArray = new String[]{"hello", "world"}; Stream stream2 = Stream.of(myArray); Stream stream3 = Arrays.stream(myArray); Stream stream4 = Arrays.asList(myArray).stream(); // 基本使用 IntStream.of(new int[]{5, 6, 7}).forEach(System.out::println); System.out.println("----[3,8)--------"); IntStream.range(3, 8).forEach(System.out::println); System.out.println("-----[3,8]-------"); IntStream.rangeClosed(3, 8).forEach(System.out::println); System.out.println("------統計源資料裡單元個數------"); long count = IntStream.of(new int[]{1, 2, 3}).count(); System.out.println(count); System.out.println("------1、求和------"); long sum = IntStream.of(new int[]{1, 2, 3}).map(value -> 2 * value).sum(); System.out.println(sum); System.out.println("------2、求和------"); long sum2 = IntStream.of(new int[]{1, 2, 3}).map(value -> 2 * value).reduce(0, Integer::sum); System.out.println(sum2);
/** * stream.toArray將一個流轉換為當前流內元素型別的陣列 * 其中toArray引數函式式介面IntFunction的建構函式第一個引數為當前流元素的個數 */ List<Person> persons = new ArrayList<Person>() { { add(new Person(20, "maomao")); add(new Person(30, "mayun")); add(new Person(26, "keke")); add(new Person(40, "angular")); } }; Stream<Person> stream = persons.stream(); // 將一個流轉換為陣列 Person[] people = stream.toArray(Person[]::new); String[] strings = Stream.of("welcome", "to", "beijing").toArray(length -> new String[length]);
注意collect是區分並行和序列流的,對於並行流和序列流執行的方式也不一樣,Collectors.toList預設幫我們實現了collect方法的引數案例如下:
/** * stream.collect */ Stream<Person> streamCol = persons.stream(); System.out.println("------將一個流轉換為集合-----"); List<Person> personListCol = streamCol.collect(Collectors.toList()); personListCol.forEach((person) -> System.out.println(person.getName()));
進入Collectors.toList可以檢視到,它幫我們實現了collect入參介面:
public static <T> Collector<T, ?, List<T>> toList() { return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); }
先來解釋以下實現類CollectorImpl建構函式引數是什麼意思:
CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Set<Characteristics> characteristics) { this(supplier, accumulator, combiner, castingIdentity(), characteristics); }
第一個引數表示供應商,第二個引數表示消費,第三個引數表示一個二元操作,第四個引數不必關心。供應商主要是提供後續操作物件的,也就是說它提供了一個物件給第二個引數BiConsumer構造方法的第一個引數,而BiConsumer構造方法的第二個引數則是當前stream內的某個資料單元。第三個操作表示一個二元操作。這麼一解釋很難懂,直接看Collectors.toList案例:
第一個引數是個供應商,提供ArrayList物件的建立,如果流是一個並行流,則會建立多個ArrayList物件,如果是一個序列流則只建立一個。第二個引數把供應商建立的某個ArrayList物件新增一個當前流內的元素。第三個引數,二元操作,在這裡指的是合併操作,如果是並行流:把建立的第二個ArrayList物件(如果存在),合併到第一個ArrayList物件裡,並返回一個ArrayList物件作為下一次合併操作的輸入。當流內所有元素操作完畢,則返回ArrayList物件(已經合併了其他所有ArrayList)。如果是序列流:則忽略該引數的任何操作,當流內所有元素操作完成後返回建立的那個唯一的ArrayList物件。
/** * Simple implementation class for {@code Collector}. * * @param <T> the type of elements to be collected * @param <R> the type of the result */ CollectorImpl(Supplier<A> supplier, // 供應商,無入參,出參:供應一個A型別的物件(A型別是一箇中間物件) BiConsumer<A, T> accumulator,// 消費者,入參:A型別的物件,T型別的集合元素,無出參 BinaryOperator<A> combiner,// 中間操作,入參:兩個A型別的物件,出參:一個A型別的物件 Function<A,R> finisher,// 封裝/轉換結果集,將combiner最終返回的結果集(A型別)作為入參,返回一個R型別的物件 Set<Characteristics> characteristics) { this.supplier = supplier; this.accumulator = accumulator; this.combiner = combiner; this.finisher = finisher; this.characteristics = characteristics; }
System.out.println("------使用serial stream測試collect方法的第三個引數-----"); Stream<Person> streamSerial = persons.stream(); List<Person> personList1 = streamSerial.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> { System.out.println(theList1); // 忽略該引數任何操作,根本不進入斷點 System.out.println(theList2); theList1.addAll(theList2); }); personList1.forEach(person -> System.out.println(person.getName())); System.out.println(personList1.size()); System.out.println("------使用parallel stream測試collect方法的第三個引數-----"); Stream<Person> streamParallel = persons.parallelStream(); List<Person> personList2 = streamParallel.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> { System.out.println(theList1); // 進入斷點 System.out.println(theList2); theList1.addAll(theList2); // 如果不合並,則返回的第一個ArrayList只添加了流裡的第一個元素,下面列印結果也就只有一個元素 }); personList2.forEach(person -> System.out.println(person.getName()));
當是序列流的時候第三個引數不可以為null,但可以是一個空的實現:
List<Person> personList1 = streamSerial.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> {});
List<Person> personList1 = streamSerial.collect(() -> new ArrayList(), (theList, item) -> theList.add(item), (theList1, theList2) -> theList1.addAll(theList2));
Collectors.toList的原理就是像上面手動封裝了一個對ArrayList的操作,我們也可以自己封裝一個對LinkedList的操作
streamParallel.collect(LinkedList::new, LinkedList::add, LinkedList::addAll); // 等效於: streamParallel.collect(Collectors.toCollection(LinkedList::new));
/** * flatMap返回一個流,它將提供的對映函式(即入參)應用於每個元素而生成的對映流的內容,替換this stream的每個元素的結果組成。 * <p> * 什麼時候用flatMap什麼時候用map? * 當你需要把多個流【首先你要把多種情況構建成流】,扁平到一個最終的流上時候需要使用flatMap * flatMap入參是一個Function,你可以使用lambda返回任何一個可以被扁平化的流,返回結果是一個合併其他流結果的的新流 */ public class StreamTest4 { public static void main(String[] args) { /** * List扁平化 */ Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6)); stream.flatMap(theList -> theList.stream()).forEach(System.out::println); System.out.println("----"); /** * 找出集合中所有單詞並去重 * 最終期望輸出如下三個單詞(不要求順序):hello welcome world */ List<String> list = Arrays.asList("hello welcome", "welcome hello", "hello world hello", "hello welcome"); // 每一個元素先轉化為陣列,然後使用陣列初始化一個新的流,在使用flatMap扁平化多個流 list.stream().flatMap(str -> Stream.of(str.split(" "))).distinct().forEach(System.out::println); // map先把字元對映為字元陣列並返回一個新的流,現在流裡面的元素都是字元陣列了,使用flatMap,flatMap把Arrays::Stream應用到每一個字元 // 陣列上以返回一個對映流,然後把該對映流的內容組合到要返回的流上,最終flatMap返回的是一個個單詞,然後去重。 list.stream().map(str -> str.split(" ")).flatMap(Arrays::stream).distinct().forEach(System.out::println); /** * 要求輸出: * Hi 毛毛 * Hi 可可 * Hi 吉吉 * Hello 毛毛 * ... */ List<String> list1 = Arrays.asList("Hi", "Hello", "你好"); List<String> list2 = Arrays.asList("毛毛", "可可", "吉吉"); Stream<String> stringStream = list1.stream().flatMap(item1 -> list2.stream().map(item2 -> item1 + " " + item2)); stringStream.forEach(System.out::println); } }
package newApi; import java.util.*; import static java.util.stream.Collectors.*; /** * 分組與分割槽 */ public class StreamTest5 { public static void main(String[] args) { ArrayList<Student> students = new ArrayList<Student>() {{ add(new Student("maomao", "english", 25, 80)); add(new Student("maomao", "chinese", 25, 90)); add(new Student("keke", "math", 25, 80)); add(new Student("keke", "english", 25, 80)); add(new Student("keke", "chinese", 25, 60)); add(new Student("jiji", "chinese", 25, 70)); add(new Student("jiji", "english", 25, 90)); }}; /** * 需求:實現select * from student group by name; * 原始寫法:1、遍歷、獲取名稱,檢視是否在map中存在,不存在創 * 建List然後把當前元素放入,存在直接放入List,最後返回map */ Map<String, List<Student>> collect1 = students.stream().collect(groupingBy(Student::getName)); System.out.println(collect1.toString()); /** * 需求:實現select name,count(*) group by name; */ Map<String, Long> collect2 = students.stream().collect(groupingBy(Student::getName, counting())); System.out.println(collect2.toString()); /** * 需求:實現select name, avg(score) group by name; */ Map<String, Double> collect3 = students.stream().collect(groupingBy(Student::getName, averagingDouble(Student::getScore))); System.out.println(collect3.toString()); Map<String, Double> collect4 = students.stream().collect(groupingBy(Student::getCourse, averagingDouble(Student::getScore))); System.out.println(collect4.toString()); System.out.println("----分割槽-----"); /** * 需求:select name,sum(score) from student group by name * 班級總平均分,學生個人平均分,各個人平均分低於總平均分的為一個區,高於總平均分的分為一個區 */ students.addAll(Arrays.asList(new Student("jiji", "math", 25, 77), new Student("maomao", "math", 25, 87))); Double avg = students.stream().collect(averagingDouble(Student::getScore)); Map<String, Double> studentAvg = students.stream().collect(groupingBy(Student::getName, averagingDouble(Student::getScore))); Map<String, List<String>> result = new HashMap<String, List<String>>() {{ put("up", new ArrayList<>()); put("down", new ArrayList<>()); }}; studentAvg.forEach((name, score) -> { if (score > avg) { result.get("up").add(name); } else { result.get("down").add(name); } }); System.out.println("班級總平均分:" + avg); System.out.println("學生平均分:" + studentAvg); System.out.println(result); System.out.println("----分割槽----"); Map<Boolean, List<Student>> collect = students.stream().collect(partitioningBy(student -> student.getScore() > 80)); System.out.println(collect); System.out.println("--------"); /** * 找出學生中最低分數 */ Map<String, Student> collect5 = students.stream().collect( groupingBy(Student::getName, collectingAndThen(minBy(Comparator.comparingDouble(Student::getScore)), Optional::get))); System.out.println(collect5); /** * 找出學生中平均分最低的 */ Collection<Double> collect6 = students.stream().collect( collectingAndThen( groupingBy( Student::getName, collectingAndThen( averagingDouble(Student::getScore), item -> Math.floor(item) ) ), ittem1 -> ittem1.values() ) ); System.out.println(collect6); System.out.println(Collections.min(collect6)); } }
public static void main(String[] args) { /** * 找出集合中所有單詞並去重 * 最終期望輸出如下三個單詞(不要求順序):hello welcome world */ List<String> list = Arrays.asList("hello welcome", "welcome hello", "hello world hello", "hello welcome"); // list.stream().flatMap(str -> Stream.of(str.split(" "))).collect(Collectors.toCollection(HashSet::new)).forEach(System.out::println); // list.stream().flatMap(str -> Stream.of(str.split(" "))).distinct().forEach(System.out::println); list.stream().map(str->str.split(" ")).flatMap(Arrays::stream).distinct().forEach(System.out::println); /** * findFirst和findAny */ Stream<String> stringStream = Stream.generate(UUID.randomUUID()::toString); Stream<List<Integer>> generate = Stream.generate(() -> Arrays.asList(1, 2, 3)); Optional<String> first = stringStream.findFirst(); Optional<List<Integer>> any = generate.findAny(); // 對於optional操作一定要先判斷,規避NPE first.ifPresent(System.out::println); any.ifPresent(System.out::println); System.out.println("----"); /** * 無限的序列流 * 傳入一個種子數1,然後不斷地對該種子數執行item -> item + 2操作 * 如果不使用limit限制,將不斷的一邊操作,一邊輸出,limit限制操作次數 * 注意limit一定要緊跟著iterate使用,否則始終會有一個流無法終止 */ Stream.iterate(1, item -> item + 2).limit(6).forEach(System.out::println); System.out.println("----"); // 注意limit一定要緊跟著iterate使用,否則始終會有一個流無法終止 // Stream.iterate(1, item -> (item + 1) % 2).distinct().limit(6).forEach(System.out::println); /** * 找出該流中大於2的元素,然後將每個元素乘以2,然後忽略掉流中的前兩個元素,然後在取出流中的前兩個元素,最後求出流中元素的總和 */ OptionalInt reduce = Stream.iterate(1, item -> item + 2) .limit(6) .filter(item -> item > 2) .mapToInt(item -> item * 2) // 使用mapToInt避免自動裝箱 .skip(2) .limit(2) .reduce((item1, item2) -> item1 + item2); // .sum不會發送NPE所以返回的不會被Optional包裝、.max和.min會返回Optional reduce.ifPresent(System.out::println); System.out.println("----"); /** * 找出該流中大於2的元素,然後將每個元素乘以2,然後忽略掉流中的前兩個元素,然後在取出流中的前3個元素,求總和、最大、最小 */ IntSummaryStatistics intSummaryStatistics = Stream.iterate(1, item -> item + 2) .limit(6) .filter(item -> item > 2) .mapToInt(item -> item * 2) // 使用mapToInt避免自動裝箱 .skip(2) .limit(3) .summaryStatistics(); System.out.println("最大:" + intSummaryStatistics.getMax()); System.out.println("最小:" + intSummaryStatistics.getMin()); System.out.println("總和:" + intSummaryStatistics.getSum()); System.out.println("----"); }
/** * 註釋略 * * @since 1.8 */ public final class Optional<T> {
Option是一種容器物件,可以包含也可以不包含非空值。如果存在值,isPresent()將返回true,get()將返回該值。
還提供了其他依賴於包含值的存在與否的方法,例如orElse()(如果值不存在,則返回預設值)和ifPresent()(如果值存在,則執行一段程式碼)。
這是一個基於值的類:在Optional的例項上使用對錶示敏感的操作(包括引用相等(=)、標識雜湊程式碼或同步)可能會產生不可預知的結果,因此應避免。
基於值的類是java8提出的新概念。說明如下:有些類,比如java.util.Optional和java.time.LocalDateTime,都是基於值的。基於值的類的例項由以下特徵:
具有equals、hashCode和toString的實現,這些實現僅根據例項的狀態而不是根據其標識或任何其他物件或變數的狀態來計算
不使用身份敏感的操作,例如例項之前的引用相等(==),例項的身份雜湊碼或例項鎖上的同步
沒有可訪問的構造引數,而是通過工廠方法例項化的,該方法對提交的例項的身份不做任何承諾
在相等時可以自由替換,這意味著在任何計算或方法呼叫中互換等於equals()的任意兩個例項x和y都不會在行為上產生任何可見的變化。
如果程式嘗試將兩個引用區分為基於值的類的相等值,則可能會產生不可預測的結果,無論使通過直接引用相等,還是間接通過同步、身份雜湊,序列化或任何其他身份敏感機制。在基於值的類的例項上使用這種對身份敏感的操作可能會產生不可預測的影響,應該避免。
Optional直譯過來就是可選的,Optional的出現是為了解決Java當中經常出現的一個NPE問題即NullPointerException。空指標異常一般發生在使用空的物件去呼叫某個行為,所以為了防止發出異常,我們一般這麼寫程式碼:
if(null != person) { Address address = person.getAddress(); if(null != address) { .... } }
既然空指標異常出現的情況非常多,出現的頻率非常高,因此很多語言都對空指標異常做了一定的規避,規避的手段其實都是採用的一種方式,即Optional。
Optional是一種基於值的類,那如何建立Option的物件例項?它建立例項提供了三種方式(這三種方式都是靜態方法):
empty():返回一個空的Optional例項【即空的容器例項】
of(T value):使用指定的當前值value返回Option例項,當前值一定不能為null
ofNullable(T value):如果指定的當前值value不是null,則使用指定的當前值value返回Option例項,否則返回空的Option例項
ifPresent(Consumer<? super T> consumer):
如果存在值,則使用該值呼叫指定的使用者,否則不執行任何操作。
如果此Optional中存在值,則返回,否則丟擲NoSuchElementException異常
filter(Predicate<? super T> predicate):
如果存在一個值,並且該值與給定的謂詞匹配,返回描述該值的Optional,否則返回一個空的Optional
map(Function<? super T,? extends U> mapper):
如果存在一個值,將提供的對映函式應用與它,如果Functoin結果返回為非空,則返回一個Optional來描述結果。否則返回一個空的Optional
orElseGet<Supplier<? extends T> other>:
如果Optional裡存在值,則返回值,否則呼叫other並返回該呼叫的結果
Optional不可以作為方法的引數,Optional也不可以作為類的成員變數,因為Optional是不可序列化的!
public static void main(String[] args) { // 原來寫法 String hello = "hello"; if (null != hello) { System.out.println(hello); } // 現在使用Optional Optional<String> optional = Optional.of("hello"); if (optional.isPresent()) { System.out.println(optional.get()); } }
如上案例:如果使用Optional會發現本質上還是和原來的寫法一樣,使用Optional的方式寫的程式碼還要更長,雖然說上面的程式碼不會出現任何的問題,但是是不推薦的,使用Optional應該轉換固有的程式設計模式,Optional提供了一些引數是函式式介面的方法,我們可以使用這些方法簡化上面的程式碼:
class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Company { private String name; private List<Employee> employees; public Company(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } } public class OptionalTest { public static void main(String[] args) { Optional<String> optional = Optional.of("hello"); if (optional.isPresent()) { System.out.println(optional.get()); } HashMap<String, Object> userInfo = new HashMap<String, Object>() {{ put("age", 23); put("name", "毛毛"); }}; // 簡化上面的寫法 // 1、ifPresent如果Optional為空什麼都不做,否則把Optional裡的值應用於Consumer optional.ifPresent(value -> System.out.println(value)); System.out.println(optional.orElse("hello")); // 如果存在則返回,不存在使用指定值other返回 System.out.println(optional.orElseGet(() -> new String("hello"))); // 如果存在,則返回,不存在,則呼叫供應商獲取 /** * ofNullable:如果p是個null,則返回一個空的Optional,否則返回一個包裝了p的Optional * orElseGet:如果當前Optional裡有值,則返回該值,否則使用供應商提供的返回結果 */ Person p = null; int age = Optional.ofNullable(p).orElseGet(() -> new Person(23, "毛毛")).getAge(); System.out.println(age); /** * 一個關於Optional的真實案例: * 如果一個集合不為空返回這個集合,否則返回一個空的集合。 */ Employee employee1 = new Employee("mao"); Employee employee2 = new Employee("ke"); // Company company = null; Company company = new Company("wonders"); List<Employee> employees = Arrays.asList(employee1, employee2); company.setEmployees(employees); // company為空最終返回一個空的集合,company不為空,若employees為空,也返回一個空的集合,若employees也不為空,則返回employees Optional<Company> companyOption = Optional.ofNullable(company); System.out.println(companyOption.map((company1 -> company1.getEmployees())).orElse(Collections.emptyList())); // company.getEmployees()不為空則直接返回集合,為空返回空集合 // Optional.ofNullable(company.getEmployees()).orElse(Collections.emptyList()); } }
package date; import java.time.*; import java.time.temporal.ChronoUnit; import java.util.Set; import java.util.TreeSet; public class MyDate { public static void main(String[] args) { /** * LocalDate關注日期 */ LocalDate localDate = LocalDate.now(); System.out.println("當前日期: " + localDate); System.out.println("當前月份: " + localDate.getMonth()); System.out.println("當前日期: " + localDate.getYear() + "," + localDate.getMonthValue() + "," + localDate.getDayOfMonth()); LocalDate localDate1 = LocalDate.of(2020, 3, 24); System.out.println("構造任意日期: " + localDate1); /** * monthDay僅僅只關注月和日,不關注年 */ LocalDate localDate2 = LocalDate.of(2020, 8, 20); MonthDay monthDay = MonthDay.of(localDate2.getMonth(), localDate2.getDayOfMonth()); MonthDay from = MonthDay.from(LocalDate.of(2010, 8, 20)); if (monthDay.equals(from)) { System.out.println("今天是我生日"); } else { System.out.println("今天不是我生日"); } /** * 只關注年月 */ YearMonth yearMonth = YearMonth.of(2008, 2); System.out.println("2008年2月:" + yearMonth); System.out.println("2008年2月有多少天?" + yearMonth.lengthOfMonth()); System.out.println("2008年是閏年嗎?" + yearMonth.isLeapYear()); /** * 日期加減 */ LocalDate after2Week = localDate.plus(2, ChronoUnit.WEEKS); System.out.println("當前日期增加兩週:" + after2Week); LocalDate before2Week1 = localDate.plus(-2, ChronoUnit.WEEKS); LocalDate before2Week2 = localDate.minus(2, ChronoUnit.WEEKS); System.out.println("當前日期減去兩週:" + before2Week1); System.out.println("當前日期減去兩週:" + before2Week2); /** * 日期前後判斷 */ System.out.println("日期前後判斷:" + after2Week.isAfter(localDate)); System.out.println("日期前後判斷:" + localDate1.isBefore(localDate)); /** * 週期 */ LocalDate localDate3 = LocalDate.now(); LocalDate localDate4 = LocalDate.of(2008, 12, 29); Period period = Period.between(localDate4, localDate3); System.out.println("這兩個日期隔了多少年?多少月?多少天?" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天"); /** * 時區 */ Set<String> availableZoneIds = ZoneId.getAvailableZoneIds(); TreeSet<String> treeSet = new TreeSet<String>() { // 對時區set排序 { addAll(availableZoneIds); } }; System.out.println(treeSet); ZoneId zoneId = ZoneId.of("Asia/Shanghai"); LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("帶時區的日期:" + localDateTime); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId); System.out.println("帶指定時區的日期:" + zonedDateTime); /** * Instant,不帶UTC(時區)的日期 */ System.out.println(Instant.now()); /** * LocaleTime關注時分秒 */ LocalTime localTime = LocalTime.now(); System.out.println("當前時分秒:" + localTime); System.out.println("一個半小時後:" + localTime.plusHours(1).plusMinutes(30)); /** * Clock系統時間 */ Clock clock = Clock.systemDefaultZone(); System.out.println("當前時間毫秒數" + clock.millis()); } }
/** * 註釋略 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface {}
FuncationInterface是一個表示資訊提示的註解,用於指示interface型別宣告是Java語言規範定義的函式介面。
概念上,函式式介面精確的只有一個abstract方法,因為jaav.lang.reflect.Method#isDefault()的預設方法有一個實現,它們不是抽象的(在jdk1.8中介面中不在是隻能宣告抽象方法,還可以宣告預設方法和靜態方法。)。如果一個介面的抽象方法重寫了java.lang.Object的public方法,則它不計入抽象方法的計數中(看下面案例),因為任何一個介面的實現類一定是直接或間接的實現了java.lang.Object類,所以把它計入抽象方法中是沒有任何意義的。
注意:函式式介面的例項可以被lambda表示式、方法引用、構造引用建立。
如果使用該註解對型別進行標註,則需要編譯器校驗,若不滿足如下條件,則生成錯誤資訊:
但是,編譯器將把滿足函式式介面定義的任何介面視為函式式介面,而不管介面宣告中使用存在該註解。
案例:介面重寫Object類中public方法,該方法不計入函式式介面所指的抽象方法中,
如下,註釋term抽象函式,@FuncationInterface不會出現下劃紅線,因為我們滿足只有一個非實現Object類public方法的抽象方法。但是如果我們取消term的註釋(或註釋abs),則@FuncationInterface會出現下劃紅線提示錯誤,原因是檢測到有兩個(或一個沒有)抽象方法。
/** * 抽象方法可以省略 abstract關鍵字 * 如果一個介面中重新了Object物件中的public方法,則該介面不計入抽象介面中 */ @FunctionalInterface public interface MyInterface { void abs(); // void term(); @Override String toString(); }
/** * 表示一個函式,什麼樣的函式呢?接收一個引數並返回一個結果的函式 * * @param <T> 表示函式的輸入型別 * @param <R> 表示函式的輸出型別 * * @since 1.8 */ @FunctionalInterface public interface Function<T, R> { /** * 將此函式應用於給定引數 * * @param t the function argument * @return the function result */ R apply(T t); /** * 返回一個函式介面,在返回該介面之前,先把輸入應用給before函式上,然後 * 把before函式的結果作為該介面的輸入,返回當前函式的介面。 * 如果丟擲異常將傳遞給呼叫者 * */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); // 表示返回一個Function介面的實現類,重寫方法時傳遞的時before.apply執行的返回值 return (V v) -> apply(before.apply(v)); } /** * 返回一個函式介面,首先把輸入應用與該函式上,然後把該介面的輸出作為before * 函式的輸入,返回after的函式介面 * 如果丟擲異常將傳遞給呼叫者 * */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
Function<String, String> function = String::toUpperCase;
System.out.println(function.getClass().getInterfaces()[0]);
interface java.util.function.Function
上下文推斷:函式式介面Funcation需要接受一個輸入引數,並返回一個輸出,而toUpperCase方法的輸出正好對應的apply返回的R,但是apply方法的輸入T是誰?toUpperCase沒有入參,而且toUpperCase是一個物件方法,肯定不能直接使用String.toUpperCase去呼叫。那麼一定是存在一個String字串物件去呼叫這個toUpperCase方法,那這個字串物件就作為輸入。
有了Function介面有什麼用?細細體會,它指代:接受一個引數並返回一個結果的方法。也就是說有了它,我們可以用它表示任意一個只有一個引數且有返回值的函式,它只是指代了這樣一個函式,函式內的具體實現它並不關心。
package funcInterface.consumer; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; /** * 在java的世界中,方法被看作是物件的行為,而Function就是行為的一種抽象表述, * 它表示這樣一種行為: 只有一個輸入和一個輸出的行為。 * 行為的具體呼叫方式是: 行為物件.apply(行為接收的入參) */ public class FunctionTest { public static void main(String[] args) { FunctionTest functionTest = new FunctionTest(); List<Person> persons = new ArrayList<Person>() { { add(new Person(20, "maomao")); add(new Person(30, "mayun")); add(new Person(26, "keke")); add(new Person(40, "angular")); } }; System.out.println("-------------有一個需求:按照年齡給person排序--------------------"); functionTest.sortByAge(persons).forEach(person -> System.out.println(person.getName() + "----" + person.getAge())); System.out.println("-------------需求變動:按照名字長度給人排序--------------------"); functionTest.sortByNameLength(persons).forEach(person -> System.out.println(person.getName() + "----" + person.getAge())); System.out.println("--------------需求變動n多次,按照各種場景排序-------------------"); System.out.println("."); System.out.println("."); System.out.println("."); System.out.println("----------------使用函式式介面Funcation,把具體的行為交給呼叫者-----------------"); functionTest.sort(persons, personList -> personList .stream() .sorted( (personA, personB) -> (int) personA.getName().charAt(0) < (int) personB.getName().charAt(0) ? -1 : 1 ) .collect(Collectors.toList()) ).forEach(person -> System.out.println((int) person.getName().charAt(0) + "----" + person.getName() + "----" + person.getAge())); } /** * 按照年齡排序 * * @param personList * @return */ public List<Person> sortByAge(List<Person> personList) { return personList.stream().sorted((personA, personB) -> personA.getAge() < personB.getAge() ? -1 : 1).collect(Collectors.toList()); } /*** * 按照名字長度排序 * @param personList * @return */ public List<Person> sortByNameLength(List<Person> personList) { return personList.stream().sorted((personA, personB) -> personA.getName().length() < personB.getName().length() ? -1 : 1).collect(Collectors.toList()); } /** * 把具體排序規則交由呼叫者 * * @param personList * @param function * @return */ public List<Person> sort(List<Person> personList, Function<List<Person>, List<Person>> function) { return function.apply(personList); } }
建立一個抽象的行為A並返回,在這個抽象的行為A被執行前要先執行另一個給定的行為B。即當抽象行為A被執行時:
/** * 需求: * 執行行為A前先執行行為B(以下指示Before) * * compose語法解讀: * compose返回一個行為X。 * * compose返回的行為X執行解讀: * 在X行為呼叫apply時【x行為被執行】,入參是v,X行為的返回結果是行為A執行的返回值。 * 執行行為A,入參是before行為的返回值,所以先執行before行為以獲取入參, * 執行行為before【before行為被執行】,before的返回結果作用入參傳入行為A, * 行為A被執行【行為A被執行】,最後行為A的執行結果被X行為返回出去。 * * 總結: * 整個過程產生了三個行為,其中行為A和行為before是我們完成需求的必須行為, * 而行為X是觸發我們需求的一個行為。 */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); }
/** * 需求: * 執行完行為A後在執行after行為,其中A指代當前行為,即andThen的觸發者。 * * andThen語法解讀: * andThen返回一個行為X。 * * andThen返回的行為X執行解讀: * 在X行為呼叫apply時【x行為被執行】,入參是t,X行為的返回結果是行為after執行的返回值。 * 執行行為after,入參是A行為的返回值,所以先執行A行為以獲取入參, * 執行行為A【A行為被執行】,A的返回結果作用入參傳入行為after, * 行為after被執行【行為after被執行】,最後行為after的執行結果被X行為返回出去。 * * 總結: * 整個過程產生了三個行為,其中行為A和行為after是我們完成需求的必須行為, * 而行為X是觸發我們需求的一個行為。 */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
System.out.println("------compose和andThen “輸入的行為” 是在 “輸入的行為” 執行前執行還是之後?"); // 定義一個相加的行為,輸入的行為是相乘 Function<Integer, Integer> A_Action = (i) -> i + i; Function<Integer, Integer> Input_Action = (Integer i) -> i * i; System.out.println("------compose---------"); // compose表示先執行輸入的行為,然後執行自己 Function<Integer, Integer> X_Action1 = A_Action.compose(Input_Action); System.out.println(X_Action1.apply(2)); // 8 System.out.println("------andThen---------"); Function<Integer, Integer> X_Action2 = A_Action.andThen(Input_Action); // andThen表示先執行自己,然後執行輸入的行為 System.out.println(X_Action2.apply(2)); // 16 // 完成需求的兩個Action: System.out.println(A_Action); System.out.println(Input_Action); // 觸發我們完成需求的Action: System.out.println(X_Action1); System.out.println(X_Action2);
BiFunction和Function十分類似,由於我們的Function指代的是隻有一個引數和一個返回值的函式,如果是兩個引數的話就不能在使用Function函式式介面。BiFunction是指代:只有兩個引數和一個返回結果的函式。
package funcInterface.consumer; import java.util.function.BiFunction; /** * BiFunction和Function類似,只不過它接收的是兩個引數,返回一個結果 */ public class BiFunctionTest { public static void main(String[] args) { BiFunctionTest biFunctionTest = new BiFunctionTest(); System.out.println(biFunctionTest.add(2, 3)); // 靈活性極大提升 System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x + y)); System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x * y)); System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x / y)); System.out.println(biFunctionTest.cal(2, 3, (x, y) -> x - y)); } public int add(int x, int y) { return x + y; // 具體行為是加操作 } public int cal(int x, int y, BiFunction<Integer, Integer, Integer> biFunction) { return biFunction.apply(x, y); // 把具體行為抽象出來,交給呼叫者實現 } }
/** * Represents a predicate (boolean-valued function) of one argument. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #test(Object)}. * * @param <T> the type of the input to the predicate * * @since 1.8 */ @FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * 從給定引數計算判斷結果 * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); /** * 邏輯與 */ default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } /** * 邏輯或 */ default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } /** * 靜態方法 * 判斷輸入物件是否個目標物件equals */ static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); }
表示一個引數的謂詞。Predicate的意思是謂詞、斷言、斷定的意思,也就是說對輸入的內容進行判斷,然後返回布林值。
Predicate更像是Function抽象行為的某種型別,這種型別的行為同樣是一個輸入,但是返回的是一個boolean值。但是有的時候,我們確實經常使用這樣一種返回boolean型別的行為,而不是使用Function抽象的方式寫:
Function<Object, Boolean> function = element -> (Integer) element > 5;
所以單獨的把返回boolean型別的行為單獨抽象出來,這樣更方便我們統一使用。
Predicate<Object> predicate = element -> (Integer)element > 5;
default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); // 返回一個Predicate的實現,當這個Predicate實現類物件呼叫test時 // 將入參傳入test(t) && other.test(t),把邏輯與的判斷結果返回 return (t) -> test(t) && other.test(t); }
default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); // 返回一個Predicate的實現,當這個Predicate實現類物件呼叫test時 // 將入參傳入test(t) || other.test(t),把邏輯與的判斷結果返回 return (t) -> test(t) || other.test(t); }
public static void main(String[] args) { List<Integer> integers = Arrays.asList(1, 3, 4, 5, 8, 9, 10, 23); System.out.println("-------找到所有的奇數----------"); Predicate<Integer> predicate = element -> element % 2 == 0 ? false : true; integers.stream().filter(predicate).forEach(e -> System.out.println(e)); System.out.println("-------找到所有的偶數----------"); Predicate<Integer> predicate1 = element -> element % 2 != 0 ? false : true; integers.stream().filter(predicate1).forEach(e -> System.out.println(e)); System.out.println("-------找到所有大於5的數----------"); Predicate<Integer> predicate3 = element -> element > 5; integers.stream().filter(predicate3).forEach(e -> System.out.println(e)); System.out.println("-------找到大於10或小於4的數----------"); Predicate<Integer> gtPredicate = element -> element > 10; Predicate<Integer> ltPredicate = element -> element < 4; integers.stream().filter(gtPredicate.and(ltPredicate)).forEach(e -> System.out.println(e)); System.out.println("-------找到大於5或小於20的數----------"); integers.stream().filter(gtPredicate.or(ltPredicate)).forEach(e -> System.out.println(e)); System.out.println("---------isEqual--------"); Person person1 = new Person(23, "m"); Person person2 = new Person(23, "m"); System.out.println(Predicate.isEqual(person1).test(person2)); System.out.println(Predicate.isEqual(person2).test(person2)); }
/** * Represents an operation that accepts a single input argument and returns no * result. Unlike most other functional interfaces, {@code Consumer} is expected * to operate via side-effects. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #accept(Object)}. * * @param <T> the type of the input to the operation * * @since 1.8 */ @FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * 對於給定引數執行此操作。 * * @param t the input argument */ void accept(T t); ...// 略 }
表示接受單個輸入引數但不返回結果的操作。與大多數其他功能介面不同,Consumer需要通過副作用來操作。
accpet是一個函式式介面的抽象方法,T表示一個操作的輸入型別。
Consumer意思是消費者,也就是它只是把輸入的內容消費掉。這裡的side-effects所說的副作用指的是:函式或表示式修改基本變數的行為。例如有兩個int型別的變數x和y,x+y只是返回相加的結果,並沒有改變x和y的值,那麼這個操作就沒有副作用;而賦值操作如x=1,由於將1賦值給x,改變了x的值,因此這個操作具有副作用。
簡單來說就是Consumer介面並不返回任何值,而只是接收引數並將他們的狀態通過副作用改變(即賦值操作)。
package funcInterface.consumer; import org.junit.Test; import java.util.function.Consumer; import java.util.function.Predicate; /** * 例如對學生而言,Student類包含姓名,分數以及待付費用,每個學生可 * 根據分數獲得不同程度的費用折扣。 */ class Student { String firstName; String lastName; Double grade; Double feeDiscount = 0.0; Double baseFee = 20000.0; public Student(String firstName, String lastName, Double grade) { this.firstName = firstName; this.lastName = lastName; this.grade = grade; } public void printFee() { Double newFee = baseFee - ((baseFee * feeDiscount) / 100); System.out.println("The fee after discount: " + newFee); } } public class PredicateConsumerDemo { public static Student updateStudentFee(Student student, Predicate<Student> predicate, Consumer<Student> consumer) { // Use the predicate to decide when to update the discount. if (predicate.test(student)) { // Use the Consumer to update the discount value. consumer.accept(student); // 副作用 } return student; } @Test public void test() { Student student1 = new Student("Ashok", "Kumar", 9.5); student1 = updateStudentFee(student1, new Predicate<Student>() { @Override public boolean test(Student t) { return t.grade > 8.5; } }, new Consumer<Student>() { @Override public void accept(Student t) { t.feeDiscount = 30.0; } }); student1.printFee(); Student student2 = new Student("Rajat", "Verma", 8.0); student2 = updateStudentFee(student2, student -> student.grade >= 8, student -> student.feeDiscount = 20.0); student2.printFee(); } }
使用謂詞判斷學生的成績是否大於8.5,如果大於8.5則可以享受折扣。
/** * 表示結果的提供者 * * 不要求每次呼叫提供商時都返回一個新的或不同的結果。 * * @since 1.8 */ @FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
public static void main(String[] args) { // 不接受引數返回一個結果 Supplier<String> stringSupplier = () -> "Hello Supplier"; System.out.println(stringSupplier.get()); Supplier<String> stringSupplier1 = String::new; System.out.println(stringSupplier1); }
lambda是一種表示匿名函式或者閉包的運算子,它後面跟著的是lambda運算元的使用。
Java Lambda表示式是一種匿名函式;它是沒有宣告的方法,即沒有訪問修飾符、返回值宣告和名字。
Lambda表示式為Java添加了缺失的函數語言程式設計特性,使我們能將函式當作一等公民看待。在沒有Lambda時,函式是依附於類的,類才是一等公民。
在將函式作為一等公民的語言中,Lambda表示式的型別是函式。但是Java中,Lambda表示式是物件,他們必須依附於一類特別的物件型別---函式式介面。
在Java中,我們無法將函式作為引數傳遞給一個方法,也無法宣告返回一個函式的方法
在JavaScript中,函式引數是一個函式,返回值是另一個函式的情況是非常常見的;JavaScript是一門非常典型的函式式語言。
public static void main(String[] args) { JFrame jFrame = new JFrame("my JFrame"); JButton jButton = new JButton("my Buutom"); jButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("click"); } }); jFrame.add(jButton); jFrame.pack(); jFrame.setVisible(true); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }
在沒有Lambda之前,我們要想寫一些圖形化程式設計時需要寫很多和業務需求無關的程式碼,以以上案例來說,我只想給button新增一個click事件,當點選它是就執行列印。但是要想執行我的列印還要建立一個多餘的匿名內部類ActionEvent,更奇葩的時我還要在一個莫名其妙的actionPerformed方法裡寫我的列印。對於我來說,我只想新增一個列印事件,這些ActionEvent和actionPerformed對於我來說就是多餘的。
有了Java8的Lambda之後,我不需要在關心和我無關的事情。
public static void main(String[] args) { JFrame jFrame = new JFrame("my JFrame"); JButton jButton = new JButton("my Buutom"); // jButton.addActionListener(new ActionListener() { // @Override // public void actionPerformed(ActionEvent e) { // System.out.println("click"); // } // }); jButton.addActionListener(e -> System.out.println("click")); jFrame.add(jButton); jFrame.pack(); jFrame.setVisible(true); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }
java是靜態型別語言,但是使用Lambda之後,那個引數e我們並沒有定義它的型別,在java8中java依舊是靜態型別語言,上面的語法完整的寫法應該如下:
jButton.addActionListener((ActionEvent e) -> System.out.println("click"));
之所以沒有加ActionEvent型別說明,其實是藉助於java編譯系統裡面的型別推斷機制,推斷出上下文裡的這個e一定是ActionEvent型別,所以就不需要我們自己顯示的聲明瞭,當然如果推斷機制推斷不出來就需要我們像上面那樣手動的宣告型別。
(param1,param2) -> { // 業務 }
public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); System.out.println("---------jdk1.5之前---------"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } System.out.println("---------jdk1.5---------"); for (int i : list) { System.out.println(i); } System.out.println("---------jdk1.8---------"); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); System.out.println("---------jdk1.8 lambda+函式式介面---------"); list.forEach(integer -> System.out.println(integer)); }
在上面jdk1.8之後使用forEach並不能使我們的程式碼簡化,反而使程式碼更加複雜了,為什麼我們可以使用lambda替代Consumer的建立呢?原因是Consumer是一個函式式介面。
最原始的寫法,使用多型,自己建立介面的實現,最終呼叫的是實現類的hello方法
package com; @FunctionalInterface interface IMyInterface { String hello(); String toString(); } public class MyInterfaceLambda { public static void printHello(IMyInterface iMyInterface) { System.out.println(1); System.out.println(iMyInterface.hello()); System.out.println(2); } public static void main(String[] args) { MyInterfaceLambda.printHello(new IMyInterface() { @Override public String hello() { return "HELLO"; } }); } }
1
HELLO
2
現在有了lambda表示式,而且知道這個介面是一個函式式介面,那我們如果實現這個介面的話肯定只是實現了它其中唯一的一個抽象方法,這樣的話,我們就沒必要在指定實現的是該介面二點那個方法,在函式的引數中,我們又明知道它的引數型別,那我們直接可以省略new IMyInterface,編譯器自動推測出上下文環境是要實現IMyInterface:
public static void main(String[] args) { //函式式介面的例項可以由lambda表示式建立 // 函式體內只有一行程式碼可以省略大括號如果有return可以直接省略return MyInterfaceLambda.printHello(() -> "HELLO"); IMyInterface iMyInterface = () -> "HELLO"; // 相當於new IMyInterface的實現 System.out.println(iMyInterface); System.out.println(iMyInterface.getClass()); System.out.println(iMyInterface.getClass().getSuperclass()); System.out.println(iMyInterface.getClass().getInterfaces().length); System.out.println(iMyInterface.getClass().getInterfaces()[0]); }
1 HELLO 2 com.MyInterfaceLambda$$Lambda$2/1096979270@404b9385 class com.MyInterfaceLambda$$Lambda$2/1096979270 class java.lang.Object 1 interface com.IMyInterface
函式式介面的例項除了可以使用Lambda表示式建立,還可以由方法引用、構造方法引用建立。
public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); System.out.println("---------jdk1.8---------"); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); System.out.println("---------jdk1.8 lambda+函式式介面---------"); list.forEach(integer -> System.out.println(integer)); System.out.println("---------jdk1.8 方法引用+函式式介面---------"); list.forEach(System.out::println); }
lambda表示式和方法引用必須依附於一類特別的物件型別,它們不能單獨存在:
一個lambda表示式到底是什麼型別的,必須由上下文資訊才能斷定,
() -> {};
因為對於一個這樣的表示式來說,你根本不知道方法名稱是什麼,它只是一個匿名函式的表示方式,但是一旦給定了上下文Consumer之後,我們就知道了這個lambda表示式的型別是Consumer型別,表示式的內容是對函式式介面唯一抽象方法進行重寫。
當我們點選lambda表示式的"箭頭"或者點選方法引用的"雙冒號",將會自動進入函式式介面中,表示當前的lambda或方法引用是該函式式介面的實現。
在之前我們要想做某一個行為,比如說下面的加和乘,要先定義好,然後在呼叫。
package com; public class TransferAction { public int add(int i, int j) { return i + j; } public int mul(int i, int j) { return i * j; } public static void main(String[] args) { TransferAction transferAction = new TransferAction(); System.out.println(transferAction.add(1, 2)); System.out.println(transferAction.mul(2, 3)); } }
public class TransferAction { public int add(int i, int j) { return i + j; } public int mul(int i, int j) { return i * j; } public int cal(int i, Function<Integer, Integer> function) { return function.apply(i); } public static void main(String[] args) { TransferAction transferAction = new TransferAction(); // 之前要想做某個具體的行為要先定義才能呼叫 System.out.println(transferAction.add(1, 2)); System.out.println(transferAction.mul(2, 3)); // 現在lambda可以傳遞一個行為,到底是做什麼操作由呼叫者決定, // 而不需要在提前定義具體的行為了,我們只需要把行為抽象出來,在根據 // 實際場景傳入不同的行為 System.out.println(transferAction.cal(1, (value) -> 1 + value)); System.out.println(transferAction.cal(2, (value) -> 3 * value)); } }
方法引用是lambda表示式的語法糖,lambda是使用箭頭的方式表示的,而方法引用則是使用雙冒號的方式表示。
應用場景:當你的lambda表示式只有一行程式碼,並且這行程式碼恰好呼叫了一個已經存在的方法時就可以使用方法引用替換掉lambda表示式。
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); // lambda表示式與方法引用: studentList.forEach(student -> System.out.println(student)); studentList.forEach(System.out::println); // lambda即可以表述簡單操作,也可以在方法體內進行復雜的操作, // 然後並不是所有的操作都是直接寫在lambda表示式函式體內的, // 我們可以語法糖直接引用方法(複雜操作需要在具體引用的方法中) studentList.forEach(student -> { // 更復雜的操作 System.out.println(student.getName()); System.out.println(student.getScore()); }); // 本質都是lambda表示式,都必須依託與類,都是作為函式式介面的的實現。 Consumer<Student> studentConsumer1 = student -> System.out.println(student); Consumer<Student> studentConsumer2 = System.out::println; System.out.println(studentConsumer1); // methodReference.MainTest$$Lambda$4/1452126962@5fd0d5ae System.out.println(studentConsumer2); // methodReference.MainTest$$Lambda$5/931919113@2d98a335 }
當要傳遞給lambda表示式方法體的操作,已經有了實現,可以使用方法引用。要求是:實現函式式介面的抽象方法的引數列表和返回值型別,必須與方法引用的方法引數列表和返回值型別保持一致。
注意:方法呼叫和方法引用是完全不同的概念,方法呼叫是直接執行這個方法【是直接執行某個行為,沒有中間商】,而方法引用表述的是對一個方法的指向,如果真正要執行某個方法時,才去找到這個方法執行【個人理解更像是代理,方法引用和lambda都像是代理一樣,本身只是指代某個抽象類的實現,真正要執行的時候才去在抽象類的具體實現裡執行某個行為】。
類名::靜態方法名 例項名::例項方法(非靜態) 類名::例項方法(非靜態) 構造方法引用: 類名::new
package methodReference; public class Student { private int score; private String name; public Student(int score, String name) { this.score = score; this.name = name; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static int comepareStudentByScoue(Student student1, Student student2) { return student1.getScore() - student2.getScore(); } public static int comepareStudentByName(Student student1, Student student2) { return student1.getName().compareToIgnoreCase(student2.getName()); } }
compareStudentName和Comparator函式式介面的抽象方法引數型別、個數相同,返回值型別相同,符合方法引用的條件:
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); // studentList.sort((studentParam1, studentParam2) -> Student.comepareStudentByScoue(studentParam1, studentParam2)); // studentList.forEach(student -> System.out.println(student.getScore())); // 註釋上面的排序,防止對下面排序影響 studentList.sort(Student::comepareStudentByScoue); studentList.forEach(student -> System.out.println(student.getScore())); // studentList.sort((studentParam1, studentParam2) -> Student.comepareStudentByName(studentParam1, studentParam2)); // studentList.forEach(student -> System.out.println(student.getName())); // studentList.sort(Student::comepareStudentByName); // studentList.forEach(student -> System.out.println(student.getName())); Comparator<Student> comparator = Student::comepareStudentByName; System.out.println(comparator); }
package methodReference; public class CompareStudent { public int compareStudentByScore(Student s1,Student s2) { return s1.getScore() - s2.getScore(); } public int compareStudentByName(Student s1,Student s2) { return s1.getName().compareToIgnoreCase(s2.getName()); } }
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); /** * 方法引用: * 類例項::例項方法 */ CompareStudent compareStudent = new CompareStudent(); studentList.sort(compareStudent::compareStudentByScore); // studentList.sort(compareStudent::compareStudentByName); studentList.forEach(student -> System.out.println(student.getScore())); }
前面講的兩種方法引用,一:類::靜態方法,因為是靜態方法,靜態方法屬於類,所以可以通過類::靜態方法的方式呼叫,二:物件::例項方法,因為例項方法屬於物件,所以可以通過物件::例項方法呼叫。現在是類::例項方法,例項方法一定是由物件來呼叫的,然而現在卻可以類::例項方法,這是為何?
現在我們來改造Student類裡的兩個比較方法,首先這兩個方法本身沒有用到Student類的任何屬性或方法,它們這個類是沒有任何關係的,它們可以寫在任意一個其他類裡,我們現在改造:在加入如下兩個方法:
public int compareStudentScore(Student student) { return this.getScore() - student.getScore(); } public int compareStudentName(Student student) { return this.getName().compareToIgnoreCase(student.getName()); }
這兩個方法依賴於這個類的呼叫者,即當前Student物件,然後和傳入的物件進行比較。
public static void main(String[] args) { Student s1 = new Student(20, "maomao"); Student s2 = new Student(60, "keke"); Student s3 = new Student(50, "jiji"); Student s4 = new Student(10, "mimi"); List<Student> studentList = Arrays.asList(s1, s2, s3, s4); /** * 方法引用 * 類::例項方法 */ // studentList.sort((studentParam1, studentParam2) -> Student.comepareStudentByScoue(studentParam1, studentParam2)); studentList.sort(Student::compareStudentScore); studentList.forEach(student -> System.out.println(student.getScore())); }
sort方法的Comparator函式式介面是接受兩個引數的,但是現在我們的compareStudentScore只是接受了一個引數。首先要明確一點例項方法一定是由例項物件來呼叫的,那這個呼叫物件是誰?就是這個sort方法lambda表示式的第一個引數來去呼叫compareStudentScore(注意:如果接受多個引數,那麼除了第一個引數,其餘引數都作為compareStudentScore方法的引數傳遞進去),所以真正呼叫排序方法compareStudentScore是那兩個待排序物件的第一個,另一個作為引數傳遞到這個排序方法內。
List<String> cites = Arrays.asList("beijing", "tianjing", "shanghai", "qingdao"); // Collections.sort(cites, (cite1, cite2) -> cite1.compareToIgnoreCase(cite2)); // cites.forEach(cite -> System.out.println(cite)); Collections.sort(cites, String::compareToIgnoreCase); cites.forEach(cite -> System.out.println(cite));
public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str); }
是例項方法,只接受一個引數,當我們使用lambda時傳入了兩個引數,所以在使用方法引用時,第一個引數cite1作為方法的呼叫者,第二個引數作為該方法的引數被傳入。
public class MainTest { public static void main(String[] args) { MainTest mainTest = new MainTest(); System.out.println(mainTest.getStr(String::new));// 點選new自動定位到無引數的建構函式 System.out.println(mainTest.getString("hello", String::new)); // 點選new自動定位到有引數的建構函式 } public String getStr(Supplier<String> stringSupplier) { return "hey" + stringSupplier.get(); } public String getString(String hello, Function<String, String> function) { return function.apply(hello); } }