(給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反射非常好用,靈活性非常大,不用花費太多的時間去寫運算元據庫的程式碼,讓重點在開發者的業務邏輯上。現在很多和資料庫操作的框架都用到反射,只要配置檔案,按照框架的規則就可以對資料庫進行相對應的操作了。
朋友會在“發現-看一看”看到你“在看”的內容