類型信息:反射-Class

在說反射前提一個概念:RTTI(在運行時,識別一個對象的類型)

public class Shapes {
    public static void main(String[] args) {
        List<Shape> shapes = Arrays.asList(new Circle(), new Square(), new Triangle());
        for (Shape shape : shapes) {
            shape.draw();
        }
    }
}
abstract class Shape{
    void draw() {
        System.out.println(this + ".draw()");
    }
    abstract public String toString();
}
class Circle extends Shape{

    @Override
    public String toString() {
        return "circle";
    }
}
class Square extends Shape{

    @Override
    public String toString() {
        return "square";
    }
}
class Triangle extends Shape {

    @Override
    public String toString() {
        return "triangle";
    }
}
在這個例子中,RTTI類型轉換並不徹底:Object被轉型爲Shape,而不是轉型爲其子類。這是因爲目前我們只知道這個list<shape>保存的都是Shape。在編譯時,將交由容器和java泛型系統來強制確保這一點;而在運行時,有類型轉換來確保這一點。接下來就是多態機制了。

但是,假如碰到這麼一個特殊的問題——我們想要找到其中一種形狀圖案將它全部變爲某種顏色,該怎麼辦呢?我們通過以上代碼並沒有辦法去獲得具體的類型。這就需要RTTI了。

要理解RTTI在java中的工作原理,首先必須知道類型信息在運行時是如何表示的——Class對象。Class對象包含了與類有關信息。java是面向對象的語言,我們可以通過創建類來抽象某一對象,也可以通過Class來抽象這些類。每個類都有一個Class對象。就是每當你執行javac編譯的時候,你創建的類就會產生一個Class對象(.class文件)。所有的類都是在對其第一次使用時,動態加載到JVM中的。當程序創建第一個對類的靜態成員的引用時,就會加載這個類。這個也證明了構造器也是類的靜態方法,雖然在構造器之前並沒有使用static關鍵字,但是new操作符創建類的新對象也會被當作類的靜態成員的引用。由此得出,java程序在它開始運行之前並非被完全加載。其各部分是在必需時才加載的。

一旦某個類的Class對象被載入內存,它就被用來創建這個類的所有對象。所以在你更改了這個類的代碼時,必須重新編譯,否則程序還是執行你上一次編譯後的Class對象.

public class SweetShop {
    public static void main(String[] args) {
        System.out.println("inside main");
        new Candy();
        System.out.println("after creating Candy");
        try {
            Class.forName("com.test.Gum");
        } catch (ClassNotFoundException e) {
            System.out.println("Couldn't create Gum");
            e.printStackTrace();
        }
        System.out.println("after creating Gum");
        new Cookie();
        System.out.println("after creating Cookie");
    }
}
class Candy {
    static {
        System.out.println("loading Candy");
    }
}
class Cookie {
    static {
        System.out.println("loading Cookie");
    }
}
public class Gum {
    static {
        System.out.println("loading Gum");
    }
}
Note:Class.forName("必須是classpath中(包名.類名)")。也就是說在使用Class.forName()的時候必須使用類的全名,這個全名包括類所在的包名。否則會拋出ClassNotFoundException。
public class FancyToy extends Toy implements HasBatt, Waterproof,Shoots {
    FancyToy() {
        super(1);
    }
}
class Toy {
    Toy() {}
    Toy(int i) {}
}
interface HasBatt{}
interface Waterproof{}
interface Shoots{}

public class ToyTest {
    static void printInfo(Class cc) {
        System.out.println("Class name: "+cc.getName()+"is interface?["+ cc.isInterface() +"]");
        System.out.println("Simple name: "+cc.getSimpleName());
        System.out.println("Canonical name: "+cc.getCanonicalName());
    }

    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("test0830.FancyToy");
        } catch (ClassNotFoundException e) {
            System.out.println("can't find FancyToy");
        }
        printInfo(c);
        System.out.println("----------------");
        for (Class face : c.getInterfaces()) {
            printInfo(face);
        }
        System.out.println("--------------");
        Class up = c.getSuperclass();
        Object obj = null;
        try {
            obj = up.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        printInfo(obj.getClass());
    }
}
運行後的結果:
Class name: test0830.FancyToyis interface?[false]
Simple name: FancyToy
Canonical name: test0830.FancyToy
----------------
Class name: test0830.HasBattis interface?[true]
Simple name: HasBatt
Canonical name: test0830.HasBatt
Class name: test0830.Waterproofis interface?[true]
Simple name: Waterproof
Canonical name: test0830.Waterproof
Class name: test0830.Shootsis interface?[true]
Simple name: Shoots
Canonical name: test0830.Shoots
--------------
Class name: test0830.Toyis interface?[false]
Simple name: Toy
Canonical name: test0830.Toy

三種獲取Class對象的方法:

public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        String str = new String("abc");
        System.out.println(Class.forName("java.lang.String"));
        System.out.println(str.getClass());
        System.out.println(java.lang.String.class);
    }
}

從Class對象中獲取類名:getName:類的全名(包括包名),getSimpleName:類的名稱(不包括包名)

public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        String str = new String("abc");
        Class aClass = Class.forName("java.lang.String");
        System.out.println("--------獲取類名--------");
        String className = aClass.getName();
        System.out.println("aClass.getName():" + className);
        String classSimpleName = aClass.getSimpleName();
        System.out.println("aClass.getSimpleName():" + classSimpleName);
    }
}
運行結果:
--------獲取類名--------
aClass.getName():java.lang.String
aClass.getSimpleName():String
獲取修飾符:
import java.lang.reflect.Modifier;

public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        String str = new String("abc");
        Class aClass = Class.forName("java.lang.String");
        System.out.println("--------獲取類名--------");
        String className = aClass.getName();
        System.out.println("aClass.getName():" + className);
        String classSimpleName = aClass.getSimpleName();
        System.out.println("aClass.getSimpleName():" + classSimpleName);
        System.out.println("--------獲取修飾符-------");
        int modifiers = aClass.getModifiers();
        //是否是抽象類型
        System.out.println("Modifier.isAbstract(modifiers): " + Modifier.isAbstract(modifiers));
        //是否是final
        System.out.println("Modifier.isFinal(modifiers): " + Modifier.isFinal(modifiers));
        //是否是接口
        System.out.println("Modifier.isInterface(modifiers): " + Modifier.isInterface(modifiers));
        //是否是native
        System.out.println("Modifier.isNative(modifiers): " + Modifier.isNative(modifiers));
        //是否是私有類型
        System.out.println("Modifier.isPrivate(modifiers): " + Modifier.isPrivate(modifiers));
        //是否是保護類型
        System.out.println("Modifier.isProtected(modifiers): " + Modifier.isProtected(modifiers));
        //是否是公有類型
        System.out.println("Modifier.isPublic(modifiers): " + Modifier.isPublic(modifiers));
        //是否是靜態類型
        System.out.println("Modifier.isStatic(modifiers): " + Modifier.isStatic(modifiers));
        //是否是精確浮點類型
        System.out.println("Modifier.isStrict(modifiers): " + Modifier.isStrict(modifiers));
        //是否加有同步鎖
        System.out.println("Modifier.isSynchronized(modifiers): " + Modifier.isSynchronized(modifiers));
        //標記爲transient的變量,在對象存儲時,這些變量狀態不會被持久化。當對象序列化的保存在存儲器上時,不希望有些字段數據被保存,爲了保證安全性,可以把這些字段聲明爲transient
        System.out.println("Modifier.isTransient(modifiers): " + Modifier.isTransient(modifiers));
        //在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。
        System.out.println("Modifier.isVolatile(modifiers): " + Modifier.isVolatile(modifiers));
    }
}

執行結果:

--------獲取類名--------
aClass.getName():java.lang.String
aClass.getSimpleName():String
--------獲取修飾符-------
Modifier.isAbstract(modifiers): false
Modifier.isFinal(modifiers): true
Modifier.isInterface(modifiers): false
Modifier.isNative(modifiers): false
Modifier.isPrivate(modifiers): false
Modifier.isProtected(modifiers): false
Modifier.isPublic(modifiers): true
Modifier.isStatic(modifiers): false
Modifier.isStrict(modifiers): false
Modifier.isSynchronized(modifiers): false
Modifier.isTransient(modifiers): false
Modifier.isVolatile(modifiers): false

Class的其他方法:

public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class aClass = Class.forName("java.util.HashMap");
        System.out.println("--------父類-------");
        System.out.println("getSuperclass(): " + aClass.getSuperclass());
        System.out.println("--------包名-------");
        System.out.println("getPackage(): " + aClass.getPackage());
        System.out.println("------實現的接口----");
        System.out.println("getInterfaces()" + aClass.getInterfaces());
        System.out.println("-------構造器------");
        System.out.println("getConstructors(): " + aClass.getConstructors());
        System.out.println("--------方法-------");
        System.out.println("getMethods(): " + aClass.getMethods());
        System.out.println("--------變量-------");
        System.out.println("getFields(): " + aClass.getFields());
        System.out.println("--------註解-------");
        System.out.println("getAnnotations(): " + aClass.getAnnotations());
    }
}
執行結果:

--------父類-------
getSuperclass(): class java.util.AbstractMap
--------包名-------
getPackage(): package java.util, Java Platform API Specification, version 1.8
------實現的接口----
getInterfaces()[Ljava.lang.Class;@7f31245a
-------構造器------
getConstructors(): [Ljava.lang.reflect.Constructor;@6d6f6e28
--------方法-------
getMethods(): [Ljava.lang.reflect.Method;@12a3a380
--------變量-------
getFields(): [Ljava.lang.reflect.Field;@29453f44
--------註解-------
getAnnotations(): [Ljava.lang.annotation.Annotation;@6e0be858

java提供了一種叫做類字面常量的方法來生成Class對象的引用。類名.class。這樣做不僅更簡單,而且更安全。因爲它在編譯時就會受到檢查(因此不需要置於try{}catch{}語句塊中)並且根除了對.forName()方法的調用,所以更高效。

類字面常量不僅可以應用於普通類,還可以應用到接口、數組以及基本數據類型。另外對於基本數據類型的包裝類,還有一個標準字段TYPE,TYPE是一個引用,指向對應的基本數據類型的Class對象。

boolean.class->Bolean.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”的形式,以保持與普通類的一致性。

注意:當使用“.class”來創建對Class對象的引用時,不會自動的初始化該Class對象。爲了使用類而做的準備工作實際包含3個步驟:

1、加載。這是由類加載器執行的。該步驟將查找字節碼(通常在classpath所指定的路徑中查找,但並非是必需的),並從這些字節碼中創建一個Class對象。

2、鏈接。在鏈接階段將驗證類中的字節碼,爲靜態域分配存儲空間,並且如果必須的話,將解析這個類創建的對其他類的所有引用。

3、初始化。如果該類具有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。

初始化被延遲到了靜態方法(構造器是隱式靜態方法)或者非 常數 靜態域進行首次引用才執行。

public class ClassInitialization {
    public static Random random = new Random(47);

    public static void main(String[] args) throws ClassNotFoundException {
        Class initable = Initable.class;
        System.out.println("after creating Initable ref");
        System.out.println(Initable.staticFinal);
        System.out.println(Initable.staticFinal2);
        System.out.println(Initable2.staticNonFinal);
        Class initable3 = Class.forName("test97.Initable3");
        System.out.println("after creating Initable3 ref");
        System.out.println(Initable3.staticNonsFinal);

    }
}
class Initable{
    static final int staticFinal = 47;
    static final int staticFinal2 = ClassInitialization.random.nextInt(1000);
    static {
        System.out.println("hello Initable");
    }
}

class Initable2{
    static final int staticNonFinal = 147;
    static {
        System.out.println("hello Initable2");
    }
}

class Initable3{
    static final int staticNonsFinal = 74;
    static {
        System.out.println("hello Initable3");
    }
}

執行結果:

after creating Initable ref
47
hello Initable
258
147
hello Initable3
after creating Initable3 ref
74

如上的執行結果可以看出,使用.class來獲取Class類的引用不會引起類的初始化。但是使用Class.forName()會引起類的初始化。

Class類的方法的使用:

package test97;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * Created by Ernest on 2016/9/21.
 */
@SuppressWarnings(value = "unchecked")
@Deprecated
public class ClassTest {
    public ClassTest() {}
    public ClassTest(String name) {
        System.out.println("執行有參數的構造器:"+name);
    }
    public void info() {
        System.out.println("執行無參info()方法");
    }
    public void info(String str) {
        System.out.println("執行有參info()方法: "+str);
    }
    //定義一個測試用的內部類
    class inner{

    }

    public static void main(String[] args) throws Exception {
        Class<ClassTest> clazz = ClassTest.class;
        //獲取該class對象所對應類的全部構造器
        Constructor[] cons = clazz.getDeclaredConstructors();
        System.out.println("classTest的全部構造器如下:");
        for (Constructor con : cons) {
            System.out.println(con);
        }
        System.out.println("------------------");
        //獲取該class對象所對應類的全部public構造器
        Constructor[] consPublic = clazz.getConstructors();
        System.out.println("classTest的全部public構造器如下: ");
        for (Constructor con : consPublic) {
            System.out.println(con);
        }
        System.out.println("-------------------");
        //獲取class對象所對應類的全部public方法
        Method[] methods = clazz.getMethods();
        System.out.println("classTest的全部public方法如下: ");
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("--------------------");
        //獲取class對象所對應類的指定方法
        Method mthd = clazz.getMethod("info", String.class);
        System.out.println(mthd);
        //獲取該class對象所對應類的全部註解
        Annotation[] annos = clazz.getAnnotations();
        System.out.println("classTest的全部註解如下: ");
        for (Annotation anno : annos) {
            System.out.println(anno);
        }
        System.out.println("---------------");
        //獲取該class對象所對應類的指定註解
        Annotation anno = clazz.getAnnotation(SuppressWarnings.class);
        System.out.println(anno);
        //獲取該class對象所對應類的全部內部類
        Class<?>[] inners = clazz.getDeclaredClasses();
        System.out.println("classTest的全部內部類如下: ");
        for (Class<?> inner : inners) {
            System.out.println(inner);
        }
        System.out.println("----------------");
        //是用Class.forName方法加載ClassTest的Inner內部類
        Class inClass = Class.forName("test97.ClassTest$inner");
        System.out.println(inClass);
        System.out.println("inClass對應類的外部類爲: "+inClass.getDeclaringClass());
        System.out.println("ClassTest的包: " + clazz.getPackage());
        System.out.println("ClassTest的父類: " + clazz.getSuperclass());
    }

}









發佈了49 篇原創文章 · 獲贊 37 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章