用最直接的大白話來聊一聊Java中的反射機制

視頻功能審覈通過了,可以看視頻啦!記得點關注啊

用最通俗易懂的話來說一說Java中的反射機制

思考:在講反射之前,先思考一個問題,java中如何創建一個對象,有哪幾種方式?

Java中創建對象大概有這幾種方式:

1、使用new關鍵字:這是我們最常見的也是最簡單的創建對象的方式

2、使用Clone的方法:無論何時我們調用一個對象的clone方法,JVM就會創建一個新的對象,將前面的對象的內容全部拷貝進去

3、使用反序列化:當我們序列化和反序列化一個對象,JVM會給我們創建一個單獨的對象

上邊是Java中常見的創建對象的三種方式,其實除了上邊的三種還有另外一種方式,就是接下來我們要討論的 “反射”

1、反射概述

1.1什麼是反射

反射就是把Java類中的各個部分,映射成一個個的Java對象,拿到這些對象後可以做一些事情。

既然說反射是反射Java類中的各個組成部分,所以說咱們得知道一個類中有哪兒些部分?

例如,一個類有:成員變量,方法,構造方法,等信息,利用反射技術咱們可以把這些組成部分映射成一個個對象。

1.2、反射能幹什麼

說完反射的概念後,咱們說一下反射能幹什麼?

一般來說反射是用來做框架的,或者說可以做一些抽象度比較高的底層代碼,反射在日常的開發中用到的不多,但是咱們還必須搞懂它,因爲搞懂了反射以後,可以幫助咱們理解框架的一些原理。所以說有一句很經典的話:反射是框架設計的靈魂。現在說完這個可能還不太能理解,不急,等下說完一個快速入門的例子後,應該會稍微有點感覺

1.3、怎麼得到想反射的類

剛纔已經說過,反射是對一個類進行解剖,想解剖一個東西,前提是首先你得拿到這個東西,那麼怎麼得到咱們想解剖的類呢?

首先大家要明白一點,咱們寫的代碼是存儲在後綴名是 .java的文件裏的,但是它會被編譯,最終真正去執行的是編譯後的 .class文件。Java是面向對象的語言,一切皆對象,所以java認爲 這些編譯後的 class文件,這種事物也是一種對象,它也給抽象成了一種類,這個類就是Class,大家可以去AIP裏看一下這個類

所以拿到這個類後,就相當於拿到了咱們想解剖的類,那怎麼拿到這個類?

看API文檔後,有一個方法forName(String className); 而且是一個靜態的方法,這樣咱們就可以得到想反射的類了

到這裏,看Class clazz = Class.forName("com.cj.test.Person");這個應該有點感覺了吧

Class.forName("com.cj.test.Person");因爲這個方法裏接收的是個字符串,字符串的話,我們就可以寫在配置文件裏,然後利用反射生成我們需要的對象,這纔是我們想要的。很多框架裏都有類似的配置

2、解剖類

我們知道一個類裏一般有構造函數、方法、成員變量(字段/屬性)這三部分組成

翻閱API文檔,可以看到

Class對象提供瞭如下常用方法:

public Constructor getConstructor(Class<?>…parameterTypes)

public Method getMethod(String name,Class<?>… parameterTypes)

public Field getField(String name)

public Constructor getDeclaredConstructor(Class<?>…parameterTypes)

public Method getDeclaredMethod(String name,Class<?>… parameterTypes)

public Field getDeclaredField(String name)

這些方法分別用於幫咱們從類中解剖出構造函數、方法和成員變量(屬性)。

然後把解剖出來的部分,分別用Constructor、Method、Field對象表示。

2.1反射構造方法

2.1.1反射無參的構造函數

可以看到 默認的無參構造方法執行了

從上邊的例子看出,要想反射,首先第一步就是得到類的字節碼

所以簡單說一下得到類的字節碼的幾種方式

(1)、Class.forName("com.cj.test.Person"); 這就是上邊我們用的方式

(2)、對象.getClass();

(3)、類名.class;

2.1.2反射“一個參數”的構造函數

2.1.3反射“多個參數”的構造函數

2.1.4反射“私有”的構造函數

注意:在反射私有的構造函數時,用普通的clazz.getConstructor()會報錯,因爲它是私有的,所以提供了專門反射私有構造函數的方法 clazz.getDeclaredConstructor(int.class);//讀取私有的構造函數,用這個方法讀取完還需要設置一下暴力反射纔可以

c.setAccessible(true);//暴力反射

2.1.5反射得到類中所有的構造函數

 

2.2反射類中的方法

package com.cj.test;

import java.util.Date;

public class Person {
	
	public Person(){
		System.out.println("默認的無參構造方法執行了");
	}

	public Person(String name){
		System.out.println("姓名:"+name);
	}
	
	public Person(String name,int age){
		System.out.println(name+"="+age);
	}
	
	private Person(int age){
		System.out.println("年齡:"+age);
	}
	
	public void m1() {
		System.out.println("m1");
	}
	
	public void m2(String name) {
		System.out.println(name);
	}
	
	public String m3(String name,int age) {
		System.out.println(name+":"+age);
		return "aaa";
	}
	
	private void m4(Date d) {
		System.out.println(d);
	}
	
	public static void m5() {
		System.out.println("m5");
	}
	
	public static void m6(String[] strs) {
		System.out.println(strs.length);
	}

        public static void main(String[] args) {
		System.out.println("main");
	}

}




package com.cj.test;

import java.lang.reflect.Method;
import java.util.Date;
import org.junit.Test;

public class Demo2 {

	@Test//public void m1()
	public void test1() throws Exception{
		Class clazz = Class.forName("com.cj.test.Person");
		Person p = (Person)clazz.newInstance();
		Method m = clazz.getMethod("m1", null);
		m.invoke(p, null);
	}
	@Test//public void m2(String name)
	public void test2() throws Exception{
		Class clazz = Person.class;
		Person p = (Person) clazz.newInstance();
		Method m = clazz.getMethod("m2", String.class);
		m.invoke(p, "張三");
	}
	@Test//public String m3(String name,int age)
	public void test3() throws Exception{
		Class clazz = Person.class;
		Person p = (Person) clazz.newInstance();
		Method m = clazz.getMethod("m3", String.class,int.class);
		String returnValue = (String)m.invoke(p, "張三",23);
		System.out.println(returnValue);
	}
	@Test//private void m4(Date d)
	public void test4() throws Exception{
		Class clazz = Person.class;
		Person p = (Person) clazz.newInstance();
		Method m = clazz.getDeclaredMethod("m4", Date.class);
		m.setAccessible(true);
		m.invoke(p,new Date());
	}
	@Test//public static void m5()
	public void test5() throws Exception{
		Class clazz = Person.class;
		Method m = clazz.getMethod("m5", null);
		m.invoke(null,null);
	}
	@Test//private static void m6(String[] strs)
	public void test6() throws Exception{
		Class clazz = Person.class;
		Method m = clazz.getDeclaredMethod("m6",String[].class);
		m.setAccessible(true);
		m.invoke(null,(Object)new String[]{"a","b"});
	}
	@Test
	public void test7() throws Exception{
		Class clazz = Person.class;
		Method m = clazz.getMethod("main",String[].class);
		m.invoke(null,new Object[]{new String[]{"a","b"}});
	}
}

*****注意:看下上邊代碼裏test6和test7的invoke方法裏傳的參數和其他的有點不一樣

這是因爲 jdk1.4和jdk1.5處理invoke方法有區別

1.5:public Object invoke(Object obj,Object…args)

1.4:public Object invoke(Object obj,Object[] args)

由於JDK1.4和1.5對invoke方法的處理有區別, 所以在反射類似於main(String[] args) 這種參數是數組的方法時需要特殊處理

啓動Java程序的main方法的參數是一個字符串數組,即public static void main(String[] args),通過反射方式來調用這個main方法時,如何爲invoke方法傳遞參數呢?按jdk1.5的語法,整個數組是一個參數,而按jdk1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作爲參數傳遞給invoke方法時,javac會到底按照哪種語法進行處理呢?jdk1.5肯定要兼容jdk1.4的語法,會按jdk1.4的語法進行處理,即把數組打散成爲若干個單獨的參數。所以,在給main方法傳遞參數時,不能使用代碼mainMethod.invoke(null,new String[]{“xxx”}),javac只把它當作jdk1.4的語法進行理解,而不把它當作jdk1.5的語法解釋,因此會出現參數個數不對的問題。

上述問題的解決方法:

(1)mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});

這種方式,由於你傳的是一個數組的參數,所以爲了向下兼容1.4的語法,javac遇到數組會給你拆開成多個參數,但是由於咱們這個Object[ ] 數組裏只有一個元素值,所以就算它拆也沒關係

(2)mainMethod.invoke(null,(Object)new String[]{"xxx"});

這種方式相當於你傳的參數是一個對象,而不是數組,所以就算是按照1.4的語法它也不會拆,所以問題搞定

編譯器會作特殊處理,編譯時不把參數當作數組看待,也就不會數組打散成若干個參數了

對上邊的描述進行一下總結:在反射方法時,如果方法的參數是一個數組,考慮到向下兼容問題,會按照JDK1.4的語法來對待(JVM會把傳遞的數組參數拆開,拆開就會報參數的個數不匹配的錯誤)
解決辦法:防止JVM拆開你的數組
    方式一:把數組看做是一個Object對象
    方式二:重新構建一個Object數組,那個參數數組作爲唯一的元素存在。

2.3反射類中的屬性字段

package com.cj.test;

import java.util.Date;

public class Person {
	
	public String name="李四";
	private int age = 18;
	public static Date time;
	
	public int getAge() {
		return age;
	}
	
	public Person(){
		System.out.println("默認的無參構造方法執行了");
	}

	public Person(String name){
		System.out.println("姓名:"+name);
	}
	
	public Person(String name,int age){
		System.out.println(name+"="+age);
	}
	
	private Person(int age){
		System.out.println("年齡:"+age);
	}
	
	public void m1() {
		System.out.println("m1");
	}
	
	public void m2(String name) {
		System.out.println(name);
	}
	
	public String m3(String name,int age) {
		System.out.println(name+":"+age);
		return "aaa";
	}
	
	private void m4(Date d) {
		System.out.println(d);
	}
	
	public static void m5() {
		System.out.println("m5");
	}
	
	public static void m6(String[] strs) {
		System.out.println(strs.length);
	}
	
	public static void main(String[] args) {
		System.out.println("main");
	}
	
}




package com.cj.test;

import java.lang.reflect.Field;
import java.util.Date;
import org.junit.Test;

public class Demo3 {
	//public String name="李四";
	@Test
	public void test1() throws Exception{
		Class clazz = Person.class;
		Person p = (Person)clazz.newInstance();
		Field f = clazz.getField("name");
		String s = (String)f.get(p);
		System.out.println(s);
		
		//更改name的值
		f.set(p, "王六");
		System.out.println(p.name);
	}
	@Test//private int age = 18;
	public void test2() throws Exception{
		Class clazz = Person.class;
		Person p = (Person)clazz.newInstance();
		Field f = clazz.getDeclaredField("age");
		f.setAccessible(true);
		int age = (Integer)f.get(p);
		System.out.println(age);
		
		f.set(p, 28);
		age = (Integer)f.get(p);
		System.out.println(age);
	}
	@Test//public static Date time;
	public void test3() throws Exception{
		Class clazz = Person.class;
		Field f = clazz.getField("time");
		f.set(null, new Date());
		System.out.println(Person.time);
	}
}

以上就是自己對Java中反射的一些學習總結,歡迎大家留言一起學習、討論

看完上邊有關反射的東西, 對常用框架裏的配置文件是不是有點思路了

上邊是Spring配置文件裏的常見的bean配置,這看起來是不是可以用反射很輕易的就可以實現:解析xml然後把xml裏的內容作爲參數,利用反射創建對象。

除了這個,常用的框架裏還有很多地方都用到了反射,反射是框架的靈魂,具備反射知識和思想,是看懂框架的基礎

平常用到的框架,除了配置文件的形式,現在很多都使用了註解的形式,其實註解也和反射息息相關,使用反射也能輕而易舉的拿到類、字段、方法上的註解,然後編寫註解解析器對這些註解進行解析,做一些相關的處理,所以說不管是配置文件還是註解的形式,它們都和反射有關

感興趣的話可以看下這篇,這篇裏邊包含了XML解析、反射的東西,模擬了一個Struts2的核心代碼

利用Java反射模擬一個Struts2框架 Struts2主要核心設計 手動實現Struts2核心代碼

跟反射相關的還有內省、自定義註解,最近也抽時間大概整理了一下

Java反射——內省(Introspector)以及BeanUtils內省框架

Java中的註解以及自定義註解

感興趣的小可愛可以瞭解一下~

覺得對你有幫助的話,可以點個關注,點個贊,3Q~

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章