學習反射看這一篇就夠了

關注公衆號:小李不禿

1. 反射

Java 分編譯期和運行期html

編譯方式說明:java

  1. 靜態編譯:在編譯時肯定類型 & 綁定對象。如常見的使用new關鍵字建立對象
  2. 動態編譯:運行時肯定類型 & 綁定對象。動態編譯體現了Java的靈活性、多態特性 & 下降類之間的耦合性

咱們帶着如下幾個問題去學習今天的知識程序員

  • 反射是什麼?
  • 反射的做用是什麼?
  • 反射的優勢是什麼?缺點是什麼?

1.1 什麼是反射

反射(Reflection)是 Java 的特性之一,它可讓運行中的 Java 程序獲取自身的信息,而且能夠操做類或者對象的內部屬性。數據庫

Oracle 官方對反射的解釋是:設計模式

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.

The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.api

經過反射,咱們能夠在程序運行時得到程序集中每個類型的成員和成員信息。咱們平時所用的 new 去建立的對象的類型,是在編譯期就肯定下來了。而 Java 反射能夠動態地建立對象並調用其屬性,這樣的對象的類型在編譯期是未知的。因此咱們能夠經過反射機制建立對象,即便這個對象的類型在編譯期是未知的。數組

反射的核心是 JVM 在運行時纔會動態加載類、調用方法和訪問屬性,它不須要在編譯期知道運行的對象是誰。安全

Java 反射主要提供如下的功能:app

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所具備的成員變量和方法
  • 在運行時調用任意一個對象的方法

咱們能夠在運行時取到「任意」你想要的類、對象、變量、方法等。框架

注:反射是在運行時操做的,而不是編譯時

1.2 反射的主要用途

  • 實現工廠模式和代理模式等設計模式。
  • JDBC 的數據庫鏈接。
  • Spring、Struts 等框架,使用反射在運行時動態加載須要加載的對象。
  • IDE 開發工具的提示,好比當咱們輸入一個對象或類並想調用它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法。

1.3 反射的優缺點

優勢:

  • 能夠在運行期對類型進行判斷,動態類加載等操做。
  • 提升代碼的靈活度。例如:JDBC 能夠動態鏈接數據庫。

缺點:

  • 性能問題

    由於反射包括了一些動態類型,因此 JVM 沒法對這些代碼進行優化。所以,反射操做的效率要比那些直接調用慢的多。因此儘可能避免在常常被執行的代碼或者對性能要求很高的程序中使用反射。

    (反射大概比直接調用慢 50 ~ 100 倍,可是須要在執行 100 萬遍的時候纔會有所感受)

  • 安全限制

    使用反射技術要求程序必須在一個沒有安全限制的環境中運行。

  • 內部暴漏

    反射容許代碼執行一些在正常狀況下不被容許的操做(訪問私有的屬性或方法),因此使用反射可能會致使意料以外的反作用 —— 代碼有功能上的錯誤,下降可移植性。反射破壞了代碼的抽象性,所以當平臺發生改變的時候,代碼的行爲就可能也隨着變化。

提問:Java 反射能夠訪問和修改私有成員變量,那封裝成 private 還有意義麼?

既然小偷能夠訪問和搬走私有成員傢俱,那封裝成防盜門還有意義麼?這是同樣的道理,而且 Java 從應用層給咱們提供了安全管理機制——安全管理器,每一個 Java 應用均可以擁有本身的安全管理器,它會在運行階段檢查須要保護的資源的訪問權限及其它規定的操做權限,保護系統免受惡意操做攻擊,以達到系統的安全策略。

因此其實反射在使用時,內部有安全控制,若是安全設置禁止了這些,那麼反射機制就沒法訪問私有成員。

1.4 具體使用

1.4.1 Class

Class 存放着對應類型的運行時信息。在 Java 程序運行時,Java 虛擬機爲全部類型維護一個 java.lang.Class 對象。該 Class 對象存放着全部關於該對象的運行時信息。

1.4.1.1 獲取 Class

那咱們如何獲取想要的 Class,look this Code

public static void main(String[] args) {
// 方式1:Object.getClass()
// Object 類的 getClass() 方法返回一個 Class 類實例
String name = "不是禿頭的小李程序員";
Class<?> classType = name.getClass();
System.out.println("Object.getClass() classType: " + classType);

// 方式2:T.Class
// T 是任意 Java 類型
Class<?> classType2 = String.class;
System.out.println("T.Class classType: " + classType2);

// 方式3:Class.forName
try {
Class<?> classType3 = Class.forName("java.lang.String");
System.out.println("Class.forName classType: " + classType2);
// 根據 className 沒有找到類,會拋出 ClassNotFoundException 異常
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

獲取 Class 的三種方法

  • getClass()
  • T.class
  • Class.forName

這三種用法須要根據具體的場景靈活去使用,好比 JDBC 獲取鏈接的數據庫類型,就經過 Class.forName(「類路徑」)。

1.4.1.2 獲取父類 Class

經過 getSuperclass() 方法獲取父類的 Class,示例以下:

Class<?> superclass = Integer.class.getSuperclass();
System.out.println(superclass);
System.out.println(superclass.getSuperclass());
System.out.println(superclass.getSuperclass().getSuperclass());
System.out.println(superclass.getSuperclass().getSuperclass().getSuperclass());

運行結果

class java.lang.Number
class java.lang.Object
null
Exception in thread "main" java.lang.NullPointerException

能夠看到 Integer 的父類是 Number,Number 的父類是 Object,Object 就沒有父類了,因此在 null 以後會拋出空指針的異常。

1.4.1.3 小結

獲取到想要的 Class 了,就能夠獲取到它的一切信息。

在獲取想要的信息前,不妨先了解個知識點。

方法中帶與不帶「Declared」的區別

  1. 不帶「Declared」的方法支持取出包括繼承、公有(public)的 Field、Method 和 Constructor。
  2. 帶「Declared」的方法支持取出包括當前類全部的構造函數(包括公有和私有的,不包括繼承)的 Field、Method 和 Constructor。

1.4.2 Field

1.4.2.1 獲取 Field

如何經過 Class 實例獲取字段信息。Class 類提供瞭如下幾個方法來獲取字段:

  • Field getField(String name):根據字段名獲取某個 public 的 field
  • Field[] getFields():獲取全部 public 的 field
  • Field getDeclaredField(String name):根據字段名獲取當前類的某個 field
  • Field[] getDeclaredFields():獲取當前類的全部 field

快快快,show me code

public class FiledTest1 {
public static void main(String[] args) throws NoSuchFieldException {
Class stdClass = Student.class;
// 獲取 public 字段 "score"
System.out.println(stdClass.getField("score"));
// 獲取繼續的 public 字段 "name"
System.out.println(stdClass.getField("name"));
// 獲取 private 字段 "grade"
System.out.println(stdClass.getDeclaredField("grade"));
}
}

class Student extends Person{
public int score;
private int grade;
}

class Person{
public String name;
}

運行結果:

public int com.javastudy.reflection.Fields.Student.score
public java.lang.String com.javastudy.reflection.Fields.Person.name
private int com.javastudy.reflection.Fields.Student.grade

1.4.2.2 獲取 Field 的信息

一個 Filed 對象包含了一個字段的全部信息:

  • getName():返回字段名稱,例如:name
  • getType():返回字段類型,也是一個 Class 實例,例如:String.class
  • getModifiers():返回字段的修飾符,它是一個 int,不一樣的 bit 表示不一樣的含義。
The java.lang.reflect.Method.getModifiers() method returns the Java language modifiers for the method represented by this Method object, as an integer. The Modifier class should be used to decode the modifiers.

getmodifiers()方法以整數的形式返回該方法對象所表示的方法的 Java 語言修飾符。應該使用修飾詞類來解碼修飾詞。

public class FieldTest2 {

private final String name = "不是禿頭的小李程序員";

public static void main(String[] args) throws NoSuchFieldException {
Class c = FieldTest2.class;
Field field = c.getDeclaredField("name");
int mod = field.getModifiers();
System.out.println("name: " + field.getName());
System.out.println("type: " + field.getType());
System.out.println("final: " + Modifier.isFinal(mod));
System.out.println("public: " + Modifier.isPublic(mod));
System.out.println("protected: " + Modifier.isProtected(mod));
System.out.println("private: " + Modifier.isPrivate(mod));
System.out.println("static: " + Modifier.isStatic(mod));
}
}

運行結果:

name: name
type: class java.lang.String
final: true
public: false
protected: false
private: true
static: false

1.4.2.3 獲取字段值

咱們拿到了 Field,該經過 Field 獲取該字段對應的值。咱們仍是用上面的例子來獲取 name 值。

public class FieldTest3 {

private final String name = "不是禿頭的小李程序員";

public static void main(String[] args) throws Exception {
Object object = new FieldTest3();
Class c = FieldTest3.class;
Field field = c.getDeclaredField("name");
Object value = field.get(object);
System.out.println(value);
}
}

運行結果:

不是禿頭的小李程序員

咱們經過 get() 來獲取 Field 的值,那麼我們在看一個下面的例子:

public class FieldTest4 {

public static void main(String[] args) throws Exception {
Object animal = new Animal("不是禿頭的小李程序員 Animal111");
Class c = Animal.class;
Field field = c.getDeclaredField("name");
Object value = field.get(animal);
System.out.println(value);
//       Animal animal = new Animal();
//       animal.testFiled();
}
}

class Animal {
private String name;

public Animal() {
}

public Animal(String name){
this.name = name;
}

public void testFiled() throws Exception {
Object animal = new Animal("不是禿頭的小李程序員 Animal222");
Class c = Animal.class;
Field field = c.getDeclaredField("name");
Object value = field.get(animal);
System.out.println(value);
}
}

運行結果:

Exception in thread "main" java.lang.IllegalAccessException: Class com.javastudy.reflection.Fields.FieldTest4 can not access a member of class com.javastudy.reflection.Fields.Animal with modifiers "private"

WTF?居然出現異常了,小李你是在玩我麼,第一次就能夠,第二次又提示沒有權限,到底能不能獲取我想要的值。固然能,不能我就禿給你看。

咱們只須要在 filed.get() 的上一步加上下面的代碼就能夠了。管你是 public 仍是 private.

field.setAccessible(true);

那咱們想想爲何第一次不加上面的代碼也能訪問呢?

由於是在本身的類裏訪問的,就拿你本身想想,你有一個鼻子和兩個耳朵,它們就是你私有的(private),你能夠隨便摸他們和摳他們(嘔),可是別人想碰的時候,必需要通過你的贊成才行(setAccessible(true))。這麼理解下剛纔的代碼,你就明白了。若是還不明白能夠打開上面的兩段註釋:

Animal animal = new Animal();
animal.testFiled();

運行結果:

不是禿頭的小李程序員222

1.4.2.4 設置字段值

經過 Field 實例既能獲取指定實例的字段值,也能夠設置字段的值。

設置字段值經過 Fieldset 方法實現。

// 第一個參數是指定的實例
// 第二個參數是待修改的值
void set(Object obj, Object value)

示例代碼以下:

public class FieldTest5 {
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher("不是禿頭的小李程序員");
Class c = teacher.getClass();
Field field = c.getDeclaredField("name");
field.setAccessible(true);
field.set(teacher,"小李不禿頭");
System.out.println(field.get(teacher));
}
}
class Teacher{
private String name;

public Teacher(String name){
this.name = name;
}

public String getName() {
return name;
}
}

打印結果:

小李不禿頭

爲了避免禿頭,我容易麼(此處嚶嚶嚶)。嚶完就該提問題了

Field 在 get 和 set 的 obj 參數有什麼做用?

回答:咱們能夠經過查看 api 的註釋,瞭解到這兩個方法在獲取靜態實例的時候,obj 能夠傳 null,若是要獲取對象的實例 obj 參數就不能爲空,不然會返回 NullException。

1.4.2.5 小結

Java 的反射 API 提供的 Field 類封裝了字段的全部信息:

  • 經過 Class 實例獲取 Field 實例的方法:getField(String name),getFields(),getDeclaredField(),getDeclaredFields()
  • 經過 Field 實例獲取字段信息的方法:getName(),getType(),getModifiers()
  • 經過 Field 實例能夠讀取或者設置某個對象的字段,若是存在訪問限制,首先要調用 setAccessible(true),再訪問非 public 字段。

1.4.3 Method

1.4.3.1 獲取 Method

經過 Class 實例獲取全部 Method 的信息,Class 類提供瞭如下幾個方法來獲取方法:

  • Method getMethod(String name, Class<?>... parameterTypes):根據方法名和參數類型獲取某個 public 的 Method
  • Method[] getMethods():獲取全部 public 的 Method
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes):根據方法名和參數獲取當前類的某個 Method
  • Method[] getDeclaredMethods():獲取當前類的全部 Method

看一下示例代碼:

public class MethodTest1 {
public static void main(String[] args) throws Exception{
Class c = Student.class;
// 獲取public方法getScore,參數爲String;
System.out.println(c.getMethod("getScore",String.class));
// 獲取繼承的public方法getName,無參數;
System.out.println(c.getMethod("getName"));
// 獲取private方法getGrade,參數爲int;
System.out.println(c.getDeclaredMethod("getGrade",int.class));
}
}

運行結果:

public int com.javastudy.reflection.Methods.Student.getScore(java.lang.String)
public java.lang.String com.javastudy.reflection.Methods.Person.getName()
private int com.javastudy.reflection.Methods.Student.getGrade(int)

1.4.3.2 獲取 Method 的信息

一個 Method 對象包含了一個方法的全部信息:

  • getName():返回方法名稱,例如:「getScore」
  • getReturnType():返回方法的返回值類型,是一個 Class 實例,例如:「String.class」
  • getParameterTypes():返回方法的參數類型,是一個 Class 數組,例如:{String.class, int.class}
  • getModifiers():返回方法的修飾符,和 Field 的 getModifiers() 大同小異

示例以下:

public class MethodTest2 {
public static void main(String[] args) throws Exception{
Class c = Student.class;
Method method= c.getDeclaredMethod("getGrade",int.class);

System.out.println("name : " + method.getName());
System.out.println("returnType : " + method.getReturnType());
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println("paramaterTypes的長度 : " + parameterTypes.length);
for (Class parameterType : parameterTypes){
System.out.println("paramaterTypes : " + parameterType);
}
}
}

運行結果:

name : getGrade
returnType : int
paramaterTypes的長度 : 1
paramaterTypes : int

1.4.3.3 調用方法

調用普通方法

先看示例:

public class MethodTest3 {
public static void main(String[] args) throws Exception {
String s = "不是禿頭的小李程序員";
Method method = String.class.getMethod("substring", int.class);
Method method2 = String.class.getMethod("substring", int.class, int.class);
String result = (String) method.invoke(s,7);
String result2 = (String) method2.invoke(s,1,9);
System.out.println(result);
System.out.println(result2);
}
}

運行結果:
程序員
是禿頭的小李程序

分析下程序員小李是如何禿頭的:

  1. 經過 Class 實例的 getMethod 方法獲取Method,getMethod 的 name 和參數不一樣,獲取的 Method 也是不一樣的。
  2. 使用 Method 的 invoke 方法就至關於調用該方法。invoke 的第一個參數是對象實例,後面的可變參數與方法參數一致,不然將報錯。
調用靜態方法

調用靜態方法,無需指定實例對象,invoke 方法傳入的第一個參數永遠是 null 或者空值 」「,咱們看下面的例子:

public class MethodTest4 {
public static void main(String[] args) throws Exception{
// 獲取Integer.parseInt(Stirng)方法,參數是String
Method method = Integer.class.getMethod("parseInt", String.class);
// 調用靜態方法獲取結果
// Integer result = (Integer)method.invoke("", "12345");
Integer result = (Integer)method.invoke(null, "12345");
System.out.println(result);
}
}

運行結果:12345

調用非public方法

對於非public方法,咱們能夠經過 Class.getDeclaredMethod() 獲取,可是調用的時候會拋出一個IllegalAccessException。爲了調用非public方法,經過Method.setAccessible(true)容許其調用:

public class MethodTest5 {
public static void main(String[] args) throws Exception{
Person p = new Person();
Method method = p.getClass().getDeclaredMethod("setName", String.class);
method.setAccessible(true);
method.invoke(p,"不是禿頭的小李程序員");
System.out.println(p.name);
}
}

此外, setAccessible(true)可能會失敗。若是JVM運行期存在 SecurityManager,那麼它會根據規則進行檢查,有可能阻止 setAccessible(true)。例如,某個 SecurityManager可能不容許對 javajavax開頭的 package的類調用 setAccessible(true),這樣能夠保證JVM核心庫的安全。

1.4.3.4 多態

若是一個 Person 定義了 hello() 方法,而且它的子類Student也重寫了該方法,那麼咱們從 Person.class 獲取的 Method 做用於 Student 實例時,調用的方法是哪一個?

public class MethodTest6 {
public static void main(String[] args) throws Exception{
// 獲取Person的hello方法
Method method = Person.class.getMethod("hello");
// 對Student實例調用hello方法
method.invoke(new Student());
}
}

public class Person {
public void hello(){
System.out.println("Person:hello");
}
}

public class Student extends Person {
public void hello(){
System.out.println("Student:hello");
}
}

1.4.3 .5運行結果

Student:hello

發現打印出的是Student:hello,因此使用反射調用方法時,仍遵循多態原則:即老是調用實際類型的覆蓋方法。

上述的反射代碼:

Method m = Person.class.getMethod("hello");
m.invoke(new Student());

至關於:

Person p = new Student();
p.hello();

1.4.3.5 小結

Java 的反射 API 提供的 Method 對象封裝了方法的全部信息:

  • 經過 Class 實例獲取 Method 實例的方法:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods()
  • 經過 Method 實例獲取字段信息的方法:getName(),getReturnType(),getParameterTypes(),getModifiers()
  • 經過 Method 實例能夠調用某個對象的方法:Object invoke(Object instance, Object... parameters)
  • 經過設置 setAccessible(true) 來訪問非 public 方法
  • 經過反射調用方法時,仍能遵循多態原則

1.4.4 Constructor

1.4.4.1 獲取 Constructor

經過 Class 實例獲取全部 Constructor 的信息,Class 類提供瞭如下幾個方法來獲取方法:

  • Constructor<T> getConstructor(Class<?>... parameterTypes):根據參數獲取 public 的 Contructor
  • Constructor<?>[] getConstructors():獲取全部 public 的 Contructor
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):根據參數獲取當前類的 Contructor
  • Constructor<?>[] getDeclaredConstructors():獲取全部當前類的 Contructor
Constructor 老是當前類定義的構造方法,和父類無關,所以不存在多態的問題。

示例以下:

public class ContructorTest1 {
public static void main(String[] args) throws Exception{
Class c = Person.class;
Person p = (Person) c.newInstance();

Constructor cons1 = c.getConstructor(int.class);
Person p1 = (Person)cons1.newInstance(30);

Constructor cons2 = c.getDeclaredConstructor(String.class);
cons2.setAccessible(true);
Person p2 = (Person)cons2.newInstance("不是禿頭的小李程序員");

Constructor cons3 = c.getConstructor(String.class, int.class);
Person p3 = (Person)cons3.newInstance("不是禿頭的小李程序員-35",35);
}
}

Person.class

public class Person {
private String name;
private int age;

public Person() {
System.out.println("Person");
}

public Person(int age) {
this.age = age;
System.out.println("Person age:" + age);
}

private Person(String name) {
this.name = name;
System.out.println("Person name:" + name);
}

public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person toString:" + toString());
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

運行結果:

Person
Person age:30
Person name:不是禿頭的小李程序員
Person toString:Person{name='不是禿頭的小李程序員-35', age=35}

1.4.4.2 小結

經過上面的結果,得出如下結論:

  1. 經過 Class 實例的 newInstance() 獲取到的是無參構造函數
  2. 獲取有參構造函數須要經過 Class 實例獲取 Constructor 實例,獲取方法:getConstructor(),getConstructors(),getDeclaredConstructor(Class<?>... parameterTypes),getDeclaredConstructors()
  3. 經過 Constructor 的 newInstance(Object... parameters) 建立實例對象
  4. 調用非 public 的 Contructor,須要經過設置 setAccessible(true) 設置容許訪問,但可能會失敗。

1.4.5 Interface

咱們能夠經過 Class 的 getInterfaces() 獲取當前類實現的全部接口,示例以下:

public class ReflectionInterfaceTest {
public static void main(String[] args) {
Class s = Integer.class;
Class[] interfaces = s.getInterfaces();
for (Class c:interfaces){
System.out.println(c);
}
}
}

運行結果:

interface java.lang.Comparable

2. 總結

經過本文你大概瞭解了反射,讓咱們再複習一下:

  • 反射是 Java 的特性之一,能夠經過反射動態的獲取對象。
  • 反射的用途:代理模式等設計模式;經過 JDBC 鏈接數據庫;Spring 框架動態加載對象
  • 反射的優勢:動態加載、提升代碼的靈活度
  • 反射的缺點:性能問題、安全限制、內部暴漏
  • 反射的使用:經過獲取到 Class 實例,就能獲取到咱們想要的全部信息,包括獲取成員變量、方法和構造函數,分別對應的是 Field,Method 和 Constructor。能夠經過這些類的內部方法獲取信息,例如:經過getName() 獲取名稱。
  • 若是咱們修改或者訪問帶有 private 的變量或方法,須要先設置 method.setAccessible(true),才能夠進行後續的操做。

下一節我帶大家看看反射的原理,讓咱們更進一步的瞭解它。

3. 參考

https://blog.csdn.net/carson_ho/article/details/80921333

https://zhidao.baidu.com/question/1639472919001036380.html

https://www.liaoxuefeng.com/wiki/1252599548343744/1264803033837024

相關文章
相關標籤/搜索