一文了解Java反射和應用
- 2019 年 10 月 7 日
- 筆記
作者:小銘同學 https://www.javazhiyin.com/34563.html
什麼是反射
反射就是指程式在運行的時候可以知道一個類的自身資訊。
- 對於任何一個類:可以知道這個類的屬性和方法。
- 對於任何一個對象:可以調用這個對象的任何一個方法和屬性。
反射就是把java類中的各種成分映射成一個個的Java對象。
例如:一個類有:成員變數、方法、構造方法、包等等資訊,利用反射技術可以對一個類進行 解剖,把個個 組成部分映射成一個個對象。(其實:一個類中這些成員方法、構造方法、在加入類中都有一個類來描述)
反射的功能
- 在運行時判斷任意一個對象所屬的類
- 在運行的時候構造任意一個類的對象
- 在運行時判斷一個類所具有的成員變數和方法
- 在運行時調用任何一個對象的方法
- 生成動態代理(會有一篇關於動態代理的文章,在這裡挖坑)
反射的優點和缺點
動態編譯和靜態編譯
反射用到的是動態編譯,既然有動態編譯,就會有靜態編譯。
那麼動態編譯和靜態編譯又有什麼區別?
靜態編譯:在編譯的時候進確定類型,如果綁定對象成功,new 是靜態載入類,就編譯通過。
1 程式碼示例
public class Phone{ public static void main(String[] args){ if("iphone".equals(args[0])){ Iphone iphone = new Iphone(); iphone.call(); } if("xiaomi".equals(args[0])){ Xiaomi xiaomi = new Xiaomi(); xiaomi.call(); } } } class Xiaomi{ public void call(){ System.out.println("xiaomi is calling"); } } class Iphone{ public void call(){ System.out.println("iphone is calling"); } }
2 解釋
當在Phone.java裡面寫好程式碼的時候,如果需要添加新的類,則需要直接在文件裡面修改程式碼。假如需要添加一個華為手機,則我需要在Phone.java文件裡面加個if語句判斷傳進來的參數是不是"huawei",這樣增加了類之間的耦合性。
當刪除一個類的時候Phone.java編譯可能會出現錯誤。 假如我刪除了小米手機這個類,phone.java文件沒有刪除if判斷語句,那麼phone.java在編譯的時候則會失敗。
沒刪除Xiaomi.java編譯的時候是成功並且成功運行。


刪除Xiaomi.java編譯的時候就會失敗了,因為Xiaomi.java不存在


動態編譯:在運行的時候確定類型,綁定對象。最大發揮了Java的多態,降低類之間的耦合性。
1 程式碼示例
Phone.java
public static void main(String[] args){ try{ Class c = Class.forName("Huawei"); PhoneInterface cellPhone = (PhoneInterface)c.newInstance(); cellPhone.ring(); }catch (Exception e){ e.printStackTrace(); } } PhoneInterface.java interface PhoneInterface{ void ring(); } Huawei.java public class Huawei implements PhoneInterface{ @Override public void ring(){ System.out.println("huawei is ringing..."); } } OnePlus.java public class OnePlus implements PhoneInterface{ @Override public void ring(){ System.out.println("OnePlus is ringing..."); } }
2 解釋
(1)對比靜態編譯,當我們需要往Phone.java裡面傳遞新的類參數的時候,根本不需要修改Phone.java的程式碼,因為這裡應用了Java的多態。只要新建一個新的類實現了PhoneInterface的介面,把類名傳進去就可以調用。這裡體現了 需要哪個類的對象就動態的創建哪個類的對象,也就是說動態的實現了類的載入。

(2)當刪除一個類的時候,Phone.java文件不會編譯失敗。
比如說刪除OnePlus.java


區別:這裡說明了動態載入的在不修改Phone.java的前提下不會因為其它類的不存在而導致整個文件不能編譯,而靜態載入則會編譯的時候綁定對象,從而導致編譯失敗。
優點
以實現動態創建對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟體,不可能一次就把把它設計的很完美,當這個程式編譯後,發布了,當發現需要更新某些功能時,我們不可能要用戶把以前的卸載,再重新安裝新的版本,假如這樣的話,這個軟體肯定是沒有多少人用的。採用靜態的話,需要把整個程式重新編譯一次才可以實現功能的更新,而採用反射機制的話,它就可以不用卸載,只需要在運行時才動態的創建和編譯,就可以實現該功能。
缺點
對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於只直接執行相同的操作。
Class類和類類型
Class類
所有的類是java.lang.Class類的對象,Class類是所有類的類,反射的基礎。
Class對象(類類型)
普通類構造對象是:Student s = new Student();
但Class對象則不是,看Class類的源碼,構造器是私有的,則用戶無法直接像普通類那樣new一個Class的對象,只有JVM才可以構造Class對象。
private Class(ClassLoader loader) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; }
但是我們可以通過一個已知的類獲得Class對象
有以下三種方式:
Class s = Student.class; Class s1 = new Student().getClass(); Class s2 = Class.forName("Student");
Class對象就是類類型,在這裡表示的是Student類的類型,下面看一個圖了解Class對象是什麼和類在JVM中載入的過程

由圖中可以看出,一個具體的Class對象就保存了具體類的基本屬性和方法,並且可以調用。
Student類
package General; import java.lang.reflect.Method; public class Student { private String name; private int age; private String msg = "I am a student"; public void fun() { System.out.println("fun"); } public void fun(String name,int age) { System.out.println("我叫"+name+",今年"+age+"歲"); } public Student(){ } private Student(String name){ } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
反射相關操作
文章開始說過,反射會把一個類的成分(成員變數,方法,構造器)各自映射成一個個對象(Field對象,Method對象,Construct對象),
一個類中這些成員方法、構造方法、在加入類中都有一個類來描述。
java.lang.reflect.Constructor; java.lang.reflect.Field; java.lang.reflect.Method; java.lang.reflect.Modifier;
通過Class對象我們可以做什麼呢?
- 獲取成員方法Method
- 獲取成員變數Field
- 獲取構造函數Construct
獲取成員方法
用法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到該類所有的方法,不包括父類的 public Method getMethod(String name, Class<?>... parameterTypes) // 得到該類所有的public方法,包括父類的 //具體使用 Method[] methods = class1.getDeclaredMethods(); //獲取class對象的所有聲明方法 Method[] allMethods = class1.getMethods(); //獲取class對象的所有public方法 包括父類的方法 Method method = class1.getMethod("info", String.class); //返回次Class對象對應類的、帶指定形參列表的public方法 Method declaredMethod = class1.getDeclaredMethod("info", String.class); //返回次Class對象對應類的、帶指定形參列表的方法
例子
public static void main(String[] args) { try { Class c = Class.forName("General.Student"); Object o = c.newInstance(); Method method = c.getMethod("fun",String.class,int.class); method.invoke(o,"jieMing",21); } catch (Exception e) { e.printStackTrace(); } }
結果

只要知道包的限定名,就可以對Student這個類進行所有操作
獲取成員變數資訊
成員變數 = 成員類型+變數名
用法
獲取成員變數,通過Class類的以下方法,變數是 成員變數名
public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變數,不包括其父類的變數 public Field getField(String name) // 獲得該類自所有的public成員變數,包括其父類變數 //具體實現 Field[] allFields = class1.getDeclaredFields(); //獲取class對象的所有屬性 Field[] publicFields = class1.getFields(); //獲取class對象的public屬性 Field ageField = class1.getDeclaredField("age"); //獲取class指定屬性 Field desField = class1.getField("des"); //獲取class指定的public屬性
例子
public static void main(String[] args) { try { Class c = Class.forName("General.Student"); Object o = c.newInstance(); Field field = c.getDeclaredField("msg"); //msg在例子中是私有變數,如果沒設置之前,用c.getField()是會報錯的 field.setAccessible(true); //private設置為public System.out.println(c.getField("msg")); //用getFiled()則可以直接訪問 } catch (Exception e) { e.printStackTrace(); } }
獲取構造函數資訊
獲取構造函數,Class的方法如下
用法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 獲得該類所有的構造器,不包括其父類的構造器 public Constructor<T> getConstructor(Class<?>... parameterTypes) // 獲得該類所以public構造器,包括父類 //具體 Constructor<?>[] allConstructors = class1.getDeclaredConstructors(); //獲取class對象的所有聲明構造函數 Constructor<?>[] publicConstructors = class1.getConstructors(); //獲取class對象public構造函數 Constructor<?> constructor = class1.getDeclaredConstructor(String.class); //獲取指定聲明構造函數 Constructor publicConstructor = class1.getConstructor(String.class); //獲取指定聲明的public構造函數
例子
Student類的私有構造函數
private Student(String name){ System.out.println(name); }
獲取私有的構造函數,並且設置為public從而可以創建對象
public static void main(String[] args) { try { Class c = Class.forName("General.Student"); Constructor constructor = c.getDeclaredConstructor(String.class); constructor.setAccessible(true); //如果把這行注釋掉,調用private的構造函數則會報錯 constructor.newInstance("JieMingLi"); } catch (Exception e) { e.printStackTrace(); } }
結果

實現對資料庫增,查。
原理
- 保存數據時:把pojo類的屬性取出來,拼湊sql語句
- 查詢數據的時:把查詢到的數據包裝成一個Java對象
- 一張數據表對應java的一個pojo對象,表中的每一個欄位(column)對應pojo的每一個屬性
- 數據表名和Pojo的類名相等,column和pojo的屬性相等,不區分大小寫(資料庫中不區分大小寫)
- pojo的每一個屬性的get和set方法,都是為了後續的操作
實例
數據表User

pojo User類
package dbtest; public class User { private int id; private String name; private String pwd; private int age; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + ''' + ", pwd='" + pwd + ''' + ", age=" + age + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
資料庫連接的工廠類
package dbtest; import java.sql.Connection; import java.sql.DriverManager; public class ConnectDBFactory { public static Connection getDBConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/sm"; String user = "root"; String password = "123456"; conn = DriverManager.getConnection(url,user,password); } catch (Exception e) { e.printStackTrace(); } return conn; } }
操作資料庫的dao
package dbtest; import org.springframework.web.bind.annotation.ResponseBody; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.*; import java.util.ArrayList; import java.util.List; public class SqlSession { public static String getSaveObjectSql(Object o) throws InvocationTargetException, IllegalAccessException { String sql = "insert into "; /*獲取Class對象*/ Class c = o.getClass(); /*獲取pojo所有的方法*/ Method[] methods = c.getDeclaredMethods(); /*獲取全類名*/ String cName = c.getName(); /*通過全類名獲取資料庫名稱*/ String tableName = cName.substring(cName.lastIndexOf(".")+1,cName.length()); sql+= tableName + "("; /*欄位名字*/ List<String> fieldList = new ArrayList<>(); /*欄位對應的值*/ List valueList = new ArrayList(); /*遍歷Class對象的Method對象,就可以執行相對於的方法了*/ for (Method method : methods) { String methodName = method.getName(); /*找出get方法,並設置值*/ if(methodName.startsWith("get") && !method.equals("getClass")){ String fieldName = methodName.substring(3,methodName.length()); fieldList.add(fieldName); Object res = method.invoke(o,null); if(res instanceof String){ valueList.add("""+res+"""); }else{ valueList.add(res); } } } /*拼接sql語句的欄位*/ for (int i = 0; i <fieldList.size() ; i++) { if(i < fieldList.size() - 1){ sql += fieldList.get(i) + ","; }else{ sql += fieldList.get(i) + ") values ("; } } /*拼接sql語句的值*/ for (int i = 0; i <valueList.size() ; i++) { if(i < valueList.size()-1){ sql += valueList.get(i) + ","; }else{ sql += valueList.get(i) + ")"; } } return sql; } /*保存數據的操作*/ public int saveObject(Object o) throws InvocationTargetException, IllegalAccessException, SQLException { Connection connection = ConnectDBFactory.getDBConnection(); String sql = getSaveObjectSql(o); PreparedStatement statement = connection.prepareStatement(sql); int i = 0; i = statement.executeUpdate(); return i; } /* * 查詢數據,查詢出來的數據映射到pojo的每一個屬性上 * */ public Object getObject(String pname,int id) throws ClassNotFoundException { /*通過包名獲取數據表名*/ String tableName = pname.substring(pname.lastIndexOf(".")+1,pname.length()); String sql = "select * from " + tableName + " where Id = " + id; Connection conn = ConnectDBFactory.getDBConnection(); Class c = Class.forName(pname); Object obj = null; try{ Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(sql); Method[] methods = c.getDeclaredMethods(); while(resultSet.next()){ obj = c.newInstance(); for (Method method :methods ) { String methodName = method.getName(); if(methodName.startsWith("set")){ /*通過方法名獲取資料庫的列名*/ String columnName = methodName.substring(3,methodName.length()); /*獲取參數的類型*/ Class[] params = method.getParameterTypes(); /*判斷參數的類型*/ if(params[0] == String.class){ method.invoke(obj,resultSet.getString(columnName)); } if(params[0] == int.class){ method.invoke(obj,resultSet.getInt(columnName)); } } } } }catch (Exception e){ e.printStackTrace(); } return obj; } public static void main(String[] args) { try{ SqlSession session = new SqlSession(); User user = new User(); user.setAge(22); user.setName("JiemingLi"); user.setId(44); user.setPwd("123456"); int resNum = session.saveObject(user); if(resNum > 0){ System.out.println("成功"); }else{ System.out.println("插入失敗"); } User res = (User)session.getObject("dbtest.User",44); System.out.println(res); }catch (Exception e){ e.printStackTrace(); } } }
結果

總結
Java反射非常好用,靈活性非常大,不用花費太多的時間去寫操作資料庫的程式碼,讓重點在開發者的業務邏輯上。現在很多和資料庫操作的框架都用到反射,只要配置文件,按照框架的規則就可以對資料庫進行相對應的操作了。
參考
http://www.cnblogs.com/jqyp/archive/2012/03/29/2423112.html https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/ https://blog.csdn.net/lwl20140904/article/details/80163880