Java基礎教程(26)--反射

一.類

  對於每一種類型的對象,Java虛擬機都會實例化一個java.lang.Class類的不可變實例。該實例提供了獲取對象的運行時屬性的方法,包括它的成員和類型信息。Class類還提供了創建新實例的方法。最重要的是,它是所有反射API的入口。下面介紹最常用的涉及到類的反射操作。

1.獲取Class對象

對象.getClass()

  如果要從一個對象獲取它的對應的Class對象,最簡單的方法就是調用getClass()方法,這個方法繼承自Object類,因此每個對象都可以調用這個方法:

import java.util.HashSet;
import java.util.Set;
import static reflect.ReflectDemo.E.A;

public class ReflectDemo {
    static enum E {A, B}
    public static void main(String[] args) {
        Class c1 = "foo".getClass();
        Class c2 = A.getClass();
        byte[] bytes = new byte[1024];
        Class c3 = bytes.getClass();
        Set<String> s = new HashSet<>();
        Class c4 = s.getClass();
    }
}

類名.class

  如果類型沒有可用實例,可以通過“類名.class”來獲取Class對象:

import java.util.Set;
import static reflect.ReflectDemo.E.A;

public class ReflectDemo {
    static enum E {A, B}
    public static void main(String[] args) {
        Class c1 = String.class;
        Class c2 = E.class;
        Class c3 = byte[].class;
        Class c4 = Set.class;
    }
}

  需要注意的是,當對基本數據類型的變量調用getClass()方法時,會產生編譯錯誤,但是可以對基本數據類型使用“.class”,例如:

boolean b = true;
Class c1 = b.getClass();             //編譯錯誤

Class c2 = boolean.class;

Class.forName()

  如果類的完全限定類名可用,則可以使用Class類的靜態方法forName來獲取Class實例,例如:

Class c = Class.forName("com.maconn.ReflectDemo");

  如果找不到完全限定類名對應的類型,則該方法會拋出一個ClassNotFound異常。此外,這種方式不能用於基本數據類型,下面的程序將會拋出異常:

try {
    Class c = Class.forName("int");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

基本類型包裝類的TYPE域

  每個基本數據類型以及void都有一個包裝類,這些類都包含一個靜態域TYPE,該域的值就是被包裝的基本類型。例如:

Class c = Integer.TYPE;

2.獲取類的信息

  一個類可能含有以下修飾元素:

  • public、protected或private:控制類的訪問權限
  • abstract:限制類必須要被繼承
  • static:僅用於內部類。創建該類對象時,無需先創建外部類的實例。
  • final:限制類不能被繼承
  • strictfp:限制該類內部所有浮點運算嚴格按照IEEE-754規範執行。
  • 註解:爲類提供一些額外的信息或標記。

注:strictfp 的意思是FP-strict,也就是說精確浮點的意思。在Java虛擬機進行浮點運算時,如果沒有指定strictfp關鍵字時,Java的編譯器以及運 行環境在對浮點運算的表達式是採取一種近似於我行我素的行爲來完成這些操作,以致於得到的結果往往無法令你滿意。而一旦使用了strictfp來聲明一個 類、接口或者方法時,那麼所聲明的範圍內Java的編譯器以及運行環境會完全依照浮點規範IEEE-754來執行。因此如果你想讓你的浮點運算更加精確, 而且不會因爲不同的硬件平臺所執行的結果不一致的話,那就請用關鍵字strictfp。(節選自《Java語言中關鍵字strictfp的用途》一文,原文鏈接https://blog.csdn.net/redv/article/details/326444)。

  下面通過一個程序來演示獲取類信息的一些方法,該程序打印出了String類的各項信息:

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;

import static java.lang.System.out;

public class ClassDeclarationSpy {
    public static void main(String[] args) {
        try {
            Class c = String.class;
            out.format("Class:%n  %s%n%n", c.getCanonicalName());
            out.format("Modifiers:%n  %s%n%n",
                    Modifier.toString(c.getModifiers()));

            out.format("Type Parameters:%n");
            TypeVariable[] tv = c.getTypeParameters();
            if (tv.length != 0) {
                out.format("  ");
                for (TypeVariable t : tv)
                    out.format("%s ", t.getName());
                out.format("%n%n");
            } else {
                out.format("  -- No Type Parameters --%n%n");
            }

            out.format("Implemented Interfaces:%n");
            Type[] intfs = c.getGenericInterfaces();
            if (intfs.length != 0) {
                for (Type intf : intfs)
                    out.format("  %s%n", intf.toString());
                out.format("%n");
            } else {
                out.format("  -- No Implemented Interfaces --%n%n");
            }

            out.format("Inheritance Path:%n");
            List<Class> l = new ArrayList<Class>();
            printAncestor(c, l);
            if (l.size() != 0) {
                for (Class<?> cl : l)
                    out.format("  %s%n", cl.getCanonicalName());
                out.format("%n");
            } else {
                out.format("  -- No Super Classes --%n%n");
            }

            out.format("Annotations:%n");
            Annotation[] ann = c.getAnnotations();
            if (ann.length != 0) {
                for (Annotation a : ann)
                    out.format("  %s%n", a.toString());
                out.format("%n");
            } else {
                out.format("  -- No Annotations --%n%n");
            }
        } catch (ClassNotFoundException x) {
            x.printStackTrace();
        }
    }

    private static void printAncestor(Class<?> c, List<Class> l) {
        Class<?> ancestor = c.getSuperclass();
        if (ancestor != null) {
            l.add(ancestor);
            printAncestor(ancestor, l);
        }
    }
}

  下面是該程序的輸出:

Class:
  java.lang.String

Modifiers:
  public final

Type Parameters:
  -- No Type Parameters --

Implemented Interfaces:
  interface java.io.Serializable
  java.lang.Comparable<java.lang.String>
  interface java.lang.CharSequence
  interface java.lang.constant.Constable
  interface java.lang.constant.ConstantDesc

Inheritance Path:
  java.lang.Object

Annotations:
  -- No Annotations --

  需要注意的是,並非所有的註解都可以通過反射獲得。只有那些使用了元註解@Retention{RetentionPolicy.RUNTIME}的註解纔會保留到運行時。

3.發現成員

  可以通過獲得的class對象來進一步獲取類的成員,例如成員變量、成員方法和構造器。下面依次介紹獲取這些成員的方法。

獲取域

方法 描述 繼承的成員 私有的成員
getDeclaredField​(String name) 根據名稱獲取該類聲明的成員變量 不包含 包含
getDeclaredFields() 獲取該類聲明的所有成員變量 不包含 包含
getField(String name) 根據名稱獲取成員變量 包含 不包含
getFields() 獲取所有的成員變量 包含 不包含

獲取方法

方法 描述 繼承的成員 私有的成員
getDeclaredMethod​(String name, Class<?>... parameterTypes) 根據名稱和參數類型獲取該類聲明的方法 不包含 包含
getDeclaredMethods() 獲取該類聲明的所有方法 不包含 包含
getMethod​(String name, Class<?>... parameterTypes) 根據名稱和參數類型獲取成員方法 包含 不包含
getMethod​s() 獲取所有的成員方法 包含 不包含

獲取構造器

方法 描述 私有的成員
getDeclaredConstructor​(Class<?>... parameterTypes) 根據參數類型獲取指定的構造器 包含
getDeclaredConstructors() 獲取所有的構造器 包含
getConstructor​(Class<?>... parameterTypes) 根據參數類型獲取指定的構造器 不包含
getConstructors() 獲取所有的構造器 不包含

二.成員

  反射API中定義了一個表示成員的接口Member,Field、Method和Constructor類都實現了這個接口。在上一節中已經對獲取這三種成員的方法進行了簡單的瞭解,本節將會對三種成員的方法進行介紹。

1.域

  在反射API中,使用Field類來表示對象的域。上文中已經介紹瞭如何通過Class對象來獲取Field對象,現在我們來討論有關Field類的內容。

獲取域的類型

  通過Field類的getType和getGenericType可以獲取當前域的類型。不同的是,getType方法返回的是Class對象,而getGenericType返回的則是Type類型的對象。此外,如果某個域是泛型類型的,getType方法不能準確地返回該域的類型,而getGenericType則可以返回該域的泛型類型。

Type是java.lang.reflect包中的一個接口,該接口表示所有類型的高級公共接口,Class類就是Type接口的一個實現類。Type包括原始類型、參數化類型、數組類型、類型變量和基本類型。

  下面的例子依次打印出了對每個域調用getType和getGenericType的結果:

import java.lang.reflect.Field;
import java.util.List;

public class FieldSpy<T> {
    public String name  = "Alice";
    public List<Integer> list;
    public T val;

    public static void main(String... args) {
        try {
            Class<?> c = FieldSpy.class;
            Field f1 = c.getField("name");
            System.out.println("Field name:");
            System.out.format("Type: %s%n", f1.getType());
            System.out.format("GenericType: %s%n", f1.getGenericType());
            Field f2 = c.getField("list");
            System.out.println("Field list:");
            System.out.format("Type: %s%n", f2.getType());
            System.out.format("GenericType: %s%n", f2.getGenericType());
            Field f3 = c.getField("val");
            System.out.println("Field val:");
            System.out.format("Type: %s%n", f3.getType());
            System.out.format("GenericType: %s%n", f3.getGenericType());
        } catch (NoSuchFieldException x) {
            x.printStackTrace();
        }
    }
}

  上面的程序會產生以下輸出:

Field name:
Type: class java.lang.String
GenericType: class java.lang.String
Field list:
Type: interface java.util.List
GenericType: java.util.List<java.lang.Integer>
Field val:
Type: class java.lang.Object
GenericType: T

  val域的類型是Object,這是因爲編譯器會在編譯期間擦除有關泛型類型的所有信息。在這種情況下,將會使用類型變量的上界代替,在這個例子中,就是Object。

獲取值

  可以獲取當前Field所表示的域在指定對象的上的值。若該域是引用類型,則使用get(Object obj)方法,該方法會返回一個Object對象;若該域是基本數據類型,則可以使用getXxx(Object obj)方法,Xxx爲Boolean、Byte、Char、Double、Float、Int、Long、Short中的一種。
  下面的程序分別獲取了foo和bar域的值:

import java.lang.reflect.Field;

public class FieldDemo {
    private String foo;
    private int bar;

    public FieldDemo(String stringField, int intField) {
        this.foo = stringField;
        this.bar = intField;
    }

    public static void main(String[] args) {
        Class<?> c = FieldDemo.class;
        FieldDemo fieldDemo = new FieldDemo("foo", 1);
        try {
            Field stringField = c.getDeclaredField("foo");
            System.out.println(stringField.get(fieldDemo));
            Field intField = c.getDeclaredField("bar");
            System.out.println(intField.getInt(fieldDemo));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

  也可以爲指定對象的域設置值。若該域是引用類型,則使用set(Object obj, Object value)方法;若該域是基本數據類型,則可以使用getXxx(Object obj, Xxx value)方法,Xxx爲Boolean、Byte、Char、Double、Float、Int、Long、Short中的一種。例如,可以通過下面的代碼爲上面的foo域和bar域設置值:

Field stringField = c.getDeclaredField("foo");
stringField.set(c, "fool");
Field intField = c.getDeclaredField("bar");
intField.set(c, 2);

獲取其他信息

  • <T extends Annotation> T getAnnotation​(Class<T> annotationClass)
    返回該域上指定類型的註解。
  • <T extends Annotation> T[] getAnnotationsByType​(Class<T> annotationClass)
    返回指定類型的註解,用數組返回。該方法可用於獲取重複註解。
  • Class<?> getDeclaringClass()
    返回聲明該域的類。
  • int getModifiers()
    用一個整數返回該域的修飾符。該整數需要使用Modifier類來解碼。
  • String getName()
    返回該域的名稱。
  • boolean isSynthetic()
    若該域是編譯器自動生成的則返回true,否則返回false。

2.方法

  在反射API中,使用Method類來表示方法。下面是對Method類中一些常見方法的介紹。

獲取方法的信息

  • <T extends Annotation> T getAnnotation​(Class<T> annotationClass)
    返回作用於該方法的指定類型的註解。
  • Annotation[] getDeclaredAnnotations()
    返回直接出現在該方法上的註解。
  • Class<?> getDeclaringClass()
    返回聲明該方法的類。
  • Type[] getGenericExceptionTypes()
    返回該方法拋出的異常
  • Type[] getGenericParameterTypes()
    返回該方法的參數類型。
  • Class<?> getReturnType()
    返回該方法的返回值類型。
  • Type getGenericReturnType()
    返回該方法的返回值類型。不能準確地返回使用了泛型的返回值類型。
  • String getName()
    返回方法名稱。

調用方法

  通過Method類的invoke方法可以對指定對象調用方法。下面是invoke方法的簽名:
    Object invoke​(Object obj, Object... args)

  第一個參數是要調用方法的對象,後面的可變參數則是該方法需要接收的參數。下面的程序獲取了一個方法並調用了它:

import java.lang.reflect.Method;

public class MethodInvokeDemo {
    public static void main(String[] args) {
        try {
            Method method = Foo.class.getMethod("bar", int.class, int.class);
            Foo foo = new Foo();
            method.invoke(foo, 1, 2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Foo {
    public void bar(int a, int b) {
        System.out.println(a + b);
    }
}

3.構造器

  在反射API中,使用Constructor類來表示構造器。因爲Constructor類中的大部分方法都和Method類相似,此處不再贅述。這裏主要講述如何通過Constructor類來構造類的實例。
  Constructor類的newInstance​(Object... initargs)方法可以用於創建新的實例。實際上,Class類也有一個newInstance方法,但是推薦使用Constructor類的newInstance方法來創建實例,而後者已經被標記爲Deprecated,不建議使用,這是因爲:

  • Class.newInstance()只能調用類的無參構造器,而Constructor.newInstance​(Object... initargs)可以調用類的任何構造器。
  • Class.newInstance()要求構造函數必須可見,而在某些情況下Constructor.newInstance()可以調用private構造函數。

  下面的程序通過Constructor類的newInstance創建了Person類的一個實例:

import java.lang.reflect.Constructor;

public class NewInstanceDemo {
    public static void main(String[] args) {
        Class<Person> c = Person.class;
        try {
            Constructor<Person> constructor = c.getConstructor(String.class, int.class);
            Person p = constructor.newInstance("foo", 26);
            System.out.println(p);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

  對於Field、Method和Constructor所表示的成員來說,如果當前所在的類沒有對這些成員的訪問權限,那麼也無法通過反射去訪問這些成員。這種情況下,需要手動調用這些成員的setAccessible(boolean flag)方法將flag設置爲true才能繼續訪問。

三.數組和枚舉

1.數組

  數組是對一組固定數量的、相同類型的元素的引用,這些元素的類型可以是基本數據類型,也可以是引用類型。反射API中提供了訪問數組類型,創建新數組,獲取和設置元素的值的方法。

識別數組

  如果我們對數組引用調用.getClass()方法,會返回什麼?下面來做一個實驗:

public class ArrayDemo {
    public static void main(String[] args) {
        byte[] b = {1, 2, 3};
        Integer[] i = {4, 5, 6};
        String[] s = {"Hello", "World"};
        System.out.println(b.getClass());
        System.out.println(i.getClass());
        System.out.println(s.getClass());
    }
}

  上面的程序將會輸出:

class [B
class [Ljava.lang.Integer;
class [Ljava.lang.String;

  與一般的類不同的是,數組的類型前面都會加上左方括號([)。如果是二維數組,則會有兩個左方括號,依次類推。如果是基本數據類型的數組,方括號右邊只有一個大寫字母,這個字母就是對應的基本類型的首字母。如果是引用類型,方括號右邊是一個大寫字母L然後加上數組元素的完全限定類名,以及最後的分號。
  雖然可以根據getClass()返回的結果來判斷一個對象是不是數組,但是這樣並不直觀。Class類提供了一個isArray()方法,通過這個方法可以直接判斷某個對象是不是數組類型。

創建數組

  反射API中使用Array類來表示數組。通過這個類的newInstance​(Class<?> componentType, int length)方法可以快速創建一個數組:

public static Object createArray(Class<?> c, int length) {
    return Array.newInstance(c, length);
}

  上面的方法根據指定的類型和長度創建了一個數組並返回了對數組的引用,調用者需要將其強制轉換爲真正類型的數組來使用,例如Integer[]。

獲取和設置值

  Array類提供了許多get和set方法。其中get(Object array, int index)用來從引用類型的數組中獲取值,而getXxx(Object array, int index)則用於從基本數據類型的數組中獲取值;set(Object array, int index, Object value)用來給引用類型的數組賦值,而setXxx(Object array, int index, Object value)則用於給基本數據類型的數組賦值。下面是一個例子:

public class ArrayAssignmentDemo {
    public static void main(String[] args) {
        int[] i = new int[2];
        String[] s = new String[2];
        Array.setInt(i, 0, 1);
        Array.set(s, 0, "Hello");
        System.out.println(Array.getInt(i, 0));
        System.out.println(Array.get(s, 0));
    }
}

2.枚舉

  反射API中提供了以下幾個枚舉專用的方法:

  • Class.isEnum()
    若該類是枚舉類型,則返回true。
  • Class.getEnumConstants()
    按照聲明的順序返回枚舉類型中的所有枚舉常量。
  • java.lang.reflect.Field.isEnumConstant()
    若該域是枚舉類型,則返回true。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章