1. 什么是反射?
1.1. 問題1
1.對象有編譯類型和運行類型 Object obj = new java.util.Date(); 編譯類型:Object 運行類型(其實就是obj對象真實的類型):java.util.Date 需求:根據對象obj調用Date類中的一個方法,toLocaleString,如何來做? obj.toLocaleString()代碼在編譯階段去編譯類型Object中檢查是否有該方法,若沒有,編譯失敗.
解決方案1:強制轉換obj為Date類型,前提:必須知道對象的真實類型是什么? Date d = (Date)obj; d.toLocaleString();//YES
如果我不知道obj的真實類型,那又如何去調用toLolcaeString方法. 如何去做? 解決方案2: 使用反射
|
1.2. 問題2
對象使用類描述的,但是Java中一些皆對象,其實所有的類,底層都有一個字節碼對象,用什么來描述這一個個類底層的字節碼對象
解決方案 : 使用反射
![]()
|
1.3. 反射
反射:(reflection):在運行時期,動態地去獲取類中的信息(類的信息,方法信息,構造器信息,字段等信息進行操作);
一個類中包含的信息有: 構造器,字段,方法
如何使用反射描述這些相關的信息
Class : 描述類 Method : 描述方法 Constructor :描述構造器 Field :描述字段 |
2. 獲取類的Class實例三種方式
在反射操作某一個類之前,應該先獲取這個類的字節碼實例
獲取字節碼實例有三種方式
注意 :同一個類在JVM的字節碼實例只有一份
1. 類名.class
2. 類的對象.getClass()
3. Class.forName(“類的全限定名”)
(1) 全限定名 = 包名 + 類名
public class User { @Test public void testName() throws Exception { //1.使用類名.class 獲取類的字節碼實例 Class<User> clz1 = User.class; System.out.println(clz1.toString());
//2.對象.getClass() User user = new User(); Class<?> clz2 = user.getClass(); System.out.println(clz2);
//3.Class.forName("全限定名"):用的最多 Class<?> clz3 = Class.forName("cn.sxt.reflect.User"); System.out.println(clz3); } } |
3. 獲取九大內置類的字節碼實例
對於對象來說,可以直接使用對象.getClass()或者Class.forName(className); 類名.class都可以獲取Class實例.
但是我們的基本數據類型,就沒有類的權限定名,也沒有getClass方法.
問題: 那么如何使用Class類來表示基本數據類型的Class實例?
八大基本數據類型和 void關鍵字都是有 字節碼實例的 byte,short,int,long,char,float,double,boolean ,void關鍵字
答 : 數據類型/void.class 即可
每個基本數據類型都是包裝類型 如 :int ----Integer包裝類型 注意: 基本數據類型和包裝數據類型底層的字節碼實例是不相同 |
//獲取8大基本數據類型和void的字節碼實例 //byte,short,int,long,char,float,double,boolean ,void關鍵字 public class BaiscDataTypeClassTest { @Test public void testName() throws Exception { //1.獲取byte的字節碼實例 Class<?> byteClz = byte.class; System.out.println(byteClz); //.... //獲取void關鍵字的字節碼實例 Class<?> voidClz =void.class; System.out.println(voidClz);
//所有的基本數據類型都有包裝類型 //int---Integer //int 和Integer 的字節碼是絕對不相等的 Class<?> intClz1 = int.class; Class<?> intClz2 = Integer.class; System.out.println(intClz1); System.out.println(intClz2); System.out.println(intClz1 == intClz2); } } |
4. 獲取數組類型的字節碼實例
表示數組的Class實例: String[] sArr1 = {"A","C"}; Class clz = String[].class;//此時clz表示就是一個String類型的一位數組類型
所有具有相同元素類型和維數的數組才共享同一份字節碼(Class對象); 注意:和數組中的元素沒有一點關系. |
package cn.sxt.reflect._01.getclass;
import static org.junit.Assert.*;
import org.junit.Test;
//獲取數組類型的字節碼實例 public class ArrayClassTest { @Test public void testName() throws Exception { //定義數組 int[] arr = {1,2,3}; Class<int[]> clz1 = (Class<int[]>) arr.getClass(); System.out.println(clz1); Class<int[]> clz2= int[].class; System.out.println(clz2);
int[] arr2 = {2,3,4}; Class<int[]> clz3 = (Class<int[]>) arr2.getClass(); System.out.println(clz3);
System.out.println(clz1 == clz2);//true System.out.println(clz1 == clz3);//true } }
|
5. 構造函數-Constructor
5.1. 獲取構造函數
類的構函數有 有參數構造函數,無參構造函數,公共構造函數,非公共構造函數,根據不同的構造函數 Class提供了幾種獲取不同構造函數的方法
|
|
|
|
|
|
|
//通過字節碼實例獲取構造器 public class ConstructorTest { @Test public void testName() throws Exception {
//1.獲取Student的字節碼實例 Class<?> stuClz = Student.class;
//2.獲取所有的公共構造函數 Constructor<?>[] cts1 = stuClz.getConstructors(); for (Constructor<?> ct : cts1) { System.out.println(ct); } System.out.println("----------------------"); //3.獲取所有的構造函數包括私有的 Constructor<?>[] cts2 = stuClz.getDeclaredConstructors(); for (Constructor<?> ct : cts2) { System.out.println(ct); } System.out.println("----------------------");
//4.獲取指定的構造函數(clz.getConstructor(...))只能獲取公共的構造函數 Constructor<?> ct1 = stuClz.getConstructor(); System.out.println(ct1);
Constructor<?> ct2 =stuClz.getConstructor(String.class); System.out.println(ct2); //4.獲取指定的構造函數(clz.getDeclaredConstructor(...))獲取的構造函數和權限沒有關系 Constructor<?> ct3=stuClz.getDeclaredConstructor(String.class,int.class); System.out.println(ct3); } } |
5.2. 調用構造函數創建對象
調用構造器,創建對象
Constructor<T>類:表示類中構造器的類型,Constructor的實例就是某一個類中的某一個構造器 常用方法: public T newInstance(Object... initargs):如調用帶參數的構造器,只能使用該方式. 參數:initargs:表示調用構造器的實際參數 返回:返回創建的實例,T表示Class所表示類的類型 如果:一個類中的構造器可以直接訪問,同時沒有參數.,那么可以直接使用Class類中的newInstance方法創建對象. public Object newInstance():相當於new 類名(); 調用私有的構造器: |
5.2.1. Constructor 創建對象的方法
|
5.2.2. Class類中創建對象的方法
如果使用Class直接創建對象,必須保證類中有一個無參數公共構造函數
|
5.2.3. 設置忽略訪問權限
默認執行私有構造函數報如下錯誤
如果是私有構造方法,反射默認是無法直接執行的,找到父類中AccessibleObject的方法,設置為true,即可忽略訪問權限
|
|
//使用構造器創建對象 public class NewInstanceTest { @Test public void testName() throws Exception {
//1.獲取Student的字節碼實例 Class<?> clz = Class.forName("cn.sxt.reflect.Student");
//1.1如果類有無參數公共構造函數,直接可以使用類的字節碼實例就創建對象 Student stu0 = (Student) clz.newInstance();
//2.獲取一個參數的構造函數 Constructor<Student> ct1 = (Constructor<Student>) clz.getConstructor(String.class); //2.1.創建對象 Student stu1 = ct1.newInstance("東方不敗");
//3.獲取私有構造函數並創建對象 Constructor<Student> ct2 = (Constructor<Student>) clz.getDeclaredConstructor(String.class,int.class); //3.1設置權限可以創建對象 ct2.setAccessible(true); //3.2創建對象 Student stu2 = ct2.newInstance("西門吹雪",50); } }
|
6. 操作方法-Method
一個類創建對象以后,一般就要執行對象的方法等等,使用反射操作對象
首先要獲取方法,再去執行
6.1. 獲取方法和方法的執行
一個類中的方法有很多,無參,有參,靜態,可變參數私有方法,等等,針對不同的方法處理,提供了不同的獲取方案
6.1.1. 使用Class 獲取對應的方法
|
|
|
|
|
6.1.2. Method執行方法
方法獲取以后就需要執行。Method對象中提供方法執行的功能
Obj :如果是對象方法,傳指定的對象,如果是類方法,傳 null Args: 方法的參數
如果方法有返回結果,可以接收 |
6.1.3. 設置忽略訪問權限
如果是私有方法,反射默認是無法直接執行的,找到父類中AccessibleObject的方法,設置為true,即可忽略訪問權限
|
|
6.2. 方法操作的代碼
6.2.1. Student類
package cn.sxt.reflect._03method;
import java.util.Arrays;
public class Person {
public void hell1() { System.out.println("我是無參數無返回值方法"); }
public String hello2(String name) {
return "你好 :"+name; }
private String hello3(String name,intage) { return "我是 :"+name+",今年 :"+age; }
public static void staticMethod(String name) { System.out.println("我是靜態方法 :"+name); }
public static void method1(int[] intArr) { System.out.println(Arrays.toString(intArr)); }
public static void method2(String[] strArr) { System.out.println(Arrays.toString(strArr)); }
}
|
6.2.2. 測試類
package cn.sxt.reflect._03method;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import org.junit.Test;
//獲取Person類的方法 public class GetMethodTest {
@Test public void testName() throws Exception { // 1.獲取Person字節碼實例 Class<Person> clz = Person.class; // 2.創建對象 Person p = clz.newInstance();
// 3.獲取方法(使用反射),獲取所有公共方法,包含父類的公共方法 Method[] methods1 = clz.getMethods(); for (Method method : methods1) { System.out.println(method); } System.out.println("------------------------------"); // 4.獲取自己類中的所有方法(包括私有) Method[] methods2 = clz.getDeclaredMethods(); for (Method method : methods2) { System.out.println(method); } System.out.println("------------------------------"); // 4.獲取單個指定名稱的方法 Method method = clz.getMethod("hello2", String.class); System.out.println(method);
// 4.1執行方法 Object res = method.invoke(p, "陸小鳳"); System.out.println(res);
// 5.獲取私有的方法 Method hello3 = clz.getDeclaredMethod("hello3", String.class, int.class); System.out.println(hello3);
// 5.1設置忽略訪問權限 hello3.setAccessible(true);
Object res1 = hello3.invoke(p, "葉孤城", 30); System.out.println(res1);
// 6.獲取靜態方法 Method staticMethod = clz.getMethod("staticMethod", String.class);
// 6.1執行靜態方法 staticMethod.invoke(null, "花滿樓");
// 7.獲取有整數數組參數的方法 Method method1 = clz.getMethod("method1", int[].class); method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});
// 8.獲取有整數數組參數的方法 /* * 如果反射傳遞的參數是引用類型,底層有一個拆箱的功能,會將數組的元素拆成一個個參數傳遞過來 * 解決方案: 將數組外面在包裝一層數組,如果拆箱一次,得到還是一個數組 */ Method method2 = clz.getMethod("method2", String[].class); method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}}); } } |
6.2.3. 可變參數的方法執行
如果方法中有可變參數(數組),如果反射傳遞的可變參數是引用類型,底層有一個拆箱的功能,會將數組的元素拆成一個個參數傳遞過來
解決方案: 將數組外面在包裝一層數組,如果拆箱一次,得到還是一個數組
package cn.sxt.reflect._03method;
import java.util.Arrays;
public class Person {
public static void method1(int... intArr) { System.out.println(Arrays.toString(intArr)); }
public static void method2(String...strArr) { System.out.println(Arrays.toString(strArr)); } }
|
// 7.獲取有整數數組參數的方法 Method method1 = clz.getMethod("method1", int[].class); method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});
// 8.獲取有整數數組參數的方法 /* * 如果反射傳遞的參數是引用類型,底層有一個拆箱的功能,會將數組的元素拆成一個個參數傳遞過來 * 解決方案: 將數組外面在包裝一層數組,如果拆箱一次,得到還是一個數組 */ Method method2 = clz.getMethod("method2", String[].class); method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}}); |
7. 操作字段(成員變量)-Field
類中的字段有各種數據類型和各種訪問權限,針對這些情況,反射操作有對應的方法來獲取和處理
7.1. Class中獲取字段方法
Field[] |
獲取當前Class所表示類中所有的public的字段,包括繼承的字段. |
Field[] |
獲取當前Class所表示類中所有的字段,不包括繼承的字段. |
獲取當前Class所表示類中 |
|
getDeclaredField(String name) :獲取當前Class所表示類中該fieldName名字的字段,不包括繼承的字段. |
7.2. Field操作設置值/獲取值
給某個類中的字段設置值和獲取值: 1,找到被操作字段所在類的字節碼 2,獲取到該被操作的字段對象 3,設置值/獲取值 Field類常用方法: void setXX(Object obj, XX value) :為基本類型字段設置值,XX表示基本數據類型 void set(Object obj, Object value) :表示為引用類型字段設置值 參數: obj: 表示字段底層所屬對象,若該字段是static的,該值應該設為null value: 表示將要設置的值 ------------------------------------------------------------------------------------- XX getXX(Object obj) :獲取基本類型字段的值,XX表示基本數據類型 Object get(Object obj) :表示獲取引用類型字段的值 參數: obj: 表示字段底層所屬對象,若該字段是static的,該值應該設為null 返回:返回該字段的值. |
|
設置引用類型的值,非基本數據類型 Obj: 要設置值得對象 Value : 要設置的值 |
package cn.sxt.reflect._04Field;
import static org.junit.Assert.*;
import java.lang.reflect.Field;
import org.junit.Test;
public class FieldTest {
@Test public void testName() throws Exception { //1.獲取People字節碼 Class<People> clz = People.class;
People p = clz.newInstance();
//2.獲取所有公共字段 Field[] fields1 = clz.getFields(); for (Field field : fields1) { System.out.println(field); } System.out.println("---------------------"); //3.獲取所有字段,和訪問權限無關 Field[] fields2 = clz.getDeclaredFields(); for (Field field : fields2) { System.out.println(field); } System.out.println("---------------------"); //3.獲取指定的公共字段 Field emialField = clz.getField("emial"); System.out.println(emialField);
//為字段設置值 emialField.set(p, "zhagnsan@qq.com"); System.out.println(p); //4.獲取指定所有的字段,和訪問權限無關 Field nameFiled = clz.getDeclaredField("name"); System.out.println(nameFiled); //設置忽略訪問權限 nameFiled.setAccessible(true); nameFiled.set(p, "張三"); System.out.println(p);
//5 獲取age字段 Field ageFile = clz.getDeclaredField("age"); ageFile.setAccessible(true); //設置忽略訪問權限 ageFile.setInt(p, 18); System.out.println(p);
} } |
8. Class的其他API方法
//Class字節碼的其他api @Test public void testName() throws Exception {
//1.獲取UserDaoImpl的字節碼實例 Class<?> clz = Class.forName("cn.sxt.reflect._05otherapi.UserDaoImpl");
//2.獲取所有的接口 Class<?>[] interfaces = clz.getInterfaces(); for (Class<?> intface : interfaces) { System.out.println(intface); }
//3.獲取全限定名 System.out.println(clz.getName());
//4.獲取簡單類名 System.out.println(clz.getSimpleName()); //5.獲取包 System.out.println(clz.getPackage().getName()); } |
9. JavaBean
問題: 什么是javaBean?
答: JavaBean就是一個個Java類,在java中符合JavaBean特點類才叫做JavaBean
9.1. JavaBean的三個特點
- JavaBean類的修飾符必須是public,也就是一個JavaBean必須是一個對應一個類文件
- JavaBean必須有無參數公共構造方法(以便於反射直接通過直接通過字節碼實例創建對象)
- 3. JavaBean中的成員變量/字段必須有get/set方法提供對應的屬性
9.1.1. JavaBean中的屬性
Java類有成員變量,成員變量絕對不是屬性,JavaBean中的屬性是有get/set方法確定的
9.1.1.1. Get方法確定屬性
public String getName() { return name; } 屬性確定規則 : get方法去掉 get前綴 ,剩余部分 首字母小寫
屬性名稱 : name |
9.1.1.2. Set方法確定屬性
public void setName(String name) { this.name = name; } 屬性確定規則 : set方法去掉 set前綴 ,剩余部分 首字母小寫
屬性名稱 : name |
如果一個成員變量get/set方法都有確定的屬性就只有一個
問題 :get/set方法確定確定的屬性一般不就和成員變量一樣啊,為什么要有屬性了
答: 一般情況下,Eclipse工具有自動生成get/set方法的功能,確定的JavaBean的屬性只是恰好和成員變量相同,但是成員變量不是屬性
如下特殊性情況,屬性和成員變量名稱不同
//Filed private String firstName; //Filed private String lastName;
//屬性 : fullName public String getFullName() { return this.firstName + this.lastName; } |
10. 小結
- 1. 理解反射概念?反射能干啥?
(1) 反射: 在jvm運行階段,動態的獲取類的信息(字節碼實例,構造器,方法,字段),動態進行對象的創建,方法執行,字段操作。
2. 反射的常用類
(1) Class :所有類的字節碼實例的描述
(2) Constructor :構造器
(3) Method :方法
(4) Field :字段
3. JDBC+反射 代碼封裝
(1) 通用 crud 方法
① 思路清晰