反射;獲取類的字節碼對象;Class類實例化對象;獲取類中的公共構造方法/成員變量/成員方法並執行;暴力反射 (Java Day27)

一,反射【是框架的靈魂】

  • 概述:在程序運行過程當中能夠對任意類型中的任意資源進行操做,這種動態獲取或操做資源的行爲就叫作反射。
  • 場景:在不知道操做類型的基礎進行操做採用反射,只有在要運行的時候才知道類型。
  • 反射就是正向運行的逆向過程。
  1. 正向:編寫源代碼---> 編譯成字節碼文件---->jvm加載字節碼文件啓動運行--->建立對象或類名--->調用對應的方法和屬性運行
  2. 反射:代碼運行的過程當中直接獲取到字節碼文件對象--->經過字節碼文件對象獲取對應的元素的對象--->經過元素對象調用對應的方法開始執行功能
  • 字節碼文件對象:字節碼文件在內存中的對象表現形式【java中使用一個Class類對字節碼文件這種事物進行相關特徵和行爲的描述 Class的對象就是jvm加載字節碼文件的體現】
  • 【元素:指的是描述成員變量、成員方法、構造方法特徵和行爲的類】
  • jvm加載.class 文件的處理機制:
  1. ​ jvm經過類加載器將字節碼文件以對象的形式加載到內存中【在方法區中建立了一個Class類型的對象】,jvm加載以後對這個Class對象的內容進行分類管理【屬性、成員方法,構造方法】,jvm分完類發現內容各自有各自的共同特徵,把這些類別內容分別的進行描述造成對應的類型【屬性對應的類型Filed方法對應的類 Method構造對應的Constract】jvm把管理着三個類的權限給Class對象來管理。
  • 反射對象操做的元素除了對應的類對象還有Filed對象 Method對象 Constract對象。
  • 元素的操做變成了本身的對象操做本身和所屬的類對象沒有關係,實現解耦合。
  • 執行的順序:jvm加載字節碼文件建立Class對象,Class對象去統籌屬性、方法、構造各自類的對象來進行具體的操做。
  • ​ 好比:使用反射執行一個方法
  1. Class對象直接獲取方法的對象,使用這個方法的對象調用對應的執行方法直接執行。
  2. Class:他就是字節碼文件在內存中的虛擬體現【字節碼文件在內存中的具體描述】每個字節碼文件都對應有一個Class對象並且是惟一的。
  • 反射的好處:
  1. 解耦合,利於編程
  2. 提升代碼的維護性。【實現代碼和維護的分離】
  • 使用反射技術的步驟:
  1. 獲取Class對象【獲取那個字節碼文件的對象】
  2. 經過字節碼文件對象獲取要操做的元素的對象、
  3. 使用元素對象調用對應的方法來執行相關的功能

二,獲取類的字節碼對象的三種方式

  1. 第一種方式:對象名稱.getClass():直接獲取對象對應類型的字節碼文件對象
  2. 第二種方式:類名.class :直接獲取到類型的字節碼文件對象
  3. 第三種方式:Class.forName(String pathName) :  根據 pathName 來建立對應類型的字節碼文件對象

​           說明:pathName:指一個類的全名稱【全限定類名:包路徑+類名】java

代碼示例

//定義一個Person類 package com.ujiuye.demo; public class Person { String name;
int age;
public Person(String name) { super(); this.name = name; }

public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } public Person(int age) { super(); this.age = age; } 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; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } //定義一個測試類 package com.ujiuye.demo; public class Demo_Class { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { //使用反射技術 獲得Person類的字節碼文件的對象 //第一種方式 對象法 Person p = new Person(); //clazz1 是class類的對象 [它person.class這個文件在內存中的對象形式] //class類是全部字節碼文件內存對象對應的類型 [一個class的對象對應一個.class爲後綴名的文件] //class的對象是內存中的字節碼文件資源的體現,.class文件是磁盤中字節碼資源的體現 //參考 file類 file類是全部磁盤文件在內存在的對象對應的類型 Class clazz1 = p.getClass(); //對象名.getClass()獲得內存中字節碼文件對象 //第二種方式 類名錶示.class 文件 ;類名.class 就是字節碼文件對象 Class clazz2 = Person.class; //第三種方式 使用全限定類名 [類字節碼文件所在的包路徑] 獲得字節碼文件對象 Class<?>clazz3 = Class.forName("com.ujiuye.demo.Person"); } }

三,Class類實例化對象的方法

  • 字節碼文件對象:指字節碼文件在內存中對應的 Class類型 對象
  • 字節碼文件實例對象:指字節碼文件對應類的對象
  • newInstance():實例化字節碼文件對應類型的具體實例對象

​ 屬於Class類的方法:編程

代碼示例app

package com.ujiuye.demo; public class Demo_Class { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { //使用反射技術 獲得Person類的字節碼文件的對象 //對象法 Person p = new Person(); //clazz1 是class類的對象 [它person.class這個文件在內存中的對象形式] //class類是全部字節碼文件內存對象對應的類型 [一個class的對象對應一個.class爲後綴名的文件] //class的對象是內存中的字節碼文件資源的體現,.class文件是磁盤中字節碼資源的體現 //參考 file類 file類是全部磁盤文件在內存在的對象對應的類型 Class clazz1=p.getClass(); //對象名.getClass()獲得內存中字節碼文件對象 //第二種方式 類名錶示.class 文件 ;類名.class 就是字節碼文件對象 Class clazz2 =Person.class; //第三種方式 使用全限定類名 [類字節碼文件所在的包路徑] 獲得字節碼文件對象 Class<?>clazz3 =Class.forName("com.ujiuye.demo.Person"); //經過字節碼文件對象獲得對應類的對象  Person person= (Person)clazz3.newInstance(); System.out.println(person); } }

 

注意事項:反射在實例化對象的時候默認走的是空參構造,使用反射技術的時候保證操做的類必須提供空參構造框架

四,榨汁機案例

  • 榨汁機擁有榨汁的功能【須要傳入水果】
  • 水果有產出果汁的功能
  • 分析:
  1. ​ 榨汁機須要的水果【寫榨汁機類:寫一個方法榨汁 方法須要參數水果】
  2. ​ 水果出果汁【寫水果接口 共享方法出果汁】
  3. ​ 設計幾個具體的水果類,實現接口

代碼示例1:【使用new對象的方式】jvm

//定義一個方法
package com.ujiuye.demo;
public class FruitMachine { // 榨果汁 public void getJuice(Fruit f) { f.flowJuice(); } }
//定義一個水果接口 package com.ujiuye.demo;
public interface Fruit { // 出果汁 void flowJuice(); }
//定義一個蘋果類實現接口 package com.ujiuye.demo;
public class Apple implements Fruit{ @Override public void flowJuice() { System.out.println("蘋果汁"); } }
//定義一個橘子類實現接口
package com.ujiuye.demo; public class Orange implements Fruit{ @Override public void flowJuice() { System.out.println("橘子汁"); } }
//定義測試類 package com.ujiuye.demo;
public class Test { public static void main(String[] args) { FruitMachine machine = new FruitMachine(); // 想喝蘋果汁 // Apple apple = new Apple(); // machine.getJuice(apple); Orange orange = new Orange(); machine.getJuice(orange); } }

 

  • 總結:
  1. 開始喝的是蘋果汁,若是想要喝橘子汁,只能把程序停下來,對代碼進行修改修改後再從新運行程序。
  2. 修改程序,容易出錯,有可能影響其餘的地方,維護成本比較大

代碼示例2:【使用反射方式】ide

package com.ujiuye.demo;
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader;
public class Test { public static void main(String[] args) throws Exception { FruitMachine machine = new FruitMachine(); // 想喝蘋果汁 // Apple apple = new Apple(); // machine.getJuice(apple); /*Orange orange = new Orange(); machine.getJuice(orange);*/ // 使用反射技術 // 首先使用熟悉的io流技術把文件中的內容讀到 使用字符緩衝流一次讀一行讀到內容 BufferedReader br = new BufferedReader(new FileReader("a.properties"));
//a.properties文件裏面的內容是 com.ujiuye.demo.Apple;com.ujiuye.demo.Orange
String name = br.readLine();// 是一個類的全限定類名
// 使用反射技術獲得對應類的實例化對象
Class<?> clazz = Class.forName(name);
Fruit f =(Fruit) clazz.newInstance();
machine.getJuice(f);
br.close(); //關流
}
}

 

  • 反射獲取類中的公共構造方法並使用

  • 如何獲取構造方法對象?
  1. getContructor(Class params ):根據參數的類型及個數返回對應的構造方法對象
  2. ​ 只能獲取到公共 (public) 的構造方法
  • ​ 說明:
  1. ​ params:指多個 class 類型的參數
  2. ​ params:傳參的時候傳的是構造方法形參類型的字節碼文件對象
  3. ​ getContructors():返回字節碼文件對象中所用的構造方法對象
  • 如何控制構造方法建立類型對象
  1. ​ 構造方法做用:建立對象並初始化值
  • ​ 使用Constructor類中的方法:
  1. newInstance():建立構造對象所在的字節碼文件對象對應類型的實例化對象【叫構造方法本身執行本身】

代碼示例測試

package com.ujiuye.demo;
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays;
public class Demo_Constractor { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //操做person類 先獲得person的字節碼文件對象 //有三種方式 Class<?> clazz = Class.forName("com.ujiuye.demo.Person"); //獲得一個構造方法的對象 獲得 滿參構造 Constructor<?> c1 = clazz.getConstructor(String.class,int.class); Constructor<?> c2 = clazz.getConstructor(); System.out.println(c1); //public com.ujiuye.demo.Person(java.lang.String,int) //c1就是滿參構造方法在內存中的一個對象形式 //想要獲取類中全部的公共的構造方法對象 //person類裏面4個構造方法的對象都羅列出來了 Constructor<?>[] contructors = clazz.getConstructors(); System.out.println(Arrays.toString(contructors)); //輸出結果 //滿參 public com.ujiuye.demo.Person(java.lang.String,int) //age public com.ujiuye.demo.Person(int) //name public com.ujiuye.demo.Person(java.lang.String) //空參 public com.ujiuye.demo.Person() //構造方法的做用是什麼? 建立對象 給屬性賦值 Person p1 = (Person)c1.newInstance("baobao",30); System.out.println(p1); //Person [name=baobao, age=30] Person p2 =(Person)c2.newInstance(); //由於c2是空參全部輸出來是null值 System.out.println(p2); //Person [name=null, age=0] } }

 

 

  • <!--記住一句話,Class對象在調用方法時,方法的實參都是字節碼文件對象-->
  • 反射獲取類中的成員變量並使用

  • 反射如何獲取公共權限成員變量【Field】對象?
  1. ​ getField(String name):根據給定的屬性名稱獲取對應屬性的對象
  2. ​ getFields():獲取全部公共的屬性對象
  3. ​ 只能獲取到 public 修飾的屬性對象
  • ​ 說明:
  1. ​ name:是指屬性名【字段名】
  • 如何來獲取屬性對象裏面的屬性值?
  1. ​ 使用Field類中的方法:get(Object o):獲取到指定對象的屬性值
  • ​ 說明:
  1. ​ o:指定對象【getFiled的調用字節碼對象的實例對象】
  • 如何設定屬性值使用Field裏面的方法:
  1. ​ set(Object o,Object V):給指定對象的指定屬性對象賦值。
  • ​ 參數說明:
  1. ​ o:  指定對象【getFiled的調用字節碼對象實例對象】
  2. ​ V:要賦值的新值
  • 使用前提:屬性公共(public)權限修飾才能夠用。

代碼示例ui

package com.ujiuye.demo;
import java.lang.reflect.Field; import java.util.Arrays;
public class Demo_Filed { public static void main(String[] args) throws NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException { //獲得對應的類的反射對象 [Preson] Class clazz= Person.class; //獲取對應的屬性對象,​ 只能獲取到public修飾的屬性對象 Field f1 = clazz.getField("name"); Field f2 = clazz.getField("age"); System.out.println(f1); //public java.lang.String com.ujiuye.demo.Person.name System.out.println(f2); //public int com.ujiuye.demo.Person.age //獲取全部公共的屬性 Field[] fields = clazz.getFields(); System.out.println(Arrays.toString(fields)); //輸出:[public java.lang.String com.ujiuye.demo.Person.name, public int com.ujiuye.demo.Person.age] //屬性的操做,獲取值 設置值 Person person =(Person) clazz.newInstance(); String name =(String )f1.get(person); //name屬性的對象對應的name屬性屬於哪一個類對象 [Person類對象] System.out.println(name); //空參,沒有賦值 null f1.set(person, "花花"); String name1 = (String)f1.get(person); System.out.println(name1); //花花 } }

 

  • 獲取類中的成員方法而且執行

  • 如何成員方法對象:
  1. ​ 使用Class類中的方法: getMethod(String name, Class<?>... parameterTypes):根據給定的名稱和參數類型獲取對應的方法對象
  • ​ 參數說明:
  1. ​ name:方法名稱
  2. ​ parameterTypes:方法形參類型的字節碼對象
  3. ​ getMethods():獲取全部的方法的對象
  4. 只能獲取public修飾的方法
  • 如何運行獲得的方法的效果:
  1. ​ 經過Method類中方法:invoke(Object obj, Object... args):使調用的方法對象的方法執行【讓方法執行】
  • ​ 參數說明:
  1. ​ obj:指定對象【getMethod 的調用字節碼對象實例對象】
  2. ​ args:方法須要的實參

代碼示例this

package com.ujiuye.demo; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; public class Demo_Method { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // 獲取person類中全部的方法,先獲得person類字節碼文件對象 // 三種獲取方式 Class clazz = Person.class; Class<? extends Person> clazz2 = new Person().getClass(); Class<?> clazz3 = Class.forName("com.ujiuye.demo.Person"); // 獲取想要操做方法的對象 // getname是空參全部後面的參數能夠不用 Method m1 = clazz.getMethod("getName"); // setName有參數,後面跟上參數類型.class Method m2 = clazz.getMethod("setName", String.class); System.out.println(m1); // public java.lang.String com.ujiuye.demo.Person.getName() System.out.println(m2); // public void com.ujiuye.demo.Person.setName(java.lang.String)  Method[] methods = clazz.getMethods(); // 遍歷全部的方法 for (Method method : methods) { System.out.println(method); } System.out.println(Arrays.toString(methods)); // 輸出的是全部的方法包括object父類的方法 //方法用來幹嗎?調用的 方法的對象獲得了,想要方法對象對應的方法執行 Person person = (Person) clazz.newInstance();  String name = (String) m1.invoke(person); System.out.println(name); // 獲得的值是null值 //要執行哪一個方法就用哪一個方法對象去invoke  m2.invoke(person, "花花"); //獲取值m1 get方法  String name1=(String)m1.invoke(person); System.out.println(name1); //花花  } }

 

五,暴力反射

  • 經過Class類中:getDeclaredXxx方法:能夠獲取類中的全部聲明的成員(屬性、方法、內部類),私有的成員也能夠獲取到。

​         Xxx表明 Field Constractor Methodspa

  • 修改該對象的訪問權限:經過AccessibleObject類中的
  1. ​ setAccessible(boolean b):改變對象的訪問權限
  2. ​ isAccessible():判斷對象的是否能夠訪問

代碼示例

package com.ujiuye.demo;
import java.lang.reflect.Field; import java.lang.reflect.Method;
public class Demo_Declared { public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException { //獲得反射對象 Class clazz = Person.class; /*Field name= clazz.getField("name"); Field age = clazz.getField("age"); System.out.println(name); //報錯 System.out.println(age); //報錯 */ //暴力反射方法,訪問屬性 //person類裏面的屬性默認的或私有的均可以獲取到 Field name = clazz.getDeclaredField("name"); Field age = clazz.getDeclaredField("age"); //增長這一步,把權限修飾符給改了,100%保證暴力的訪問到 name.setAccessible(true); System.out.println(name); //private java.lang.String com.ujiuye.demo.Person.name System.out.println(age); //private int com.ujiuye.demo.Person.age System.out.println(name.isAccessible()); //true //暴力反射方法,訪問構造方法 Method getName = clazz.getDeclaredMethod("getName"); System.out.println(getName); //private java.lang.String com.ujiuye.demo.Person.getName() } }

 

  • 暴力反射的步驟:
  1. 獲取字節碼文件對象
  2. 採用getDeclaredXxx方法獲取到操做的的對象【解決獲取不到操做對象的問題】
  3. 使用setAccessible(true)方法改變操做對象訪問的權限【解決了操做不了問題】
  4. 使用操做對象的內部功能進行具體操做。
  • 練習

  • //有以下集合
  • ​ ArrayList<Integer> list = new ArrayList<>();
  • ​ list.add(666);
  • //設計代碼,將字符串類型的"abc",添加到上述帶有Integer泛型的集合list中
  • 分析:正向編程的時候放不進去,由於list集合指定泛型爲Integer,在編譯的過程當中這個指定的泛型是起做用的,他是要去對放進來的數據進行類型和範圍的審覈,符合泛型類型的要求能夠添加,不符合直接報錯不讓添加。泛型只在編譯時期起做用,一旦類開始運行的時候失去做用。類加載到方法區泛型就消失。我能夠在集合字節碼文件被加載到方法區之後去給list添加元素,就不會有泛型的約束,就能夠添加進去了。
  • ​ 能作到運行時進行添加操做的只能是反射技術。採用反射來實現

代碼示例

package com.ujiuye.demo; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; public class Test_02 { public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { ArrayList<Integer>list = new ArrayList<>(); list.add(666); //list.add("abc"); //編譯階段泛型約束着添加的元素的數據類型 //獲取list集合字節碼文件對象  Class<? extends ArrayList> clazz = list.getClass(); //經過反射對象獲取 add方法的對象  Method add = clazz.getDeclaredMethod("add", Object.class); //執行add方法添加"abc"字符串  add.invoke(list, "abc"); System.out.println(list); //[666, abc] } }

 

  • 注意:
  1. ​ 泛型的擦除:到了運行時期泛型就不起做用【至關於消失】
  2. ​ 泛型只在編譯時期起做用叫作僞泛型
相關文章
相關標籤/搜索