反射
1.問題:根據配置檔案re.properties指定資訊,建立cat物件並呼叫方法hi
re.properties配置檔案
classpath=entiy.cat
method=hi
具體程式碼實現:
public class question { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //根據配置檔案re.properties指定資訊,建立cat並呼叫方法hi //1.傳統方法 cat c= new cat(); c.hi(); //2.使用Properties類讀寫配置檔案 Properties properties=new Properties(); properties.load(new FileInputStream("src/main/resources/re.properties")); String classpath= properties.get("classpath").toString(); String method= properties.get("method").toString(); //3.使用反射機制解決 //載入類,返回class型別物件 Class cls= Class.forName(classpath); Object o=cls.newInstance(); System.out.println("o的執行型別"+o.getClass()); Method method1= cls.getMethod(method); method1.invoke(o);//傳統方式 物件.方法() 反射機制 方法.invoke(物件) } }
如果呼叫的是cat的其他方法,不需要修改原始碼,只需要更改配置檔案re.properties中的value值即可,method=value,這符合設計模式的OCP原則(開閉原則:不修改原始碼,擴容功能)
2.反射機制
反射優點和缺點:
1.優點:可以動態的建立和使用物件(也是框架底層核心),使用靈活沒有反射機制,框架技術就失去底層支撐。
2.缺點:使用反射基本是解釋執行,對執行速度有影響.
public class Reflection01 { public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { m1(); m2(); } //用傳統方式呼叫hi方法 public static void m1(){ cat c = new cat(); long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { c.hi(); } long end= System.currentTimeMillis(); System.out.println("用傳統方式呼叫hi方法 耗時="+(end-start)); } //用反射方式呼叫hi方法 public static void m2() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Class<?> cls = Class.forName("entiy.cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射呼叫方法時,取消訪問檢查 耗時=2293, long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o); } long end= System.currentTimeMillis(); System.out.println("用反射方式呼叫hi方法 耗時="+(end-start)); } }
反射呼叫最佳化關閉訪問檢查
1.Method和Field、Constructor物件都有setAccessible(方法
2. setAccessible作用是啟動和禁用訪問安全檢查的開關
3.引數值為true表示反射的物件在使用時取消訪問檢查,提高反射的效率。引數值為false則表示反射的物件執行訪問檢查
3.Class類
3.1.基本介紹
1. Class也是類,因此也繼承Object類
2. Class類物件不是new出來的,而是系統建立的
3.對於某個類的Class類物件,在記憶體中只有一份,因為類只載入一次
4.每個類的例項都會記得自己是由哪個Class例項所生成
5.透過Class物件可以完整地得到一個類的完整結構,透過一系列API
6. Class物件是存放在堆的
7.類的位元組碼進位制資料,是放在方法區的,有的地方稱為類的後設資料(包括方法程式碼,變數名,方法名,訪問許可權等等)
3.2.獲取Class物件
1.在程式碼階段(編譯階段),可以透過Class類的靜態方法forName(String className)獲取,例如:Class cls = Class.forName("entiy.cat");
應用場景:多用於配置檔案,讀取類全路徑,載入類
2.Class類階段(載入階段),透過類名.class獲取,該方式最為安全可靠,程式效能最高。例如:Class cls = cat.class;
應用場景:多用於引數傳遞,比如透過反射得到對應構造器物件
3.Runtime(執行階段),前提是已知某個類的例項,透過呼叫該例項的getClass(),例項:
Class<?> aClass = c.getClass();
應用場景:不經常用,這種是透過建立好的物件,來獲取Class物件,一般有物件就可以直接透過物件呼叫對應的成員方法,成員變數等,沒必要再透過反射呼叫。
4.透過類載入器來獲取類的class物件
cat c = new cat(); //得到類載入器 ClassLoader loader = c.getClass().getClassLoader(); //透過載入器得到Class物件 Class<?> aClass = loader.loadClass("entiy.cat");
5.基本資料型別(int,char,boolean,float,double,byte,long,short),可以按以下方式獲得class物件 例如Class<Integer> integerClass = int.class;
6.基本型別對應的包裝類,可以透過.Type得到Class物件,例如:Class<Integer> type = Integer.TYPE;
3.3.有Class物件的型別
1.外部類,成員內部類,靜態內部類,區域性內部類,匿名內部類
2.interface :介面
3.陣列
4. enum :列舉
5.annotation :註解
6.基本資料型別
7.void
3.4.Class類的基本方法
4.類載入
反射機制是java實現動態語言的關鍵,也就是透過反射實現類動態載入。
1.靜態載入:編譯時載入相關的類,如果沒有則報錯,依賴性太強
2.動態載入:執行時載入需要的類,如果執行時不用該類,即使不存在該類,也不報錯,降低了依賴性
4.1類載入時機
1.當建立物件時(new) //靜態載入
2.當子類被載入時,父類也載入//靜態載入
3.呼叫類中的靜態成員時//靜態載入
4.透過反射//動態載入
4.2.類載入的五個階段
載入階段
JVM在該階段主要目的是將位元組碼從不同的資料來源(可能是class檔案,也可能是jar包,甚至是網路)轉化為二進位制位元組流載入到記憶體中,並生成一個代表該類的Java.lang.Class物件。
連線階段—驗證
1.目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
2.包括:檔案格式驗證(是否以魔數oxcafebabe開頭)、後設資料驗證、位元組碼驗證和符號引用驗證
3.可以考慮使用-Xverify:none引數來關閉大部分的類驗證措施,縮短虛擬機器類載入的時間。
連線階段—準備
JVM會在該階段對靜態變數,分配記憶體並預設初始化(對應資料型別的預設初始值,如0、OL、null、 false等) 這些變數所使用的記憶體都將在方法區中進行分配。
static class A{ public int n1=10; public static int n2=20; public static final int n3=30; /* n1是例項屬性,不是靜態變數,因此在準備階段,不會分配記憶體 n2是靜態變數,分配記憶體,預設初始化賦值0 n3 static final 是常量,一旦賦值就不變,分配記憶體賦值30, */ }
連線階段—解析
虛擬機器將常量池內的符號引用替換為直接引用的過程(相對位置--物理位置)
類載入
1.到初始化階段,才真正開始執行類中定義的Java程式程式碼,此階段是執行< clinit> ()方法的過程。
2. <clinit> ()方法是由編譯器按語句在原始檔中出現的順序,依次自動收集類中的所有靜態變數的賦值動作和靜態程式碼塊中的語句,並進行合併。
3.虛擬機器會保證一個類的 <clinit> 0)方法在多執行緒環境中被正確地加鎖、同步,如果多個執行緒同時去初始化一個類, 那麼只會有一個執行緒去執行這個類的<clinit>() 方法,其他執行緒都需要阻塞等待,直到活動執行緒執行<clinit> )方法完畢
5.透過反射獲取類的結構資訊
反射的第一步是先得到類物件,然後從類物件中獲取類的成分物件。
首先先建立有繼承關係兩個實體類,如下:
public class animal {
public int age;
public animal() {
}
public animal(int age) {
this.age = age;
}
private void eat(){
System.out.println("吃飯");
}
}
public class cat extends animal {
private String name;
public String hobby;
protected String work;
//構造方法
public cat() {
System.out.println("呼叫了公共,無參構造方法執行了");
}
public cat(String name){
System.out.println("姓名"+name);
}
private cat(int age){
System.out.println("私有的構造方法, 年齡"+age);
}
public cat(String name, String hobby, String work) {
this.name = name;
this.hobby = hobby;
this.work = work;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void hi(){
System.out.println("你好"+name);
}
}
5.1.使用反射技術獲取構造器物件並使用
Class類中用於獲取構造器的方法:
獲取構造器的作用依然是初始化一個物件返回。
Constructor類中用於建立物件的方法:
Class c=Class.forName("entiy.cat");
System.out.println("------------呼叫所有公共的構造方法--------");
final Constructor[] constructors = c.getConstructors();
for (Constructor t:
constructors) {
System.out.println(t);
}
System.out.println("------------呼叫所有的構造方法(包括:私有、受保護、預設、公有)-------");
final Constructor[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor t:
declaredConstructors) {
System.out.println(t);
}
System.out.println("------------呼叫的構造方法公共,有參的-------");
Constructor constructor = c.getConstructor(String.class);
System.out.println("constructor"+constructor);
final Object o = constructor.newInstance("招財貓");
System.out.println("------------呼叫的構造方法私有,有參的-------");
final Constructor declaredConstructor = c.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);
//暴力訪問(忽略訪問修飾符)
declaredConstructor.setAccessible(true);
final Object o1 = declaredConstructor.newInstance(5);
5.2.使用反射技術獲取成員變數物件並使用
Class類中用於獲取成員變數的方法:
獲取成員變數的作用依然是在某個物件中取值、賦值
Field類中用於取值、賦值的方法:
Class c=Class.forName("entiy.cat");
System.out.println("************獲取本類和父類所有公有的欄位********************");
final Field[] fields = c.getFields();
for (Field f:
fields ) {
System.out.println(f);
}
System.out.println("************獲取本類所有的欄位(包括私有、受保護、預設的)********************");
final Field[] declaredFields = c.getDeclaredFields();
for (Field f:
declaredFields ) {
System.out.println(f);
}
System.out.println("************獲取父類單個公共欄位並呼叫********************");
final Field f = c.getField("age");
System.out.println(f);
final Object o2 = c.getConstructor().newInstance();//產生cat物件
f.set(o2,2);
final cat cat = (cat) o2;
System.out.println("驗證年齡"+cat.age);
System.out.println("************獲取本類單個私有欄位並呼叫********************");
final Field name = c.getDeclaredField("name");
name.setAccessible(true);//暴力反射,解除私有限定
name.set(o2,"幸運貓");
System.out.println("驗證名字"+cat.getName());
5.3.使用反射技術獲取方法物件並使用
Class類中用於獲取成員方法的方法:
獲取成員方法的作用依然是在某個物件中進行執行此方法
Method類中用於觸發執行的方法:
public class animal {
public void eat(){
System.out.println("吃飯");
}
}
public class cat extends animal {
public void hi(){
System.out.println("你好");
}
public void show1(String s){
System.out.println("呼叫了:公有的,String引數的show1(): s = " + s);
}
protected void show2(){
System.out.println("呼叫了:受保護的,無參的show2()");
}
void show3(){
System.out.println("呼叫了:預設的,無參的show3()");
}
private String show4(int age){
System.out.println("呼叫了,私有的,並且有返回值的,int引數的show4(): age = " + age);
return "abcd";
}
}
Class c=Class.forName("entiy.cat");
System.out.println("*************獲取本類和父類的所有公有成員方法******");//父類不僅包括animal類,還有基類object
final Method[] methods = c.getMethods();
for (Method m:
methods) {
System.out.println(m);
}
System.out.println("*************獲取本類所有成員方法******");
final Method[] declaredMethods = c.getDeclaredMethods();
for (Method m:
declaredMethods) {
System.out.println(m);
}
System.out.println("*************獲取父類公有單個成員方法******");
final Method m = c.getMethod("eat");
System.out.println(m);
//例項化一個物件呼叫eat方法
final Object o3 = c.getConstructor().newInstance();
m.invoke(o3);
System.out.println("*************獲取本類私有的show4()方法******");
final Method s = c.getDeclaredMethod("show4", int.class);
System.out.println(s);
s.setAccessible(true);//解除私有限定
final Object result = s.invoke(o3, 8);
System.out.println("返回值"+result);
6.反射作用
6.1.繞過編譯階段為集合新增資料
反射是作用在執行時的技術,此時集合的泛型將不能產生約束了,此時是可以為集合存入其他任意型別的元素
泛型只是在編譯階段可以約束集合只能操作某種資料型別,在編譯成Class檔案進入執行階段的時候,其真實型別都是ArrayList了,泛型相當於被擦除了。
{
final ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
// strList.add(100);
//獲取ArrayList的Class物件,在執行階段沒有泛型約束,可以新增其他型別
Class listClass = list.getClass();
final Method m = listClass.getMethod("add", Object.class);
m.invoke(list,100);
m.invoke(list,23.45);
for (Object o:
list) {
System.out.println(o);
}
}
6.2.反射做通用框架
簡單demo:
需求如下:給你任意一個物件,在不清楚物件欄位的情況下,可以將物件的欄位名稱(變數名)和對應值(具體值,或預設值)儲存到檔案中去。
分析:
- 定義一個方法,可以接收任意類的物件。public class BatisUtil{}
- 每次收到一個物件後,需要解析這個物件的全部成員變數名稱。
- 使用反射獲取物件的Class類物件,然後獲取全部成員變數資訊。
- 遍歷成員變數資訊,然後提取本成員變數在物件中的具體值
- 存入成員變數名稱和值到檔案中去即可。
public static void save(Object obj) throws FileNotFoundException, IllegalAccessException { try { PrintStream ps = new PrintStream(new FileOutputStream("src/main/resources/data.txt", true)); Class<?> c = obj.getClass(); // 表明物件的類簡易名稱 ps.println("--------" + c.getSimpleName() + "-----------"); final Field[] declaredFields = c.getDeclaredFields(); for (Field f : declaredFields) { final String var = f.getName(); // 記錄當前的成員變數名稱 f.setAccessible(true);// 注:暴力開啟許可權的方法,將類物件的私有成員暴露給本類使用,將獲取完整的資料資訊 final String value = f.get(obj) + "";//假設學生類,變數名為name,獲取name在obj物件下設定的具體值 ps.println(var + "==>" + value); /*JDK1.7及以上,IO流自動關閉總結: (1)需要關閉的資源必須實現Closeable或者AutoCloseable;檢視JDK原始碼,Closeable繼承自AutoCloseable,這兩個介面中只有一個方法:void close() throws Exception; (2)需要關閉的資源必須重寫close()方法,方法中必須包含完整的資源關閉操作程式碼; (3)呼叫寫法:自動關閉功能屬於try的增強特性,此時無需顯式的書寫資源關閉程式碼,try段程式碼執行完畢後,系統會自動呼叫資源的關閉方法關閉資源:*/ } } catch (Exception e) { e.printStackTrace(); } }
public static void main(String[] args) throws FileNotFoundException, IllegalAccessException { cat c = new cat(); c.setAge(3); c.setHobby("吃魚"); c.setName("招財貓"); c.setWork("招財"); // 輸入基本資訊,並送給BatisUtil類中的save()方法中進行儲存 BatisUtil.save(c); }