1. 程式人生 > 其它 >每天一道LeetCode Day10:找不同

每天一道LeetCode Day10:找不同

技術標籤:# 基礎

目錄

一、概念:

二、反射的作用

三、JAVA 反射API

1、Class類:反射的核心類,可以獲取類的屬性,方法等資訊。

2、Field類:Java.lang.reflec包中的類,表示類的成員變數,可以用來獲取和設定類之中的屬性值。

3、Method類:Java.lang.reflec包中的類,表示類的方法,它可以用來獲取類中的方法資訊或者執行方法。

4、Constructor類:Java.lang.reflec包中的類,表示類的構造方法。

5、獲取繼承關係

四、反射應用(動態代理)

五、Optional類(輕量級代理)


一、概念:

“反射”機制:允許我們在執行時發現和使用類的資訊。指在執行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法;並且對於任意一個物件,都能夠呼叫它的任意一個方法;這種動態獲取資訊以及動態呼叫物件方法的功能成為Java語言的反射機制。

從反射角度說JAVA屬於半動態語言,動態語言,是指程式在執行時可以改變其結構:新的函式可以引進,已有的函式可以被刪除等結構上的變化。比如常見的JavaScript就是動態語言,除此之外Ruby,Python等也屬於動態語言,而C、C++則不屬於動態語言。

二、反射的作用

  • 反射是為了解決在執行期,對某個例項一無所知的情況下,如何呼叫其方法。
  • 編譯時型別和執行時型別,在Java程式中許多物件在執行是都會出現兩種型別:編譯時型別和執行時型別。編譯時的型別由宣告物件時使用的型別來決定,執行時的型別由實際賦值給物件的型別決定。如:
Fruit fruit = new Apple();

其中編譯時型別為Fruit,執行時型別為Apple;在編譯時fruit無法獲取apple具體方法(非繼承)。

  • 程式在執行時還可能接收到外部傳入的物件,該物件的編譯時型別為Object,但是程式有需要呼叫該物件的執行時型別的方法。為了解決這些問題,程式需要在執行時發現物件和類的真實資訊。然而,如果編譯時根本無法預知該物件和類屬於哪些類,程式只能依

靠執行時資訊來發現該物件和類的真實資訊,此時就必須使用到反射了。

三、JAVA 反射API

1、Class類:反射的核心類,可以獲取類的屬性,方法等資訊。

  • Java的型別全部都是class(除了基本型別外),所以class(包括interface)的本質是資料型別(Type)。無繼承關係的資料型別無法賦值:而class是由JVM在執行過程中動態載入的。
  • JVM在第一次讀取到一種class型別時,將其載入進記憶體,每載入一種class,JVM就為其建立一個Class型別的例項,並關聯起來。

注意:這裡的Class型別是一個名叫Class的class。它長這樣:

public final class Class {
  private Class() {}
}

以String類為例,當JVM載入String類時,它首先讀取String.class檔案到記憶體,然後,為String類建立一個Class例項並關聯起來:
Class cls = new Class(String);


這個Class例項是JVM內部建立的,如果我們檢視JDK原始碼,可以發現Class類的構造方法是private,只有JVM能建立Class例項,我們自己的Java程式是無法建立Class例項的。所以,JVM持有的每個Class例項都指向一個數據型別(class或interface):由於 JVM為每個載入的class建立了對應的Class例項,並在例項中儲存了該class的所有資訊,包括類名、包名、父類、實現的介面、所有方法、欄位等,因此,如果獲取了某個Class例項,我們就可以通過這個Class例項獲取到該例項對應的class的所有資訊。

  • 獲取Class物件的3種方法

1、呼叫某個物件的getClass()方法

Person p=new Person();

Class clazz=p.getClass();

2、呼叫某個類的class屬性來獲取該類對應的Class物件

Class clazz=Person.class;

3、使用Class類中的forName()靜態方法(最安全/效能最好)

Class clazz=Class.forName("類的全路徑"); (最常用)

當我們獲得了想要操作的類的Class物件後,可以通過Class類中的方法獲取並檢視該類中的方法和屬性。

  • 動態載入應用
// Commons Logging優先使用Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
    factory = createLog4j();
} else {
    factory = createJdkLog();
}

boolean isClassPresent(String name) {
    try {
        Class.forName(name);
        return true;
    } catch (Exception e) {
        return false;
    }
}

這就是為什麼我們只需要把Log4j的jar包放到classpath中,Commons Logging就會自動使用Log4j的原因。

2、Field類:Java.lang.reflec包中的類,表示類的成員變數,可以用來獲取和設定類之中的屬性值。

  • 獲取屬性API
  1. Field getField(name):根據欄位名獲取某個public的field(包括父類)
  2. Field getDeclaredField(name):根據欄位名獲取當前類的某個field(不包括父類)
  3. Field[] getFields():獲取所有public的field(包括父類)
  4. Field[] getDeclaredFields():獲取當前類的所有field(不包括父類)
  • 一個Field物件包含了一個欄位的所有資訊:
  1. getName():返回欄位名稱,例如,"name"
  2. getType():返回欄位型別,也是一個Class例項,例如,String.class
  3. getModifiers():返回欄位的修飾符,它是一個int,不同的bit表示不同的含義。
  • Field.setAccessible(true)的意思是,別管這個欄位是不是public,一律允許訪問
public class Fruit {
    private String name;

    public Fruit() {
    }

    public Fruit(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

class Apple extends Fruit {
    public int color;
    private int shape ;
}


public class FruitTest {
    @Test
    public void test() throws NoSuchFieldException, IllegalAccessException {
        Class appleClass = Apple.class;
        // 獲取public欄位"color":
        System.out.println(appleClass.getField("color"));
        // 獲取繼承的public欄位"name":
//        System.out.println(appleClass.getField("name"));
        // 獲取private欄位"shape":
        System.out.println(appleClass.getDeclaredField("shape"));

        // 獲取所有public的field(包括父類)
        List<Field> fields = Arrays.asList(appleClass.getFields());
        for (Field field: fields) {
            System.out.println(field);
        }

        // 獲取當前類的所有field(不包括父類)
        List<Field> declaredfields = Arrays.asList(appleClass.getDeclaredFields());
        for (Field field: declaredfields) {
            System.out.println(field);
        }

        // 獲取public欄位"color":
        Field field = appleClass.getField("color");
        // 返回欄位名稱
        System.out.println(field.getName());
        // 返回欄位型別
        System.out.println(field.getType());
        // 返回欄位的修飾符
        System.out.println(field.getModifiers());
        int m = field.getModifiers();
        Modifier.isFinal(m); // false
        Modifier.isPublic(m); // true
        Modifier.isProtected(m); // false
        Modifier.isPrivate(m); // true
        Modifier.isStatic(m); // false

        // 獲取欄位值
        Object obj = new Fruit("ping guo");
        Class objClass = obj.getClass();
        Field name = objClass.getDeclaredField("name");
        name.setAccessible(true);
        Object value = name.get(obj);
        System.out.println(value); // "ping guo"

        // 設定欄位值
        Fruit fruit = new Fruit("ping guo");
        System.out.println(fruit.getName());
        Class c = fruit.getClass();
        Field f = c.getDeclaredField("name");
        f.setAccessible(true);
        f.set(fruit, "Xiao jiao");
        System.out.println(fruit.getName());


    }

}

3、Method類:Java.lang.reflec包中的類,表示類的方法,它可以用來獲取類中的方法資訊或者執行方法。

  • 獲取Method API;
  1. getName():返回方法名稱,例如:"getScore";
  2. getReturnType():返回方法返回值型別,也是一個Class例項,例如:String.class;
  3. getParameterTypes():返回方法的引數型別,是一個Class陣列,例如:{String.class, int.class};
  4. getModifiers():返回方法的修飾符,它是一個int,不同的bit表示不同的含義。
  • 獲取Method方法資訊API
  1. getName():返回方法名稱,例如:"getScore";
  2. getReturnType():返回方法返回值型別,也是一個Class例項,例如:String.class;
  3. getParameterTypes():返回方法的引數型別,是一個Class陣列,例如:{String.class, int.class};
  4. getModifiers():返回方法的修飾符,它是一個int,不同的bit表示不同的含義。
  • 呼叫方法,:Object invoke(Object instance, Object... parameters);
  • 呼叫靜態方法,由於無需指定例項物件,所以invoke方法傳入的第一個引數永遠為null
  • 呼叫非public方法,通過設定setAccessible(true)來訪問非public方法;
  • 使用反射呼叫方法時,仍然遵循多型原則

public class Person {
    public String getName() {
        return "xiaom";
    }
}

class Student extends Person {
    public int getScore(String type) {
        return 100;
    }
    private int getGrade(int year) {
        return 6;
    }
}

public class PersonTest {
    @Test
    public void test() throws Exception {
        Class stdClass = Student.class;
        // 獲取public方法getScore,引數為String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 獲取繼承的public方法getName,無引數:
        System.out.println(stdClass.getMethod("getName"));
        // 獲取private方法getGrade,引數為int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));


        /**
         * 呼叫方法
         */
        // String物件:
        String s = "Hello world";
        // 獲取String substring(int)方法,引數為int:
        Method m = String.class.getMethod("substring", int.class);
        // 在s物件上呼叫該方法並獲取結果:
        String r = (String) m.invoke(s, 6);
        // 列印呼叫結果:
        System.out.println(r);


        /**
         * 呼叫靜態方法
         */
        // 獲取Integer.parseInt(String)方法,引數為String:
        Method method = Integer.class.getMethod("parseInt", String.class);
        // 呼叫該靜態方法並獲取結果:
        Integer n = (Integer) method.invoke(null, "89898");
        // 列印呼叫結果:
        System.out.println(n);

        /**
         *  呼叫非public方法
         */

        Car car = new Car();
        Method staticMethod = car.getClass().getDeclaredMethod("setName", String.class);
        staticMethod.setAccessible(true);
        staticMethod.invoke(car, "寶馬");
        System.out.println(car.name);
        
    }
}

4、Constructor類:Java.lang.reflec包中的類,表示類的構造方法。

  • 獲取構造方法API
  1. getConstructor(Class...):獲取某個public的Constructor;
  2. getDeclaredConstructor(Class...):獲取某個Constructor;
  3. getConstructors():獲取所有public的Constructor;
  4. getDeclaredConstructors():獲取所有Constructor。
  • 建立物件
  1. 使用Class物件的newInstance()方法來建立該Class物件對應類的例項,但是這種方法要求該Class物件對應的類有預設的空構造器。
  2. 先使用Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立Class物件對應類的例項,通過這種方法可以選定構造方法建立例項。
//獲取Person類的Class物件
Class clazz=Class.forName("reflection.Person");
//使用.newInstane方法建立物件
Person p=(Person) clazz.newInstance();
//獲取構造方法並建立物件
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//建立物件並設定屬性
Person p1=(Person) c.newInstance("張三","女",18);

5、獲取繼承關係

通過Class物件可以獲取繼承關係:

  • Class getSuperclass():獲取父類型別;
  • Class[] getInterfaces():獲取當前類實現的所有介面。

通過Class物件的isAssignableFrom()方法可以判斷一個向上轉型是否可以實現。

四、反射應用(動態代理)

  • 代理是基本的設計模式之一。一個物件封裝真實物件,代替其提供其他或不同的操作---這些操作通常涉及到與“真實”物件的通訊,因此代理通常充當中間物件。不僅動態建立代理物件而且動態處理對代理方法的呼叫。在動態代理上進行的所有呼叫都被重定向到單個呼叫處理程式,該處理程式負責發現呼叫的內容並決定如何處理。
  • 比較Java的class和interface的區別,可以例項化class(非abstract);不能例項化interface,標準庫提供了一種動態代理(Dynamic Proxy)的機制:可以在執行期動態建立某個interface的例項
  • 在執行期動態建立一個interface例項的方法如下:
  1. 定義一個InvocationHandler例項,它負責實現介面的方法呼叫;
  2. 通過Proxy.newProxyInstance()建立interface例項,它需要3個引數:

使用的ClassLoader,通常就是介面類的ClassLoader

需要實現的介面陣列,至少需要傳入一個介面進去;

用來處理介面方法呼叫的InvocationHandler例項。

3.將返回的Object強制轉型為介面。

import java.lang.reflect.*;

class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

    DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object
    invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println(
                "**** proxy: " + proxy.getClass() +
                        ", method: " + method + ", args: " + args);
        if (args != null)
            for (Object arg : args)
                System.out.println("  " + arg);
        return method.invoke(proxied, args);
    }
}

class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        RealObject real = new RealObject();
        consumer(real);
        // Insert a proxy and call again:
        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynamicProxyHandler(real));
        consumer(proxy);
    }
}

五、Optional類(輕量級代理)

Optional 是 Java 8 為了支援流式程式設計才引入的,Optional 物件可以防止你的程式碼直接丟擲 NullPointException。實際上,在所有地方都使用 Optional 是沒有意義的,有時候檢查一下是不是 null 也挺好的,或者有時我們可以合理地假設不會出現 null,甚至有時候檢查 NullPointException 異常也是可以接受的。Optional 最有用武之地的是在那些“更接近資料”的地方,在問題空間中代表實體的物件上。舉個簡單的例子,很多系統中都有 Person 型別,程式碼中有些情況下你可能沒有一個實際的 Person 物件(或者可能有,但是你還沒用關於那個人的所有資訊)。這時,在傳統方法下,你會用到一個 null 引用,並且在使用的時候測試它是不是 null。而現在,我們可以使用 Optional:

class Person {
    public final Optional<String> first;
    public final Optional<String> last;
    public final Optional<String> address;
    // etc.
    public final Boolean empty;

    Person(String first, String last, String address) {
        this.first = Optional.ofNullable(first);
        this.last = Optional.ofNullable(last);
        this.address = Optional.ofNullable(address);
        empty = !this.first.isPresent()
                && !this.last.isPresent()
                && !this.address.isPresent();
    }

    Person(String first, String last) {
        this(first, last, null);
    }

    Person(String last) {
        this(null, last, null);
    }

    Person() {
        this(null, null, null);
    }

    @Override
    public String toString() {
        if (empty)
            return "<Empty>";
        return (first.orElse("") +
                " " + last.orElse("") +
                " " + address.orElse("")).trim();
    }

    public static void main(String[] args) {
        System.out.println(new Person());
        System.out.println(new Person("Smith"));
        System.out.println(new Person("Bob", "Smith"));
        System.out.println(new Person("Bob", "Smith",
                "11 Degree Lane, Frostbite Falls, MN"));
    }
}