一文了解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