前言
最近一些朋友怎麼是說自己的代碼太複雜、太臃腫、靈活性太差,也不知道問題出在哪裡。
首先表揚一下你的精神,可以時刻關注著自己代碼的問題。
作為一個優秀的碼農,總是希望用最少的代碼來實現某一項功能,我也會經常翻看自己寫的舊代碼,看看有沒有可以提升的空間。
代碼太複雜要考慮的是有沒有「殺雞用牛刀」,架構過度的設計,代碼層級過度設計等等。
代碼臃腫一般都是為了一些簡單的功能快速上線,一直往裡面堆功能性或兼容性的代碼,這就是一直在做加法,但優秀的程式設計師應該學會做減法,時不時檢查一下自己的代碼是否存在冗餘和無用的功能性代碼。
而關於代碼的靈活性那就更有的說了,今天就來帶大家來聊聊關於這方面的技術點:反射機制。
反射機制
反射是框架設計的靈魂,java的反射機制就是增加程序的靈活性,避免將代碼寫死在程序裡
反射的概述
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
要想解剖一個類,必須先要獲取到該類的字節碼文件對象,而解剖使用的就是Class類中的方法。
所以先要獲取到每一個字節碼文件對應的Class類型的對象。
Class
在Java編譯運行過程中,當程序new一個新對象或者引用靜態成員變量時,Java虛擬機(JVM)中的類加載器子系統會將對應Class對象加載到JVM中,然後JVM再根據這個類型信息相關的Class對象創建我們需要實例對象或者提供靜態變量的引用值。
需要特別注意的是,手動編寫的每個class類,無論創建多少個實例對象,在JVM中都只有一個Class對象,即在內存中每個類有且只有一個相對應的Class對象。
類加載
實際上所有的類都是在對其第一次使用時動態加載到JVM中的,當程序創建第一個對類的靜態成員引用時,就會加載這個被使用的類。下面通過一個簡單例子來說明Class對象被加載的時間節點:
package first.test;class Student {static { System.out.println("study"); }}class Teacher{static { System.out.println("education"); }}public class watch{public static void print(Object obj) {System.out.println(obj);}public static void main(String[] args) {print("1");new Student();print("2");new Teacher();print("3");}}
在上述代碼中,每個類Student、Teacher都存在一個static語句:
運行結果:
1study2education3
Java程序在它們開始運行之前並非都被加載到內存的,其各個部分是按需加載,所以在程序使用該類時,類加載器首先會檢查這個類的Class對象是否已被加載,如果還沒有加載,默認的類加載器就會先根據類名查找.class文件(編譯後Class對象被保存在同名的.class文件中),在這個類的字節碼文件被加載時,它們必須接受相關驗證,以確保其沒有被破壞並且不包含不良Java代碼(這是java的安全機制檢測),完全沒有問題後就會被動態加載到內存中,此時相當於Class對象也就被載入內存了(畢竟.class字節碼文件保存的就是Class對象),同時也就可以被用來創建這個類的所有實例對象。
Class.forName方法
Class clazz=Class.forName("com.wk.new.Student");
forName方法是Class類的一個static成員方法,程序中所創建的所有的Class對象都源於這個Class類,因此Class類中定義的方法將適應所有Class對象。通過forName方法,我們可以獲取到類對應的Class對象引用。因此如果我們想獲取一個類的運行時類型信息並加以使用時,可以調用Class.forName()方法獲取Class對象的引用,這樣做的好處是無需通過持有該類的實例對象引用而去獲取Class對象。
Class字面常量
在Java中存在另一種方式來生成Class對象的引用,它就是Class字面常量,如下:
Class clazz = Student.class;
這種方式相對前面兩種方法更加簡單,更安全。因為它在編譯器就會受到編譯器的檢查同時由於無需調用forName方法效率也會更高,因為通過字面量的方法獲取Class對象的引用不會自動初始化該類。
理解Java的反射技術
如前言的反射機制所說,反射即是在程序動態運行的時候,對於任意一個類,可以獲得其所有的方法以及變量。
在反射包中,我們常用的類主要有:
Constructor類:表示的是Class 對象所表示的類的構造方法,利用它可以在運行時動態創建對象;Field類:Field表示Class對象所表示的類的成員變量,通過它可以在運行時動態修改成員變量的屬性值(包含private);Method類:Method表示Class對象所表示的類的成員方法,通過它可以動態調用對象的方法(包含private)。下面將對這幾個重要類進行分別說明(類中的方法其實很好區分,每個方法後面+s,表示獲取到相應類的所有對象;每個方法前面+declared,表示獲取到不僅僅是public對象,還包括private對象)。
Constructor類
獲取構造方法們//返回指定參數類型、具有public訪問權限的構造函數對象 * Constructor<?>[] getConstructors() * Constructor<T> getConstructor(類<?>... parameterTypes) //返回指定參數類型、所有聲明的(包括private)構造函數對象 * Constructor<T> getDeclaredConstructor(類<?>... parameterTypes) * Constructor<?>[] getDeclaredConstructors()
示例
Class<?> clazz = null;//獲取Class對象的引用clazz = Class.forName("com.wk.new.Student");//此時必須無參構造函數,否則將拋異常Student student = (Student) clazz.newInstance();student.setAge(3);student.setName("hali");
獲取帶String參數的public構造函數
Constructor cs =clazz.getConstructor(String.class);//創建StudentStudent student= (Student) cs.newInstance("lixiang");student.setAge(18);
取得指定帶int和String參數構造函數,該方法是私有構造
Constructor cs=clazz.getDeclaredConstructor(int.class,String.class);//由於是private必須設置可訪問,否則創建示例沒有權限(底層)cs.setAccessible(true);//創建Dog對象Dog dog= (Dog) cs.newInstance(3,"hali");
Method類
獲取成員方法們:* Method[] getMethods() * Method getMethod(String name, 類<?>... parameterTypes)* Method[] getDeclaredMethods() * Method getDeclaredMethod(String name, 類<?>... parameterTypes)
示例
Class clazz = Class.forName("com.wk.new.Student");//根據參數獲取public的Method,包含繼承自父類的方法Method method = clazz.getMethod("study",int.class,String.class);//獲取所有public的方法:Method[] methods =clazz.getMethods();//獲取當前類的方法包含private,該方法無法獲取繼承自父類的methodMethod methodSelf = clazz.getDeclaredMethod("studySelf");//獲取當前類的所有方法包含private,該方法無法獲取繼承自父類的methodMethod[] methodsSelf=clazz.getDeclaredMethods();
Field類
獲取成員變量們
* Field[] getFields() :獲取所有public修飾的成員變量 * Field getField(String name) 獲取指定名稱的 public修飾的成員變量 * Field[] getDeclaredFields() 獲取所有的成員變量,不考慮修飾符 * Field getDeclaredField(String name)
示例
Class<?> clazz = Class.forName("com.wk.new.Student");//獲取指定欄位名稱的Field類,注意欄位修飾符必須為public而且存在該欄位Field field = clazz.getField("age");//獲取所有修飾符為public的欄位,包含父類欄位Field fields[] = clazz.getFields();//獲取當前類所欄位(包含private欄位),不包含父類的欄位Field fields2[] = clazz.getDeclaredFields();
注意:Field其中的set(Object obj, Object value)方法是Field類本身的方法,用於設置欄位的值,而get(Object obj)則是獲取欄位的值,相信這個不難理解。另外,final關鍵字修飾的Field欄位是安全的,在運行時可以接收任何修改,但最終其實際值是不會發生改變的。
簡單的框架實現
一直說反射是框架的核心,接下來就簡單寫一個框架,來幫助我們創建任何類的對象(以Student為例)。編寫一個Student類
package com.wk.new;public class Student {public void study(){System.out.println("study");}}
編寫配置文件 pro.properties
className=com.wk.new.StudentmethodName=study
編寫框架類
/*** 框架類*/public class ReflectTest {public static void main(String[] args) throws Exception {//可以創建任意類的對象,可以執行任意方法//Properties對象Properties pro = new Properties();//獲取class目錄下的配置文件ClassLoader classLoader = ReflectTest.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("pro.properties");pro.load(is);//獲取配置文件中定義的數據String className = pro.getProperty("className");String methodName = pro.getProperty("methodName");Class cls = Class.forName(className);//創建對象Object obj = cls.newInstance();Method method = cls.getMethod(methodName);//執行獲取的方法method.invoke(obj);}}
好了,寫到這裡一個簡單的框架就實現了