java反射詳解

## 引言

### java中創(chuàng)建對象有幾種方式布蔗?

#### 1.使用new關(guān)鍵字

#### 2.使用clone方法

#### 3.使用反序列化

#### 4.使用反射

關(guān)于這幾種創(chuàng)建對象方式的詳解选浑,請看這篇文章 [java創(chuàng)建對象的幾種方式](https://juejin.im/post/5d44530a6fb9a06aed7103bd#heading-8)

接下來主要詳細介紹反射相關(guān)知識

## 反射簡介

反射之中包含了一個「反」字,所以想要解釋反射就必須先從「正」開始解釋流妻。<br />一般情況下溃卡,我們使用某個類時必定知道它是什么類染簇,是用來做什么的。于是我們直接對這個類進行實例化拉宗,之后使用這個類對象進行操作。

```java

Apple apple = new Apple(); //直接初始化辣辫,「正射」

apple.setPrice(4);

```

上面這樣子進行類對象的初始化旦事,我們可以理解為「正」。<br />而反射則是一開始并不知道我要初始化的類對象是什么急灭,自然也無法使用 new 關(guān)鍵字來創(chuàng)建對象了姐浮。<br />這時候,我們使用 JDK 提供的反射 API 進行反射調(diào)用:

```java

Class clz = Class.forName("com.eft.reflect.Apple");

Method method = clz.getMethod("setPrice", int.class);

Constructor constructor = clz.getConstructor();

Object object = constructor.newInstance();

method.invoke(object, 4);

```

上面兩段代碼的執(zhí)行結(jié)果葬馋,其實是完全一樣的卖鲤。但是其思路完全不一樣,第一段代碼在未運行時(編譯時)就已經(jīng)確定了要運行的類(Apple)畴嘶,而第二段代碼則是在運行時通過字符串值才得知要運行的類(com.eft.reflect.Apple)蛋逾。

所以說什么是反射?

反射就是在運行時才知道要操作的類是什么掠廓,并且可以在運行時獲取類的完整構(gòu)造换怖,并調(diào)用對應(yīng)的方法。

<a name="5052b311"></a>

### 官方定義

JAVA反射機制是在運行狀態(tài)中蟀瞧,對于任意一個類沉颂,都能夠知道這個類的所有屬性和方法;對于任意一個對象悦污,都能夠調(diào)用它的任意一個方法和屬性铸屉;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制。

> 反射機制很重要的一點就是“運行時”切端,其使得我們可以在程序運行時加載彻坛、探索以及使用編譯期間完全未知的 `.class` 文件。換句話說踏枣,Java 程序可以加載一個運行時才得知名稱的 `.class` 文件昌屉,然后獲悉其完整構(gòu)造,并生成其對象實體茵瀑、或?qū)ζ?fields(變量)設(shè)值间驮、或調(diào)用其 methods(方法)。

<a name="79f27b45"></a>

### 通俗概括

反射就是讓你可以通過名稱來得到對象 ( 類马昨,屬性竞帽,方法扛施。。 ) 的技術(shù)

<a name="976a3e8e"></a>

### 核心

Java反射機制是Java語言被視為“準動態(tài)”語言的關(guān)鍵性質(zhì)屹篓。Java反射機制的核心就是允許在運行時通過Java Reflection APIs來取得已知名字的class類的內(nèi)部信息(包括其modifiers(諸如public, static等等)疙渣、superclass(例如Object)、實現(xiàn)interfaces(例如Serializable)堆巧,也包括fields和methods的所有信息)妄荔,動態(tài)地生成此類,并調(diào)用其方法或修改其域(甚至是本身聲明為private的域或方法)

<a name="997c7a5d"></a>

### 功能

- 在運行時判斷任意一個對象所屬的類谍肤;

- 在運行時構(gòu)造任意一個類的對象懦冰;

- 在運行時判斷任意一個類所具有的成員變量和方法;

- 在運行時調(diào)用任意一個對象的方法谣沸;

- 生成動態(tài)代理

<a name="3c9fe113"></a>

## 反射原理

類加載的流程:<br />![](https://user-gold-cdn.xitu.io/2019/8/2/16c52dcd4957b070?w=601&h=850&f=webp&s=19482)<br />類加載的完整過程如下:<br />(1)在編譯時刷钢,Java 編譯器編譯好 .java 文件之后,在磁盤中產(chǎn)生 .class 文件乳附。.class 文件是二進制文件内地,內(nèi)容是只有 JVM 能夠識別的機器碼。<br />(2)JVM 中的類加載器讀取字節(jié)碼文件赋除,取出二進制數(shù)據(jù)阱缓,加載到內(nèi)存中,解析.class 文件內(nèi)的信息举农。類加載器會根據(jù)類的全限定名來獲取此類的二進制字節(jié)流荆针;然后,將字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)颁糟;接著航背,在內(nèi)存中生成代表這個類的 java.lang.Class 對象。<br />(3)加載結(jié)束后棱貌,JVM 開始進行連接階段(包含驗證玖媚、準備、初始化)婚脱。經(jīng)過這一系列操作今魔,類的變量會被初始化。

**要想使用反射障贸,首先需要獲得待操作的類所對應(yīng)的 Class 對象错森。Java 中,無論生成某個類的多少個對象篮洁,這些對象都會對應(yīng)于同一個 Class 對象涩维。這個 Class 對象是由 JVM 生成的,通過它能夠獲悉整個類的結(jié)構(gòu)嘀粱。所以激挪,java.lang.Class 可以視為所有反射 API 的入口點。**<br />**反射的本質(zhì)就是:在運行時锋叨,把 Java 類中的各種成分映射成一個個的 Java 對象垄分。**

<a name="d79ea903"></a>

## 簡單例子

通過前面引言-使用反射創(chuàng)建對象的例子,我們了解了使用反射創(chuàng)建一個對象的步驟:

獲取類的 Class 對象實例

```java

Class clz = Class.forName("com.eft.reflect.Person");

```

根據(jù) Class 對象實例獲取 Constructor 對象

```java

Constructor constructor = clz.getConstructor();

```

使用 Constructor 對象的 newInstance 方法獲取反射類對象

```java

Object personObj = constructor.newInstance();

```

而如果要調(diào)用某一個方法娃磺,則需要經(jīng)過下面的步驟:

- 獲取方法的 Method 對象

```

Method setNameMethod = clz.getMethod("setName", String.class);

```

- 利用 invoke 方法調(diào)用方法

```

setNameMethod.invoke(personObj, "酸辣湯");

```

通過反射調(diào)用方法的測試代碼:

```java

Class clz = Person.class;

Method setNameMethod = clz.getMethod("setName", String.class);

// Person person= (Person) clz.getConstructor().newInstance();

Person person= (Person) clz.newInstance();

setNameMethod.invoke(person, "酸辣湯888");//調(diào)用setName方法薄湿,傳入?yún)?shù)

Method getNameMethod = clz.getMethod("getName", null);

String name= (String) getNameMethod.invoke(person,null);//調(diào)用getName方法,獲取返回值

System.out.println("name:" +name);

運行結(jié)果:

name:酸辣湯888

```

到這里偷卧,我們已經(jīng)能夠掌握反射的基本使用豺瘤。但如果要進一步掌握反射,還需要對反射的常用 API 有更深入的理解

<a name="24acf62e"></a>

## 反射API詳解

在 JDK 中听诸,反射相關(guān)的 API 可以分為下面3個方面:

<a name="1a10424a"></a>

### 一坐求、獲取反射的 Class 對象

每一種類型(如:String,Integer,Person...)都會在初次使用時被加載進虛擬機內(nèi)存的『方法區(qū)』中,包含類中定義的屬性字段晌梨,方法字節(jié)碼等信息桥嗤。Java 中使用類 java.lang.Class 來指向一個類型信息,**通過這個 Class 對象仔蝌,我們就可以得到該類的所有內(nèi)部信息**泛领。

> Class沒有公共構(gòu)造方法。Class對象是在加載類時由Java虛擬機以及通過調(diào)用類加載器中的defineClass方法自動構(gòu)造的敛惊。

獲取一個 Class 對象的方法主要有以下三種:

<a name="e7afd1e0"></a>

#### 使用類字面常量或TYPE字段

- 類.class渊鞋,如Person.class

? - 類字面常量不僅可以應(yīng)用于普通的類,也可以應(yīng)用于**接口瞧挤、數(shù)組以及基本數(shù)據(jù)類型**

? - 這種方式不僅更簡單锡宋,而且更安全,因為它在編譯時就會受到檢查特恬,并且根除了對forName方法的調(diào)用员辩,所以也**更高效,建議使用“.class”的形式**

- Boolean.TYPE鸵鸥,如Integer.TYPE

? - TYPE是基本數(shù)據(jù)類型的包裝類型的一個標準字段奠滑,它是一個引用,指向?qū)?yīng)的基本數(shù)據(jù)類型的Class對象

表格兩邊等價:

| boolean.class | Boolean.TYPE |

| --- | --- |

| char.class | Character.TYPE |

| byte.class | Byte.TYPE |

| short.class | Short.TYPE |

| int.class | Integer.TYPE |

| long.class | Long.TYPE |

| float.class | Float.TYPE |

| double.class | Double.TYPE |

| void.class | Void.TYPE |

這種方式最直接妒穴,但僅能獲取到我已知的類的**Class對象**宋税,也就是工程內(nèi)用過的類的對象都可以通過類.class方式獲取其Class對象,但是這種方式有一個**不足就是對于未知的類讼油,或者說不可見的類是不能獲取到其Class對象**的杰赛。

<a name="2cf02d8a"></a>

#### 對象.getClass()

如:person.getClass()

Java中的祖先類 **Object提供了一個方法getClass()** 來獲取當著實例的Class對象,這種方式是開發(fā)中用的最多的方式矮台,同樣乏屯,它也不能獲取到未知的類根时,比如說某個接口的實現(xiàn)類的Class對象。

API:

```java

public final native Class<?> getClass();

```

> 這是一個native方法(一個Native Method就是一個java調(diào)用非java代碼的接口)辰晕,并且不允許子類重寫蛤迎,所以理論上所有類型的實例都具有同一個 getClass 方法。

使用:

```java

Integer integer = new Integer(12);

Class clz=integer.getClass();

```

<a name="7904a263"></a>

#### Class.forName("類全路徑")

如:Class.forName("com.eft.xx.Person")<br />這種方式最常用含友,可以獲取到任何類的Class對象替裆,前提是該類存在,否則會拋出ClassNotFoundException異常窘问。通過這種方式辆童,我們只需要知道類的全路徑(完全限定名)即可獲取到其Class對象(如果存在的話).

API:

```java

//由于方法區(qū) Class 類型信息由類加載器和類全限定名唯一確定,

//所以想要去找這么一個 Class 就必須提供類加載器和類全限定名惠赫,

//這個forName的重載方法允許你傳入類加載器和類全限定名來匹配方法區(qū)類型信息

//參數(shù)說明? name:class名把鉴,initialize是否加載static塊

public static Class<?> forName(String name, boolean initialize,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ClassLoader loader)

? ? throws ClassNotFoundException{

? ? ..

? ? }?

// 這個 forName 方法默認使用調(diào)用者的類加載器,將類的.class文件加載到j(luò)vm中

//這里傳入的initialize為true儿咱,會去執(zhí)行類中的static塊

public static Class<?> forName(String className)

? ? throws ClassNotFoundException {

? ? return forName0(className, true, ClassLoader.getCallerClassLoader());

}

```

使用:

```java

Class clz = Class.forName("com.eft.reflect.Person");

```

<a name="996f616c"></a>

### 二纸镊、判斷是否為某個類的實例

- 用 instanceof 關(guān)鍵字

- 用 Class 對象的 isInstance 方法(Native 方法)

```java

public class InstanceofDemo {

? ? public static void main(String[] args) {

? ? ? ? ArrayList arrayList = new ArrayList();

? ? ? ? if (arrayList instanceof List) {

? ? ? ? ? ? System.out.println("ArrayList is List");

? ? ? ? }

? ? ? ? if (List.class.isInstance(arrayList)) {

? ? ? ? ? ? System.out.println("ArrayList is List");

? ? ? ? }

? ? }

}

//Output:

//ArrayList is List

//ArrayList is List

```

被不同加載器加載過的類不屬于同一種類(即時包名、類名相同)概疆,所創(chuàng)建出的對象所屬的類也不相同逗威,如下:

```java

ClassLoader myLoader = new ClassLoader() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public Class<?> loadClass(String name) throws ClassNotFoundException {

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";

? ? ? ? ? ? ? ? ? ? InputStream is = getClass().getResourceAsStream("./bean/" + fileName);

? ? ? ? ? ? ? ? ? ? if (is == null) {

? ? ? ? ? ? ? ? ? ? ? ? return super.loadClass(name);//返回父 類加載器

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? byte[] b = new byte[is.available()];

? ? ? ? ? ? ? ? ? ? is.read(b);

? ? ? ? ? ? ? ? ? ? return defineClass(name, b, 0, b.length);

? ? ? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? ? ? throw new ClassNotFoundException();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? };

Object obj = null;

Class clz=myLoader.loadClass("eft.reflex.bean.Person");

System.out.println("person被自定義類加載器加載了");

obj = clz.newInstance();

System.out.println(obj instanceof Person);

運行結(jié)果:

person被自定義類加載器加載完成

person的靜態(tài)塊被調(diào)用了

false

```

> 原理應(yīng)該是:jvm會根據(jù)instanceof右邊的操作符用默認的類加載器去加載該類到方法區(qū),然后根據(jù)左操作符對象的對象頭中類引用地址信息去查找方法區(qū)對應(yīng)的類岔冀,如果找到的類是剛剛加載的類凯旭,則結(jié)果為true,否則為false使套。對于這個例子而言罐呼,obj對象指向的類在創(chuàng)建對象之前就已經(jīng)加載到了方法區(qū),而進行instanceof運算時侦高,由于方法區(qū)中已經(jīng)存在的該類并非用此時的默認加載器進行加載嫉柴,因此jvm認為該類還沒有加載,所以右側(cè)操作符指向的類此時才會加載奉呛,所以這個例子的結(jié)果為false计螺。 如果將括號中的類名改為測試類的類名,結(jié)果也是類似的瞧壮,只不過測試類會在main方法執(zhí)行之前就會被加載登馒。

<a name="8bd40ad8"></a>

### 三、通過反射獲取構(gòu)造器咆槽,并創(chuàng)建實例對象

通過反射創(chuàng)建類對象主要有兩種方式:通過 Class 對象的 newInstance() 方法陈轿、通過 Constructor 對象的 newInstance() 方法。

第一種:通過 Class 對象的 newInstance() 方法。

API

```java

public T newInstance()

```

第二種:通過 Constructor 對象的 newInstance() 方法<br />這個操作涉及到的幾個api如下:

- **public Constructor<?>[] getConstructors() //獲取類對象的所有可見的構(gòu)造函數(shù)**

- **public Constructor<?>[] getDeclaredConstructors()//獲取類對象的所有的構(gòu)造函數(shù)**

> 注意:

> 1.getConstructors和getDeclaredConstructors獲取的構(gòu)造器數(shù)組無序麦射,所以不要通過索引來獲取指定的構(gòu)造方法

> 2.getXXXX 與getDeclaredXXXX 區(qū)別是蛾娶,帶Declared的方法不會返回父類成員,但會返回私有成員潜秋;不帶Declared的方法恰好相反下面類似的方法不贅述

- **public Constructor getConstructor(Class<?>... parameterTypes)**

? - 獲取指定的可見的構(gòu)造函數(shù)蛔琅,參數(shù)為:指定構(gòu)造函數(shù)的參數(shù)類型數(shù)組

? - 如果該構(gòu)造函數(shù)不可見或不存在,會拋出 NoSuchMethodException 異常

使用舉例:

```java

Class p = Person.class;

Constructor constructor1 = p.getConstructor();//獲取沒有任何參數(shù)的構(gòu)造函數(shù)

Constructor constructor = p.getConstructor(String.class,int.class);//獲取Person(String name,int age)這個構(gòu)造函數(shù)

```

- **public Constructor getDeclaredConstructor(Class<?>... parameterTypes)**

? - 獲取指定的構(gòu)造函數(shù)半等,參數(shù)為:指定構(gòu)造函數(shù)的參數(shù)類型數(shù)組

? - 無論構(gòu)造函數(shù)可見性如何,均可獲取

使用舉例:

```java

Class p = Person.class;

Constructor constructor = p.getDeclaredConstructor(String.class,int.class);//獲取Person(String name,int age)這個構(gòu)造函數(shù)

```

- **Constructor的setAccessible和newInstance方法**

```java

//關(guān)閉訪問檢查呐萨,需要先將此設(shè)置為true才可通過反射訪問不可見的構(gòu)造器

//但編譯器不允許使用普通的代碼該字段杀饵,因為僅適用于反射

public void setAccessible(boolean flag)

//創(chuàng)建對象,使用可變長度的參數(shù),但是在調(diào)用構(gòu)造函數(shù)時必須為每一個參數(shù)提供一個準確的參量.

public T newInstance(Object ... initargs)


使用舉例:

//假設(shè)Person有個 private Person(String name){}的構(gòu)造方法

Constructor constructor = Person.class.getConstructor(String.class);

constructor.setAccessible(true);

Person person = (Person)constructor.newInstance("酸辣湯");

```

使用Constructor創(chuàng)建對象的完整例子:詳見上面的[使用反射創(chuàng)建對象-調(diào)用類對象的構(gòu)造方法](#ko1Mp)

<a name="69ab400f"></a>

### 四谬擦、通過反射獲取類的屬性切距、方法

使用反射可以獲取Class對象的一系列屬性和方法,接下來列舉下Class類中相關(guān)的API

<a name="aa95a7b8"></a>

#### 類名

- **public String getName() //獲取類全路徑名(返回的是虛擬機里面的class的表示)**

- **public String getCanonicalName()//獲取類全路徑名(返回的是更容易理解的表示)**

- **public String getSimpleName() //獲取不包含包名的類名**

那么以上三者區(qū)別是惨远?舉個栗子<br />普通類名:

```java

Class clz=Person.class;

System.out.println(clz);

System.out.println(clz.toString());

System.out.println(clz.getName());

System.out.println(clz.getCanonicalName());

System.out.println(clz.getSimpleName());

運行結(jié)果:

class reflex.Person

class reflex.Person//Class里面重寫了toString方法,并且在里面調(diào)用了getName()方法

reflex.Person

reflex.Person

Person

```

數(shù)組:

```java

Class clz=Person[][].class;

System.out.println(clz.getName());

System.out.println(clz.getCanonicalName());

System.out.println(clz.getSimpleName());

運行結(jié)果:

[[Lreflex.Person;

reflex.Person[][]

Person[][]

```

<a name="96b94929"></a>

#### 修飾符

- **public native int getModifiers(); //獲取修飾符**

修飾符被包裝進一個int內(nèi),每一個修飾符都是一個標志位(置位或清零)皇忿×嗽可以使用java.lang.reflect.Modifier類中的以下方法來檢驗修飾符:

```java

Modifier.isAbstract(int mod)

? ? Modifier.isFinal(int mod)

? ? Modifier.isInterface(int mod)

? ? Modifier.isNative(int mod)

? ? Modifier.isPrivate(int mod)

? ? Modifier.isProtected(int mod)

? ? Modifier.isPublic(int mod)v

? ? Modifier.isStatic(int mod)

? ? Modifier.isStrict(int mod)//如果mod包含strictfp(strict float point (精確浮點))修飾符,則為true; 否則為:false贺氓。

? ? Modifier.isSynchronized(int mod)

? ? Modifier.isTransient(int mod)

? ? Modifier.isVolatile(int mod)

```

使用舉例:

```java

Class clz= Person.class;

int modifier = clz.getModifiers();(ps:這里為什么是復(fù)數(shù)形式蔚叨??)

System.out.println("修飾符是否為public:" + Modifier.isPublic(modifier));

運行結(jié)果:

true

```

<a name="097fbf7a"></a>

#### 包信息

- **public Package getPackage()? //獲取包信息**

從Package對象中你可以訪問諸如名字等包信息辙培。您還可以訪問類路徑上這個包位于JAR文件中Manifest這個文件中指定的信息蔑水。例如,你可以在Manifest文件中指定包的版本號扬蕊〔蟊穑可以在java.lang.Package中了解更多包類信息。

<a name="bdd1b9c2"></a>

#### 父類

- **public native Class<? super T> getSuperclass(); //獲取直接父類**

父類的Class對象和其它Class對象一樣是一個Class對象尾抑,可以繼續(xù)使用反射

<a name="404745fa"></a>

#### 實現(xiàn)的接口

? - **public native Class<?>[] getInterfaces();** //獲取實現(xiàn)的接口列表

? - 一個類可以實現(xiàn)多個接口歇父。因此返回一個Class數(shù)組。在Java反射機制中再愈,接口也由Class對象表示庶骄。

? - 注意:只有給定類聲明實現(xiàn)的接口才會返回。例如践磅,如果類A的父類B實現(xiàn)了一個接口C单刁,但類A并沒有聲明它也實現(xiàn)了C,那么C不會被返回到數(shù)組中。即使類A實際上實現(xiàn)了接口C羔飞,因為它的父類B實現(xiàn)了C肺樟。

為了得到一個給定的類實現(xiàn)接口的完整列表,需要遞歸訪問類和其超類

- **public Type[] getGenericInterfaces()** //getGenericInterface返回包括泛型的類型

<a name="9caecd93"></a>

#### 字段

- **public Field[] getFields()** //獲取所有可見的字段信息逻淌,F(xiàn)ield數(shù)組為類中聲明的每一個字段保存一個Field 實例

- **public Field[] getDeclaredFields()**//獲取所有的字段信息

- **public Field getField(String name)** //通過字段名稱獲取字符信息么伯,**該字段必須可見,否則拋出異常**

- **public Field getDeclaredField(String name)**?//通過字段名稱獲取可見的字符信息

<a name="d25bfdde"></a>

##### 關(guān)于Field

- **public String getName() //獲取字段名字**

- **public Class<?> getType() //獲取一個字段的類型**

使用舉例:

```java

Class clz = Person.class;

Field field = clz.getDeclaredField("name");

System.out.println("獲取字段名稱:" + field.getName());

System.out.println("獲取字段類型:" +field.getType());

運行結(jié)果:

獲取字段名稱:name

獲取字段類型:class java.lang.String

```

- **public Object get(Object obj) //獲取字段的值**

- **public void set(Object obj, Object value)//設(shè)置字段的值卡儒,**

注意:

1. 如果獲取的字段不可見田柔,則再通過set和get訪問之前,必須先使用 **setAccessible(true)** 設(shè)置為可訪問

1. **如果是靜態(tài)字段骨望,obj傳入null**硬爆,而不是具體的對象;不過擎鸠,如果傳具體的對象也是能正常操作的

使用舉例:

```java

Person person= (Person) clz.newInstance();

field.setAccessible(true);//設(shè)置為可訪問

field.set(person, "酸辣湯");//通過set方法設(shè)置字段值

System.out.println("通過get獲取的值:"+field.get(person));

運行結(jié)果:

通過get獲取的值:酸辣湯

```

關(guān)于Field更多API自行查看源碼

<a name="ea340b9d"></a>

#### 方法

- **public Method[] getMethods() 獲取所有可見的方法**

- **public Method[] getDeclaredMethods() 獲取所有的方法缀磕,無論是否可見**

- **public Method getMethod(String name, Class<?>... parameterTypes)**

? - 通過**方法名稱、參數(shù)類型**獲取方法

? - 如果你想訪問的方法不可見劣光,會拋出異常

? - 如果你想訪問的方法沒有參數(shù),傳遞?`null`作為參數(shù)類型數(shù)組袜蚕,或者不傳值)

- **public Method getDeclaredMethod(String name, Class<?>... parameterTypes)**

? - 通過**方法名稱、參數(shù)類型**獲取方法

? - 如果你想訪問的方法沒有參數(shù),傳遞?`null`作為參數(shù)類型數(shù)組绢涡,或者不傳值)

<a name="5bd43883"></a>

##### 關(guān)于Method

- **public Class<?>[] getParameterTypes() //獲取方法的所有參數(shù)類型**

- **public Class<?> getReturnType() //獲取方法的返回值類型**

- **public Object invoke(Object obj, Object... args)//調(diào)用方法**

? - obj:想要調(diào)用該方法的對象牲剃;args:方法的具體參數(shù),必須為每個參數(shù)提供一個準確的參量

? - 如果方法是靜態(tài)的雄可,這里obj傳入null

? - 如果方法沒有參數(shù)颠黎,args傳null或者不傳

使用舉例:

```java

Person類里有這么一個方法:

private void testMethod(String param){

? ? System.out.println("調(diào)用了testMethod方法,參數(shù)是:"+param);

}

//通過反射調(diào)用方法

Class clz = Person.class;

Method method=clz.getDeclaredMethod("testMethod",String.class);

method.setAccessible(true);

method.invoke(clz.newInstance(),"我是具體的參數(shù)值");

運行結(jié)果:

調(diào)用了testMethod方法,參數(shù)是:我是具體的參數(shù)值

```

關(guān)于Method更多API自行查看源碼

使用Java反射可以在運行時檢查類的方法并調(diào)用它們。這可以用來檢測一個給定的類有哪些get和set方法滞项∠凉椋可以通過掃描一個類的所有方法并檢查每個方法是否是get或set方法。<br />下面是一段用來找到類的get和set方法的代碼:

```java

public static void printGettersSetters(Class aClass){

? ? ? ? Method[]methods = aClass.getMethods();

? ? ? ? for(Methodmethod : methods){

? ? ? ? ? if(isGetter(method))System.out.println("getter: " + method);

? ? ? ? ? if(isSetter(method))System.out.println("setter: " + method);

? ? ? ? }

}

public staticboolean isGetter(Method method){

? ? ? ? if(!method.getName().startsWith("get"))? ? ? return false;

? ? ? ? if(method.getParameterTypes().length!= 0)? return false;

? ? ? ? if(void.class.equals(method.getReturnType())return false;

? ? ? ? return true;

}

public staticboolean isSetter(Method method){

? ? if(!method.getName().startsWith("set"))return false;

? ? if(method.getParameterTypes().length!= 1) return false;

? ? return true;

}

```

<a name="c11db1c1"></a>

#### 注解

- **public Annotation[] getAnnotations()** //獲取當前成員所有的注解文判,不包括繼承的过椎;(since jdk1.5)

- **public Annotation[] getDeclaredAnnotations()**//獲取包括繼承的所有注解;(since jdk1.5)

關(guān)于注解戏仓,下回詳解

<a name="79da8efa"></a>

### 反射與數(shù)組

數(shù)組:定義多個類型相同的變量<br />我們都知道疚宇,數(shù)組是一種特殊的類型,它本**質(zhì)上由虛擬機在運行時動態(tài)生成**赏殃,所以在反射這種類型的時候會稍有不同敷待。<br />因為數(shù)組類直接由虛擬機運行時動態(tài)創(chuàng)建,所以你不可能從一個數(shù)組類型的 Class 實例中得到構(gòu)造方法仁热,**編譯器根本沒機會為類生成默認的構(gòu)造器**榜揖。于是你也不能以常規(guī)的方法通過 Constructor 來創(chuàng)建一個該類的實例對象。<br />如果你非要嘗試使用 Constructor 來創(chuàng)建一個新的實例的話,那么運行時程序?qū)⒏嬖V你無法匹配一個構(gòu)造器举哟。像這樣:

```java

Class<String[]> cls = String[].class;

Constructor constructor = cls.getConstructor();

String[] strs = (String[]) constructor.newInstance();

```

程序會拋出 NoSuchMethodException的異常思劳,告訴你Class 實例中根本找不到一個無參的構(gòu)造器

那我們要怎么動態(tài)創(chuàng)建一個數(shù)組?妨猩?<br />Java 中有一個類 java.lang.reflect.Array 提供了一些靜態(tài)的方法用于動態(tài)的創(chuàng)建和獲取一個數(shù)組類型

- **public static Object newInstance(Class<?> componentType, int length)**

? - //創(chuàng)建一個一維數(shù)組潜叛,componentType 為數(shù)組元素類型,length 數(shù)組長度

- **public static Object newInstance(Class<?> componentType, int... dimensions)**

? - //可變參數(shù) dimensions壶硅,指定多個維度的單維度長度

- **public static native void set(Object array, int index, Object value)**

? - 把數(shù)組array索引位置為index的設(shè)為value值

- **public static native Object get(Object array, int index)**

? - 獲得數(shù)組array的index位置上的元素

補充下威兜,Class類中獲取組件類型的API:

- **public native Class<?> getComponentType()**;

? - 如果class是數(shù)組類型, 獲取其元素的類型庐椒,如果是非數(shù)組椒舵,則返回null

**一維數(shù)組實例**

```java

//用反射來定義一個int類型,3長度的數(shù)組

int[] intArray = (int[]) Array.newInstance(int.class, 3);

Array.set(intArray, 0, 123);

Array.set(intArray, 1, 456);

Array.set(intArray, 2, 789);

System.out.println("intArray[0] = " + Array.get(intArray, 0));

System.out.println("intArray[1] = " + Array.get(intArray, 1));

System.out.println("intArray[2] = " + Array.get(intArray, 2));

//獲取類對象的一個數(shù)組

Class stringArrayClass = Array.newInstance(int.class, 0).getClass();

System.out.println("is array: " + stringArrayClass.isArray());

//獲取數(shù)組的組件類型

String[] strings = new String[3];

Class stringArrayClass2 = strings.getClass();

Class stringArrayComponentType = stringArrayClass2.getComponentType();

System.out.println(stringArrayComponentType);

運行結(jié)果:

intArray[0] = 123

intArray[1] = 456

intArray[2] = 789

is array: true

class java.lang.String

```

多維數(shù)組:

```java

// 創(chuàng)建一個三維數(shù)組扼睬,每個維度長度分別為5,10,15

int[] dims = new int[] { 5, 10,15 };

Person[][][] array = (Person[][][]) Array.newInstance(Person.class, dims); // 可變參數(shù)逮栅,也可以這樣寫:Object array = Array.newInstance(Integer.TYPE, 5,10,15);

Class<?> classType0 = array.getClass().getComponentType();? ? // 返回數(shù)組元素類型

System.out.println("三維數(shù)組元素類型:"+classType0);? ? // 三維數(shù)組的元素為二維數(shù)組

Object arrayObject = Array.get(array, 2);// 獲得三維數(shù)組中索引為2的元素悴势,返回的是一個二維數(shù)組

System.out.println("二維數(shù)組元素類型:"+arrayObject.getClass().getComponentType());

Object oneObject = Array.get(arrayObject, 0);// 獲得二維數(shù)組中索引為0的數(shù)組窗宇,返回的是一個一維數(shù)組

System.out.println("一維數(shù)組元素類型:"+oneObject.getClass().getComponentType());

Array.set(oneObject,14,new Person("酸辣湯",18));//設(shè)置以為數(shù)組索引為3的位置的元素

System.out.println("未被設(shè)置元素的位置:"+array[0][0][0]);

System.out.println("已被設(shè)置元素的位置:"+array[2][0][14]);

運行結(jié)果:

三維數(shù)組元素類型:class [[Left.reflex.bean.Person;

二維數(shù)組元素類型:class [Left.reflex.bean.Person;

一維數(shù)組元素類型:class eft.reflex.bean.Person

person的靜態(tài)塊被調(diào)用了

未被設(shè)置元素的位置:null

已被設(shè)置元素的位置:Person{name='酸辣湯', age=18}

```

<a name="38e051a8"></a>

### 反射與泛型

泛型是 Java 編譯器范圍內(nèi)的概念,它能夠在程序運行之前提供一定的安全檢查特纤,而反射是運行時發(fā)生的军俊,也就是說如果你反射調(diào)用一個泛型方法,實際上就繞過了編譯器的泛型檢查了捧存。我們看一段代碼:

```java

ArrayList<Integer> list = new ArrayList<>();

list.add(23);

//list.add("fads");編譯不通過

Class<?> cls = list.getClass();

Method add = cls.getMethod("add",Object.class);

add.invoke(list,"hello");

for (Object obj:list){

? ? System.out.println(obj);

}

運行結(jié)果:

23

hello

```

最終你會發(fā)現(xiàn)我們從整型容器中取出一個字符串粪躬,因為虛擬機只管在運行時從方法區(qū)找到 ArrayList 這個類的類型信息并解析出它的 add 方法,接著執(zhí)行這個方法昔穴。它不像一般的方法調(diào)用镰官,調(diào)用之前編譯器會檢測這個方法存在不存在,參數(shù)類型是否匹配等吗货,所以沒了編譯器的這層安全檢查泳唠,反射地調(diào)用方法更容易遇到問題。

<a name="e3150dff"></a>

##### **使用反射來獲取泛型信息**

在實際應(yīng)用中宙搬,為了獲得和泛型有關(guān)的信息笨腥,Java就新增了幾種類型來代表不能被歸一到Class類中的類型,但又和基本數(shù)據(jù)類型齊名的類型勇垛,通常使用的是如下兩個:

- GenericType: 表示一種元素類型是參數(shù)化的類型或者類型變量的數(shù)組類型脖母。[@since ]() 1.5

- ParameterizedType: 表示一種參數(shù)化的類型。? ??[@since ]() 1.5

為什么要引入這兩種呢闲孤,實際上谆级,在通過反射獲得成員變量時,F(xiàn)ield類有一個方法是getType,可以獲得該字段的屬性哨苛,但是這種屬性如果是泛型就獲取不到了鸽凶,所以才引入了上面兩種類型。

實例:

```java

public class Person {

? ? ...

? ? private Map<String,Integer> map;? ?

? ? ...

}

Class<Person> clazz = Person.class;

Field f = clazz.getDeclaredField("map");

//通過getType方法只能獲得普通類型

System.out.println("map的類型是:" + f.getType()); //打印Map

//1. 獲得f的泛型類型

Type gType = f.getGenericType();

//2.如果gType是泛型類型對像

if(gType instanceof ParameterizedType)

{

? ? ParameterizedType pType = (ParameterizedType)gType;

? ? //獲取原始類型

? ? Type rType = pType.getRawType();

? ? System.out.println("原始類型是: " + rType);

? ? //獲得泛型類型的泛型參數(shù)

? ? Type[] gArgs = pType.getActualTypeArguments();

? ? //打印泛型參數(shù)

? ? for(int i=0; i < gArgs.length; i ++)

? ? {

? ? ? ? System.out.println("第"+ i +"個泛型類型是:" + gArgs[i]);

? ? }

}

else {

? ? System.out.println("獲取泛型信息失敗");

}

運行結(jié)果:

map的類型是:interface java.util.Map

原始類型是: interface java.util.Map

第0個泛型類型是:class java.lang.String

第1個泛型類型是:class java.lang.Integer

```

<a name="deada25c"></a>

## 反射源碼與性能開銷

只列舉個別方法的源碼建峭,其他的有興趣可以自行查看源碼(大部分都是native方法)

<a name="a1235e7a"></a>

### 調(diào)用invoke()方法

獲取到Method對象之后玻侥,調(diào)用invoke方法的流程如下:<br />![](https://user-gold-cdn.xitu.io/2019/8/2/16c52dcd50358852?w=724&h=508&f=png&s=105299)

可以看到,調(diào)用Method.invoke之后亿蒸,會直接去調(diào)MethodAccessor.invoke凑兰。MethodAccessor就是上面提到的所有同名method共享的一個實例,由ReflectionFactory創(chuàng)建边锁。創(chuàng)建機制采用了一種名為inflation的方式(JDK1.4之后):如果該方法的累計調(diào)用次數(shù)<=15姑食,會創(chuàng)建出NativeMethodAccessorImpl,它的實現(xiàn)就是直接調(diào)用native方法實現(xiàn)反射茅坛;如果該方法的累計調(diào)用次數(shù)>15音半,會由java代碼創(chuàng)建出字節(jié)碼組裝而成的MethodAccessorImpl。(是否采用inflation和15這個數(shù)字都可以在jvm參數(shù)中調(diào)整)<br />以調(diào)用MyClass.myMethod(String s)為例贡蓖,生成出的MethodAccessorImpl字節(jié)碼翻譯成Java代碼大致如下:

```java

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {? ?

? public Object invoke(Object obj, Object[] args)? throws Exception {

? ? ? try {

? ? ? ? ? MyClass target = (MyClass) obj;

? ? ? ? ? String arg0 = (String) args[0];

? ? ? ? ? target.myMethod(arg0);

? ? ? } catch (Throwable t) {

? ? ? ? ? throw new InvocationTargetException(t);

? ? ? }

? }

}

```

至于native方法的實現(xiàn)曹鸠,由于比較深入本文就不探討了

### 直接調(diào)用方法與通過反射調(diào)用方法對比

```java

? ? public static void main(String[] args) throws Exception {

? ? ? // directCall();//直接調(diào)用

? ? ? ? reflectCall();//反射調(diào)用

? ? }

? ? public static void target(int i) {

? ? }

? ? //直接調(diào)用

? ? private static void directCall() {

? ? ? ? long current = System.currentTimeMillis();

? ? ? ? for (int i = 1; i <= 2_000_000_000; i++) {

? ? ? ? ? ? if (i % 100_000_000 == 0) {

? ? ? ? ? ? ? ? long temp = System.currentTimeMillis();

? ? ? ? ? ? ? ? System.out.println(temp - current);

? ? ? ? ? ? ? ? current = temp;

? ? ? ? ? ? }

? ? ? ? ? ? MethodTest.target(128);

? ? ? ? }

? ? }

? ? //反射調(diào)用同一個方法

? ? private static void reflectCall() throws Exception {

? ? ? ? Class<?> klass = Class.forName("eft.reflex.MethodTest");

? ? ? ? Method method = klass.getMethod("target", int.class);

? ? ? ? long current = System.currentTimeMillis();

? ? ? ? for (int i = 1; i <= 2_000_000_000; i++) {

? ? ? ? ? ? if (i % 100_000_000 == 0) {

? ? ? ? ? ? ? ? long temp = System.currentTimeMillis();

? ? ? ? ? ? ? ? System.out.println(temp - current);

? ? ? ? ? ? ? ? current = temp;

? ? ? ? ? ? }

? ? ? ? ? ? method.invoke(null, 128);

? ? ? ? }

? ? }


運行結(jié)果:

直接調(diào)用結(jié)果:

...

121

126

105

115

100 (取最后5個值,作為預(yù)熱后的峰值性能)

反射調(diào)用結(jié)果:

...

573

581

593

557

594 (取最后5個值斥铺,作為預(yù)熱后的峰值性能)

```

結(jié)果分析:普通調(diào)用作為性能基準彻桃,大約100多秒,通過反射調(diào)用的耗時大約為基準的4倍

#### 為何反射會帶來性能開銷晾蜘?

先看下使用反射調(diào)用的字節(jié)碼文件:

```shell

63: aload_1? ? ? ? ? ? ? ? ? ? ? ? // 加載Method對象

64: aconst_null? ? ? ? ? ? ? ? ? ? // 靜態(tài)方法邻眷,反射調(diào)用的第一個參數(shù)為null

65: iconst_1

66: anewarray? ? ? ? ? ? ? ? ? ? ? // 生成一個長度為1的Object數(shù)組

69: dup

70: iconst_0

71: sipush? ? ? ? 128

74: invokestatic Integer.valueOf? ? // 將128自動裝箱成Integer

77: aastore? ? ? ? ? ? ? ? ? ? ? ? // 存入Object數(shù)組

78: invokevirtual Method.invoke? ? // 反射調(diào)用

```

可以看出反射調(diào)用前的兩個動作

- Method.invoke是一個變長參數(shù)方法,最后一個參數(shù)在字節(jié)碼層面會是Object數(shù)組

? ? - Java編譯器會在方法調(diào)用處生成一個長度為入?yún)?shù)量的Object數(shù)組剔交,并將入?yún)⒁灰淮鎯M該數(shù)組

- Object數(shù)組不能存儲基本類型肆饶,Java編譯器會對傳入的基本類型進行自動裝箱

上述兩個步驟會帶來性能開銷和GC

#### 如何降低開銷?

- 1. 增加啟動JVM參數(shù):-Djava.lang.Integer.IntegerCache.high=128岖常,減少裝箱


? ? > 經(jīng)測試驯镊,峰值性能:280.4ms,為基準耗時的2.5倍

- 2. 減少自動生成Object數(shù)組腥椒,測試代碼如下:

```java

private static void reflectCall() throws Exception {

? ? ? ? Class<?> klass = Class.forName("eft.reflex.MethodTest");

? ? ? ? Method method = klass.getMethod("target", int.class);

? ? ? ? // 在循環(huán)外構(gòu)造參數(shù)數(shù)組

? ? ? ? Object[] arg = new Object[1];

? ? ? ? arg[0] = 128;

? ? ? ? long current = System.currentTimeMillis();

? ? ? ? for (int i = 1; i <= 2_000_000_000; i++) {

? ? ? ? ? ? if (i % 100_000_000 == 0) {

? ? ? ? ? ? ? ? long temp = System.currentTimeMillis();

? ? ? ? ? ? ? ? System.out.println(temp - current);

? ? ? ? ? ? ? ? current = temp;

? ? ? ? ? ? }

? ? ? ? ? ? method.invoke(null, 128);

? ? ? ? }

? ? }

```

字節(jié)碼:

```shell

80: aload_2? ? ? ? ? ? ? ? ? ? ? ? // 加載Method對象

81: aconst_null? ? ? ? ? ? ? ? ? ? // 靜態(tài)方法阿宅,反射調(diào)用的第一個參數(shù)為null

82: aload_3

83: invokevirtual Method.invoke? ? // 反射調(diào)用,無anewarray指令

```

經(jīng)測試笼蛛,峰值性能:312.4ms洒放,為基準耗時的2.8倍

- 3. 關(guān)閉inflation機制

? ? - -Dsun.reflect.noInflation=true,關(guān)閉Inflation機制滨砍,反射調(diào)用在一開始便會直接使用動態(tài)實現(xiàn)往湿,而不會使用委派實現(xiàn)或者本地實現(xiàn) (即一開始invoke方法就使用java實現(xiàn)的而不使用native方法)

? ? - 關(guān)閉權(quán)限校驗:每次反射調(diào)用都會檢查目標方法的權(quán)限

```java

// -Djava.lang.Integer.IntegerCache.high=128

// -Dsun.reflect.noInflation=true

public static void main(String[] args) throws Exception {

? ? ? ? Class<?> klass = Class.forName("eft.reflex.MethodTest");

? ? ? ? Method method = klass.getMethod("target", int.class);

? ? ? ? // 關(guān)閉權(quán)限檢查

? ? ? ? method.setAccessible(true);

? ? ? ? long current = System.currentTimeMillis();

? ? ? ? for (int i = 1; i <= 2_000_000_000; i++) {

? ? ? ? ? ? if (i % 100_000_000 == 0) {

? ? ? ? ? ? ? ? long temp = System.currentTimeMillis();

? ? ? ? ? ? ? ? System.out.println(temp - current);

? ? ? ? ? ? ? ? current = temp;

? ? ? ? ? ? }

? ? ? ? ? ? method.invoke(null, 128);

? ? ? ? }

? ? }

```

峰值性能:186.2ms妖异,為基準耗時的1.7倍

## 反射優(yōu)缺點

### 優(yōu)點

1.增加程序的靈活性,避免將程序?qū)懰赖酱a里领追。

例:定義了一個接口他膳,實現(xiàn)這個接口的類有20個,程序里用到了這個實現(xiàn)類的地方有好多地方绒窑,如果不使用配置文件手寫的話棕孙,代碼的改動量很大,因為每個地方都要改而且不容易定位些膨,如果你在編寫之前先將接口與實現(xiàn)類的寫在配置文件里蟀俊,下次只需改配置文件,利用反射(java API已經(jīng)封裝好了订雾,直接用就可以用 Class.newInstance())就可完成

2.代碼簡潔肢预,提高代碼的復(fù)用率,外部調(diào)用方便

### 缺點

- **性能開銷** - 由于反射涉及動態(tài)解析的類型洼哎,因此無法執(zhí)行某些 Java 虛擬機優(yōu)化烫映。因此,反射操作的性能要比非反射操作的性能要差噩峦,應(yīng)該在性能敏感的應(yīng)用程序中頻繁調(diào)用的代碼段中避免锭沟。

- **破壞封裝性** - 反射調(diào)用方法時可以忽略權(quán)限檢查,因此可能會破壞封裝性而導(dǎo)致安全問題壕探。

- **模糊程序內(nèi)部邏輯** - 程序人員希望在源代碼中看到程序的邏輯冈钦,反射等繞過了源代碼的技術(shù)郊丛,因而會帶來維護問題李请。反射代碼比相應(yīng)的直接代碼更復(fù)雜。

- **內(nèi)部曝光** - 由于反射允許代碼執(zhí)行在非反射代碼中非法的操作厉熟,例如訪問私有字段和方法导盅,所以反射的使用可能會導(dǎo)致意想不到的副作用,這可能會導(dǎo)致代碼功能失常并可能破壞可移植性揍瑟。反射代碼打破了抽象白翻,因此可能會隨著平臺的升級而改變行為。

<a name="5176cfc3"></a>

##### Java反射可以訪問和修改私有成員變量绢片,那封裝成private還有意義么滤馍?

既然小偷可以訪問和搬走私有成員家具,那封裝成防盜門還有意義么底循?這是一樣的道理巢株,并且Java從應(yīng)用層給我們提供了安全管理機制——安全管理器,每個Java應(yīng)用都可以擁有自己的安全管理器熙涤,它會在運行階段檢查需要保護的資源的訪問權(quán)限及其它規(guī)定的操作權(quán)限阁苞,保護系統(tǒng)免受惡意操作攻擊困檩,以達到系統(tǒng)的安全策略。所以其實反射在使用時那槽,內(nèi)部有安全控制悼沿,如果安全設(shè)置禁止了這些,那么反射機制就無法訪問私有成員骚灸。

<a name="c0f0ea00"></a>

##### 反射是否真的會讓你的程序性能降低?

1.反射大概比直接調(diào)用慢50~100倍糟趾,但是需要你在執(zhí)行100萬遍的時候才會有所感覺<br />2.判斷一個函數(shù)的性能,你需要把這個函數(shù)執(zhí)行100萬遍甚至1000萬遍<br />3.如果你只是偶爾調(diào)用一下反射甚牲,請忘記反射帶來的性能影響<br />4.如果你需要大量調(diào)用反射拉讯,請考慮緩存。<br />5.你的編程的思想才是限制你程序性能的最主要的因素

<a name="2772f6c0"></a>

## 開發(fā)中使用反射的場景

**工廠模式**:Factory類中用反射的話鳖藕,添加了一個新的類之后魔慷,就不需要再修改工廠類Factory了,如下例子

**數(shù)據(jù)庫JDBC中通過Class.forName(Driver).來獲得數(shù)據(jù)庫連接驅(qū)動**

**開發(fā)通用框架** - 反射最重要的用途就是開發(fā)各種通用框架著恩。很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 JavaBean院尔、Filter 等),為了保證框架的通用性喉誊,它們可能需要根據(jù)配置文件加載不同的對象或類邀摆,調(diào)用不同的方法,這個時候就必須用到反射——運行時動態(tài)加載需要加載的對象伍茄。

**動態(tài)代理** - 在切面編程(AOP)中栋盹,需要攔截特定的方法,通常敷矫,會選擇動態(tài)代理方式例获。這時,就需要反射技術(shù)來實現(xiàn)了曹仗。

**注解** - 注解本身僅僅是起到標記作用榨汤,它需要利用反射機制,根據(jù)注解標記去調(diào)用注解解釋器怎茫,執(zhí)行行為收壕。如果沒有反射機制,注解并不比注釋更有用轨蛤。

**可擴展性功能** - 應(yīng)用程序可以通過使用完全限定名稱創(chuàng)建可擴展性對象實例來使用外部的用戶定義類蜜宪。

使用反射的工廠模式舉例:

```java

//用反射機制實現(xiàn)工廠模式:

interface fruit{

? ? public abstract void eat();

}

class Apple implements fruit{

? ? public void eat(){

? ? ? ? System.out.println("Apple");

? ? }

}

class Orange implements fruit{

? ? public void eat(){

? ? ? ? System.out.println("Orange");

? ? }

}

class Factory{

? ? public static fruit getInstance(String ClassName){

? ? ? ? fruit f=null;

? ? ? ? try{

? ? ? ? ? ? f=(fruit)Class.forName(ClassName).newInstance();

? ? ? ? }catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? return f;

? ? }

}

客戶端:

class hello{

? ? public static void main(String[] a){

? ? ? ? fruit f=Factory.getInstance("Reflect.Apple");

? ? ? ? if(f!=null){

? ? ? ? ? ? f.eat();

? ? ? ? }

? ? }

}

```

<a name="d3d7ee72"></a>

## 反射與內(nèi)省

內(nèi)省(自氏樯健):<br />內(nèi)省**基于反射**實現(xiàn)圃验,也就是對反射的再次包裝,主要用于操作JavaBean枪蘑,通過內(nèi)省 可以獲取bean的getter/setter<br />通俗地說:javaBean 具有的自省機制可以在不知道javaBean都有哪些屬性的情況下损谦,設(shè)置他們的值岖免。核心也是反射機制

> 一般在開發(fā)框架時,當需要操作一個JavaBean時照捡,如果一直用反射來操作颅湘,顯得很麻煩;所以sun公司開發(fā)一套API專門來用來操作JavaBean

內(nèi)省是 Java 語言對 Bean 類屬性栗精、事件的一種缺省處理方法闯参。例如類 A 中有屬性 name, 那我們可以通過 getName,setName 來得到其值或者設(shè)置新的值。通過 getName/setName 來訪問 name 屬性悲立,這就是默認的規(guī)則鹿寨。 Java 中提供了一套 API 用來訪問某個屬性的 getter/setter 方法,通過這些 API 可以使你不需要了解這個規(guī)則(但你最好還是要搞清楚)薪夕,這些 API 存放于包 java.beans 中脚草。<br />一般的做法是通過類 Introspector 來獲取某個對象的 BeanInfo 信息,然后通過 BeanInfo 來獲取屬性的描述器( PropertyDescriptor )原献,通過這個屬性描述器就可以獲取某個屬性對應(yīng)的 getter/setter 方法馏慨,然后我們就可以通過反射機制來調(diào)用這些方法。下面我們來看一個例子姑隅,這個例子把某個對象的所有屬性名稱和值都打印出來:

```java

package introspector;

//這些api都是在java.beans下(rt.jar包下)

import java.beans.BeanInfo;

import java.beans.Introspector;

import java.beans.PropertyDescriptor;

public class IntrospectorDemo{

? ? String name;

? ? public static void main(String[] args) throws Exception{

? ? ? ? IntrospectorDemo demo = new IntrospectorDemo();

? ? ? ? // 如果不想把父類的屬性也列出來的話写隶,

? ? ? ? //那 getBeanInfo 的第二個參數(shù)填寫父類的信息

? ? ? ? BeanInfo bi = Introspector.getBeanInfo(demo.getClass(), Object. class );//Object類是所有Java類的根父類

? ? ? ? PropertyDescriptor[] props = bi.getPropertyDescriptors();//獲得屬性的描述器

? ? ? ? for ( int i=0;i<props.length;i++){

? ? ? ? ? ? System.out.println("獲取屬性的Class對象:"+props[i].getPropertyType());

? ? ? ? ? ? props[i].getWriteMethod().invoke(demo, "酸辣湯" );//獲得setName方法,并使用invoke調(diào)用

? ? ? ? ? ? System.out.println("讀取屬性值:"+props[i].getReadMethod().invoke(demo, null ));

? ? ? ? }

? ? }

? ? public String getName() {

? ? ? ? return name;

? ? }

? ? public void setName(String name) {

? ? ? ? this .name = name;

? ? }

}

運行結(jié)果:

獲取屬性的Class對象:class java.lang.String

讀取屬性值:酸辣湯

```

JDK內(nèi)省類庫:**PropertyDescriptor類:<br />?PropertyDescriptor類表示JavaBean類通過存儲器導(dǎo)出一個屬性讲仰。主要方法:<br />1. getPropertyType()慕趴,獲得屬性的Class對象;<br />2. getReadMethod(),獲得用于讀取屬性值的方法鄙陡;(如上面的獲取getName方法)<br />3.getWriteMethod()冕房,獲得用于寫入屬性值的方法;(如上面的獲取setName方法)<br />4.hashCode(),獲取對象的哈希值;<br />5. setReadMethod(Method readMethod)柔吼,設(shè)置用于讀取屬性值的方法;<br />6. setWriteMethod(Method writeMethod)毒费,設(shè)置用于寫入屬性值的方法丙唧。**

> Apache開發(fā)了一套簡單愈魏、易用的API來操作Bean的屬性——BeanUtils工具包。

<a name="25f9c7fa"></a>

## 參考資料

https://zhuanlan.zhihu.com/p/34168509

http://fanyilun.me/2015/10/29/Java%E5%8F%8D%E5%B0%84%E5%8E%9F%E7%90%86/

http://zhongmingmao.me/2018/12/20/jvm-basic-reflection/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末想际,一起剝皮案震驚了整個濱河市培漏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胡本,老刑警劉巖牌柄,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異侧甫,居然都是意外死亡珊佣,警方通過查閱死者的電腦和手機蹋宦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咒锻,“玉大人冷冗,你說我怎么就攤上這事』笸В” “怎么了蒿辙?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滨巴。 經(jīng)常有香客問我思灌,道長,這世上最難降的妖魔是什么恭取? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任泰偿,我火速辦了婚禮,結(jié)果婚禮上蜈垮,老公的妹妹穿的比我還像新娘甜奄。我一直安慰自己,他們只是感情好窃款,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布课兄。 她就那樣靜靜地躺著,像睡著了一般晨继。 火紅的嫁衣襯著肌膚如雪烟阐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天紊扬,我揣著相機與錄音蜒茄,去河邊找鬼。 笑死餐屎,一個胖子當著我的面吹牛檀葛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腹缩,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼屿聋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了藏鹊?” 一聲冷哼從身側(cè)響起润讥,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盘寡,沒想到半個月后楚殿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡竿痰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年脆粥,在試婚紗的時候發(fā)現(xiàn)自己被綠了砌溺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡变隔,死狀恐怖抚吠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弟胀,我是刑警寧澤楷力,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站孵户,受9級特大地震影響萧朝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夏哭,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一检柬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竖配,春花似錦何址、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胁镐,卻和暖如春偎血,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盯漂。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工颇玷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人就缆。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓帖渠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親竭宰。 傳聞我的和親對象是個殘疾皇子空郊,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容