歡迎光臨
每天分享高質量文章

一篇文章瞭解 Java 反射和應用

(給ImportNew加星標,提高Java技能)

 

作者:小銘同學,

連結: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 getDeclaredConstructor(Class>... parameterTypes) //  獲得該類所有的建構式,不包括其父類的建構式
public Constructor 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 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             if(i < fieldList.size() - 1){
                sql += fieldList.get(i) + ",";
            }else{
                sql += fieldList.get(i) + ") values (";
            }
        }
 
        /*拼接sql陳述句的值*/
        for (int i = 0; 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反射非常好用,靈活性非常大,不用花費太多的時間去寫運算元據庫的程式碼,讓重點在開發者的業務邏輯上。現在很多和資料庫操作的框架都用到反射,只要配置檔案,按照框架的規則就可以對資料庫進行相對應的操作了。

已同步到看一看
贊(0)

分享創造快樂