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