Java Class物件簡介

coding_1994發表於2018-07-28

一. 反射機制概念

  主要是指程式可以訪問,檢測和修改它本身狀態或行為的一種能力,並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。在java中,只要給定類的名字, 那麼就可以通過反射機制來獲得類的所有資訊。

  反射是Java中一種強大的工具,能夠使我們很方便的建立靈活的程式碼,這些程式碼可以再執行時裝配,無需在元件之間進行原始碼連結。但是反射使用不當會成本很高!

  類中有什麼資訊,利用反射機制就能可以獲得什麼資訊,不過前提是得知道類的名字。

二. 反射機制的作用

  1. 在執行時判斷任意一個物件所屬的類;
  2. 在執行時獲取類的物件;
  3. 在執行時訪問java物件的屬性,方法,構造方法等。

三. 反射機制的優點與缺點

首先要搞清楚為什麼要用反射機制?直接建立物件不就可以了嗎,這就涉及到了動態與靜態的概念。 
靜態編譯:在編譯時確定型別,繫結物件,即通過。 
動態編譯:執行時確定型別,繫結物件。動態編譯最大限度發揮了java的靈活性,體現了多型的應用,有以降低類之間的藕合性。 

反射機制的優點:可以實現動態建立物件和編譯,體現出很大的靈活性(特別是在J2EE的開發中它的靈活性就表現的十分明顯)。通過反射機制我們可以獲得類的各種內容,進行了反編譯。對於JAVA這種先編譯再執行的語言來說,反射機制可以使程式碼更加靈活,更加容易實現物件導向。

  比如,一個大型的軟體,不可能一次就把把它設計的很完美,當這個程式編譯後,釋出了,當發現需要更新某些功能時,我們不可能要使用者把以前的解除安裝,再重新安裝新的版本,假如這樣的話,這個軟體肯定是沒有多少人用的。採用靜態的話,需要把整個程式重新編譯一次才可以實現功能的更新,而採用反射機制的話,它就可以不用解除安裝,只需要在執行時才動態的建立和編譯,就可以實現該功能。 

反射機制的缺點:對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它 滿足我們的要求。這類操作總是慢於只直接執行相同的操作。

四. 反射機制的示例

1.通過一個物件獲得完整的包名和類名

新增一句:所有類的物件其實都是Class的例項。

package Reflect;

class Demo{
    //other codes...
}

class hello{
    public static void main(String[] args) {
        Demo demo=new Demo();
        System.out.println(demo.getClass().getName());
    }
}
//【執行結果】:Reflect.Demo

 

2.例項化Class類物件

 

package Reflect;

class Demo{

    //other codes...

}

class hello{

    public static void main(String[] args) {

        Class<?> demo1=null;

        Class<?> demo2=null;

        Class<?> demo3=null;

        try{

            //一般儘量採用這種形式

            demo1=Class.forName("Reflect.Demo");

        }catch(Exception e){

            e.printStackTrace();

        }

        demo2=new Demo().getClass();

        demo3=Demo.class;

        System.out.println("類名稱   "+demo1.getName());

        System.out.println("類名稱   "+demo2.getName());

        System.out.println("類名稱   "+demo3.getName());

    }

}

//【執行結果】:

//類名稱   Reflect.Demo

//類名稱   Reflect.Demo

//類名稱   Reflect.Demo

3.通過Class例項化其他類的物件

 

package Reflect;

class Person{

    private String name;
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString(){
        return "["+this.name+"  "+this.age+"]";
    }
    
}

class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Person per=null;
        try {
            per=(Person)demo.newInstance();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        per.setName("Rollen");
        per.setAge(20);
        System.out.println(per);
    }
}
//【執行結果】:
//[Rollen  20]

但是注意一下,當我們把Person中的預設的無參建構函式取消的時候,比如自己定義只定義一個有引數的建構函式之後,會出現錯誤:

比如定義了一個建構函式:

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

然後繼續執行上面的程式,會出現:

 

java.lang.InstantiationException: Reflect.Person
at java.lang.Class.newInstance0(Class.java:340)
at java.lang.Class.newInstance(Class.java:308)
at Reflect.hello.main(hello.java:39)
Exception in thread "main" java.lang.NullPointerException
at Reflect.hello.main(hello.java:47)

用newInstance與用new是區別的,區別在於建立物件的方式不一樣,前者是使用類載入機制,那麼為什麼會有兩種建立物件方式?這個就要從可伸縮、可擴充套件,可重用等軟體思想上解釋了。Java中工廠模式經常使用newInstance來建立物件,因此從為什麼要使用工廠模式上也可以找到具體答案。
例如:
Class c = Class.forName(“A”);
factory = (AInterface)c.newInstance();
其中AInterface是A的介面,如果下面這樣寫,你可能會理解:
String className = “A”;
Class c = Class.forName(className);
factory = (AInterface)c.newInstance();
進一步,如果下面寫,你可能會理解:
String className = readfromXMlConfig;//從xml 配置檔案中獲得字串
Class c = Class.forName(className);factory = (AInterface)c.newInstance();
上面程式碼就消滅了A類名稱,優點:無論A類怎麼變化,上述程式碼不變,甚至可以更換A的兄弟類B , C , D….等,只要他們繼承Ainterface就可以。
從jvm的角度看,我們使用new的時候,這個要new的類可以沒有載入;
但是使用newInstance時候,就必須保證:1、這個類已經載入;2、這個類已經連線了。而完成上面兩個步驟的正是class的靜態方法forName()方法,這個靜態方法呼叫了啟動類載入器(就是載入javaAPI的那個載入器)。
有了上面jvm上的理解,那麼我們可以這樣說,newInstance實際上是把new這個方式分解為兩步,即,首先呼叫class的載入方法載入某個類,然後例項化。
這樣分步的好處是顯而易見的。我們可以在呼叫class的靜態載入方法forName時獲得更好的靈活性,提供給了我們降耦的手段。

[補充:]
newInstance: 弱型別。低效率。只能呼叫無參構造。
new: 強型別。相對高效。能呼叫任何public構造。
newInstance()是實現IOC、反射、依賴倒置 等技術方法的必然選擇,new 只能實現具體類的例項化,不適合於介面程式設計。類裡面就是通過這個類的預設建構函式構建了一個物件,如果沒有預設建構函式就丟擲InstantiationException, 如果沒有訪問預設建構函式的許可權就丟擲IllegalAccessException

所以大家以後再編寫使用Class例項化其他類的物件的時候,一定要自己定義無參的建構函式。

4.通過Class呼叫其他類中的建構函式 (也可以通過這種方式通過Class建立其他類的物件)

package Reflect;

import java.lang.reflect.Constructor;

class Person{
    public Person() {   

    }
    public Person(String name){
        this.name=name;
    }
    public Person(int age){
        this.age=age;
    }
    public Person(String name, int age) {
        this.age=age;
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString(){
        return "["+this.name+"  "+this.age+"]";
    }
    private String name;
    private int age;
}

class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Person per1=null;
        Person per2=null;
        Person per3=null;
        Person per4=null;
        //取得全部的建構函式
        Constructor<?> cons[]=demo.getConstructors();
        try{
            per1=(Person)cons[0].newInstance();
            per2=(Person)cons[1].newInstance("Rollen");
            per3=(Person)cons[2].newInstance(20);
            per4=(Person)cons[3].newInstance("Rollen",20);
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println(per1);
        System.out.println(per2);
        System.out.println(per3);
        System.out.println(per4);
    }
}
//【執行結果】:
//[null  0]
//[Rollen  0]
//[null  20]
//[Rollen  20]

 

 

5.返回一個類實現的介面

 

 

package Reflect;

interface China{
    public static final String name="Rollen";
    public static  int age=20;
    public void sayChina();
    public void sayHello(String name, int age);
}

class Person implements China{
    public Person() {

    }
    public Person(String sex){
        this.sex=sex;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public void sayChina(){
        System.out.println("hello ,china");
    }
    @Override
    public void sayHello(String name, int age){
        System.out.println(name+"  "+age);
    }
    private String sex;
}

class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        //儲存所有的介面
        Class<?> intes[]=demo.getInterfaces();
        for (int i = 0; i < intes.length; i++) {
            System.out.println("實現的介面   "+intes[i].getName());
        }
    }
}
//【執行結果】:
//實現的介面   Reflect.China

(以下幾個例子,都會用到這個例子的Person類,所以為節省篇幅,此處不再貼上Person的程式碼部分,只貼上主類hello的程式碼)

6.取得其他類中的父類

 

 

class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        //取得父類
        Class<?> temp=demo.getSuperclass();
        System.out.println("繼承的父類為:   "+temp.getName());
    }
}
//【執行結果】
//繼承的父類為:   java.lang.Object

7.獲得其他類中的全部建構函式

//這個例子需要在程式開頭新增import java.lang.reflect.*;
class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Constructor<?>cons[]=demo.getConstructors();
        for (int i = 0; i < cons.length; i++) {
            System.out.println("構造方法:  "+cons[i]);
        }
    }
}
//【執行結果】:
//構造方法:  public Reflect.Person()
//構造方法:  public Reflect.Person(java.lang.String)

 


 

 

class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Constructor<?>cons[]=demo.getConstructors();
        for (int i = 0; i < cons.length; i++) {
            Class<?> p[]=cons[i].getParameterTypes();
            System.out.print("構造方法:  ");
            int mo=cons[i].getModifiers();
            System.out.print(Modifier.toString(mo)+" ");
            System.out.print(cons[i].getName());
            System.out.print("(");
            for(int j=0;j<p.length;++j){
                System.out.print(p[j].getName()+" arg"+i);
                if(j<p.length-1){
                    System.out.print(",");
                }
            }
            System.out.println("){}");
        }
    }
}
//【執行結果】:
//構造方法:  public Reflect.Person(){}
//構造方法:  public Reflect.Person(java.lang.String arg1){}

 

 

8.取得其他類的全部屬性,將這些整理在一起,也就是通過class取得一個類的全部框架

 

 

class hello {
    public static void main(String[] args) {
        Class<?> demo = null;
        try {
            demo = Class.forName("Reflect.Person");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("===============本類屬性========================");
        // 取得本類的全部屬性
        Field[] field = demo.getDeclaredFields();
        for (int i = 0; i < field.length; i++) {
            // 許可權修飾符
            int mo = field[i].getModifiers();
            String priv = Modifier.toString(mo);
            // 屬性型別
            Class<?> type = field[i].getType();
            System.out.println(priv + " " + type.getName() + " "
                    + field[i].getName() + ";");
        }
        System.out.println("===============實現的介面或者父類的屬性========================");
        // 取得實現的介面或者父類的屬性
        Field[] filed1 = demo.getFields();
        for (int j = 0; j < filed1.length; j++) {
            // 許可權修飾符
            int mo = filed1[j].getModifiers();
            String priv = Modifier.toString(mo);
            // 屬性型別
            Class<?> type = filed1[j].getType();
            System.out.println(priv + " " + type.getName() + " "
                    + filed1[j].getName() + ";");
        }
    }
}
//【執行結果】:
//===============本類屬性========================
//private java.lang.String sex;
//===============實現的介面或者父類的屬性========================
//public static final java.lang.String name;
//public static final int age;

9.通過反射呼叫其他類中的方法

 

 

class hello {
    public static void main(String[] args) {
        Class<?> demo = null;
        try {
            demo = Class.forName("Reflect.Person");
        } catch (Exception e) {
            e.printStackTrace();
        }
        try{
            //呼叫Person類中的sayChina方法
            Method method=demo.getMethod("sayChina");
            method.invoke(demo.newInstance());
            //呼叫Person的sayHello方法
            method=demo.getMethod("sayHello", String.class,int.class);
            method.invoke(demo.newInstance(),"Rollen",20);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//【執行結果】:
//hello ,china
//Rollen  20

10.呼叫其他類的set和get方法

 

 

class hello {
    public static void main(String[] args) {
        Class<?> demo = null;
        Object obj=null;
        try {
            demo = Class.forName("Reflect.Person");
        } catch (Exception e) {
            e.printStackTrace();
        }
        try{
         obj=demo.newInstance();
        }catch (Exception e) {
            e.printStackTrace();
        }
        setter(obj,"Sex","男",String.class);
        getter(obj,"Sex");
    }

    /**
     * @param obj   操作的物件
     * @param att   操作的屬性
     * */
    public static void getter(Object obj, String att) {
        try {
            Method method = obj.getClass().getMethod("get" + att);
            System.out.println(method.invoke(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param obj   操作的物件    
     * @param att   操作的屬性
     * @param value 設定的值
     * @param type  引數的屬性
     * */
    public static void setter(Object obj, String att, Object value,
            Class<?> type) {
        try {
            Method method = obj.getClass().getMethod("set" + att, type);
            method.invoke(obj, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}// end class
//【執行結果】:
//男

11.通過反射操作屬性

class hello {
    public static void main(String[] args) throws Exception {
        Class<?> demo = null;
        Object obj = null;

        demo = Class.forName("Reflect.Person");
        obj = demo.newInstance();

        Field field = demo.getDeclaredField("sex");
        field.setAccessible(true);
        field.set(obj, "男");
        System.out.println(field.get(obj));
    }
}// end class

 

 

12.通過反射取得並修改陣列的資訊

 

 

import java.lang.reflect.*;

class hello{
    public static void main(String[] args) {
        int[] temp={1,2,3,4,5};
        Class<?>demo=temp.getClass().getComponentType();
        System.out.println("陣列型別: "+demo.getName());
        System.out.println("陣列長度  "+Array.getLength(temp));
        System.out.println("陣列的第一個元素: "+Array.get(temp, 0));
        Array.set(temp, 0, 100);
        System.out.println("修改之後陣列第一個元素為: "+Array.get(temp, 0));
    }
}
//【執行結果】:
//陣列型別: int
//陣列長度  5
//陣列的第一個元素: 1
//修改之後陣列第一個元素為: 100

13.通過反射修改陣列大小

class hello{
    public static void main(String[] args) {
        int[] temp={1,2,3,4,5,6,7,8,9};
        int[] newTemp=(int[])arrayInc(temp,15);
        print(newTemp);
        System.out.println("=====================");
        String[] atr={"a","b","c"};
        String[] str1=(String[])arrayInc(atr,8);
        print(str1);
    }
    /**
     * 修改陣列大小
     * */
    public static Object arrayInc(Object obj,int len){
        Class<?>arr=obj.getClass().getComponentType();
        Object newArr=Array.newInstance(arr, len);
        int co=Array.getLength(obj);
        System.arraycopy(obj, 0, newArr, 0, co);
        return newArr;
    }
    /**
     * 列印
     * */
    public static void print(Object obj){
        Class<?>c=obj.getClass();
        if(!c.isArray()){
            return;
        }
        System.out.println("陣列長度為: "+Array.getLength(obj));
        for (int i = 0; i < Array.getLength(obj); i++) {
            System.out.print(Array.get(obj, i)+" ");
        }
    }
}
//【執行結果】:
//陣列長度為: 15
//1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 =====================
//陣列長度為: 8
//a b c null null null null null

 

 

14.動態代理

首先來看看如何獲得類載入器:

 

 

class test{

}
class hello{
    public static void main(String[] args) {
        test t=new test();
        System.out.println("類載入器  "+t.getClass().getClassLoader().getClass().getName());
    }
}
//【程式輸出】:
//類載入器  sun.misc.Launcher$AppClassLoader

其實在java中有三種類類載入器。

1)Bootstrap ClassLoader 此載入器採用c++編寫,一般開發中很少見。

2)Extension ClassLoader 用來進行擴充套件類的載入,一般對應的是jre\lib\ext目錄中的類

3)AppClassLoader 載入classpath指定的類,是最常用的載入器。同時也是java中預設的載入器。

如果想要完成動態代理,首先需要定義一個InvocationHandler介面的子類,已完成代理的具體操作。

 

 

package Reflect;

import java.lang.reflect.*;

//定義專案介面
interface Subject {
    public String say(String name, int age);
}

// 定義真實專案
class RealSubject implements Subject {
    @Override
    public String say(String name, int age) {
        return name + "  " + age;
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object obj = null;

    public Object bind(Object obj) {
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
                .getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object temp = method.invoke(this.obj, args);
        return temp;
    }
}

class hello {
    public static void main(String[] args) {
        MyInvocationHandler demo = new MyInvocationHandler();
        Subject sub = (Subject) demo.bind(new RealSubject());
        String info = sub.say("Rollen", 20);
        System.out.println(info);
    }
}
//【執行結果】:
//Rollen  20

類的生命週期

  在一個類編譯完成之後,下一步就需要開始使用類,如果要使用一個類,肯定離不開JVM。在程式執行中JVM通過裝載,連結,初始化這3個步驟完成。

  類的裝載是通過類載入器完成的,載入器將.class檔案的二進位制檔案裝入JVM的方法區,並且在堆區建立描述這個類的java.lang.Class物件。用來封裝資料。 但是同一個類只會被類裝載器裝載以前

連結就是把二進位制資料組裝為可以執行的狀態。

連結分為校驗,準備,解析這3個階段:

校驗一般用來確認此二進位制檔案是否適合當前的JVM(版本),

準備就是為靜態成員分配記憶體空間,。並設定預設值

解析指的是轉換常量池中的程式碼作為直接引用的過程,直到所有的符號引用都可以被執行程式使用(建立完整的對應關係)。

  完成之後,型別也就完成了初始化,初始化之後類的物件就可以正常使用了,直到一個物件不再使用之後,將被垃圾回收。釋放空間。當沒有任何引用指向Class物件時就會被解除安裝,結束類的生命週期。

五. IoC原理

Spring中的IoC的實現原理就是工廠模式加反射機制。

1.我們首先看一下不用反射機制時的工廠模式:

 

/**
 * 工廠模式
 */
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 fruitName){
        fruit f=null;
        if("Apple".equals(fruitName)){
            f=new Apple();
        }
        if("Orange".equals(fruitName)){
            f=new Orange();
        }
        return f;
    }
}

class hello{
    public static void main(String[] a){
        fruit f=Factory.getInstance("Orange");
        f.eat();
    }
}

當我們在新增一個子類的時候,就需要修改工廠類了。如果我們新增太多的子類的時候,改的就會很多。

2. 利用反射機制的工廠模式:

 

package Reflect;

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();
        }
    }
}

  現在就算我們新增任意多個子類的時候,工廠類就不需要修改。

  使用反射機制的工廠模式可以通過反射取得介面的例項,但是需要傳入完整的包和類名。而且使用者也無法知道一個介面有多少個可以使用的子類,所以我們通過屬性檔案的形式配置所需要的子類。

3.使用反射機制並結合屬性檔案的工廠模式(即IoC)

首先建立一個fruit.properties的資原始檔:

1 apple=Reflect.Apple
2 orange=Reflect.Orange

然後編寫主類程式碼:

package Reflect;

import java.io.*;
import java.util.*;

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 init{
    public static Properties getPro() throws FileNotFoundException, IOException{
        Properties pro=new Properties();
        File f=new File("fruit.properties");
        if(f.exists()){
            pro.load(new FileInputStream(f));
        }else{
            pro.setProperty("apple", "Reflect.Apple");
            pro.setProperty("orange", "Reflect.Orange");
            pro.store(new FileOutputStream(f), "FRUIT CLASS");
        }
        return pro;
    }
}

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) throws FileNotFoundException, IOException{
        Properties pro=init.getPro();
        fruit f=Factory.getInstance(pro.getProperty("apple"));
        if(f!=null){
            f.eat();
        }
    }
}
//【執行結果】:Apple


 

相關文章