Java中的反射,枚舉及lambda表達式的使用詳解
一、反射
1.1 定義
Java的反射(reflection
)機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性,既然能拿到,那麼我們就可以修改部分類型信息;這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射(reflection
)機制。
1.2 用途
1、在日常的第三方應用開發過程中,經常會遇到某個類的某個成員變量、方法或是屬性是私有的或是隻對系統應用開放,這時候就可以利用Java的反射機制通過反射來獲取所需的私有成員或是方法 。
2、反射最重要的用途就是開發各種通用框架,比如在spring
中,我們將所有的類Bean
交給spring
容器管理,無論是XML
配置Bean
還是註解配置,當我們從容器中獲取Bean
來依賴註入時,容器會讀取配置,而配置中給的就是類的信息,spring
根據這些信息,需要創建哪些Bean
,spring
就動態的創建這些類。
1.3 反射基本信息
Java程序中許多對象在運行時會出現兩種類型:運行時類型(RTTI)和編譯時類型,例如Person p = newStudent();
這句代碼中p
在編譯時類型為Person
,運行時類型為Student
。程序需要在運行時發現對象和類的真實信息。而通過使用反射程序就能判斷出該對象和類屬於哪些類。
1.4 與反射相關的類
類名 | 用途 |
---|---|
Class類 | 代表類的實體,在運行的Java應用程序中表示類和接口 |
Field類 | 代表類的成員變量/類的屬性 |
Method類 | 代表類的方法 |
Constructor類 | 代表瞭類的構造方法 |
1.5 Class類(反射機制的起源 )
Class代表類的實體,在運行的Java應用程序中表示類和接口 .
Java
文件被編譯後,生成瞭.class
文件,JVM
此時就要去解讀.class
文件 ,被編譯後的Java
文件.class
也被JVM
解析為一個對象,這個對象就是 java.lang.Class
.這樣當程序在運行時,每個類文件就最終變成瞭Class
類對象的一個實例。我們通過Java
的反射機制應用到這個實例,就可以去獲得甚至去添加改變這個類的屬性和動作,使得這個類成為一個動態的類 .
1.6 Class類中的相關方法
常用獲得類相關的方法:
方法 | 用途 |
---|---|
getClassLoader() | 獲得類的加載器 |
getDeclaredClasses() | 返回一個數組,數組中包含該類中所有類和接口類的對象(包括私有的) |
forName(String className) | 根據類名返回類的對象 |
newInstance() | 創建類的實例 |
getName() | 獲得類的完整路徑名字 |
常用獲得類中屬性相關的方法(以下方法返回值為Field相關)
方法 | 用途 |
---|---|
getField(String name) | 獲得某個公有的屬性對象 |
getFields() | 獲得某個公有的屬性對象 |
getDeclaredField(String name) | 獲得某個屬性對象 |
getDeclaredFields() | 獲得某個屬性對象 |
獲得類中註解相關的方法:
方法 | 屬性 |
---|---|
getAnnotation(Class annotationClass) | 返回該類中與參數類型匹配的公有註解對象 |
getAnnotations() | 返回該類所有的公有註解對象 |
getDeclaredAnnotation(Class annotationClass) | – |
getDeclaredAnnotations() | 返回該類所有的註解對象 |
獲得類中構造器相關的方法(以下方法返回值為Constructor相關)
方法 | 屬性 |
---|---|
getConstructor(Class…<?> parameterTypes) | 獲得該類中與參數類型匹配的公有構造方法 |
getConstructors() | 獲得該類的所有公有構造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 獲得該類中與參數類型匹配的構造方法 |
getDeclaredConstructors() | 獲得該類中所以構造方法 |
1.7 獲得Class對象的三種方式
在反射之前,我們需要做的第一步就是先拿到當前需要反射的類的Class
對象,然後通過Class
對象的核心方法,達到反射的目的,即:在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性,既然能拿到,那麼我們就可以修改部分類型信息。
第一種,使用 Class.forName("類的全路徑名")
; 靜態方法。
前提:已明確類的全路徑名。
第二種,使用 .class
方法。
說明:僅適合在編譯前就已經明確要操作的 Class
。
第三種,使用類對象的 getClass()
方法。
代碼示例:
本節代碼均在一個包下面。
package reflectTest; class Student{ //私有屬性name private String name = "bit"; //公有屬性age public int age = 18; //不帶參數的構造方法 public Student(){ System.out.println("Student()"); } 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 eat"); } public void sleep(){ System.out.println("i am pig"); } private void function(String str) { System.out.println(str); } @ Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class test01 { public static void main(String[] args) { try { //通過 Class 對象的 forName() 靜態方法來獲取,用的最多, //但可能拋出 ClassNotFoundException 異常 Class<?> c1 = Class.forName("reflectTest.Student"); //直接通過 類名.class 的方式得到,該方法最為安全可靠,程序性能更高 //這說明任何一個類都有一個隱含的靜態成員變量 class Class<?> c2 = Student.class; //通過getClass獲取Class對象 Student student = new Student(); Class<?> c3 = student.getClass(); System.out.println(c1.equals(c2)); System.out.println(c1.equals(c3)); System.out.println(c2.equals(c3)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
輸出結果:
1.8 反射的使用
package reflectTest; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 通過class類的newInstance方法獲取類的實例 */ public class ReflectClassDemo { public static void reflectNewInstance(){ try { //獲得Class對象 Class<?> c1 = Class.forName("reflectTest.Student"); //創建類的實例 Student student = (Student) c1.newInstance(); System.out.println(student); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } /** * 反射私有的構造方法 */ public static void reflectPrivateConstructor() { try { Class<?> c1 = Class.forName("reflectTest.Student"); //構造方法 Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class); //設置為true後可修改訪問權限 constructor.setAccessible(true); Student student = (Student) constructor.newInstance("world",18); System.out.println(student); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } /** * 反射私有屬性 */ public static void reflectPrivateField() { try { Class<?> c1 = Class.forName("reflectTest.Student"); Student student = (Student) c1.newInstance(); Field field = c1.getDeclaredField("name"); field.setAccessible(true); field.set(student,"zhang"); System.out.println(student); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } // 反射私有方法 public static void reflectPrivateMethod() { try { Class<?> c1 = Class.forName("reflectTest.Student"); Student student = (Student) c1.newInstance(); Method method = c1.getDeclaredMethod("function",String.class); method.setAccessible(true); method.invoke(student,"我是私有的方法的參數"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } public static void main(String[] args) { // reflectNewInstance(); // reflectPrivateConstructor(); // reflectPrivateField(); reflectPrivateMethod(); } }
1.9 反射優點和缺點
優點:
1.對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法
2.增加程序的靈活性和擴展性,降低耦合性,提高自適應能力
3.反射已經運用在瞭很多流行框架如:Struts、Hibernate、Spring
等等。
缺點:
1.使用反射會有效率問題。會導致程序效率降低。
2.反射技術繞過瞭源代碼的技術,因而會帶來維護問題。反射代碼比相應的直接代碼更復雜 。
二、枚舉
枚舉的主要用途是:將一組常量組織起來,在這之前表示一組常量通常使用定義常量的方式:
public static int final RED = 1; public static int final GREEN = 2; public static int final BLACK = 3;
但是常量舉例有不好的地方,例如:可能碰巧有個數字1,但是他有可能誤會為是RED,現在我們可以直接用枚舉來進行組織,這樣一來,就擁有瞭類型,枚舉類型。而不是普通的整形1。
代碼示例:
package enumTest; public enum test01 { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { // System.out.println(test01.BLACK); // System.out.println(BLACK); test01 te = test01.BLACK; switch (te) { case RED: System.out.println("red"); break; case BLACK: System.out.println("black"); break; case WHITE: System.out.println("white"); break; case GREEN: System.out.println("green"); break; default: break; } } }
輸出結果:
優點:將常量組織起來統一進行管理;
場景:錯誤狀態碼,消息類型,顏色的劃分,狀態機等等…
本質:是 java.lang.Enum
的子類,也就是說,自己寫的枚舉類,就算沒有顯示的繼承 Enum
,但是其默認繼承瞭這個類。
2.1 Enum 類的常用方法
方法名稱 | 描述 |
---|---|
values() | 以數組形式返回枚舉類型的所有成員 |
ordinal() | 獲取枚舉成員的索引位置 |
valueOf() | 將普通字符串轉換為枚舉實例 |
compareTo() | 比較兩個枚舉成員在定義時的順序 |
values()代碼示例 :
public enum test01 { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test01[] te = test01.values(); for (int i = 0; i < te.length; i++) { System.out.println(te[i]); } } }
輸出結果:
ordinal() 代碼示例:
public enum test01 { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { test01[] te = test01.values(); for (int i = 0; i < te.length; i++) { System.out.println(te[i] + " --> " + te[i].ordinal()); } } }
輸出結果:
valueOf() 、compareTo() 代碼示例:
public enum test01 { RED,BLACK,GREEN,WHITE; public static void main(String[] args) { //把字符串變成對應的枚舉對象 test01 te = test01.valueOf("RED"); System.out.println(te); System.out.println(RED.compareTo(GREEN));//-2 System.out.println(BLACK.compareTo(RED));//1 } }
輸出結果:
枚舉的構造方法默認是私有的
public enum test01 { //枚舉對象 RED("red",1),BLACK(),GREEN(),WHITE(); public String color; public int ordinal; //private 加或者不加其都是私有的 test01(String color, int ordinal) { this.color = color; this.ordinal = ordinal; } //無參構造 test01(){ } }
2.2 枚舉的優點和缺點
優點:
1.枚舉常量更簡單安全 。
2.枚舉具有內置方法 ,代碼更優雅。
缺點:
1.不可繼承,無法擴展。
- 枚舉非常安全,不能通過反射,拿到實例對象。
- 枚舉本身就是一個類,其構造方法默認為私有的,且都是默認繼承於
java.lang.Enum
- 枚舉可以避免反射和序列化問題
三、Lambda 表達式
Lambda
表達式是Java SE 8
中一個重要的新特性。lambda
表達式允許你通過表達式來代替功能接口。 lambda
表達式就和方法一樣,它提供瞭一個正常的參數列表和一個使用這些參數的主體(body,可以是一個表達式或一個代碼塊)。 Lambda
表達式(Lambda expression)
可以看作是一個匿名函數,基於數學中的λ
演算得名,也可稱為閉包(Closure
)。
3.1 Lambda表達式的語法及基本使用
基本語法: (parameters) -> expression 或 (parameters) ->{ statements; }
Lambda表達式由三部分組成:
1.paramaters
:類似方法中的形參列表,這裡的參數是函數式接口裡的參數。這裡的參數類型可以明確的聲明也可不聲明而由JVM
隱含的推斷。另外當隻有一個推斷類型時可以省略掉圓括號。
2.->
:可理解為“被用於”的意思
3.方法體:可以是表達式也可以代碼塊,是函數式接口裡方法的實現。代碼塊可返回一個值或者什麼都不反回,這裡的代碼塊等同於方法的方法體。如果是表達式,也可以返回一個值或者什麼都不返回。
// 1. 不需要參數,返回值為 2 () -> 2 // 2. 接收一個參數(數字類型),返回其2倍的值 x -> 2 * x // 3. 接受2個參數(數字),並返回他們的和 (x, y) -> x + y // 4. 接收2個int型整數,返回他們的乘積 (int x, int y) -> x * y // 5. 接受一個 string 對象,並在控制臺打印,不返回任何值(看起來像是返回void) (String s) -> System.out.print(s)
代碼示例:
package lambdaTest; @FunctionalInterface //函數式接口 interface NoParameterNoReturn { //註意:隻能有一個方法 void test(); } //無返回值一個參數 @FunctionalInterface interface OneParameterNoReturn { void test(int a); } //無返回值多個參數 @FunctionalInterface interface MoreParameterNoReturn { void test(int a,int b); } //有返回值無參數 @FunctionalInterface interface NoParameterReturn { int test(); } //有返回值一個參數 @FunctionalInterface interface OneParameterReturn { int test(int a); } //有返回值多參數 @FunctionalInterface interface MoreParameterReturn { int test(int a,int b); } public class test01 { public static void main(String[] args) { // {} return 可以省略 NoParameterReturn noParameterReturn = ()->{return 10;}; int ret = noParameterReturn.test(); System.out.println(ret);//10 //()可以省略 OneParameterReturn oneParameterReturn = (a) -> a; System.out.println(oneParameterReturn.test(10));//10 MoreParameterReturn moreParameterReturn = (a,b) -> a+b; System.out.println(moreParameterReturn.test(1,2));//3 } public static void main3(String[] args) { //() {} 可省略 OneParameterNoReturn oneParameterNoReturn = (a)-> System.out.println(a); oneParameterNoReturn.test(10);//10 //int類型可以省略 MoreParameterNoReturn moreParameterNoReturn = (a,b)-> System.out.println(a+b); moreParameterNoReturn.test(10,20);//30 } public static void main2(String[] args) { NoParameterNoReturn noParameterNoReturn = () -> System.out.println("重寫方法"); noParameterNoReturn.test(); } public static void main1(String[] args) { NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn(){ public void test(){ System.out.println("重寫方法"); } }; noParameterNoReturn.test(); } }
3.2 函數式接口
函數式接口定義:一個接口有且隻有一個抽象方法 。
註意:
1.如果一個接口隻有一個抽象方法,那麼該接口就是一個函數式接口。
2.如果我們在某個接口上聲明瞭 @FunctionalInterface
註解,那麼編譯器就會按照函數式接口的定義來要求該接口,這樣如果有兩個抽象方法,程序編譯就會報錯的。所以,從某種意義上來說,隻要你保證你的接口中隻有一個抽象方法,你可以不加這個註解。加上就會自動進行檢測的。
代碼示例:
@FunctionalInterface //函數式接口 interface NoParameterNoReturn { //註意:隻能有一個方法 void test(); } public static void main1(String[] args) { NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn(){ public void test(){ System.out.println("重寫方法"); } }; noParameterNoReturn.test(); } }
3.3 變量捕獲
Lambda 表達式中存在變量捕獲 ,瞭解瞭變量捕獲之後,我們才能更好的理解Lambda 表達式的作用域 。Java當中的匿名類中,會存在變量捕獲。
package lambdaTest; class Test { public void func(){ System.out.println("func()"); } } public class test02 { public static void main(String[] args) { int a = 100; new Test(){ @Override public void func() { System.out.println("我是內部類,且重寫瞭func這個方法!"); System.out.println("捕獲遍歷" + a);//能捕獲到的變量,要麼是常量,要麼未發生改變過。 } }; } }
Lambda表達式的優點很明顯,在代碼層次上來說,使代碼變得非常的簡潔。缺點也很明顯,代碼不易讀。
優點:
代碼簡潔,開發迅速;方便函數式編程;非常容易進行並行計算;Java 引入 Lambda,改善瞭集合操作;
缺點:
代碼可讀性變差;在非並行計算中,很多計算未必有傳統的 for 性能要高;不容易進行調試;
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- 關於JavaEE匿名內部類和Lambda表達式的註意事項
- Java8語法糖之Lambda表達式的深入講解
- Java中Lambda表達式的使用詳細教程
- Java8新特性之方法引用的實踐指南
- 簡單易懂的java8新特性之lambda表達式知識總結