1. 程式人生 > >JavaSE(四)反射與泛型

JavaSE(四)反射與泛型

反射與泛型

反射

Class類

class本身是一種資料型別(Type),class/interface的資料型別是Class,JVM為每個載入的class建立唯一的Class例項。
Class例項包含該class的所有資訊,通過Class例項獲取class資訊的方法稱為反射(Reflection)

獲取一個class的Class例項:
	1)Class cls = String.class;
	2)Class cls = "str".getClass();
	3)Class cls = Class.forName("java.lang.String");
注意 Class的==比較和instanceof的區別
Integer n = new Integer(123);

boolean b3 = n instanceof Integer; // true
boolean b4 = n instanceof Number; // true

// 精確判斷時
boolean b1 = n.getClass() == Integer.class; // true
boolean b2 = n.getClass() == Number.class; // false
從Class例項獲取class資訊:
	1)getName():獲取完整類名
	2)getSimpleName():獲取簡單的類名
	3)getPackage():獲取包名
從Class例項判斷class型別:
	1)isInterface():判斷是否是介面型別
	2)isEnum():判斷是否是列舉型別
	3)isArray():判斷是否是陣列型別
	4)isPrimitive():判斷是否是基本型別
建立class例項:
	cls.newInstance()
	JVM總是動態載入class,可以在執行期根據條件控制載入class
// commons logging優先使用Log4j
LogFactory factory;
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;
	}
}

訪問欄位

通過Class例項獲取欄位field資訊:
	1)getField(name)——獲取某個public的field(包括父類)
	2)getDeclaredField(name)——獲取當前類的某個field(不包括父類)
	3)getFields()——獲取所有public的field(包括父類)
	4)getDeclaredFields()——獲取當前類的所有field(不包括父類)
Field物件包含一個field的所有資訊
	1)getName():欄位名稱
	2)getType():欄位定義的資料型別
	3)getModifiers():欄位修飾符
獲取和設定field的值
	1)get(Object obj)
	2)set(Object, Object)
通過反射訪問Field需要通過SecurityManager設定的規則
通過設定setAccessible(true)來訪問非public欄位

呼叫方法

通過Class例項方法獲取Method資訊:
	1)getMethod(name, Class ...):獲取某個public的method(包括父類)
	2)getDeclaredMethod(name, Class ...):獲取當前類的某個method(不包括父類)
	3)getMethods():獲取所有public的method(包括父類)
	4)getDeclaredMethods():獲取當前類的所有method(不包括父類)
Method物件包含一個method的所有資訊
	1)getName():獲取方法名
	2)getReturnType():獲取返回值型別
	3)getParameterTypes():獲取方法引數型別
	4)getModifiers():獲取方法修飾符
呼叫Method:
	Object invoke(Object obj, Object ...args)
通過設定setAccessible(true)來訪問非public方法
反射呼叫Method也遵守多型的規則

呼叫構造方法

Class.newInstance()只能呼叫public無引數構造方法
	String s = (String) String.class.newInstance();	// ^_^
	Integer n = (Integer) Integer.class.newInstance();  // ERROR
通過Class例項獲取Constructor資訊:
	1)getConstructor(Class...):獲取某個public 的 Constructor
	2)getDeclaredConstructor(Class...):獲取某個Constructor
	3)getConstructors():獲取所有public的Constructor
	4)getDeclareConstructors():獲取所有的Constructor
通過Constructor例項可以建立一個例項物件
	newInstance(Object ... parameters)
通過設定setAccessible(true)來訪問非public構造方法

獲取繼承關係

獲取父類的Class:
	1)Class getSuperclass()
	2)Object的父類是null
	3)interface的父類是null
獲取當前類直接實現的interface:
	1)Class[] getInterfaces()
	2)getInterfaces()獲取的不包括間接實現的interface;如果想獲取所有的,可以遞迴呼叫獲取
	3)沒有interface的class返回空陣列
	4)interface返回繼承的interface
判斷一個向上轉型是否成立:
	boolean isAssignableFrom(Class)

註解 Annotation

使用註解

註解是放在Java原始碼的類、方法、欄位、引數前的一種標籤
註解本身對程式碼邏輯沒有任何影響,如何使用註解由工具決定
編譯器可以使用的註解
	@Override:讓編譯器檢查該方法是否正確實現了覆寫
	@Deprecated:告訴編譯器該方法已經標記為“作廢”
	@SuppressWarnings:讓編譯器忽略警告
註解可以定義配置引數和預設值
	配置引數由註解型別定義
	配置引數可以包括:
		所有基本型別
		String
		列舉型別
		陣列
	配置引數必須是常量

定義註解

使用@interface定義註解,例如
@Target(ElementType.METHOD)
public @interface Report {
	int type() default 0;
	String level() default "info";
	String value() default "";
}

使用元註解(meta annotation)定義註解
1)@Target
2)@Retention
3)@Repeatable
4)@Inherited

@Target

使用@Target定義Annotation可以被應用於原始碼的哪些位置:
	類或介面:ElementType.TYPE
	欄位:ElementType.FIELD
	方法:ElementType.METHOD
	構造方法:ElementType.CONSTRUCTOR
	方法引數:ElementType.PARAMETER

@Retention

使用@Retention定義Annotation的生命週期
	僅編譯器:RetentionRolicy.SOURCE
	僅class檔案:RetentionRolicy.CLASS
	執行期:RetentionRolicy.RUNTIME
如果@Retention不存在,則該Annotation預設為CLASS
通常自定義的Annotation都是RUNTIME

@Repeatable

使用@Repeatable定義Annotation是否可重複
JDK>=1.8

@Inherited

使用@Inherited定義子類是否可繼承父類定義的Annotation
	僅針對@Target為TYPE型別的Annotation
	僅針對class的繼承
	對interface的繼承無效

定義Annotation的步驟:

1)用@interface定義註解
2)用元註解配置註解
	Target:必須設定
	Retention:一般設定為RUNTIME
	通常不必寫@Inherited,@Repeatable等
3)定義註解引數和預設值

處理註解

註解本身對程式碼邏輯沒有任何影響,SOURCE型別的註解在編譯期就被丟掉了,CLASS型別的註解僅儲存在class檔案中,RUNTIME型別的註解在執行期可以被讀取,如何使用註解由工具決定
Q:如何讀取RUNTIME型別的註解?

Annotation也是class,所有Annotation繼承自java.lang.annotation.Annotation,可以使用反射API

使用反射API讀取Annotation:isAnnotationPresent、getAnnotation

isAnnotationPresent

判斷某個註解是否存在

	Class.isAnnotationPresent(Class)
	Field.isAnnotationPresent(Class)
	Method.isAnnotationPresent(Class)
	Constructor.isAnnotationPresent(Class)

getAnnotation

獲取某個註解資訊

	Class.getAnnotation(Class)
	Field.getAnnotation(Class)
	Method.getAnnotation(Class)
	Constructor.getAnnotation(Class)

getParameterAnnotations

獲取引數型別的註解:getParameterAnnotations()

Annotation[][] annos = m.getParameterAnnotations();
可以在執行期通過反射讀取RUNTIME型別的註解
	不要漏寫@Retention(RetentionPolicy.RUNTIME)
可以通過工具處理註解來實現相應的功能:
	1)對JavaBean的屬性值按規則進行檢查
	2)JUnit會自動執行@Test標記的測試方法

練習

請根據註解;
	@NotNull:檢查該屬性為非null
	@Range:檢查整型介於min~max,或者檢查字串長度介於min~max
	@ZipCode:檢查字串是否全部由數字構成,且長度恰好為value
實現對Java Bean的屬性值檢查。如果檢查未通過,丟擲異常

泛型

什麼是泛型

泛型(Generic)就是定義一種模板,例如ArrayList<T>
在程式碼中為用到的類建立對應的ArrayList<型別>
	ArrayList<String> strList = new ArrayList<String>();
編譯器會針對泛型作檢查,要注意泛型的繼承關係
	可以吧ArrayList<Integer>向上轉型為List<Integer>(T不能變)
	不能把ArrayList<Integer>向上轉型為ArrayList<Number>或List<Number>
	ArrayList<Number>和ArrayList<Integer>兩者沒有繼承關係

使用泛型

定義泛型型別<Number>:
	List<T>的泛型介面為強型別
		void add(Number)
		Number get(int)
不指定泛型引數型別時,編譯器會給出警告,且只能將<T>視為Object型別

編寫泛型

泛型一般用在集合類中,編寫型別時,需要定義泛型型別<T>
	public class Pair<T> {...}
靜態方法不能引用泛型型別<T>,必須定義其他型別<K>來實現泛型
	public static <K> Pair<k> create(K first, K last){ ... }
泛型可以同時定義多種型別<T, K>

擦拭法

Java的泛型是採用擦拭法(Type Erasure)實現的,編譯器把型別視為Object,再根據實現安全的強制轉型

擦拭法是侷限:
	1)<T>不能是基本型別,例如int
	2)Object欄位無法持有基本型別
	3)無法取得帶泛型的Class
	4)無法判斷帶泛型的Class
	5)不能例項化T型別,因為擦拭後實際上是new Object()

可以繼承自泛型類

public class IntPair extends Pair<Integer>{ 
}
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if(t instanceof ParameterizedType){
	ParameterizedType pt = (ParameterizedType)t;
	Type[] types = pt.getActualTypeArguments();
	Type firstType = types[0];
	Class<?> typeClass = (Class<?>) firstType;
	System.out.println(typeClass); // Integer
}

父類型別是Pair<Integer>;子類的型別是IntPair;子類可以獲取父類的泛型型別
繼承關係:
在這裡插入圖片描述

extends萬用字元

public class Pair<T> { ... }
public class PairHelper {
	static int get(Pair<Number> p) {
		Number first = p.getFirst();
		Number last = p.getLast();
		return first.intValue() + last.intValue();
	}
}
PairHelper.get(new Pair<Number>(1, 2));  // √
PairHelper.get(new Pair<Integer>(1, 2));  // ×

Pair<Integer>不是Pair<Number>的子類;get()不接受Pair<Integer>,所以可以使用<? extends Number>使方法接收所有泛型型別為Number或Number類的Pair類

public class PairHelper {
	static int get(Pair<? extends Number> p) {
		Number first = p.getFirst();
		Number last = p.getLast();
		return first.intValue() + last.intValue();
	}
}
PairHelper.get(new Pair<Number>(1, 2));  // √
PairHelper.get(new Pair<Integer>(1, 2));  // √
對Pair<? extends Number>呼叫getFirst()方法:
	方法簽名:? extends Number getFirst()
	可以安全賦值給Number型別的變數:Number x = p.getFirst();
	不可預測實際型別就是Integer:Integer x = p.getFirst();
對Pair<? extends Number>呼叫setFirst()方法會報錯:
	方法簽名:void setFirst(? extends Number)
	無法傳遞任何Number型別給setFirst(? extends Number)

<? extends Number> 的萬用字元:
允許呼叫get方法獲得Number的引用
不允許呼叫set方法傳入Number的引用
唯一例外:可以呼叫setFirst(null)
<T extends Number> 的萬用字元
限定定義Pair<T>時只能是Number或Number的子類

super萬用字元

public class Pair<T> { ... }
public class PairHelper {
	static int set(Pair<Integer> p, Integer first, Integer last) {
		p.setFirst(first);
		p.setLast(last);
	}
}
PairHelper.set(new Pair<Integer>(1, 2), 3, 4);  // √
PairHelper.set(new Pair<Number>(1, 2), 3, 4);  // ×

Pair<Integer>不是Pair<Number>的子類;set()不接受Pair<Number>,所以可以使用<? super Integer>使方法接收所有泛型型別為Integer或Integer超類的Pair類

public class PairHelper {
	static int set(Pair<? super Integer> p, Integer first, Integer last) {
		p.setFirst(first);
		p.setLast(last);
	}
}
PairHelper.set(new Pair<Integer>(1, 2), 3, 4);  // √
PairHelper.set(new Pair<Number>(1, 2), 3, 4);  // √
對Pair<? super Integer>呼叫setFirst()方法:
	方法簽名:void setFirst(? super Integer)
	可以安全傳入Integer型別的變數:p.setFirst(new Integer(123));
對Pair<? super Integer>呼叫getFirst()方法會報錯:
	方法簽名:? super Integer getFirst()
	無法賦值給Integer型別的變數

<T super Integer> 的萬用字元
允許呼叫set方法傳入Integer的引用
不允許呼叫get方法獲得Integer的引用
唯一例外:可以獲取Object引用Object o = p.getFirst();
<T extends Number> 的萬用字元
限定定義Pair時只能是Integer或Integer的超類
extends和super萬用字元的區別

<? extends T> 允許呼叫方法獲取T的引用
<? super T> 允許呼叫方法傳入T的引用
public class Collections{
	// 把src的每個元素複製到dest中
	public static <T> void copy(List<? super T> dest, List<? extends T> src){
		for(var i=0; i<src.size(); i++){
			T t = src.get(i);
			dest.add(t);
		}
	}
}

無限定萬用字元<?>

不允許呼叫set方法(null除外)
只能呼叫get方法獲取Object引用
可以用<T>消除<?>

泛型與反射

部分反射API是泛型
	Class<T>
	Constructor<T>
可以宣告帶泛型的陣列,但不能直接建立帶泛型的陣列,必須強制轉型,例如
Pair<String>[] ps = null; // √
Pair<String>[] ps = new Pair<String>[2]; // ×
Pair<String>[] ps = (Pair<String>[])new Pair[2]; // √
// 不安全地使用帶泛型的陣列==>通過引用
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;

ps[0] = new Pair<String>("a", "b");
arr[1] = new Pair<Integer>(1, 2);

// ClassCastException
Pair<String> p = ps[1];
String s = p.getFirst();
// 安全地使用帶泛型的陣列 ==> 不要使用arr的引用
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
不能直接建立 T[] 陣列:
	因為擦拭後變為new Object[5];必須藉助Class<T>
可以通過Array.newInstance(Class<T>, int)建立T[]陣列,需要強制轉型
T[] createArray(Class<T> cls){
	return (T[]) Array.newInstance(cls, 5);
}
public class ArrayHelper{
	@SafeVarargs  // 消除編譯器警告
	static <T> T[] asArray(T... objs){
		return objs;
	}
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);