本篇文章給大家?guī)?lái)了關(guān)于java的相關(guān)知識(shí),其中主要介紹了關(guān)于反射、枚舉、lambda表達(dá)式的相關(guān)內(nèi)容,包括了反射的概述、使用以及優(yōu)缺點(diǎn)、自定義構(gòu)造枚舉對(duì)象等等內(nèi)容,下面一起來(lái)看一下,希望對(duì)大家有幫助。
程序員必備接口測(cè)試調(diào)試工具:立即使用
Apipost = Postman + Swagger + Mock + Jmeter
Api設(shè)計(jì)、調(diào)試、文檔、自動(dòng)化測(cè)試工具
后端、前端、測(cè)試,同時(shí)在線協(xié)作,內(nèi)容實(shí)時(shí)同步
推薦學(xué)習(xí):《java視頻教程》
一. 反射
1. 反射的概述
- 什么是反射
Java的反射(reflection)機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意方法和屬性,既然能拿到那么,我們就可以修改部分類型信息;這種動(dòng)態(tài)獲取信息以及動(dòng)態(tài)調(diào)用對(duì)象方法的功能稱為java語(yǔ)言的反射(reflection)機(jī)制。
- 反射的基本信息
Java程序中許多對(duì)象在運(yùn)行時(shí)會(huì)出現(xiàn)兩種類型:運(yùn)行時(shí)類型(RTTI)和編譯時(shí)類型,例如Person p = new Student();這句代碼中p在編譯時(shí)類型為Person,運(yùn)行時(shí)類型為Student。程序需要在運(yùn)行時(shí)發(fā)現(xiàn)對(duì)象和類的真實(shí) 信心。而通過(guò)使用反射程序就能判斷出該對(duì)象和類屬于哪些類。
- 反射的用途
- 在日常的第三方應(yīng)用開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)遇到某個(gè)類的某個(gè)成員變量、方法或是屬性是私有的或是只對(duì)系統(tǒng)應(yīng)用開(kāi)放,這時(shí)候就可以利用Java的反射機(jī)制通過(guò)反射來(lái)獲取所需的私有成員或是方法 。
- 反射最重要的用途就是開(kāi)發(fā)各種通用框架,比如在spring中,我們將所有的類Bean交給spring容器管理,無(wú)論是XML配置Bean還是注解配置,當(dāng)我們從容器中獲取Bean來(lái)依賴注入時(shí),容器會(huì)讀取配置,而配置中給的就是類的信息,spring根據(jù)這些信息,需要?jiǎng)?chuàng)建那些Bean,spring就動(dòng)態(tài)的創(chuàng)建這些類。
2. 反射的使用
2.1 反射常用的類
類名 | 用途 |
---|---|
Class類 | 代表類的實(shí)體,在運(yùn)行的Java應(yīng)用程序中表示類和接口 |
Field類 | 代表類的成員變量/類的屬性 |
Method類 | 代表類的方法 |
Constructor類 | 代表類的構(gòu)造方法 |
Class代表類的實(shí)體,在運(yùn)行的Java應(yīng)用程序中表示類和接口 ,Java文件被編譯后,生成了.class
文件,JVM此時(shí)就要去加載.class
文件 ,被編譯后的Java文件,也就是.class
文件會(huì)被JVM解析為一個(gè)對(duì)象,這個(gè)對(duì)象就是 java.lang.Class
。這樣當(dāng)程序在運(yùn)行時(shí),每個(gè)java文件就最終變成了Class類的一個(gè)實(shí)例。我們通過(guò)Java的反射機(jī)制應(yīng)用到這個(gè)實(shí)例,就可以去獲得甚至去添加改變Class對(duì)象所對(duì)應(yīng)類的屬性和動(dòng)作, 使得這個(gè)類成 為一個(gè)動(dòng)態(tài)的類 .
2.2 通過(guò)反射獲取Class對(duì)象
反射獲取對(duì)象一共有三種方式:
- 通過(guò)Class類中的通過(guò)forName方法。
- 通過(guò)類名.class獲取。
- 通過(guò)使用實(shí)例對(duì)象調(diào)用getclass方法獲取。
下面我們演示使用三種方式得到的對(duì)象是否是同一個(gè)對(duì)象,我們來(lái)獲取相關(guān)Student類的類信息對(duì)象。
Student類定義:
class Student{ //私有屬性name private String name = "rong"; //公有屬性age public int age = 18; //不帶參數(shù)的構(gòu)造方法 public Student(){ System.out.println("Student()"); } //帶兩個(gè)參數(shù)的構(gòu)造方法 private Student(String name,int age) { this.name = name; this.age = age; System.out.println("Student(String,name)"); } private void eat(){ System.out.println("i am eating"); } public void sleep(){ System.out.println("i am sleeping"); } private void function(String str) { System.out.println("私有方法function被調(diào)用:"+str); } @Override public String toString() { return "Student{" + "name='" + name + ''' + ", age=" + age + '}'; }}
獲取對(duì)應(yīng)類的Class對(duì)象:
public static void main(String[] args) { //有3種方式可以獲取Class對(duì)象 //1.通過(guò)對(duì)象的getClass()方法 Student student1 = new Student(); Class<?> c1 = student1.getClass(); //2、通過(guò)類名.class獲取 Class<?> c2 = Student.class; //3. forName(“路徑”) Class<?> c3 = null; try { c3 = Class.forName("Student"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } System.out.println(c1.equals(c2)); System.out.println(c1.equals(c3)); System.out.println(c2.equals(c3));}
執(zhí)行結(jié)果:
通過(guò)結(jié)果發(fā)現(xiàn), 三種方式獲取到的對(duì)象是同一個(gè).
2.3 獲得Class類相關(guān)的方法
方法 | 用途 |
---|---|
getClassLoader() | 獲得類的加載器 |
getDeclaredClasses() | 返回一個(gè)數(shù)組,數(shù)組中包含該類中所有類和接口類的對(duì)象(包括私有的) |
forName(String className) | 根據(jù)類名返回類的對(duì)象 |
newInstance() | 創(chuàng)建類的實(shí)例 |
getName() | 獲得類的完整路徑名字 |
2.4 使用反射創(chuàng)建實(shí)例對(duì)象
首先獲取到Class對(duì)象,然后通過(guò)Class對(duì)象中的newInstance()方法創(chuàng)建實(shí)例對(duì)象 .
需要注意的是newInstance()方法的返回值的是一個(gè)泛型,在編譯階段會(huì)被擦除為Object,所以我們?cè)诮邮盏臅r(shí)候需要強(qiáng)制類型轉(zhuǎn)換 .
public static void main(String[] args) { //獲取相關(guān)類的Class對(duì)象 Class<?> c = Student.class; //使用newInstance方法創(chuàng)建實(shí)例 try { //需要進(jìn)行強(qiáng)轉(zhuǎn) Student student = (Student) c.newInstance(); System.out.println(student); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }}
執(zhí)行結(jié)果:
通過(guò)反射成功創(chuàng)建了Student類的實(shí)例。
2.5 使用反射獲取實(shí)例對(duì)象中的構(gòu)造方法
方法 | 用途 |
---|---|
getConstructor(Class…<?> parameterTypes) | 獲得該類中與參數(shù)類型匹配的公有構(gòu)造方法 |
getConstructors() | 獲得該類的所有公有構(gòu)造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 獲得該類中與參數(shù)類型匹配的構(gòu)造方法 |
getDeclaredConstructors() | 獲得該類所有構(gòu)造方法 |
使用反射獲取實(shí)例對(duì)象中構(gòu)造方法然后創(chuàng)建實(shí)例對(duì)象:
-
獲取Class對(duì)象。
-
通過(guò)上述的方法獲取構(gòu)造器。
-
如果獲取的是私有的構(gòu)造方法,則需要記得通過(guò)構(gòu)造器的
setAccessible
方法將訪問(wèn)權(quán)限開(kāi)啟。 -
調(diào)用構(gòu)造器中的
newInstance
方法獲取對(duì)象。
public static void main(String[] args) throws ClassNotFoundException { //1.獲取Clas對(duì)象 Class<?> c = Class.forName("Student"); //2.獲取指定參數(shù)列表的構(gòu)造器,演示獲取Student中的一個(gè)私有構(gòu)造器,參數(shù)傳形參列表類型 try { Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class); //獲取的私有構(gòu)造方法,需要打開(kāi)訪問(wèn)權(quán)限,默認(rèn)關(guān)閉 constructor.setAccessible(true); //3.根據(jù)獲取到的構(gòu)造器獲取實(shí)例對(duì)象,使用newInstance方法,需要傳入構(gòu)造器需要的參數(shù) Student student = (Student) constructor.newInstance("張三", 20); System.out.println(student); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }}
運(yùn)行結(jié)果:
獲取到了私有的構(gòu)造器,按照所傳參數(shù)創(chuàng)建實(shí)例對(duì)象。
2.6 通過(guò)反射獲取實(shí)例對(duì)象的屬性
方法 | 用途 |
---|---|
getField(String name) | 獲得某個(gè)公有的屬性對(duì)象 |
getFields() | 獲得所有公有的屬性對(duì)象 |
getDeclaredField(String name) | 獲得某個(gè)屬性對(duì)象 |
getDeclaredFields() | 獲得所有屬性對(duì)象 |
通過(guò)如下過(guò)程修改一個(gè)對(duì)象的私有屬性:
-
獲取Class對(duì)象。
-
創(chuàng)建或通過(guò)反射實(shí)例化一個(gè)需要修改其私有字段的類。
-
通過(guò)屬性名,調(diào)用上述getDeclaredField方法獲取對(duì)應(yīng)的屬性對(duì)象。
-
通過(guò)setAccessible方法設(shè)置為訪問(wèn)私有屬性開(kāi)權(quán)限。
-
通過(guò)Field對(duì)象的set方法,修改傳入對(duì)象中的對(duì)應(yīng)屬性。
public static void main(String[] args) { //1.獲取Class對(duì)象 Class<?> c = Student.class; try { //2.通過(guò)反射創(chuàng)建實(shí)例對(duì)象 Student student = (Student) c.newInstance(); //3.獲取私有屬性name Field field = c.getDeclaredField("name"); //4.給該私有屬性開(kāi)權(quán)限 field.setAccessible(true); //5.修改該私有屬性 field.set(student, "被反射修改的私有屬性"); System.out.println(student); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }}
運(yùn)行結(jié)果:
實(shí)例對(duì)象里面的私有屬性name被修改了。
2.7 通過(guò)反射獲取實(shí)例對(duì)象的方法
方法 | 用途 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 獲得該類某個(gè)公有的方法 |
getMethods() | 獲得該類所有公有的方法 |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 獲得該類某個(gè)方法 |
getDeclaredMethods() | 獲得該類所有方法 |
通過(guò)如下過(guò)程獲取Student對(duì)象中的私有方法function:
-
獲取相關(guān)Student類的Class對(duì)象。
-
創(chuàng)建或通過(guò)反射實(shí)例化一個(gè)Student。
-
通過(guò)class對(duì)象獲取到實(shí)例對(duì)象中的方法對(duì)象,參數(shù)為方法名,形參類型列表。
-
為獲取的私有方法開(kāi)訪問(wèn)權(quán)限。
-
通過(guò)invork方法調(diào)用方法。
public static void main(String[] args) { try { //1.獲取Class對(duì)象 Class<?> c = Class.forName("Student"); //2.獲取Student的一個(gè)實(shí)例對(duì)象 Student student = (Student) c.newInstance(); //3.通過(guò)class對(duì)象獲取實(shí)例的方法對(duì)象,參數(shù)為方法名,以及形參列表 Method method = c.getDeclaredMethod("function", String.class); //4.為私有方法開(kāi)訪問(wèn)權(quán)限 method.setAccessible(true); //5.通過(guò)invork方法調(diào)用方法 method.invoke(student, "傳入私有方法參數(shù)"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }}
運(yùn)行結(jié)果:
通過(guò)反射可以獲取到實(shí)例對(duì)象的私有方法并進(jìn)行調(diào)用。
2.8 獲得類中注解相關(guān)的方法
方法 | 用途 |
---|---|
getAnnotation(Class annotationClass) | 返回該類中與參數(shù)類型匹配的公有注解對(duì)象 |
getAnnotations() | 返回該類所有的公有注解對(duì)象 |
getDeclaredAnnotation(Class annotationClass) | 返回該類中與參數(shù)類型匹配的所有注解對(duì)象 |
getDeclaredAnnotations() | 返回該類所有的注解對(duì)象 |
3. 反射的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
-
對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法
-
增加程序的靈活性和擴(kuò)展性,降低耦合性,提高自適應(yīng)能力
-
反射已經(jīng)運(yùn)用在了很多流行框架如:Struts、Hibernate、Spring 等等。
缺點(diǎn):
-
使用反射會(huì)有效率問(wèn)題。會(huì)導(dǎo)致程序效率降低。
-
反射技術(shù)繞過(guò)了源代碼的技術(shù),因而會(huì)帶來(lái)維護(hù)問(wèn)題。反射代碼比相應(yīng)的直接代碼更復(fù)雜 。
二. 枚舉
1. 枚舉的概述
枚舉是在JDK1.5以后引入的; 關(guān)鍵字enum可以將一組具名的值的有限集合創(chuàng)建為一種新的類型,而這些具名的值可以作為常規(guī)的程序組件使用,這個(gè)新的類型就是枚舉。
主要用途是:將一組常量組織起來(lái),在這之前表示一組常量通常使用定義常量的方式:
public static int final RED = 1;public static int final GREEN = 2;public static int final BLACK = 3;
但是常量舉例有不好的地方,例如:可能碰巧有個(gè)數(shù)字1,但是他有可能誤會(huì)為是RED,現(xiàn)在我們可以直接用枚舉來(lái)進(jìn)行組織,這樣一來(lái),就擁有了類型,枚舉類型。而不是普通的整形1.
下面是創(chuàng)建一個(gè)Color枚舉類型 :
public enum Color { RED,BLUE,GREEN,YELLOW,BLACK;}
優(yōu)點(diǎn):將常量組織起來(lái)統(tǒng)一進(jìn)行管理
場(chǎng)景:錯(cuò)誤狀態(tài)碼,消息類型,顏色的劃分,狀態(tài)機(jī)等等…
本質(zhì):是 java.lang.Enum 的子類,也就是說(shuō),自己寫的枚舉類,就算沒(méi)有顯示的繼承 Enum ,但是其默認(rèn)繼承了這個(gè)類。
2. 枚舉的使用
2.1 switch語(yǔ)句中使用枚舉
switch語(yǔ)句中可以使用枚舉來(lái)提高代碼的可讀性。
其實(shí)enum關(guān)鍵字組織的是一個(gè)特殊的類,里面包含一個(gè)或多個(gè)的枚舉對(duì)象,下面定義的Color,其實(shí)里面包含了3個(gè)枚舉對(duì)象,每個(gè)對(duì)象都是Color類型。
enum Color { BLACK,YELLOW,GREEN;}public class Test { public static void main(String[] args) { Color color = Color.YELLOW; switch (color) { case BLACK: System.out.println("BLACK"); break; case YELLOW: System.out.println("YELLOW"); break; case GREEN: System.out.println("GREEN"); break; default: break; } }}
運(yùn)行結(jié)果:
2.2 枚舉enum中的常用方法
枚舉中常用的方法如下:
方法名稱 | 描述 |
---|---|
values() | 以數(shù)組形式返回枚舉類型的所有成員 |
ordinal() | 獲取枚舉成員的索引位置 |
valueOf() | 將普通字符串轉(zhuǎn)換為枚舉實(shí)例 |
compareTo() | 比較兩個(gè)枚舉成員在定義時(shí)的順序 |
關(guān)于Enum類源碼中找不到values()方法的解釋:
values方法,在編譯前無(wú)法找到,這是因?yàn)閑num聲明實(shí)際上定義了一個(gè)類,我們可以通過(guò)定義的enum調(diào)用一些方法,Java編譯器會(huì)自動(dòng)在enum類型中插入一些方法,其中就包括values(),valueOf(),所以我們的程序在沒(méi)編譯的時(shí)候,就沒(méi)辦法查看到values()方法以及源碼,這也是枚舉的特殊性。
- 使用values()得到一個(gè)含有所有枚舉對(duì)象的一個(gè)數(shù)組:
public enum Color { BLACK,YELLOW,GREEN; public static void main(String[] args) { Color[] colors = Color.values(); for (Color c : colors) { System.out.println(c); } }}
運(yùn)行結(jié)果:
- 使用valueOf()通過(guò)一個(gè)字符串獲取同名枚舉:
public enum Color { BLACK,YELLOW,GREEN; public static void main(String[] args) { Color color = Color.valueOf("BLACK"); System.out.println(color); }}
運(yùn)行結(jié)果:
- 使用ordinal()獲取枚舉在枚舉類中的位置次序,也就是索引:
public enum Color { BLACK,YELLOW,GREEN; public static void main(String[] args) { Color[] colors = Color.values(); for (Color c : colors) { System.out.println(c + "的索引:" + c.ordinal()); } }}
運(yùn)行結(jié)果:
- 使用compareTo() 比較兩個(gè)枚舉成員在定義時(shí)的順序:
public enum Color { BLACK,YELLOW,GREEN; public static void main(String[] args) { System.out.println(Color.GREEN.compareTo(Color.YELLOW)); System.out.println(Color.BLACK.compareTo(Color.YELLOW)); }}
運(yùn)行結(jié)果:
3. 自定義構(gòu)造枚舉對(duì)象
上面的例子中enum本質(zhì)上其實(shí)是一個(gè)特殊的類,默認(rèn)繼承了抽象類java.lang.Enum,里面包含了一個(gè)或多個(gè)枚舉對(duì)象,并且這些枚舉對(duì)象默認(rèn)情況下都是通過(guò)無(wú)參數(shù)的構(gòu)造方法構(gòu)造的,
其實(shí)我們可以在枚舉類中自定義屬性方法以及構(gòu)造方法,實(shí)現(xiàn)自定義枚舉對(duì)象.
看下面的寫法, 和上面的例子是一樣的 , 只不過(guò)上面的寫法是無(wú)參構(gòu)造省略了 ( )
我們可以自己在枚舉類中定義一些屬性, 然后去寫含有含有參數(shù)的構(gòu)造方法, 實(shí)現(xiàn)自定義枚舉;
注意 : 枚舉中的構(gòu)造方法必須(默認(rèn))是私有的, 且當(dāng)我們寫了含有參數(shù)的構(gòu)造方法時(shí), 編譯器不會(huì)再提提供無(wú)參的構(gòu)造方法 , 所以此時(shí)需要按照我們自己寫的構(gòu)造方法傳入?yún)?shù);
public enum Color { BLACK("BLACK", 11, 1), YELLOW("YELLOW", 12, 2), GREEN("GREEN", 13, 3); public String colorName; public int colorId; public int ordonal; Color(String colorName, int colorId, int ordonal) { this.colorName = colorName; this.colorId = colorId; this.ordonal = ordonal; } @Override public String toString() { return "Color{" + "colorName='" + colorName + ''' + ", colorId=" + colorId + ", ordonal=" + ordonal + '}'; } public static void main(String[] args) { Color[] colors = Color.values(); for (Color c : colors) { System.out.println(c); } }}
運(yùn)行結(jié)果:
4. 枚舉的安全性
首先看下面的代碼, 我們想要從外部通過(guò)反射獲取到枚舉類:
public class Test { public static void main(String[] args) { //嘗試獲取枚舉對(duì)象 Class<?> c = Color.class; try { //獲取構(gòu)造方法對(duì)象 Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class, int.class); //開(kāi)權(quán)限 constructor.setAccessible(true); //通過(guò)構(gòu)造方法構(gòu)造對(duì)象 Color color = (Color) constructor.newInstance("藍(lán)色", 88, 2); System.out.println(color); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }}
運(yùn)行結(jié)果:
結(jié)果中拋出一個(gè)java.lang.NoSuchMethodException: Color.<init>(java.lang.String, int, int)
異常,表示沒(méi)有找到我們給定參數(shù)列表的構(gòu)造方法,但是我們枚舉類中是定義過(guò)這個(gè)構(gòu)造方法的,那么這里報(bào)錯(cuò)的原因是什么呢?
上面說(shuō)過(guò)枚舉類是默認(rèn)繼承抽象類java.lang.Enum的,所以要構(gòu)造enum需要先幫助父類完成構(gòu)造,但是枚舉類與一般的類相比比較特殊,它不是使用super關(guān)鍵字進(jìn)行顯示地幫助父類構(gòu)造,而是在編譯后會(huì)多插入兩個(gè)參數(shù)來(lái)幫助父類構(gòu)造,也就是說(shuō),我們傳參時(shí)要在原本所定義的構(gòu)造方法參數(shù)列表基礎(chǔ)上前面再添加String和int類型的兩個(gè)參數(shù)
所以實(shí)際情況下,我們需要在反射獲取構(gòu)造器時(shí),多寫兩個(gè)參數(shù)
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class, String.class, int.class, int.class);
再次運(yùn)行程序結(jié)果如下:
可以發(fā)現(xiàn)結(jié)果還是會(huì)拋出異常,但是此時(shí)拋的不是構(gòu)造方法找不到的異常,而是枚舉無(wú)法進(jìn)行反射異常Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
;
所以枚舉對(duì)象是無(wú)法通過(guò)反射得到的, 這也就保證了枚舉的安全性;
其實(shí)枚舉無(wú)法通過(guò)反射獲取到枚舉對(duì)象是因?yàn)樵?*newInstance****()**中獲取枚舉對(duì)象時(shí),會(huì)過(guò)濾掉枚舉類型,如果遇到的是枚舉類型就會(huì)拋出異常。
5. 總結(jié)
-
枚舉本身就是一個(gè)類,其構(gòu)造方法默認(rèn)為私有的,且都是默認(rèn)繼承與 java.lang.Enum
-
枚舉可以避免反射和序列化問(wèn)題
-
枚舉實(shí)現(xiàn)單例模式是安全的
枚舉的優(yōu)點(diǎn):
- 枚舉常量更簡(jiǎn)單安全
- 枚舉具有內(nèi)置方法 ,代碼更優(yōu)雅
枚舉的缺點(diǎn):
- 不可繼承,無(wú)法擴(kuò)展
三. Lambda表達(dá)式
1. 函數(shù)式接口
要了解Lambda表達(dá)式,首先需要了解什么是函數(shù)式接口,函數(shù)式接口定義:一個(gè)接口有且只有一個(gè)抽象方法 。
注意:
-
如果一個(gè)接口只有一個(gè)抽象方法,那么該接口就是一個(gè)函數(shù)式接口
-
如果我們?cè)谀硞€(gè)接口上聲明了
@FunctionalInterface
注解,那么編譯器就會(huì)按照函數(shù)式接口的定義來(lái)要求該接 口,這樣如果有兩個(gè)抽象方法,程序編譯就會(huì)報(bào)錯(cuò)的。所以,從某種意義上來(lái)說(shuō),只要你保證你的接口中只有一個(gè)抽象方法,你可以不加這個(gè)注解。加上就會(huì)自動(dòng)進(jìn)行檢測(cè)的。
定義方式:
@FunctionalInterfaceinterface NoParameterNoReturn { //注意:只能有一個(gè)方法 void test();}
基于jdk1.8, 還以有如下定義:
@FunctionalInterfaceinterface NoParameterNoReturn { void test(); default void test2() { System.out.println("JDK1.8新特性,default默認(rèn)方法可以有具體的實(shí)現(xiàn)"); }}
2. 什么是Lambda表達(dá)式?
Lambda表達(dá)式是Java SE 8中一個(gè)重要的新特性。lambda表達(dá)式允許你通過(guò)表達(dá)式來(lái)代替功能接口。 lambda表達(dá) 式就和方法一樣,它提供了一個(gè)正常的參數(shù)列表和一個(gè)使用這些參數(shù)的主體(body,可以是一個(gè)表達(dá)式或一個(gè)代碼 塊)。 Lambda 表達(dá)式(Lambda expression),基于數(shù)學(xué)中的λ演算得名,也可稱為閉包(Closure) 。
Lambda表達(dá)式的語(yǔ)法:
(parameters) -> expression 或 (parameters) ->{ statements; }
Lambda表達(dá)式由三部分組成:
-
paramaters:類似方法中的形參列表,這里的參數(shù)是函數(shù)式接口里的參數(shù)。這里的參數(shù)類型可以明確的聲明也可不聲明而由JVM隱含的推斷。另外當(dāng)只有一個(gè)推斷類型時(shí)可以省略掉圓括號(hào)。
-
->:可理解為“被用于”的意思
-
方法體:可以是表達(dá)式也可以代碼塊,是函數(shù)式接口里方法的實(shí)現(xiàn)。代碼塊可返回一個(gè)值或者什么都不反回,這里的代碼塊塊等同于方法的方法體。如果是表達(dá)式,也可以返回一個(gè)值或者什么都不反回。
常用的lambda表達(dá)式格式:
// 1. 不需要參數(shù),返回值為 2() -> 2 // 2. 接收一個(gè)參數(shù)(數(shù)字類型),返回其2倍的值x -> 2 * x // 3. 接受2個(gè)參數(shù)(數(shù)字),并返回他們的和(x, y) -> x + y // 4. 接收2個(gè)int型整數(shù),返回他們的乘積(int x, int y) -> x * y // 5. 接受一個(gè) string 對(duì)象,并在控制臺(tái)打印,不返回任何值(看起來(lái)像是返回void)(String s) -> System.out.print(s)
3. Lambda表達(dá)式的基本使用
-
參數(shù)類型可以省略,如果需要省略,每個(gè)參數(shù)的類型都要省略。
-
參數(shù)的小括號(hào)里面只有一個(gè)參數(shù),那么小括號(hào)可以省略
-
如果方法體當(dāng)中只有一句代碼,那么大括號(hào)可以省略
-
如果方法體中只有一條語(yǔ)句,其是return語(yǔ)句,那么大括號(hào)可以省略,且去掉return關(guān)鍵字
以下面這些接口為例:
//無(wú)返回值無(wú)參數(shù)@FunctionalInterfaceinterface NoParameterNoReturn { void test();}//無(wú)返回值一個(gè)參數(shù)@FunctionalInterfaceinterface OneParameterNoReturn { void test(int a);}//無(wú)返回值多個(gè)參數(shù)@FunctionalInterfaceinterface MoreParameterNoReturn { void test(int a,int b);}//有返回值無(wú)參數(shù)@FunctionalInterfaceinterface NoParameterReturn { int test();}//有返回值一個(gè)參數(shù)@FunctionalInterfaceinterface OneParameterReturn { int test(int a);}//有返回值多參數(shù)@FunctionalInterfaceinterface MoreParameterReturn { int test(int a,int b);}
實(shí)現(xiàn)接口最原始的方式就是定義一個(gè)類去重寫對(duì)應(yīng)的方法,其次更簡(jiǎn)便的方式就是使用匿名內(nèi)部類去實(shí)現(xiàn)接口;
public class TestDemo { public static void main(String[] args) { NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn(){ @Override public void test() { System.out.println("hello"); } }; noParameterNoReturn.test(); }}
那么這里使用lambda表達(dá)式, 可以進(jìn)一步進(jìn)行簡(jiǎn)化;
public class TestDemo { public static void main(String[] args) { NoParameterNoReturn noParameterNoReturn = ()->{ System.out.println("無(wú)參數(shù)無(wú)返回值"); }; noParameterNoReturn.test(); OneParameterNoReturn oneParameterNoReturn = (int a)->{ System.out.println("一個(gè)參數(shù)無(wú)返回值:"+ a); }; oneParameterNoReturn.test(10); MoreParameterNoReturn moreParameterNoReturn = (int a,int b)->{ System.out.println("多個(gè)參數(shù)無(wú)返回值:"+a+" "+b); }; moreParameterNoReturn.test(20,30); NoParameterReturn noParameterReturn = ()->{ System.out.println("有返回值無(wú)參數(shù)!"); return 40; }; //接收函數(shù)的返回值 int ret = noParameterReturn.test(); System.out.println(ret); OneParameterReturn oneParameterReturn = (int a)->{System.out.println("有返回值有一個(gè)參數(shù)!"); return a; }; ret = oneParameterReturn.test(50); System.out.println(ret); MoreParameterReturn moreParameterReturn = (int a,int b)->{ System.out.println("有返回值多個(gè)參數(shù)!"); return a+b; }; ret = moreParameterReturn.test(60,70); System.out.println(ret); }}
上面的的代碼根據(jù)開(kāi)頭的省略規(guī)則還可以進(jìn)一步省略, 如下:
public class TestDemo { public static void main(String[] args) { NoParameterNoReturn noParameterNoReturn = ()->System.out.println("無(wú)參數(shù)無(wú)返回值"); noParameterNoReturn.test(); OneParameterNoReturn oneParameterNoReturn = a-> System.out.println("一個(gè)參數(shù)無(wú)返回值:"+ a); oneParameterNoReturn.test(10); MoreParameterNoReturn moreParameterNoReturn = (a,b)-> System.out.println("多個(gè)參數(shù)無(wú)返回值:"+a+" "+b); moreParameterNoReturn.test(20,30); //有返回值無(wú)參數(shù)! NoParameterReturn noParameterReturn = ()->40; int ret = noParameterReturn.test(); System.out.println(ret); //有返回值有一個(gè)參數(shù)! OneParameterReturn oneParameterReturn = a->a; ret = oneParameterReturn.test(50); System.out.println(ret); //有返回值多個(gè)參數(shù)! MoreParameterReturn moreParameterReturn = (a,b)->a+b; ret = moreParameterReturn.test(60,70); System.out.println(ret); }}
還有一種寫法更加簡(jiǎn)潔, 但可讀性就… , 比如:
OneParameterNoReturn oneParameterNoReturn = a-> System.out.println(a);
可以簡(jiǎn)化成下面的樣子, 看不太懂了…
OneParameterNoReturn oneParameterNoReturn = System.out::println;
4. 變量捕獲
Lambda 表達(dá)式中存在變量捕獲 ,了解了變量捕獲之后,我們才能更好的理解Lambda 表達(dá)式的作用域 。
在匿名內(nèi)部類中,只能捕獲到常量,或者沒(méi)有發(fā)生修改的變量,因?yàn)閘ambda本質(zhì)也是實(shí)現(xiàn)函數(shù)式接口,所以lambda也滿足此變量捕獲的規(guī)則。
下面的代碼捕獲的變量num未修改, 程序可以正常編譯和運(yùn)行;
當(dāng)捕獲的變量num是修改過(guò)的, 則會(huì)報(bào)錯(cuò);
5. Lambda在集合當(dāng)中的使用
5.1 Collection接口中的forEach方法
注意:Collection的forEach()方 法是從接口 java.lang.Iterable 拿過(guò)來(lái)的。
forEach方法需要傳遞的參數(shù)是Consumer<? super E> action,這個(gè)參數(shù)也是一個(gè)函數(shù)式接口,需要重寫里面的accept方法。
使用匿名內(nèi)部類,accept中的t參數(shù)表示集合中迭代出的元素,我們可以對(duì)該元素設(shè)定操作, 這里重寫的方法只做輸出操作;
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("欣"); list.add("欣"); list.add("向"); list.add("榮"); list.forEach(new Consumer<String>(){ @Override public void accept(String str){ //簡(jiǎn)單遍歷集合中的元素。 System.out.print(str+" "); } });}
執(zhí)行結(jié)果:
我們可以將上面的匿名內(nèi)部類使用lambda表示,它只有一個(gè)參數(shù)沒(méi)有返回值,上面的代碼變?yōu)?/p>
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("欣"); list.add("欣"); list.add("向"); list.add("榮"); list.forEach(s -> System.out.print(s + " "));}
5.2 Map中forEach方法
map中的forEach方法和前面Collection中的forEach方法的使用其實(shí)都差不多,換了一個(gè)參數(shù)而已,這個(gè)參數(shù)BiConsumer<? super K, ? super V> action同樣是一個(gè)函數(shù)式接口,我們需要傳入一個(gè)實(shí)現(xiàn)該接口的實(shí)現(xiàn)類。
使用匿名內(nèi)部類:
public static void main(String[] args) { Map<Integer, String> map = new HashMap<>(); map.put(1, "欣"); map.put(2, "欣"); map.put(3, "向"); map.put(4, "榮"); map.forEach(new BiConsumer<Integer, String>(){ @Override public void accept(Integer k, String v){ System.out.println(k + "=" + v); } });}
運(yùn)行結(jié)果:
同樣的對(duì)上面代碼可以使用lambda表達(dá)式來(lái)實(shí)現(xiàn),這是一個(gè)含有兩個(gè)參數(shù)無(wú)返回值的函數(shù)式接口,上面的代碼改為:
public static void main(String[] args) { Map<Integer, String> map = new HashMap<>(); map.put(1, "欣"); map.put(2, "欣"); map.put(3, "向"); map.put(4, "榮"); map.forEach((k,v)-> System.out.println(k + "=" + v));}
5.3 大部分接口中的sort方法
大部分接口中的sort方法,默認(rèn)都是按照升序的方式進(jìn)行排序,如果需要對(duì)自定義類進(jìn)行排序或者實(shí)現(xiàn)自定義規(guī)則的排序,需要額外傳入一個(gè)Comparator的實(shí)現(xiàn)類對(duì)象(比較器) ; 這里以List集合中的sort方法為例 .
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("aaaa"); list.add("bbb"); list.add("cc"); list.add("d"); list.sort(new Comparator<String>() { @Override public int compare(String str1, String str2){ //注意這里比較的是長(zhǎng)度 return str1.length()-str2.length(); } }); System.out.println(list);}
運(yùn)行結(jié)果:
同樣的對(duì)上面代碼可以使用lambda表達(dá)式來(lái)實(shí)現(xiàn),這是一個(gè)含有兩個(gè)參數(shù)有返回值的函數(shù)式接口,上面的代碼改為:
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("aaaa"); list.add("bbb"); list.add("cc"); list.add("d"); //調(diào)用帶有2個(gè)參數(shù)的方法,且返回長(zhǎng)度的差值 list.sort((str1,str2)-> str1.length()-str2.length()); System.out.println(list);}
6. 總結(jié)
Lambda表達(dá)式的優(yōu)點(diǎn)很明顯,在代碼層次上來(lái)說(shuō),使代碼變得非常的簡(jiǎn)潔。缺點(diǎn)也很明顯,代碼不易讀。
優(yōu)點(diǎn):
-
代碼簡(jiǎn)潔,開(kāi)發(fā)迅速
-
方便函數(shù)式編程
-
非常容易進(jìn)行并行計(jì)算
-
Java 引入 Lambda,改善了集合操作
缺點(diǎn):
-
代碼可讀性變差
-
在非并行計(jì)算中,很多計(jì)算未必有傳統(tǒng)的 for 性能要高
-
不容易進(jìn)行調(diào)試
推薦學(xué)習(xí):《java視頻教程》