javaSE反射(高級開發)


在這個專題,我們主要談一談java的高級開發的反射問題,我們的目標有以下幾個
1、認識反射機制
2、反射與類操作
3、反射與簡單java類
4、ClassLoader類加載器
5、反射與代理設計模式
6、反射與Annotation
這些問題,我總共分爲兩篇博客來講,上乾貨吧

認識反射機制

初識反射
我們大多數人聽到反射兩個字都會一頭霧水,到底什麼是反射,反射到底有什麼用,我們應該怎麼用我們的反射,這些問題我相信都困擾着你和我,所以我們就從最忌粗話的地方一點點抽絲剝繭吧
反射指的是對象的反向處理操作,既然是反向處理。我們先來觀察以下“正向”的操作。在默認的情況下,必須要先導入一個包,然後才能實例化對象
我們現在來觀察以下我們的正向處理的代碼:

import java.util.Date;

public class Test {
    public static void main(String[] args) {
        Date date=new Date();
        System.out.println(date);
    }
}

我們都很熟悉上面的操作,我們現在來總結以下我們的操作,以上是我們正常的關於對象的處理流程:根據包名.類名找到類
所謂的“反”指的是根據對象來取得對象的來源信息,而這個“反”的操作核心的處理就在與Object類的一個方法:
取得Class對象:

    public final native Class<?> getClass();

該方法返回的是一個Class類對象,這個Class描述的就是類。
範例:調用getClass()方法

import java.util.Date;

public class Test {
    public static void main(String[] args) {
        Date date=new Date();
        System.out.println(date.getClass());
    }
}

我們來看一下它的運行結果:

class java.util.Date

Process finished with exit code 0

我們發現,我們此時通過對象取得了對象的包,這就是“反”的本質
在反射的世界裏面,看中的不再是一個對象,而是對象後的組成(類,構造、普通、成員等)
1.2Class類對象的三種實例化模式:
Class類是描述整個類的概念,也是整個反射的操作源頭,在使用Class類的時候需要關注的一雖然是這個類的對象,而這個類的對象的產生模式一共有三種:
1、任何類的實例化對象可以通過Object類中的getClass()方法取得Class類對象
2、“類.Class”直接根據某個具體的類來取得Class類的實例化對象。
3、使用Class類提供的方法:“publlic static Class<?> forName(String className)throws ClassNotFoundException”

import java.util.Date;
public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //類加載的時候便產生
        Date date=new Date();
        //根據類,正向產生了對象
        //System.out.println(date);
        //1、通過對象。getClass
        System.out.println(date.getClass());
        //2、通過類名稱.Class
        System.out.println(Date.class);
        //3.通過調用Class類提供的靜態方法forName(String className)
        System.out.println(Class.forName("java.util.Date"));
    }
}

在以上給出的三個方法中我們可以發現,除了第一種方法會產生Date類的實例化對象之外,其他的兩種都不會產生Date類的實例化對象。於是取得了Class類對象有一個最直接的好處:可以通過反射實例化對象,在Class類種定義有如下方法:

public T newInstance()
    throws InstantiationException, IllegalAccessException

範例:反射實例化對象

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls=Class.forName("java.util.Date");
        Object obj=cls.newInstance();
        System.out.println(obj);
    }
}
Fri Mar 29 13:03:34 CST 2019

Process finished with exit code 0

現在發現除了關鍵字new之外,對於對象的實例化模式有了第二種做法,通過反射進行。
取得了Class對象就以爲着取得了一個指定類的操作權。
我們也可以通過上面看到,我們不需要再進行導包,便可以實例化出我們的Date對象

1.3反射與工廠設計模式
工廠設計模式曾經給過原則:如果是自己編寫的接口,想要取得本接口的實例化對象,最好使用工廠類來設計。但是也需要知道傳統工廠設計所帶來的問題。
接下來,我們就進行我麼的傳統的工廠模式與我們的反射工廠模式的對比,從而看出我們的反射對與我們的工廠模式的優化。
傳統工廠類:


interface IFruit{
    void eat();

}
class Apple implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an apple");
    }
}
class Orange implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an Orange");
    }

}
class Factor{
    private Factor(){}
    public static IFruit getInstance(String className){
        if(className.equals("apple")){
            return new Apple();
        }else if(className.equals("orange")){
            return new Orange();
        }
        return null;

    }
}
public class FactorTest {
    public static void main(String[] args) {
        IFruit fruit=Factor.getInstance("Apple");
        fruit.eat();
    }
}


interface IFruit{
    void eat();

}
class Apple implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an apple");
    }
}
class Orange implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an Orange");
    }

}
//用反射修改我們的工廠方法
class Factor{
    private Factor(){}
    public static IFruit getInstance(String className){
        IFruit fruit=null;
        try {
            //取得任意子類的反射對象
            Class<?> cls=Class.forName(className);
            //通過反射取得實例化對象
            fruit=(IFruit) cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fruit;
    }
}
public class FactorTest {
    public static void main(String[] args) {
        IFruit fruit=Factor.getInstance("Apple");
        fruit.eat();
    }
}

通過對比我們可以發現,我們的傳統的工廠模式,並不實用。我們每每增加新的“產品”時,我們的工廠當中就要增加新的分支,這往往會造成我們的代碼冗長,每次修改的時候改動的代碼量增加,我們再用來我們的反射機制之後,我麼的工廠類不需要再進行變動。這樣我們的工廠類纔有了自己的意義。
我們將我們的利用反射機制構建的工廠類稱爲簡單工廠類。

反射與類操作

利用反射可以做出一個對象具備的所有操作行爲;最爲關鍵的是這一切的操作都可以基於Object進行

2.1取得父類信息

在java中任何的程序都一定會有父類,在Class類中就可以通過如下方法來取得父類或者實現的父接口

//我們先來看以下我們需要用到的接口
取得類的包名稱: public Package getPackage()
取得父類的Class對象: public native Class<? super T> getSuperclass()
取得實現的父接口:public Class<?>[] getInterfaces()

範例:取得包名稱和取得父接口

class person{}
interface Massage{}
interface subject{}
class student extends person implements Massage ,subject{}
public class MypackgeTest {
    public static void main(String[] args) {
     Class<?> cls=student.class;
     //取得我們的父類信息
        System.out.println(cls.getSuperclass().getName());
        //取得我們的父接口的信息
        Class<?> []clases=cls.getInterfaces();
        for(Class clss:clases){
            System.out.println(clss.getName());
        }
    }
}

通過反射可以取得類結構上的所有關鍵信息

2.2反射調用構造

一個類中可以存在多個構造方法,如果想要取得類中構造的調用,就可以用Class類中提供的兩個方法:

  • 取得指定參數類型的構造
public Constructor<T> getConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException`
  • 取得類中所有的構造
 public Constructor<?>[] getConstructors() throws SecurityException 

以上兩個方法返回類型都是java.lang.reflect.Constructor類的實例化對象,這個類之中大家只需要關注一個方法:
實例化對象:

  public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException

範例:取得類中所有構造信息

import java.lang.reflect.Constructor;

class Person{
    String name;
    Integer age;
   public Person(){}
    public Person(String name){}
    public Person(String name,Integer age){}
}
public class uniqueConstructor {
    public static void main(String[] args) {
        Class<?>cls=Person.class;
        Constructor<?>[] constructors=cls.getConstructors();
        for(Constructor<?> constructor:constructors){
            System.out.println(constructor);
        }
    }
}

運行結果是:

public Person()
public Person(java.lang.String)
public Person(java.lang.String,java.lang.Integer)

Process finished with exit code 0

所以我們通過上述結果,我們可以看到,以上結果是直接利用了Constructor類中的toString()方法取得了構造方法的完整信息(包含方法權限,參數列表),而如果你指使用了getName()方法,只會返回構造方法的包名.類名
在定義簡單java類的時候一定要保留有一個無參構造
範例:觀察Class實例化對象的問題

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

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 +
                '}';
    }
}
public class uniqueConstructor {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<?> cls=Person.class;

        System.out.println(cls.newInstance());

    }
}

Exception in thread "main" java.lang.InstantiationException: Person
	at java.lang.Class.newInstance(Class.java:427)
	at uniqueConstructor.main(uniqueConstructor.java:24)
Caused by: java.lang.NoSuchMethodException: Person.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.newInstance(Class.java:412)
	... 1 more

Process finished with exit code 1

我們發現我們直接通過我們的Class實例化我們的對象,我們會出先我們的沒有初始化異常,這是因爲我們的Class類通過反射實例化對象的時候,只能夠調用類中的午餐構造。如果現在類中沒有無參構造則無法使用Class調用,只能夠通過明確的構造調用實例化處理。
範例:通過Constructor類實例化對象

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class uniqueConstructor {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class<?>cls=Person.class;
        Constructor<?> constructors
    = cls.getConstructor(String.class,int.class);
        System.out.println(constructors.newInstance("lele",18));

    }
}

我們來看一下我們的運行結果:

Person{name='lele', age=18}

Process finished with exit code 0

我們可以得到的經驗是:以後我們的簡單java類要寫我們的無參構造

2.3反射調用普通方法(核心)

類中普通方法調用在我們的平常開發之中非常常見,如果使用合理可以節省大量的重複編碼工作。在Class類中如下兩種取得類中普通方法的函數

  • 取得全部普通方法
 public Method[] getMethods() throws SecurityException 
  • 取得指定普通方法
  public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException

以上兩個方法的雷響時java.lang.reflect.Method類的對象,在類中提供有一個調用方法的支持:

 public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

範例:取得一個類中的全部普通方法

import java.lang.reflect.Method;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + 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;
    }
}
public class getNewMethod {
    public static void main(String[] args) {
Class<?> cls=SPerson.class;
        Method[]methods=cls.getMethods();
        for(Method method:methods){
            System.out.println(method);
        }
    }
}

我們來觀察以下我們的運行結果:

public java.lang.String SPerson.toString()
public java.lang.String SPerson.getName()
public void SPerson.setName(java.lang.String)
public void SPerson.setAge(int)
public int SPerson.getAge()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

Process finished with exit code 0

我們得到了我們的Person類的所有方法
之前程序編寫的簡單java類中的getter、settter方法採用的都是明確的對象調用。
而現在有了反射機制處理之後,即使你沒有明確的Person類型對象(依然需要實例化對象,Object對象描述,所有的普通方法必須在有實例化對象之後纔可以進行調用。)就可以通過反射調用。
範例:通過反射調用我們的setter、gettter方法

import java.lang.reflect.Method;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + 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;
    }
}
public class getNewMethod {
    public static void main(String[] args) throws Exception{
Class<?> cls=Class.forName("SPerson");
//任何時候調用類中的普通方法都需要實例化對象
        Object object=cls.newInstance();
//取得setName方法的實例化對象,並且設置方法名稱與類型參數
        Method setMethod=cls.getMethod("setName",String.class);
        //隨後需要通過Method類實例化對象,調用指定的方法
        setMethod.invoke(object,"lele");//相當於Person類,setName(lele)
        Method getMethod=cls.getMethod("getName");//相當於Person類,getName
        Object result=getMethod.invoke(object);
        System.out.println(result);
        }
    }

這種操作的好處在於不再侷限於某一類型的對象,而是可以通過Object而類型進行所有類的方法調用。這個操作必須掌握

2.4反射調用類中屬性

在之前已經成功的實現了類的構造調用,方法調用,除了這兩種模式之外還有類中屬性調用。
前提:類中的所有屬性一定在類對象實例化之後纔會進行空間分配,所以此時如果要想調用類的屬性,必須保證其有實例化對象。通過反射的newInstance()可以直接取得實例化對象(Object類型)
在Class類中提供有兩組取得屬性的方法

//1、第一組(父類中)-取得類中全部屬性:
public Field[] getFields() throws SecurityException 
//2、第一組(父類中)-取得類中指定名稱屬性
 public Field getField(String name) throws NoSuchFieldException, SecurityException
 //3、第二組(本類中)-取得類中全部屬性:
  public Field[] getDeclaredFields() throws SecurityException
 //4、第二組(本類中)-取得類中指定名稱屬性:
  public Field getDeclaredField(String name)throws NoSuchFieldException, SecurityException 

範例:取得類中全部屬性

import java.lang.reflect.Field;
/*
* 這是一個我們通過反射獲取類屬性的代碼
* */
class Teacher{
    public int age;
    public String name;
}
class Student extends Teacher{
    private String school;
}
public class FiledsTest {
    public static void main(String[] args) throws Exception {
    Class<?> cls=Class.forName("Student");
        {
            Field[] fields=cls.getFields();
            for(Field field:fields){
                System.out.println(field);
            }
        }
        System.out.println("-----------------");
        {
            Field[] fields=cls.getDeclaredFields();
            for(Field field:fields){
                System.out.println(field);
            }
        }
    }
}

我們的運行結果:

//父類的
public int Teacher.age
public java.lang.String Teacher.name
-----------------
//子類的
private java.lang.String Student.school

在實際開發之中,屬性基本上都會進行封裝處理,所以沒有必要去關注父類中的屬性。也就是說以後取得的屬性都以本類屬性爲主。
而後就需要關注屬性的核心描述類:java.lang.reflect.Field,在這個類之中有兩個重要方法:

//1、設置屬性內容
  public void set(Object obj, Object value)
        throws IllegalArgumentException, IllegalAccessException
//2、取得屬性內容
    public Object get(Object obj)
        throws IllegalArgumentException, IllegalAccessException

範例:通過反射操作屬性

import java.lang.reflect.Field;

/*
* 這是一個我們通過反射設置屬性的代碼
*
* */
class Person1{
    public  String name;
}
public class SetField {
    public static void main(String[] args) throws Exception {
        Class<?> cls=Class.forName("Person1");
        Object object=cls.newInstance();
        //實例化操作我們的類屬性
        Field nameField=cls.getDeclaredField("name");
        nameField.set(object,"lele");
        System.out.println(nameField.get(object));
    }
}

java反射繼承
在AccessibleObject類中提供有一個方法:
動態設置封裝:

public void setAccessible(boolean flag) throws SecurityException 

範例:動態設置封裝:

import java.lang.reflect.Field;

/*
* 這是我們的一個破壞類中屬性封裝的代碼
*/
class Person2{
    private String name;
}
public class rudeAccess {
    public static void main(String[] args) throws Exception {
        Class<?> cls=Class.forName("Person2");
        Object object=cls.newInstance();
        Field field=cls.getDeclaredField("name");
        field.setAccessible(true);
        field.set(object,"lele");
        System.out.println(field.get(object));
    }
}

我們現在來看一下我們的結果:

lele

Process finished with exit code 0

好了,代碼就這些了,我們今天的代碼量還是很大的,所以我們需要的就是將我們的今天學習的東西總結一下:
在這裏插入圖片描述
在這裏插入圖片描述

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