Java學(xué)習(xí)總結(jié)之反射

反射庫(kù)提供了一個(gè)非常豐富且精心設(shè)計(jì)的工具集站宗,以便能夠動(dòng)態(tài)編寫能夠操縱Java代碼的程序。這項(xiàng)功能被大量應(yīng)用于JavaBeans中,它是Java組件的體系結(jié)構(gòu)麸塞。

1.什么是反射昵慌?

反射是可以讓我們?cè)谶\(yùn)行時(shí)獲取類的方法假夺、屬性、父類斋攀、接口等類的內(nèi)部信息的機(jī)制已卷,在編寫一些通用性較高的代碼或者框架的時(shí)候使用。也就是說(shuō)淳蔼,反射本質(zhì)上是一個(gè)“反著來(lái)”的過(guò)程侧蘸。我們通過(guò)new創(chuàng)建一個(gè)類的實(shí)例時(shí)裁眯,實(shí)際上是由Java虛擬機(jī)根據(jù)這個(gè)類的Class對(duì)象在運(yùn)行時(shí)構(gòu)建出來(lái)的,而反射是通過(guò)一個(gè)類的Class對(duì)象來(lái)獲取它的定義信息讳癌,從而我們可以訪問(wèn)到它的屬性穿稳、方法,知道這個(gè)類的父類晌坤、實(shí)現(xiàn)了哪些接口等信息逢艘。
  要想理解反射的原理,首先要了解什么是類型信息骤菠。Java讓我們識(shí)別對(duì)象和類的信息它改,主要有2種方式:一種是傳統(tǒng)的RTTI,它假定我們?cè)诰幾g時(shí)已經(jīng)知道了所有的類型信息商乎;另一種是反射機(jī)制央拖,它允許我們?cè)谶\(yùn)行時(shí)發(fā)現(xiàn)和使用類的信息。
  程序中一般的對(duì)象的類型都是在編譯期就確定下來(lái)的鹉戚,而Java反射機(jī)制可以動(dòng)態(tài)地創(chuàng)建對(duì)象并調(diào)用其屬性鲜戒,這樣的對(duì)象的類型在編譯期是未知的。所以我們可以通過(guò)反射機(jī)制直接創(chuàng)建對(duì)象抹凳,即使這個(gè)對(duì)象的類型在編譯期是未知的袍啡。
  反射的核心JVM在運(yùn)行時(shí)才動(dòng)態(tài)加載類或調(diào)用方法/訪問(wèn)屬性,它不需要事先(寫代碼的時(shí)候或編譯期)知道運(yùn)行對(duì)象是誰(shuí)却桶。

2.反射的作用

  • 在運(yùn)行時(shí)獲取任意一個(gè)類的父類境输、接口、構(gòu)造器颖系、屬性嗅剖、方法等;
  • 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象嘁扼;
  • 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法(通過(guò)反射甚至可以調(diào)用private方法)

3.Class類

我們知道使用javac能夠?qū)?java文件編譯為.class文件信粮,這個(gè).class文件包含了我們對(duì)類的原始定義信息(父類、接口趁啸、構(gòu)造器强缘、屬性、方法等)不傅。Class 類的實(shí)例表示正在運(yùn)行的 Java 應(yīng)用程序中的類或接口旅掂。在 Java 中,每個(gè) Class 都有一個(gè)相應(yīng)的 Class 對(duì)象访娶,即對(duì)于每一個(gè)類商虐,.class文件在運(yùn)行時(shí)會(huì)被ClassLoader加載到JVM中,當(dāng)一個(gè).class文件被加載后,JVM會(huì)為之生成一個(gè)Class對(duì)象秘车,用于表示這個(gè)類的類型信息典勇,我們?cè)诔绦蛑型ㄟ^(guò)new實(shí)例化的對(duì)象實(shí)際上是在運(yùn)行時(shí)根據(jù)相應(yīng)的Class對(duì)象構(gòu)造出來(lái)的。確切的說(shuō)叮趴,這個(gè)Class對(duì)象實(shí)際上是java.lang.Class類的一個(gè)實(shí)例割笙,從中我們可以得出結(jié)論:萬(wàn)物皆對(duì)象,任何類型(包括基本類型眯亦,引用類型伤溉,void關(guān)鍵字等).class都是java.lang.Class的實(shí)例,簡(jiǎn)言之搔驼,class對(duì)象是Class泛型類的實(shí)例谈火,它代表了一個(gè)類型侈询。由于java.lang.Class類不存在公有構(gòu)造器舌涨,它在每個(gè)類第一次被加載時(shí)由JVM自動(dòng)調(diào)用,因此我們不能直接實(shí)例化這個(gè)類扔字,我們可以通過(guò)以下方法獲取一個(gè)Class對(duì)象囊嘉。
  在下面的講解中,我們將以People類和Student類為例:

public class People {
    private String name;
    private int age;
    
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public void speak() {
        System.out.println(getName() + " " + getAge());
    }
}
public class Student extends People {
    private int grade;
    
    public Student(String name, int age) {
        super(name, age);
    }
    
    public Student(String name, int age, int grade) {
        super(name, age);
        this.grade = grade;
    }
    
    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }
  
    private void learn(String course) {
        System.out.println(name + " learn " + course);
    }
 }

獲取class對(duì)象有以下三種:

  1. 可以通過(guò)類名.class得到相應(yīng)類的Class對(duì)象革为,如:
  Class peopleClass = People.class;
  1. 如果已知類的全限定名稱(包含包名)扭粱,可以通過(guò)Class的forName靜態(tài)方法得到類的Class對(duì)象,如:
Class peopleClass = Class.forName("cn.habitdiary.People");
//假設(shè)People類在cn.habitdiary包中

在使用forName時(shí)必須要保證傳入的字符串是一個(gè)類名或接口名震檩,否則會(huì)拋出一個(gè)ClassNotFoundException,這是一個(gè)必檢異常琢蛤,所以我們?cè)谑褂迷摲椒〞r(shí)必須提供一個(gè)異常處理器,例如:

try{
    String name = "xxx";
    Class c1 = Class.forName(name);
    }
    catch(Exception e){
    e.printStackTrace();
    }

在JDBC開發(fā)中常用此方法加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)

  1. 可以通過(guò)類的實(shí)例對(duì)象.getClass()得到相應(yīng)類的Class對(duì)象,如:
People people = new People("Steven"抛虏, 20);
Class peopleClass = people.getClass();

實(shí)例對(duì)象.getClass().getName() 可以獲取當(dāng)前對(duì)象的類的全限定名稱(包含包名)
實(shí)例對(duì)象.getClass().getCanonicalName()大部分情況和getName()相同博其,但在表示數(shù)組或內(nèi)部類時(shí)有所區(qū)別,比如對(duì)于String數(shù)組迂猴,getName返回的是[Ljava.lang.String之類的表現(xiàn)形式慕淡,而getCanonicalName返回的就是跟我們聲明類似的形式。
實(shí)例對(duì)象.getClass().getSimpleName()只是去掉getCanonicalName()返回結(jié)果前面的包部分
  但在類加載的時(shí)候需要的是getName得到的那樣的名字沸毁,而在根據(jù)類名字創(chuàng)建文件的時(shí)候最好使用getCanonicalName()
具體分析見:源碼解析getCanonicalName(), getName(), getSimpleName()的不同

對(duì)于基本數(shù)據(jù)類型的封裝類峰髓,還可以采用.TYPE來(lái)獲取相對(duì)應(yīng)的基本數(shù)據(jù)類型的 Class 實(shí)例

三種方式的比較:
1.調(diào)用Class.forName()方法,如果類沒(méi)有加載就加載息尺,加載時(shí)執(zhí)行static語(yǔ)句携兵,找不到就拋出異常,也可以理解為手動(dòng)加載類的一種方法搂誉,它會(huì)自動(dòng)初始化Class對(duì)象眉孩。

2.getClass()方法,在已經(jīng)持有該類的對(duì)象時(shí)來(lái)獲取Class引用。其Class對(duì)象已經(jīng)被初始化浪汪。

3..class方式創(chuàng)建Class對(duì)象引用時(shí)巴柿,不會(huì)自動(dòng)初始化Class對(duì)象。主要進(jìn)行下面的步驟:
1)加載死遭,類加載器查找字節(jié)碼(classpath)創(chuàng)建Class對(duì)象广恢;
2)鏈接,為靜態(tài)域分配存儲(chǔ)空間呀潭;
3)初始化钉迷,其被延遲到靜態(tài)方法或非常數(shù)靜態(tài)域首次引用時(shí)。

總結(jié):Java獲得Class對(duì)象的引用的方法中钠署,Class.forName()方法會(huì)自動(dòng)初始化Class對(duì)象糠聪,而.class方法不會(huì),.class的初始化被延遲到靜態(tài)方法或非常數(shù)靜態(tài)域的首次引用谐鼎。

注意:
  1.一個(gè)Class對(duì)象實(shí)際上表現(xiàn)的是一個(gè)類型舰蟆,而這個(gè)類型未必一定是一種類。例如狸棍,int不是類身害,但int.class是一個(gè)Class對(duì)象。
  2.Class類實(shí)際上是一個(gè)泛型類草戈。 Class c = T.class實(shí)際上是Class<T> c = T.class塌鸯。Class c = x.getClass()實(shí)際上是Class<? extends T> c = x.getClass()(T的x的聲明類型,x.getClass()獲得的是x的實(shí)際類型的Class對(duì)象)唐片。但有時(shí)候我們不能提前確定class對(duì)象的類型,如Class c = Class.forName("T")實(shí)際上是Class<?> c = Class.forName("T")丙猬。
  3.虛擬機(jī)為每個(gè)類型管理一個(gè)Class對(duì)象,可以用 == 運(yùn)算符實(shí)現(xiàn)兩個(gè)類對(duì)象比較的操作费韭,這可以用來(lái)判斷兩個(gè)對(duì)象屬不屬于同一個(gè)類茧球。
  4.getClass()方法返回的是對(duì)象實(shí)際類型的class對(duì)象,而不是聲明類型的class對(duì)象揽思。
  5.newInstance()方法可以返回一個(gè)Class對(duì)象對(duì)應(yīng)類的新實(shí)例(返回值類型是Object)袜腥,前提要有無(wú)參的構(gòu)造方法,newInstance()方法是通過(guò)調(diào)用無(wú)參構(gòu)造方法來(lái)創(chuàng)建對(duì)象的钉汗。比如:

String s = "java.util.Random";
Object m = Class.forName(s).newInstance();

如果希望給構(gòu)造器提供參數(shù)羹令,就不能使用這種寫法,必須先獲取指定的Constructor對(duì)象损痰,再調(diào)用Constructor對(duì)象的newInstance()方法來(lái)創(chuàng)建實(shí)例福侈。這種方法可以用指定的構(gòu)造器構(gòu)造類的實(shí)例:

//獲取String所對(duì)應(yīng)的Class對(duì)象
Class<?> c = String.class;
//獲取String類帶一個(gè)String參數(shù)的構(gòu)造器
Constructor constructor = c.getConstructor(String.class);
//根據(jù)構(gòu)造器創(chuàng)建實(shí)例
Object obj = constructor.newInstance("23333");
System.out.println(obj);

類的靜態(tài)加載和動(dòng)態(tài)加載
靜態(tài)加載:在編譯時(shí)就需要加載所有可能用到的類,比如new關(guān)鍵字就是靜態(tài)加載類卢未。
動(dòng)態(tài)加載:在運(yùn)行時(shí)加載類肪凛。
靜態(tài)加載類的缺點(diǎn)是:比如用new創(chuàng)建了多個(gè)類的對(duì)象堰汉,其中某一個(gè)類不存在,則整個(gè)程序無(wú)法通過(guò)編譯伟墙。而如果動(dòng)態(tài)加載類翘鸭,只要不使用不存在的類,其他類還可以正常使用戳葵。

比如

class Office{
    public static void main(String[] args){
        //靜態(tài)加載類
        if("Word".equals(args[0])){
            Word w = new Word();
            w.start();
         }
         if("Excel".equals(args[0]){
            Excel e = new Excel();
            e.start();
         }
 }

上面的程序通過(guò)new關(guān)鍵字創(chuàng)建對(duì)象就乓,是靜態(tài)加載類,所以如果Word類和Excel類中缺少一個(gè)拱烁,另一個(gè)類即使存在也無(wú)法通過(guò)編譯生蚁。

如果通過(guò)反射動(dòng)態(tài)加載類可以解決這個(gè)問(wèn)題,如下:

class OfficeBetter{
    public static void main(String[] args){
        try{
            //動(dòng)態(tài)加載類,在運(yùn)行時(shí)刻加載
            Class c = Class.forName(args[0]);
            /*通過(guò)類類型,創(chuàng)建該類的對(duì)象,此時(shí)需要強(qiáng)制轉(zhuǎn)換為Excel和Word的公有類型戏自,
            所以可以定義OfficeAble接口邦投,讓Excel和Word實(shí)現(xiàn)這個(gè)接口*/
            OfficeAble oa = (OfficeAble)c.newInstance();
            oa.start();
            }
            catch(Exception e){
            e.printStackTrace();
            }
 }

由于是動(dòng)態(tài)加載類,新增其他實(shí)現(xiàn)OfficeAble接口的類不必重新編譯OfficeBetter類擅笔。

4.在運(yùn)行時(shí)分析類的能力

下面簡(jiǎn)要介紹一下反射機(jī)制最重要的內(nèi)容 —— 檢查類的結(jié)構(gòu)志衣。
Java中為了支持反射機(jī)制主要提供了以下的類:
java.lang.Class
java.lang.reflect.Field
java.lang.reflect.Constructor
java.lang.reflect.Method
java.lang.reflect.Modifier

圖解.png

java.lang.Class類的常用API如下:

  • Field[] getFields()
  • Field[] getDeclaredFields()
  • Method[] getMethods()
  • Method[] getDeclaredMethods()
  • Constructor< ? >[] getConstructors()
  • Constructor< ? >[] getDeclaredConstructors()
  • Class< ? > getSupperClass()
  • Class< ? >[] getInterfaces()

提示:getFields、getMethods和getConstructors方法將分別返回類提供的public域剂娄、方法和構(gòu)造器數(shù)組蠢涝,其中包括超類的公有成員;而getDeclaredFields玄呛、getDeclaredMethods和getDeclaredConstructors方法將分別返回類中聲明的全部域阅懦、方法和構(gòu)造器,不論訪問(wèn)權(quán)限徘铝,但不包括超類的成員耳胎。getSupperClass()返回class對(duì)象對(duì)應(yīng)類的超類的class對(duì)象,沒(méi)有顯式繼承的類的超類是Object惕它。getInterfaces返回class對(duì)象對(duì)應(yīng)類的所有接口的class對(duì)象

其中java.lang.reflect包中的三個(gè)類Field怕午、Constructor和Method分別用于描述類的域、構(gòu)造器淹魄、方法郁惜。
這三個(gè)類的常用API如下:

  • String getName() 返回一個(gè)用于描述域名、構(gòu)造器或方法的字符串
  • Class< ? > getDeclaringClass() 返回一個(gè)用于描述類中定義的域甲锡、構(gòu)造器或方法的Class對(duì)象
  • Class< ? >[] getExceptionTypes() (在Constuctor和Method類中)
    返回一個(gè)用于描述方法拋出異常類型的Class對(duì)象數(shù)組
  • int getModifiers() 返回一個(gè)描述域兆蕉、構(gòu)造器或方法的修飾符的整型數(shù)值。使用Modifier的toString靜態(tài)方法可以分析這個(gè)返回值
  • Class< ? >[] getParameterTypes() (在Constructor和Method類中) 返回一個(gè)用于描述參數(shù)類型的Class對(duì)象數(shù)組
  • Class< ? > getReturnType() (在Method類中) 返回一個(gè)用于描述返回類型的Class對(duì)象

java.lang.reflect.Modifier類的常用API如下

  • static String toString(int modifiers)
    返回修飾符對(duì)應(yīng)的字符串描述
  • static boolean isAbstract(int modifiers)
  • static boolean isFinal(int modifiers)
  • static boolean isInterface(int modifiers)
  • static boolean isNative(int modifiers)
  • static boolean isPrivate(int modifiers)
  • static boolean isProtected(int modifiers)
  • static boolean isPublic(int modifiers)
  • static boolean isStatic(int modifiers)
  • static boolean isStrict(int modifiers)
  • static boolean isSynchronized(int modifiers)
  • static boolean isVolatile(int modifiers)

上述方法檢測(cè)修飾符是否是某一特定修飾符
java.lang.reflect.Modifier類一般和Field缤沦、Constructor虎韵、Method類的getModifiers()方法配套使用,用于解析該方法返回的整型數(shù)值的含義

下面是一個(gè)檢測(cè)類內(nèi)部結(jié)構(gòu)的例子

import java.lang.reflect.*;
import java.lang.Class;
import java.util.Scanner;

public class ReflectionTest {

    public static void main(String[] args) {
        String name;
        if(args.length > 0) 
            name = args[0];
        else {
            Scanner in = new Scanner(System.in);
            System.out.println("Enter class name (e.g. java.util.Date)");
            name = in.next();
        }
        
        try {
            Class<?> c1 = Class.forName(name);
            Class<?> superc1 = c1.getSuperclass();
            String modifiers = Modifier.toString(c1.getModifiers());
            if (modifiers.length() > 0) 
                System.out.print(modifiers + " " );
            System.out.print("class " + name);
            if(superc1 != null && superc1 != Object.class)
                System.out.print("  extends "  +  superc1.getSimpleName());
            System.out.print("\n{\n");
            printFields(c1);
            System.out.println();
            printConstructors(c1);
            System.out.println();
            printMethods(c1);
            System.out.println("}");
                
        }
        catch(ClassNotFoundException e)
        {
            e.printStackTrace();
        }
        System.exit(0);
    }
    
    public static void printConstructors(Class<?> c1) {
        Constructor<?>[] constructors = c1.getDeclaredConstructors();
        
        for(Constructor<?> c : constructors) {
            String name = c.getName();
            System.out.print("     ");
            String modifiers = Modifier.toString(c.getModifiers());
            if(modifiers.length() > 0)
                System.out.print(modifiers + " ");
            System.out.print(name + "(");
            Class<?>[] paramTypes = c.getParameterTypes();
            for(int j = 0;j < paramTypes.length;j++) {
                if(j > 0)
                    System.out.print(", ");
                System.out.print(paramTypes[j].getSimpleName());
            }
            System.out.println(");");
            
        }
    }
    
    public static void printMethods(Class<?> c1)
    {
        Method[] methods = c1.getDeclaredMethods();
        
        for(Method m :methods) {
            Class<?> retType = m.getReturnType();
            String name = m.getName();
            
            System.out.print("     ");
            String modifiers = Modifier.toString(m.getModifiers());
            if(modifiers.length() > 0)
                System.out.print(modifiers + " ");
            System.out.print(retType.getSimpleName() + " " + name + "(");
            Class<?>[] paramTypes = m.getParameterTypes();
            for(int j = 0;j < paramTypes.length;j++) {
                if(j > 0)
                    System.out.print(", ");
                System.out.print(paramTypes[j].getSimpleName());
            }
            System.out.println(");");
        }
        
        
        }
    public static void printFields(Class<?>  c1)
    {
        Field[] Fields = c1.getDeclaredFields();
        
        for(Field f : Fields) {
            Class<?> type = f.getType();
            String name = f.getName();
            System.out.print("     ");
            String modifiers = Modifier.toString(f.getModifiers());
            if(modifiers.length() > 0)
                System.out.print(modifiers + "  ");
            System.out.println(type.getSimpleName() + " " + name + ";");
            }
    }

}

補(bǔ)充:instanceof運(yùn)算符缸废、Class的isInstance( )與isAssignableFrom()的區(qū)別

5.在運(yùn)行時(shí)使用反射分析對(duì)象

反射不僅可以查看類的域包蓝、構(gòu)造器驶社、方法等,還可以進(jìn)一步查看某個(gè)對(duì)象指定數(shù)據(jù)域的值测萎。
  查看對(duì)象域的關(guān)鍵方法是Field類中的get方法亡电。如果f是一個(gè)Field類型的對(duì)象(例如,通過(guò)getDeclaredFields得到的對(duì)象)硅瞧,obj是某個(gè)包含f域的類的對(duì)象逊抡,f.get(obj)將返回一個(gè)Object對(duì)象,其值為obj域的當(dāng)前值零酪。比如:

Employee harry = new Empolyee("Harry Hacker",35000,10,1,1989);
Class<Employee> c1 = harry.getClass();
Field f = c1.getDeclaredField("name"); //返回某一個(gè)特定域
f.setAccessible(true); //由于name是私有域冒嫡,必須先設(shè)置為可訪問(wèn)
Object v = f.get(harry);

上述的String可以作為Object返回,但如果某個(gè)域是基本數(shù)據(jù)類型四苇,比如double孝凌,可以使用Field類的getDouble方法返回double類型數(shù)值,也可以使用get方法月腋,反射機(jī)制會(huì)將其自動(dòng)裝箱成Double類型對(duì)象蟀架。f.set(obj,value) 可以把obj對(duì)象的f域設(shè)置為value

下面是一些相關(guān)API
在java.lang.reflect.Field中:

  • Object get(Object obj)
    返回obj對(duì)象中用Field對(duì)象表示的域值
  • xxx getXxx(Object obj)
    返回obj對(duì)象的基本類型的域的值
  • void set(Object obj,Object newValue)
    用一個(gè)新值設(shè)置obj對(duì)象中Field對(duì)象表示的域

在java.lang.Class中:

  • Field getField(String name)
    返回指定名稱的公有域
  • Field getDeclaredField(String name)
  • 返回指定名稱的聲明的域

在java.lang.reflect.AccessibleObject中:

  • void setAccessible(boolean flag)
    為反射對(duì)象設(shè)置可訪問(wèn)標(biāo)志,flag為true表明屏蔽Java語(yǔ)言的訪問(wèn)檢查榆骚,使得對(duì)象的私有屬性也可以被查詢和設(shè)置
  • boolean isAccessible()
    返回反射對(duì)象的可訪問(wèn)標(biāo)志的值
  • static void setAccessible(AccessibleObject[] array,boolean flag)
    批量設(shè)置AccessibleObject(是Field片拍、Constructor、Method的公共超類)數(shù)組的所有元素的可訪問(wèn)標(biāo)志

6.使用反射編寫泛型數(shù)組代碼

java.lang.reflect包中的Array類允許動(dòng)態(tài)地創(chuàng)建數(shù)組妓肢。例如捌省,在Arrays類中有copyOf方法,可以擴(kuò)展已經(jīng)填滿的數(shù)組碉钠。

Employee[] a = new Employee[100];
a = Arrays.copyOf(a,2 * a.length);

我們想要編寫一個(gè)適用于所有數(shù)組類型的copyOf方法纲缓,下面是第一次嘗試:

public static Object[] badCopyOf(Object[] a,int newLength){
    Object[] newArray = new Object[newLength];
    System.arraycopy(a,0,newArray,0,Math.min(a.length,newLength);
    return newArray;
}

上述代碼存在一個(gè)錯(cuò)誤,即使用了new Object[newLength]創(chuàng)建數(shù)組喊废,這樣會(huì)在運(yùn)行時(shí)拋出ClassCastException,將一個(gè)Employee[]臨時(shí)轉(zhuǎn)換成Object[]祝高,再把它轉(zhuǎn)回來(lái)是可以的,但從一開始就是Object[]的數(shù)組永遠(yuǎn)不能轉(zhuǎn)換成Employee[]數(shù)組污筷。
為了解決這個(gè)問(wèn)題工闺,下面提供java.lang.reflect.Array中的API

  • static Object get(Object array,int index)
    返回對(duì)象數(shù)組某個(gè)位置上的元素
  • static xxx getXxx(Object array,int index)
    (xxx是基本數(shù)據(jù)類型)返回基本類型數(shù)組某個(gè)位置上的值
  • static void set(Object array,int index,Object newValue)
    設(shè)置對(duì)象數(shù)組某個(gè)位置上的元素
  • static void setXxx(Object array,int index,xxx newValue)
    (xxx是基本數(shù)據(jù)類型)設(shè)置基本類型數(shù)組某個(gè)位置上的值
  • static int getLength(Object array)
    返回?cái)?shù)組的長(zhǎng)度
  • static Object newInstance(Class componentType,int length)
  • static Object newInstance(Class componentType,int[] length)
    返回一個(gè)具有給定類型、給定維數(shù)的新數(shù)組

下面給出正確的代碼實(shí)現(xiàn):

 public static Object goodCopyOf(Object a,int newLength){
 {
    Class c1 = a.getClass();//獲取a數(shù)組的類對(duì)象
     if(!c1.isArray()) return null;//確認(rèn)是一個(gè)數(shù)組
     Class componentType = c1.getComponentType();
     //獲取數(shù)組類型
     int length = Array.getLength(a);
     Object newArray = Array.newInstance(componentType,newLength);
     System.arraycopy(a,0,newArray,0,Math.min(length,newLength));
     return newArray;   
 }

這個(gè)CopyOf方法可以擴(kuò)展任意類型的數(shù)組瓣蛀,不僅僅是對(duì)象數(shù)組使用示例:

int[] a = {1,2,3,4,5};
a = (int[]) goodCopyOf(a,10);

為了實(shí)現(xiàn)上述操作陆蟆,應(yīng)該將goodCopyOf的參數(shù)聲明為Object類型,而不要聲明為Object[],因?yàn)檎麛?shù)數(shù)組類型可以被轉(zhuǎn)換為Object揪惦,但不能轉(zhuǎn)換成Object[]類型

7.調(diào)用任意方法

通過(guò)反射還可以調(diào)用任意方法遍搞,這是通過(guò)Method類的invoke方法實(shí)現(xiàn)的,方法簽名是:Object invoke(Object obj,Object... args),Object obj表示調(diào)用方法的對(duì)象器腋,Object...args表示方法的參數(shù)列表溪猿。
如果方法是靜態(tài)方法钩杰,將第一個(gè)參數(shù)設(shè)置為null;如果方法是非靜態(tài)無(wú)參方法,第二個(gè)參數(shù)列表可以忽略诊县。
  例如:String n = (String)m1.invoke(harry);(m1表示Employee類的getName方法)讲弄。如果方法m1的返回值是void,則invoke方法返回null依痊,否則返回具體類型避除。如果返回值是基本類型,invoke方法會(huì)返回其包裝器類型,可以利用自動(dòng)開箱將其還原為基本數(shù)據(jù)類型胸嘁。例如:double s = (Double)m2.invoke(harry);(m2表示Employee類的getSalary方法)
getMethods方法和getDeclaredMethods會(huì)返回一個(gè)Method對(duì)象列表瓶摆,如果要得到特定的Method對(duì)象,可以調(diào)用Class類的getMethod方法性宏,其簽名是Method getMethod(String Methodname,Class...parameterTypes)群井。
例如:

Method m1 = Employee.class.getMethod("getName");  
Method m2 = Employee.class.getMethod("raiseSalary",double.class);  

下面給出一個(gè)調(diào)用任意方法打印函數(shù)表的程序(以自定義的square和Math.sqrt方法為例):

import java.lang.reflect.*;
public class MethodTableTest
{
   public static void main(String[] args)
   {
Method square =                     MethodTableTest.class.getMethod("square",double.class);
    Method sqrt = Math.class.getMethod("sqrt",double.class);
    
    printTable(1,10,10,square);
    printTable(1,10,10,sqrt);
    }
    
    public static double square(double x)
    {
    return x * x;
    }
    
    public static void printTable(double from,double to,int n,Method f)
    {
    System.out.println(f);
    double dx = (to - from) / (n - 1);
    
    for(double x = from;x <= to;x += dx)
    {
      try
      {
         double y = (Double)f.invoke(null,x);
         System.out.printf("%10.4f | %10/4f\n",x,y);
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
    }

invoke方法如果提供了錯(cuò)誤的參數(shù),會(huì)拋出一個(gè)異常毫胜,所以要提供一個(gè)異常處理器
建議在有必要的時(shí)候才使用invoke方法书斜,有如下原因:
1.invoke方法的參數(shù)和返回值必須是Object類型,意味著必須進(jìn)行多次類型轉(zhuǎn)換酵使,這樣會(huì)使編譯器錯(cuò)過(guò)檢查代碼的機(jī)會(huì)荐吉,等到測(cè)試階段才發(fā)現(xiàn)錯(cuò)誤,找到并改正會(huì)更加困難
2.通過(guò)反射調(diào)用方法比直接調(diào)用方法要明顯慢一些

特別重申:建議Java開發(fā)者不要使用Method對(duì)象的回調(diào)功能口渔,使用接口進(jìn)行回調(diào)會(huì)使代碼的執(zhí)行速度更快样屠,更易于維護(hù)。

8.通過(guò)反射了解泛型本質(zhì)

來(lái)看下面一段代碼:

ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2); //true
/*反射的操作都是編譯之后的操作搓劫,編譯之后會(huì)發(fā)生類型擦除瞧哟,即ArrayList<String>被擦除為ArrayList混巧,所以c1 == c2結(jié)果為true*/

Java中集合的泛型枪向,是防止錯(cuò)誤輸入的,只在編譯階段有效咧党,編譯之后就會(huì)發(fā)生類型擦除秘蛔,所以繞過(guò)編譯泛型就無(wú)效了

驗(yàn)證:我們可以通過(guò)方法的反射來(lái)操作,繞過(guò)編譯

    try {
    Method m = c2.getMethod("add", Object.class);
    m.invoke(list1, 20);//繞過(guò)編譯操作就繞過(guò)了泛型
    System.out.println(list1.size());
    System.out.println(list1);
    } catch (Exception e) {
          e.printStackTrace();
    }

推薦博客:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末傍衡,一起剝皮案震驚了整個(gè)濱河市深员,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛙埂,老刑警劉巖倦畅,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異绣的,居然都是意外死亡叠赐,警方通過(guò)查閱死者的電腦和手機(jī)欲账,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芭概,“玉大人赛不,你說(shuō)我怎么就攤上這事“罩蓿” “怎么了踢故?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惹苗。 經(jīng)常有香客問(wèn)我殿较,道長(zhǎng),這世上最難降的妖魔是什么桩蓉? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任斜脂,我火速辦了婚禮,結(jié)果婚禮上触机,老公的妹妹穿的比我還像新娘帚戳。我一直安慰自己,他們只是感情好儡首,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布片任。 她就那樣靜靜地躺著,像睡著了一般蔬胯。 火紅的嫁衣襯著肌膚如雪对供。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天氛濒,我揣著相機(jī)與錄音产场,去河邊找鬼。 笑死舞竿,一個(gè)胖子當(dāng)著我的面吹牛京景,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骗奖,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼确徙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了执桌?” 一聲冷哼從身側(cè)響起鄙皇,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仰挣,沒(méi)想到半個(gè)月后伴逸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膘壶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年错蝴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了博烂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漱竖,死狀恐怖禽篱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馍惹,我是刑警寧澤躺率,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站万矾,受9級(jí)特大地震影響悼吱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜良狈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一后添、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薪丁,春花似錦遇西、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至漫玄,卻和暖如春茄蚯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睦优。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工渗常, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汗盘。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓皱碘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親衡未。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尸执,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法缓醋,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法绊诲,異常的語(yǔ)法送粱,線程的語(yǔ)...
    子非魚_t_閱讀 31,596評(píng)論 18 399
  • Java是一種可以撰寫跨平臺(tái)應(yīng)用軟件的面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言。Java 技術(shù)具有卓越的通用性掂之、高效性抗俄、平臺(tái)移植性和...
    Java小辰閱讀 696評(píng)論 1 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理脆丁,服務(wù)發(fā)現(xiàn),斷路器动雹,智...
    卡卡羅2017閱讀 134,626評(píng)論 18 139
  • 我的寶貝呱呱: 呱呱啊槽卫,首先我忍不住想要吐槽一下啊,你娘勤儉節(jié)約這么多個(gè)日夜胰蝠,終于給你買了個(gè)幸運(yùn)鈴歼培,你敢不敢給娘帶...
    胡木桃閱讀 187評(píng)論 0 2
  • 就像那夾縫中的野草 肆無(wú)忌憚的飛揚(yáng)吧 久違的簡(jiǎn)書 久未提筆的自己偶然翻翻手機(jī)便簽 看見下面的這幾句上禮拜爬山...
    瓊筆茗閱讀 403評(píng)論 0 2