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

點擊上方 "程序員小樂"關注, 星標或置頂一起成長

每天凌晨00點00分, 第一時間與你相約


每日英文

Never expect, never assume, and never demand. Just let it be, because if it's meant to be, it will happen the way you want it to.

永不期待,永不假設,永不強求。順其自然,若是註定發生,必會如你所願。


每日掏心話

其實,許多事從一開始就已預感到了結局,往後所有的折騰,都不過只是為了拖延散場的時間 。


鏈接:.javazhiyin.com/34563.html


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

程序員小樂(ID:study_tech)第 812 次推文 圖片來自百度


往日回顧:百度和谷歌到底有什麼區別?看完終於明白了!


正文


什麼是反射


反射就是指程序在運行的時候可以知道一個類的自身信息。


對於任何一個類:可以知道這個類的屬性和方法。


對於任何一個對象:可以調用這個對象的任何一個方法和屬性。


反射就是把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編譯的時候是成功並且成功運行


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


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


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


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


一篇文章瞭解 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的接口,把類名傳進去就可以調用。這裡體現了 需要哪個類的對象就動態的創建哪個類的對象,也就是說動態的實現了類的加載。


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


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


比如說刪除OnePlus.java


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


一篇文章瞭解 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中加載的過程


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


由圖中可以看出,一個具體的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();
}
}


結果


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


只要知道包的限定名,就可以對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();
}
}


結果


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


實現對數據庫增,查。


原理


保存數據時:把pojo類的屬性取出來,拼湊sql語句


查詢數據的時:把查詢到的數據包裝成一個Java對象


一張數據表對應java的一個pojo對象,表中的每一個字段(column)對應pojo的每一個屬性


數據表名和Pojo的類名相等,column和pojo的屬性相等,不區分大小寫(數據庫中不區分大小寫)


pojo的每一個屬性的get和set方法,都是為了後續的操作


實例


數據表User


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


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> if(i < fieldList.size() - 1){
sql += fieldList.get(i) + ",";
}else{
sql += fieldList.get(i) + ") values (";
}
}

/*拼接sql語句的值*/
for (int i = 0; i <valuelist.size> 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();
}
}

}/<valuelist.size>/<fieldlist.size>/<string>


結果


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


總結


Java反射非常好用,靈活性非常大,不用花費太多的時間去寫操作數據庫的代碼,讓重點在開發者的業務邏輯上。現在很多和數據庫操作的框架都用到反射,只要配置文件,按照框架的規則就可以對數據庫進行相對應的操作了。


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

歡迎在留言區留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發,學習能力的提升上有新的認識,歡迎轉發分享給更多人。


猜你還想看


阿里、騰訊、百度、華為、京東最新面試題彙集

統一異常處理介紹及實戰,看這篇就對了!

真正理解Mysql的四種隔離級別,看了都說好!

GitHub宣佈收購npm,微軟或成最大贏家!開源界野蠻競爭影響1200萬開發者

關注訂閱號「程序員小樂」,收看更多精彩內容
嘿,你在看嗎?


分享到:


相關文章: