Java 8教程
- 2020 年 3 月 18 日
- 筆記
Java 8 – 簡介
Java 8於2014年初發佈。在java 8中,大多數關於功能的是lambda表達式。它還有許多其他重要功能,如默認方法,Streams API和新的日期/時間API。讓我們在java 8中了解這些新功能的例子。
—————–來自小馬哥的故事
Lambda表達
我們許多已經使用高級語言(如Scala)的人們並不知道Lambda表達式。在編程中,Lambda表達式(或函數)只是一個匿名函數,即一個沒有名稱而不被綁定到一個標識符的函數。它們被完全寫在需要的地方,通常作為其他功能的參數。
lambda表達式的基本語法是:
either (parameters) -> expression or (parameters) -> { statements; } or () -> expression
典型的lambda表達式示例將如下所示:
(x, y) -> x + y //This function takes two parameters and return their sum.
請注意,根據x和y的類型,方法可能會在多個地方使用。參數可以匹配int,或整數或簡單的字符串。基於上下文,它將添加兩個整數或兩個字符串。
編寫lambda表達式的規則
1. lambda表達式可以具有零個,一個或多個參數。
2. 可以顯式聲明參數的類型,也可以從上下文推斷參數的類型。
3. 多個參數用強制括號括起來,用逗號分隔。空括號用於表示一組空的參數。
4. 當有一個參數時,如果推斷出它的類型,則不必使用括號。例如a – > return a * a。
5. lambda表達式的主體可以包含零個,一個或多個語句。
6. 如果lambda表達式的主體具有單個語句,則大括號不是強制性的,匿名函數的返回類型與body表達式的返回類型相同。當身體中有多於一個的聲明必須用大括號括起來。
閱讀更多:Java 8 Lambda表達式教程
函數式接口
函數式接口也稱為單抽象方法接口(SAM接口)。正如名字所暗示的,他們只允許一個抽象方法。Java 8引入了一個注釋,即@FunctionalInterface當您注釋的界面違反了函數式接口的合同時,可以用於編譯器級錯誤。
典型的函數式接口示例:
@FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); }
請注意,即使@FunctionalInterface省略注釋,函數式接口也是有效的。它僅用於通知編譯器在界面內強制執行單個抽象方法。
此外,由於默認方法不是抽象的,您可以隨意添加默認方法到您的函數式接口儘可能多的你喜歡。
要記住的另一個重要的一點是,如果一個接口聲明一個覆蓋其中一個公共方法的抽象方法java.lang.Object,也不會計入接口的抽象方法計數,因為接口的任何實現都將具有來自java.lang.Object或其他地方的實現。例如,下面是完全有效的函數式接口。
@FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); @Override public String toString();//Overridden from Object class @Override public boolean equals(Object obj);//Overridden from Object class }
閱讀更多:Java 8函數式接口教程
默認方法
Java 8允許您在接口中添加非抽象方法。這些方法必須被聲明為默認方法。java 8中引入了默認方法來啟用lambda表達式的功能。
默認方法使您能夠向庫的接口添加新功能,並確保與舊版本的這些接口編寫的代碼的二進制兼容性。
我們來看一個例子:
public interface Moveable { default void move(){ System.out.println("I am moving"); } }
Moveable接口定義了一個方法,move()並提供了一個默認的實現。如果任何類實現了這個接口,那麼它不需要實現它自己的move()方法版本。它可以直接打電話instance.move()。例如
public class Animal implements Moveable{ public static void main(String[] args){ Animal tiger = new Animal(); tiger.move(); } } Output: I am moving
如果類願意定製move()方法的行為,那麼它可以提供它自己的自定義實現並覆蓋該方法。
Reda更多:Java 8默認方法教程
Streams
另一個重大改變引入了Java 8 Streams API,它提供了一種以各種方式處理一組數據的機制,可以包括過濾,轉換或可能對應用程序有用的任何其他方式。
Java 8中的Streams API支持一種不同類型的迭代,其中您只需定義要處理的項目集合,對每個項目執行的操作以及要存儲這些操作的輸出的位置。
StreamsAPI的一個例子。在此示例中,items是String值的集合,並且要刪除以某些前綴文本開頭的條目。
List<String> items; String prefix; List<String> filteredList = items.stream().filter(e -> (!e.startsWith(prefix))).collect(Collectors.toList());
這裡items.stream()表示我們希望items使用Streams API處理集合中的數據。
閱讀更多:Java 8內部與外部迭代
日期/時間API更改
新的日期和時間API /類(JSR-310)也稱為ThreeTen,它們簡單地改變了在java應用程序中處理日期的方式。
日期
日期類甚至已經過時了。旨在取代Date類新的類LocalDate,LocalTime和LocalDateTime。
1. 本LocalDate類代表一個日期。沒有時間或時區的表示。
2. 這個LocalTime班代表一個時間。沒有表示日期或時區。
3. 本LocalDateTime類代表一個日期-時間。沒有時區的表示。
4. 如果您希望使用區域信息的日期功能,那麼拉姆達為您提供額外的3類類似於一個,即上面OffsetDate,OffsetTime和OffsetDateTime。時區偏移可以用「+05:30」或「歐洲/巴黎」格式表示。這是通過使用另一個類來完成的ZoneId。
LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.of(12, 20); LocalDateTime localDateTime = LocalDateTime.now(); OffsetDateTime offsetDateTime = OffsetDateTime.now(); ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
時間戳和持續時間
為了表示任何時刻的具體時間戳,需要使用的類是Instant。本Instant類表示時間納秒的精度瞬間。Instant上的操作包括與另一個進行比較,Instant並添加或減少持續時間。
Instant instant = Instant.now(); Instant instant1 = instant.plus(Duration.ofMillis(5000)); Instant instant2 = instant.minus(Duration.ofMillis(5000)); Instant instant3 = instant.minusSeconds(10);
Durationclass是Java語言第一次帶來的全新概念。它代表兩個時間戳之間的時差。
Duration duration = Duration.ofMillis(5000); duration = Duration.ofSeconds(60); duration = Duration.ofMinutes(10);
Duration處理小時間單位,例如毫秒,秒,分鐘和小時。它們更適合與應用程序代碼交互。要與人交互,你需要獲得更多的持續時間,這是與Period課堂呈現的。
Period period = Period.ofDays(6); period = Period.ofMonths(6); period = Period.between(LocalDate.now(), LocalDate.now().plusDays(60));
閱讀更多:Java 8日期和時間API更改
Java 8 Lambda表達式教程
一個非常全新而令人興奮的功能,java 8搭配它,是Lambda表達式。我們許多已經從事高級語言(如scala)工作的人們並不為人所知。事實上,如果你看歷史,並嘗試在過去二十年里發現java中的任何語言改進,你將無法回想起許多令人興奮的事情。在過去十年中,只有少數並發類,泛型,如果您同意注釋,在java中是顯着的增加。Lambda表達式打破了這場乾旱,感覺像一個愉快的禮物。
Java中的lambda表達式是什麼?
在編程中,Lambda表達式(或函數)只是一個匿名函數,即一個沒有名稱而不被綁定到一個標識符的函數。
換句話說,lambda表達式是作為常量值給出的無名函數,並且準確地寫在需要的地方,通常作為一些其他函數的參數。
Lambda表達式最重要的特徵是它們在其外觀的上下文中執行。所以,類似的lambda表達式可以在某些其他上下文中執行不同的方式(即邏輯將是相同的,但結果會根據傳遞給函數的不同參數而不同)。 以上定義充滿了關鍵字,只有當您已經深入了解什麼是lambda表達式才能被理解。所以,一旦你在下一節更好地了解了lambda表達式,我建議你重新閱讀上面的段落。
所以,很明顯,lambda是沒有名稱和標識符的某種功能。那麼什麼大事呢?為什麼大家都很興奮?
答案在於面向對象編程(OOP)的功能編程所帶來的好處。大多數OOP語言圍繞對象和實例進行演化,僅對待他們的一Streams公民。另一個重要的實體即功能佔據了位置。這在java中尤其如此,其中函數不能存在於對象之外。一個函數本身並不意味着java中的任何東西,直到它與某個對象或實例相關。
但是在函數式編程中,您可以定義函數,給它們引用變量,並將它們作為方法參數傳遞。JavaScript是一個很好的例子,您可以將回調方法傳遞給Ajax調用。這是非常有用的功能,一開始就缺少java。現在用java 8,我們也可以使用這些lambda表達式。
典型的lambda表達式語法將如下所示:
(x, y) -> x + y //This function takes two parameters //and return their sum.
現在基於x和y的類型,方法可以在多個地方使用。參數可以匹配int,或整數或簡單的字符串。基於上下文,它將添加兩個整數或兩個字符串。
句法:
lambda表達式的基本語法是
either (parameters) -> expression or (parameters) -> { statements; } or () -> expression
讓我們看一些例子:
(int a, int b) ->a * b // takes two integers and returns their multiplication (a, b) -> a - b // takes two numbers and returns their difference () -> 99 // takes no values and returns 99 (String a) -> System.out.println(a) // takes a string, prints its value to the console, and returns nothing a -> 2 * a // takes a number and returns the result of doubling it c -> { //some complex statements } // takes a collection and do some procesing
我們來確定一些可以幫助我們編寫lambda表達式的規則:
● lambda表達式可以具有零個,一個或多個參數。
● 可以顯式聲明參數的類型,也可以從上下文推斷參數的類型。
● 多個參數用強制括號括起來,用逗號分隔。空括號用於表示一組空的參數。
● 當有一個參數時,如果推斷出它的類型,則不必使用括號。例如a – > return a * a。
● lambda表達式的主體可以包含零個,一個或多個語句。
● 如果lambda表達式的主體具有單個語句,則大括號不是強制性的,匿名函數的返回類型與body表達式的返回類型相同。當身體中有多於一個的聲明必須用大括號括起來。
所以,我們簡要概述了什麼是lambda表達式。如果您感到遺失並且無法關聯,請耐心等待,如何在Java編程語言中使用。我們將在接下來的30分鐘內做出一切。所以我們來吧
在深入討論lambda表達式和java編程之前,您還必須了解功能接口。這太重要了
什麼是函數式接口?
單抽象方法接口(SAM接口)不是一個新概念。這意味着只有一種方法的接口。在java中,我們已經有了很多這樣的SAM接口的例子。從java 8,他們也將被稱為功能接口。Java 8通過使用新的注釋(即@FunctionalInterface)標記這些接口來強制執行單一責任規則。
例如,Runnable界面的新定義如下:
@FunctionalInterface public interface Runnable { public abstract void run(); }
如果您嘗試在任何函數式接口中添加新方法,編譯器將不允許您執行此操作,並將拋出編譯時錯誤。
到現在為止還挺好。但是,它們如何與Lambda表達式相關?讓我們找出答案。
我們知道Lambda表達式是沒有名稱的匿名函數,它們(主要)被傳遞給其他函數作為參數。那麼在java方法中,參數總是有一個類型,並且這種類型的信息被查找以確定在方法重載或甚至簡單的方法調用的情況下需要調用哪個方法。因此,基本上每個lambda表達式也必須可轉換為某些類型才能被接受為方法參數。那麼lambda表達式轉換的類型總是功能接口類型。
讓我們以一個例子來理解它。如果我們要編寫一個在控制台中打印「howtodoinjava」的線程,那麼最簡單的代碼將是:
new Thread(new Runnable() { @Override public void run() { System.out.println("howtodoinjava"); } }).start();
如果我們使用lambda表達式來執行此任務,那麼代碼將是:
new Thread( () -> { System.out.println("My Runnable"); } ).start();
我們還看到,Runnable是一個函數式接口,具有單一方法run()。因此,當您將lambda表達式傳遞給Thread類的構造函數時,編譯器將嘗試將表達式轉換為等效的Runnable代碼,如第一個代碼示例所示。如果編譯器成功,那麼一切運行正常,如果編譯器無法將表達式轉換為等效的實現代碼,則會抱怨。這裡,在上面的例子中,lambda表達式被轉換為Runnable類型。
簡單來說,lambda表達式是函數式接口的一個實例。但是,lambda表達式本身並不包含其實現的功能接口的信息; 該信息是從其使用的上下文推導出來的。
幾個Lambda表達式的例子
我列出了一些代碼示例,您可以閱讀和分析如何在日常編程中使用lambda表達式。
1)迭代列表並執行一些操作
List pointList = new ArrayList(); pointList.add("1"); pointList.add("2");
pointList.forEach(p -> { System.out.println(p); //Do more work } );
2)創建一個新的runnable並傳遞給線程
new Thread( () -> System.out.println("My Runnable"); ).start();
3)按照他們的名字對僱員對象進行排序
public class LambdaIntroduction { public static void main (String[] ar){ Employee[] employees = { new Employee("David"), new Employee("Naveen"), new Employee("Alex"), new Employee("Richard")}; System.out.println("Before Sorting Names: "+Arrays.toString(employees)); Arrays.sort(employees, Employee::nameCompare); System.out.println("After Sorting Names "+Arrays.toString(employees)); } } class Employee { String name; Employee(String name) { this.name = name; } public static int nameCompare(Employee a1, Employee a2) { return a1.name.compareTo(a2.name); } public String toString() { return name; } } Output: Before Sorting Names: [David, Naveen, Alex, Richard] After Sorting Names [Alex, David, Naveen, Richard]
4)將事件偵聽器添加到GUI組件
JButton button = new JButton("Submit"); button.addActionListener((e) -> { System.out.println("Click event triggered !!"); });
以上是java 8中的lambda表達式的非常基本的例子。我將不時提出更有用的示例和代碼示例。
Java 8方法引用與示例
在Java 8中,您可以使用class::methodName類型語法引用類或對象的方法。讓我們在java 8中了解不同類型的可用方法引用。
方法參考的類型 – 快速概述
Java 8有四種類型的方法引用。
方法參考 |
描述 |
例 |
---|---|---|
參考靜態方法 |
用於引用類中的靜態方法 |
Math::max 相當於 Math.max(x,y) |
從實例引用instance方法 |
請參考實例方法使用提供的對象的引用 |
System.out::println 相當於System.out.println(x) |
從類類型引用instance方法 |
在對上下文提供的對象的引用上調用實例方法 |
String::length 相當於str.length() |
參考構造函數 |
引用構造函數 |
ArrayList::new 相當於 new ArrayList() |
引用靜態方法 – Class :: staticMethodName
一個使用Math.max()靜態方法的例子。
List<Integer> integers = Arrays.asList(1,12,433,5); Optional<Integer> max = integers.stream().reduce( Math::max ); max.ifPresent(value -> System.out.println(value));
輸出:
433
引用實例方法從實例 – ClassInstance :: instanceMethodName
在上面的例子中,我們System.out.println(value)用來打印找到的最大值。我們可以System.out::println用來打印值。
List<Integer> integers = Arrays.asList(1,12,433,5); Optional<Integer> max = integers.stream().reduce( Math::max ); max.ifPresent( System.out::println );
輸出:
433
引用類類型的實例方法 – Class :: instanceMethodName
在這個例子中,s1.compareTo(s2)被稱為String::compareTo。
List<String> strings = Arrays .asList("how", "to", "do", "in", "java", "dot", "com"); List<String> sorted = strings .stream() .sorted((s1, s2) -> s1.compareTo(s2)) .collect(Collectors.toList()); System.out.println(sorted); List<String> sortedAlt = strings .stream() .sorted(String::compareTo) .collect(Collectors.toList()); System.out.println(sortedAlt);
輸出:
[com,do,dot,how,in,java,to] [com,do,dot,how,in,java,to]
參考構造函數 – Class :: new
可以更新第一種方法來創建從1到100的整數列表。使用lambda表達式相當容易。要創建一個新的實例ArrayList,我們有使用ArrayList::new。
List<Integer> integers = IntStream .range(1, 100) .boxed() .collect(Collectors.toCollection( ArrayList::new )); Optional<Integer> max = integers.stream().reduce(Math::max); max.ifPresent(System.out::println);
輸出:
99
這是Java 8 lambda增強功能中的4種類型的方法引用。
Java 8默認方法教程
我們了解了Lambda表達式和函數式接口。現在,讓我們繼續討論,並談談另一個相關的功能,即默認方法。那麼這對java開發者來說真的是革命性的。直到java 7,我們已經了解了很多關於接口的事情,所有這些事情都在我們的頭腦中,每當我們編寫代碼或設計應用程序。在引入默認方法後,其中一些概念將從java 8大幅改變。
java 8中的默認方法是什麼?
默認方法使您能夠向庫的接口添加新功能,並確保與舊版本的這些接口編寫的代碼的二進制兼容性。 顧名思義,java 8中的默認方法是默認的。如果不覆蓋它們,則它們將由調用者類調用的方法。它們在接口中定義。
我們來看一個例子:
public interface Moveable { default void move(){ System.out.println("I am moving"); } }
可移動界面定義了一個方法move(); 並提供了默認實現。如果任何類實現了這個接口,那麼它不需要實現它自己的move()方法。它可以直接調用instance.move();
public class Animal implements Moveable{ public static void main(String[] args){ Animal tiger = new Animal(); tiger.move(); } } Output: I am moving
而且如果類願意自定義行為,那麼它可以提供它自己的自定義實現並覆蓋該方法。現在自己定製的方法會被調用。
public class Animal implements Moveable{ public void move(){ System.out.println("I am running"); } public static void main(String[] args){ Animal tiger = new Animal(); tiger.move(); } } Output: I am running
這不是在這裡完成的。最好的部分來自以下好處:
1.靜態默認方法:可以在接口中定義靜態默認方法,該方法對於實現此接口的所有類的實例都可用。這使您更容易在庫中組織幫助方法; 您可以將靜態方法保留在同一接口中的接口,而不是單獨的類中。這使您能夠定義類的方法,然後與所有子類共享。
2.它們為您提供了一種非常需要的功能,即使在不接觸代碼的情況下也可以添加多個類的功能。只需在界面中添加一個默認方法即可實現。
為什麼java 8中需要默認的方法?
這是下一個面試問題的好候選人。最簡單的答案是在java中啟用lambda表達式的功能。Lambda表達式基本上是函數式接口的類型。為了無縫地支持lambda表達式,所有的核心類都必須被修改。但是,像java.util.List這樣的核心類不僅在JDK類中實現,而且還在數千個客戶端代碼中實現。核心課程的任何不相容的變化肯定會回火,根本不會被接受。
默認方法打破了這個死鎖,並允許在核心類中添加對函數式接口的支持。我們來看一個例子。下面是一個添加到java.lang.Iterable的方法。
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
在java 8之前,如果你必須迭代一個java集合,那麼你會得到一個迭代器實例,並調用它的下一個方法,直到hasNext()返回false。這是常見的代碼,已被我們日常使用的數千次使用。語法也是一樣的。所以我們可以使它緊湊,所以它只需要一行代碼,仍然像以前一樣為我們做這個工作。以上功能是這樣做的。
現在要對列表中的每個項目進行迭代和執行一些簡單的操作,所有你需要做的是:
import java.util.ArrayList; import java.util.List; public class Animal implements Moveable{ public static void main(String[] args){ List<Animal> list = new ArrayList(); list.add(new Animal()); list.add(new Animal()); list.add(new Animal()); //Iterator code reduced to one line list.forEach((Moveable p) -> p.move()); } }
所以在這裡,一個額外的方法已添加到列表中,而不會破壞它的任何自定義實現。很久以來,它一直非常需要java的功能。現在跟我們在一起
調用默認方法時如何解決衝突?
到現在為止還挺好。我們所有的基礎知識都很好。現在轉向複雜的事情。在java中,一個類可以實現N個接口。另外,接口也可以擴展另一個接口。如果任何默認方法在由單個類實現的兩個這樣的接口中聲明。那麼明顯的類會混淆哪個方法來調用。
此衝突解決的規則如下:
1)最喜歡的是在類中被覆蓋的方法。如果在匹配任何東西之前找到,它們將被匹配並調用。
2)選擇「最具體的默認提供界面」中具有相同簽名的方法。這意味着如果類Animal實現了兩個接口,即可移動和可移動,以便Walkable擴展Moveable。那麼Walkable是這裡最具體的界面,如果方法簽名匹配,將從這裡選擇默認方法。 3)如果「可移動」和「可步行」是獨立的接口,那麼會發生嚴重的衝突情況,編譯器會抱怨然後無法確定。您必須通過提供額外的信息幫助編譯器,從哪個接口應該調用默認方法。例如
Walkable.super.move(); //or Moveable.super.move();
Java 8函數式接口教程
我們了解了lambda表達式和功能接口的一些基礎知識。在本教程中,我將在函數式接口的上下文中擴展主題。
什麼是函數式接口
函數式接口是java 8中的新增功能,它們在其中只允許一個抽象方法。這些接口也稱為單抽象方法接口(SAM接口)。這些可以使用Lambda表達式,方法引用和構造函數引用來表示。Java 8引入了一個注釋,即@FunctionalInterface,當您注釋的界面違反了函數式接口的合同時,可以將其用於編譯器級錯誤。
我們來構建我們的第一個函數式接口:
package functionalInterfaceExample; @FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); }
我們嘗試添加另一個抽象界面:
package functionalInterfaceExample; @FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); public void doSomeMoreWork(); }
以上將導致下面給出的編譯器錯誤:
Unexpected @FunctionalInterface annotation @FunctionalInterface ^ MyFirstFunctionalInterface is not a functional interface multiple non-overriding abstract methods found in interface MyFirstFunctionalInterface
Do not do函數式接口
以下是允許的東西和哪些不在函數式接口中的列表。
A)如上所述,在任何函數式接口中只允許一種抽象方法。函數式接口中不允許使用第二抽象方法。如果我們刪除@FunctionInterface注釋,那麼我們被允許添加另一個抽象方法,但它將使接口非功能接口。
B)即使省略@FunctionalInterface註解,函數式接口也是有效的。它僅用於通知編譯器在界面內強制執行單個抽象方法。
C)在概念上,函數式接口只有一個抽象方法。由於默認方法有一個實現,它們不是抽象的。由於默認方法不是抽象的,您可以隨意添加默認方法到您的函數式接口,儘可能多的你喜歡。 以下是有效的函數式接口:
package functionalInterfaceExample; @FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); default void doSomeMoreWork1(){ //Method body } default void doSomeMoreWork2(){ //Method body } }
D)如果接口聲明一個覆蓋java.lang.Object的公共方法之一的抽象方法,那麼這個方法也不會計入接口的抽象方法計數,因為接口的任何實現都將具有java.lang.Object或別處。比如,Comparator是一個函數式接口,儘管它聲明了兩種抽象方法。為什麼?因為這些抽象方法之一「equals()」在Object類中具有與public方法相同的簽名。
例如下面的界面是一個有效的函數式接口。
package functionalInterfaceExample; @FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); @Override public String toString(); //Overridden from Object class @Override public boolean equals(Object obj); //Overridden from Object class }
本文由 小馬哥 創作,採用 知識共享署名4.0 國際許可協議進行許可 本站文章除註明轉載/出處外,均為本站原創或翻譯,轉載前請務必署名