天天看點

Java語言——反射、枚舉以及lambda表達式

作者:Java碼農之路

一.反射

1.1反射的基本情況

定義:Java在 運作 狀态時,對于任意一個類,都能知道這個類的所有屬性和方法。

這種動态擷取資訊以及動态調用對象方法的功能稱為java語言的反射(reflection)機制

用途:1.在日常的第三方應用開發過程中,經常會遇到某個類的某個成員變量、方法或是屬性是私有的或是隻對系統應用開放,這時候就可以利用 Java的反射機制 來擷取所需的私有成員或是方法 。

2. 反射最重要的用途就是開發各種通用架構,比如在spring中,我們将所有的類Bean交給spring容器管理,無論是XML配置Bean還是注解配置,當我們從容器中擷取Bean來依賴注入時,容器會讀取配置,而配置中給的就是類的資訊,spring根據這些資訊,需要建立那些Bean,spring就動态的建立這些類。

1.2反射中最重要的類

Java語言——反射、枚舉以及lambda表達式

在講解這些類之前,我們需要先建構一個類,友善進行反射的操作:

class Student{
//私有屬性name
    private String name = "tq02";
//公有屬性age
    public int age = 22;
//不帶參數的構造方法
    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 +
    '}';
}
}           

注:1.反射私有的構造方法、屬性、方法時,Java具有安全性,是以我們需要使用.setAccessible("boolean");

2.使用Class類、Field、Constructor類時,需要處理異常。

1.2.1 Class類

在反射之前,第一步就是先拿到目前需要反射的類的Class對象,然後通過Class對象的核心方法,達到反射的目的,即:在運作狀态中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象, 都能夠調用它的任意方法和屬性,既然能拿到,我們就可以修改部分類型資訊。

使用Class擷取 類 的三種方法:

第一種:使用Class.forName("類的全路徑名“”); //靜态方法

第二種:使用.class方法。

第三種:使用類對象的getClass()方法;

注:無論哪種方法擷取,其實擷取的都是同一個類。

代碼執行個體 :

public class TestDemo {
public static void main(String[] args) {
//1.通過getClass擷取Class對象
    Student s1 = new Student();
    Class c1 = s1.getClass();
 
//2.直接通過 類名.class 的方式得到,該方法最為安全可靠,程式性能更高
//這說明任何一個類都有一個隐含的靜态成員變量 class
    Class c2 = Student.class;
 
//3、通過 Class 對象的 forName() 靜态方法來擷取,用的最多,
//但可能抛出 ClassNotFoundException 異常
    Class c3 = null;
    try {
//注意這裡是類的全路徑,如果有包需要加包的路徑
    c3 = Class.forName("Student");
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } 
 
//一個類在 JVM 中隻會有一個 Class 執行個體,即我們對上面擷取的
//c1,c2,c3進行 equals 比較,發現都是true
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c2.equals(c3));
}           

1.2.2Field類

作用:可對類中屬性進行操作

Java語言——反射、枚舉以及lambda表達式
public static void reflectPrivateField() {
  try {
            Class<?> classStudent = Class.forName("Student");
                                                     //擷取name成員變量
            Field field = classStudent.getDeclaredField("name");
            field.setAccessible(true);
            Student student= (Student)classStudent.newInstance();
//修改成員變量,将student中的name值改成"小明";
            field.set(student, "小明");
            String name = (String) field.get(student);
            System.out.println("反射私有屬性修改了name:" + name);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
}           

1.2.3Constructor類

作用:對構造方法進行操作

Java語言——反射、枚舉以及lambda表達式

代碼執行個體:

//反射構造方法
    public static void reflect() {
       try {
           Class<?> c1 = Class.forName("Student");
           Constructor<?> c2= c1.getDeclaredConstructor(String.class,int.class);
           c2.setAccessible(true);
 
           c2.newInstance("湯琦",22);
       }catch(Exception ex)
       {
           ex.printStackTrace();
       }
    }           

1.2.4Method類

作用:對類中方法進行操作

Java語言——反射、枚舉以及lambda表達式

執行個體代碼:

public static void reflectPrivateMethod() {
        try {
            Class<?> c1 = Class.forName("Student");
            Method m1=c1.getDeclaredMethod("function", String.class);
            m1.setAccessible(true);
 
            Student fw=(Student) c1.newInstance();
            m1.invoke(fw,"給私有的function函數傳的參數");
 
        }catch(Exception ex)
        {
            ex.printStackTrace();
        }
    }           

1.3反射優缺點

優點: 1. 對于任意一個類,都能夠知道這個類的所有屬性和方法;對 于任意一個對象,都能夠調用它的任意一個方法

2. 增加程式的靈活性和擴充性,降低耦合性,提高自适應能力

3. 反射已經運用在了很多流行架構如:Struts、Hibernate、Spring 等等。

缺點: 1. 使用反射會有效率問題。會導緻程式效率降低。

2. 反射技術繞過了源代碼的技術,因而會帶來維護問題。反射代碼比相應的直接代碼更複雜

二.枚舉

2.1概念

在Java中,可以說是一個集合,從下标0開始的集合。注:枚舉是jdk1.5以後引用的。

使用格式: public enum 類名{

常量1、常量2、常量3;

}

就是将class換成了enum

代碼執行個體:

public enum TestEnum {
    RED,BLACK,GREEN,WHITE;//相當于集合,第一個常量下标值為0,第二個常量下标值為1......
    public static void main(String[] args) {
         TestEnum testEnum2 = TestEnum.BLACK;
         switch (testEnum2) {
         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("black");break;
         default:break;
}
}           

2.2枚舉(enum)類方法

2.3枚舉的構造

枚舉的構造方法預設是私有的。

public enum TestEnum {
    RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
    private String name;
    private int key;
/**
* 1、當枚舉對象有參數後,需要提供相應的構造函數
* 2、枚舉的構造函數預設是私有的 這個一定要記住
* @param name
* @param key
*/
    private TestEnum (String name,int key) {
        this.name = name;
        this.key = key;
    }
    public static TestEnum getEnumKey (int key) {
        for (TestEnum t: TestEnum.values()) {
        if(t.key == key) {
        return t;
        }
    } 
    return null;
}
    public static void main(String[] args) {
        System.out.println(getEnumKey(2));
    }
}           

注:自己寫的枚舉類,預設繼承與enum這個類的。

三.Lambda表達式

3.1Lambda介紹

Lambda本質是匿名函數,基于數學中的λ演算得名,也可稱為閉包(Closure)

文法格式:(parameters)->expression 或 (parameters)->{ statements;}

parameters:類似方法中的形參清單,這裡的參數是函數式接口裡的參數。這裡的參數類型可以明确的聲明也可不聲明而由JVM隐含的推斷。另外當隻有一個推斷類型時可以省略掉圓括号。

->:可了解為“被用于”的意思

方法體:可以是表達式也可以代碼塊,是函數式接口裡方法的實作。代碼塊可傳回一個值或者什麼都不反回,這裡的代碼塊塊等同于方法的方法體。如果是表達式,也可以傳回一個值或者什麼都不反回。

常見表達式:

// 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)           

3.2 函數式接口

定義:該接口有且隻有一個 抽象方法

注:如果某接口含有@FunctionalInterface 注解,那麼編譯器就會按照函數式接口的定義來要求該接口,這樣如果有兩個抽象方法,程式編譯就會報錯的。

代碼執行個體:

@FunctionalInterface
interface NoParameterNoReturn {
	//注意:隻能有一個方法
	void test();
}           

3.2使用lambda表達式

先建立幾個接口:

//無傳回值無參數
@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);
}           

Lambda就是匿名内部類的簡化,實際上是建立了一個類,實作了接口,重寫了接口的方法 。

3.2.1不使用Lambda表達式調用

public class TestDemo {
public static void main(String[] args) {
//接口使用匿名内部類
    NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn(){
    @Override
    public void test() {
    System.out.println("hello");
    }
    };
noParameterNoReturn.test();
}           

3.2.2使用Lambda表達式

public class TestDemo {
  public static void main(String[] args) {
    NoParameterNoReturn noParameterNoReturn = ()->{
    System.out.println("無參數無傳回值");
};
noParameterNoReturn.test();
    OneParameterNoReturn oneParameterNoReturn = (int a)->{
    System.out.println("一個參數無傳回值:"+ a);
};
oneParameterNoReturn.test(10);
 
    MoreParameterNoReturn moreParameterNoReturn = (int a,int b)->{
    System.out.println("多個參數無傳回值:"+a+" "+b);
};
moreParameterNoReturn.test(20,30);
 
    NoParameterReturn noParameterReturn = ()->{
    System.out.println("有傳回值無參數!");
    return 40;
};
//接收函數的傳回值
int ret = noParameterReturn.test();
System.out.println(ret);
    OneParameterReturn oneParameterReturn = (int a)->{
    System.out.println("有傳回值有一個參數!");
    return a;
};
ret = oneParameterReturn.test(50);
System.out.println(ret);
 
    MoreParameterReturn moreParameterReturn = (int a,int b)->{
    System.out.println("有傳回值多個參數!");
    return a+b;
};
ret = moreParameterReturn.test(60,70);
System.out.println(ret);
}
}           

3.2.3二者差別

代碼執行個體:

Java語言——反射、枚舉以及lambda表達式

1. 參數類型可以省略,如果需要省略,每個參數的類型都要省略。

2. 參數的小括号裡面隻有一個參數,那麼小括号可以省略

3. 如果方法體當中隻有一句代碼,那麼大括号可以省略

4. 如果方法體中隻有一條語句,且是return語句,那麼大括号可以省略,且去掉return關鍵字。

3.3變量捕獲

變量捕獲,在匿名内部類中也存在,而類似匿名内部類的Lambda表達式,自然而然也存在。

3.3.1匿名内部類的變量捕獲

Java語言——反射、枚舉以及lambda表達式

外,已經定義了a的值,是以匿名内部類直接捕獲了外部的a變量。

3.3.2Lambda變量捕獲

Java語言——反射、枚舉以及lambda表達式

如上圖,使用直接捕獲了外部的a變量。

注:無論是匿名内部類的變量捕獲還是Lambda變量捕獲,方法體裡,不可修改外部變量的值。

總結

反射、枚舉以及Lambda表達式很少使用,算是偏僻的知識點,是以不要求掌握,隻要求熟悉。