每天一道LeetCode Day10:找不同
技術標籤:# 基礎
目錄
1、Class類:反射的核心類,可以獲取類的屬性,方法等資訊。
2、Field類:Java.lang.reflec包中的類,表示類的成員變數,可以用來獲取和設定類之中的屬性值。
3、Method類:Java.lang.reflec包中的類,表示類的方法,它可以用來獲取類中的方法資訊或者執行方法。
4、Constructor類:Java.lang.reflec包中的類,表示類的構造方法。
一、概念:
“反射”機制:允許我們在執行時發現和使用類的資訊。指在執行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法;並且對於任意一個物件,都能夠呼叫它的任意一個方法;這種動態獲取資訊以及動態呼叫物件方法的功能成為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
- Field getField(name):根據欄位名獲取某個public的field(包括父類)
- Field getDeclaredField(name):根據欄位名獲取當前類的某個field(不包括父類)
- Field[] getFields():獲取所有public的field(包括父類)
- Field[] getDeclaredFields():獲取當前類的所有field(不包括父類)
- 一個
Field
物件包含了一個欄位的所有資訊:
getName()
:返回欄位名稱,例如,"name"
;getType()
:返回欄位型別,也是一個Class
例項,例如,String.class
;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;
- getName():返回方法名稱,例如:"getScore";
- getReturnType():返回方法返回值型別,也是一個Class例項,例如:String.class;
- getParameterTypes():返回方法的引數型別,是一個Class陣列,例如:{String.class, int.class};
- getModifiers():返回方法的修飾符,它是一個int,不同的bit表示不同的含義。
- 獲取Method方法資訊API
- getName():返回方法名稱,例如:"getScore";
- getReturnType():返回方法返回值型別,也是一個Class例項,例如:String.class;
- getParameterTypes():返回方法的引數型別,是一個Class陣列,例如:{String.class, int.class};
- 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
- getConstructor(Class...):獲取某個public的Constructor;
- getDeclaredConstructor(Class...):獲取某個Constructor;
- getConstructors():獲取所有public的Constructor;
- getDeclaredConstructors():獲取所有Constructor。
- 建立物件
- 使用Class物件的newInstance()方法來建立該Class物件對應類的例項,但是這種方法要求該Class物件對應的類有預設的空構造器。
- 先使用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
例項的方法如下:
- 定義一個
InvocationHandler
例項,它負責實現介面的方法呼叫; - 通過
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"));
}
}