一泉褐、反射
通過Class實例獲取 class 信息的方法稱為反射(Reflection)函荣。
(一) Class 類
Java 除基本類型外其他都是class類型(包括 interface搭盾,例如:String统阿、Object躯肌、Runnable滩援、Exception),class 的本質(zhì)就是一種數(shù)據(jù)類型膘侮;
1屈糊、Class 的簡介
(1)class/interface 的數(shù)據(jù)類型是 Class(如下代碼),每加載一個 class琼了,JVM 就為其創(chuàng)建了一個 Class 類型的實例逻锐,并關(guān)聯(lián)起來(如下圖:“創(chuàng)建class.png”)
public final class Class{
private Class(){}
}
(2)JVM 持有的每個 Class 實例都指向一個數(shù)據(jù)類型(class 或 interface,如下圖:”Class實例.png“)雕薪。
(3)一個 Class 實例包含了該 class 的完整信息谦去,例如下圖是指向 String 類(class)的 Class 實例:它包含了 String 這個類的 name、package蹦哼、super鳄哭、interface、field纲熏、method 等信息妆丘。
(4)JVM 為每個加載的 class 創(chuàng)建對應(yīng)的 Class 實例,并在實例中保存該 class 的所有信息局劲。
2勺拣、反射:通過Class實例獲取class信息的方法稱為反射。如果獲取了某個 Class 實例鱼填,則可以獲取到該實例對應(yīng)的 class 的所有信息药有。
3、獲取一個 class 的 Class 實例有三種方法:
(1)使用 Type.class :
// 獲取 String 類的 Class 實例
Class cls = String.class;
(2)對實例變量調(diào)用 getClass 方法:
// 通過 String 類的實例 s 來獲取 String 類的 Class 實例
String s = "hello";
Class cls = s.getClass();
(3)使用 Class 提供的 靜態(tài)方法 forName():
Class cls = Class.forName("java.lang.String");
4苹丸、Class 實例在 JVM 中是唯一的愤惰,可以使用 == 比較兩個 Class 實例
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
Class cls3 = Class.forName("java.lang.String");
boolean b1 = cls1 == cls2; // true
boolean b2 = cls2 == cls3; // true
5、Class 實例比較和 instanceof 的差別:
(1)instanceof 可以判斷是否相等赘理,判斷子類關(guān)系宦言。
(2)Class 實例比較符 "==",只能判斷是否相等商模,而不能判斷是否是子類關(guān)系奠旺。
6、從 Class 實例獲取 class 信息:
(1)getName()
(2)getSimpleName()
(3)getPackage()
Class cls = String.class;
String fname = cls.getName(); // "java.lang.String"
String sname = cls.getSimpleName; // "String"
String pkg = cls.getPackage().getName(); // "java.lang"
7施流、從 Class 實例本身判斷 class 的類型:
(1)isInterface() 判斷是不是 interface 類型
(2)isEnum() 判斷是不是 枚舉 類型
(3)isArray() 判斷是不是 Array 類型
(4)isPrimitive() 判斷是不是 Primitive 類型
Runnable.class.isInterface(); // true
java.time.Month.class.isEnum(); // true
String[].class.isArray(); // true
int.class.isPrimitive(); // true
8响疚、使用 Class 創(chuàng)建 class 實例
Class cls = String.class;
// new String();
String s = (String) cls.newInstance();
9、利用 JVM 動態(tài)加載 class 的特性瞪醋,可以在運行期根據(jù)條件加載不同的實現(xiàn)類(例如 Commons Logging 的使用):
// Commons Logging 優(yōu)先使用 Log4j忿晕,沒有 Log4j,則使用 JVM 自帶的 Log 模塊
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;
}
}
10趟章、Class 類使用實際案例
package com.test.sample
public class Main{
public static void main(String[] args) throws Exception{
// Class cls = Student.class;
Class cls;
if (true){ // JVM 總是動態(tài)加載 class杏糙,所以我們可以在運行期根據(jù)條件控制加載 class
cls = Class.forName("com.test.sample.Student");
} else {
cls = Class.forName(""com.test.sample.Teacher");
}
System.out.println("class name" + cls.getName());
System.out.println("class simple name" + cls.getSimpleName());
System.out.println("package name" + cls.getPackage().getName());
System.out.println("is interface?" + cls.isInterface());
// Student s = new Student();
Student s = (Student) cls.newInstance();
s.hello();
}
}
(二) 訪問字段
1、通過Class實例獲取字段field信息:
(1)getField(name):獲取某個 public 的field(包括父類)
(2)getDeclaredField(name):獲取當(dāng)前類的某個field(不包括父類)
(3)getFields():獲取所有 public 的field(包括父類)
(4)getDeclaredFields():獲取當(dāng)前類的所有field(不包括父類)
2蚓土、Field對象包含一個field的所有信息:
(1)getName()
(2)getType()
(3)getModifiers()
Integer n = new Integer(123);
Class cls = n.getClass();
Field[] fs = cls.getFields();
for (Field f : fs) {
f.getName(); // field name
f.getType(); // field type
f.getModifiers(); // modifiers
}
3宏侍、獲取和設(shè)置field的值:
(1)get(Object obj) 獲取一個實例的該字段 field 的值
Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("value");
f.get(n); // 123 相當(dāng)于 n.value
(2)set(Object, Object) 設(shè)置 一個實例的該字段 field 的值
Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("value");
f.set(n, 234); // 相當(dāng)于 n.value = 234
4、通過設(shè)置 setAccessible(true) 來訪問非 public 字段
package com.test;
public class Main{
public static void main(String[] args) throws Exception{
Student s = new Student();
Class cls = s.getClass();
Field f = cls.getDeclaredField("address");
f.setAccessible(true); // 設(shè)置為 true蜀漆,使 private 字段能夠被訪問
System.out.println(f.get(s));
s.hello();
}
static void printFieldInfo(Field f){
System.out.println("field name: " + f.getName());
System.out.println("file type: " + f.getType());
System.out.println("filed modifier: " + f.getModifiers());
System.out.println("is public? " + Modifier.isPublic(f.getModifiers()));
System.out.println("is protected? " + Modifier.isProtected(f.getModifiers()));
System.out.println("is final? " + Modifier.isFinal(f.getModifiers()));
}
}
package com.test;
public class Student extends Person{
public static int number;
public String name;
private String address = "北京";
public Student(){
// TODO
}
}
package com.test;
public class Person {
public int age;
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
}
注意:setAccessible(true) 可能會失敗谅河,如果 JVM 中定義了 SecurityManager ,SecurityManager會有一個規(guī)則确丢,如果這個規(guī)則阻止對 Field 設(shè)置 accessible(例如:規(guī)則應(yīng)用于所有 java 和 javax 開頭的 package 的類绷耍,則就不能訪問 private 字段)。通常情況下我們自己編寫的類 和 第三方類沒有這個限制鲜侥。
(三) 調(diào)用方法
1褂始、通過Class實例獲取方法Method信息:
(1)getMethod(name, Class...):獲取某個public的method(包括父類)
(2)getDeclaredMethod(name, Class...):獲取當(dāng)前類的某個method(不包括父類)
(3)getMethods():獲取所有public的method(包括父類)
(4)getDeclaredMethods():獲取當(dāng)前類的所有method(不包括父類)
2、Method對象包含一個method的所有信息:
(1)getName()
(2)getReturnType()
(3)getParameterTypes()
(4)getModifiers()
Integer n = new Integer(123);
Class cls = n.getClass();
Method[] ms = cls.getMethods();
for (Method m : ms){
m.getName(); // method name
// return type:
Class ret = m.getReturnType();
// parameter types:
Class[] params = m.getParameterTypes();
m.getModifiers(); // modifiers
}
3描函、調(diào)用Method:
(1)調(diào)用無參數(shù)的 Method 方法:Object invoke(Object obj, Object... args)
Integer n = new Integer(123);
Class cls = n.getClass();
Method m = cls.getMethod("toString"); // 獲取 n 的 toString 方法
String s = (String) m.invoke(n) // "123" 相當(dāng)于 String s = n.toString();
(2)調(diào)用帶參數(shù)的 Method 方法:Object invoke(Object obj, Object...args)崎苗,即第一個參數(shù)是實例變量,第二個參數(shù)是方法參數(shù)
Integer n = new Integer(123);
Class cls = n.getClass();
Method m = cls.getMethod("compareTo", Integer.class); // 獲取 compareTo 方法
int r = (Integer) m.invoke(n, 456); // -1, 相當(dāng)于 int r = n.compareTo(456)
4舀寓、通過設(shè)置setAccessible(true)來訪問非public方法胆数。
5、反射調(diào)用Method也遵守 多態(tài) 的規(guī)則互墓。
例:從 Person.class 獲取的 Method必尼,作用于 Student 實例時,實際調(diào)用的 方法是 Student 覆寫的方法篡撵, 保證了多態(tài)的正確性
class Person{
public void hello(){
System.out.println("Person: hello");
}
}
class Student extends Person{
public void hello(){
System.out.println("Student: hello");
}
}
Method m = Person.class.getMethod("hello");
m.invoke(new Student()); // 實際傳入的是 Student判莉,這里實現(xiàn)了多態(tài) 相當(dāng)于 Person p = new Student(); p.hello();
(四) 調(diào)用構(gòu)造方法
1、調(diào)用 public 無參數(shù)構(gòu)造方法:
(1)使用 Class.newInstance() 調(diào)用
(2)newInstance() 只能調(diào)用無參數(shù)的構(gòu)造方法
String s = (String) String.class.newInstance(); // String.class 是 Class 對象育谬,String 類定義的了一個 無參數(shù)構(gòu)造方法骂租,所以可以使用 newInstance()
Integer n = (Integer) Integer.class.newInstance(); // error Integer 類沒有無參數(shù)的構(gòu)造方法,無法使用 newInstance()
2斑司、調(diào)用帶參數(shù)的 構(gòu)造方法
(1)調(diào)用帶參數(shù)的構(gòu)造方法渗饮,不能直接使用 Class 的 newInstance 方法。需要獲取 Constructor 對象宿刮,Constructor 對象包含一個構(gòu)造方法的所有信息互站,通過 Constructor 對象的 newInstance() 方法來調(diào)用。(即Constructor實例使用 newInstance 可以創(chuàng)建一個實例對象)
(2)實例:
Class cls = Integer.class;
// 調(diào)用構(gòu)造方法 Integer(int)
Constructor cons1 = cls.getConstructor(int.class); // 獲取一個 Constructor 對象
Integer n1 = (Integer) cons1.newInstance(123); // 通過 Constructor 對象的 newInstance 方法來調(diào)用有參數(shù)的構(gòu)造方法
// 調(diào)用構(gòu)造方法 Integer(String)
Constructor cons2 = cls.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("123");
(3)通過Class實例獲取 構(gòu)造方法 的信息:
a僵缺、getConstructor(Class...):獲取某個public的Constructor
b胡桃、getDeclaredConstructor(Class...):獲取某個Constructor
c、getConstructors():獲取所有 public 的 Constructor
d磕潮、getDeclaredConstructors():獲取所有Constructor
package com.test;
import java.lang.reflect.Constructor;
import java.util.Arrays;
public class Main{
public static void main(String[] arg) throws Exception {
Class cls = Student.class;
Constructor c = cls.getDeclaredConstructor(String.class, int.class);
printConstructorInfo(c);
c.setAccessible(true);
Student s = (Student) c.newInstance("XiaoMing", 12); // Constructor實例使用 newInstance 方法創(chuàng)建一個 Student實例
s.hello();
}
static void printConstructorInfo(Constructor c){
System.out.println(c);
System.out.println("parameters: " + Arrays.toString(c.getParameterTypes()));
System.out.println("modifier: " + c.getModifiers());
}
}
package com.test;
public class Student extends Person{
public static int number = 0;
public String name;
private String address = "beijing";
private Student(){
this("Unanmed");
}
private Student(String name){
this(name, 20);
}
private Student(String name, int age){
this.name = name;
this.age = age;
number++;
}
public void hello(){
System.out.printf(this.name + ":" + this.age);
}
}
(4)通過設(shè)置setAccessible(true)來訪問非public構(gòu)造方法翠胰。
3容贝、注意區(qū)分 Class.newinstance 和 Constructor.newinstance 的區(qū)別
(五) 獲取繼承關(guān)系
1、獲取父類的Class:
(1)使用 Class getSuperclass() 獲取父類 Class
(2)Object 的父類是null
(3)interface 的父類是null
Class sup = Integer.class.getSuperclass(); // Number.class
Object.class.getSuperclass(); // null
Runnable.class.getSuperclass(); // null
2之景、獲取當(dāng)前類直接實現(xiàn)的 interface(不包括間接實現(xiàn)的interface) :
(1)使用 Class[] getInterfaces() 來獲取實現(xiàn)的 interface
(2)沒有interface的class返回空數(shù)組
(3)interface 返回繼承的 interface
Class[] ifs = Integer.class.getInterfaces(); // [Comparable.class]
Class[] ifs = java.util.ArrayList.class.getInterfaces();
Class[] ifs = Math.class.getInterfaces(); // []
Class[] ifs = java.util.List.class.getInterfaces(); // [Collection.class]
3斤富、判斷一個向上轉(zhuǎn)型是否成立:bool isAssignableFrom(Class)
// Integer i = ...
// Number x = i ?
Number.class.isAssignableFrom(Integer.class); // true Integer 向上轉(zhuǎn)型為 Number 成立
// Number n = ...
// Integer i = n ?
Integer.class.isAssignableFrom(Number.class); // false
4、總結(jié)
(1)getSuperclass() // 獲取類的父類
(2)getInterfaces() // 獲取類直接實現(xiàn)的接口
(3)isAssignableFrom() // 判斷向上轉(zhuǎn)型是否正確
二锻狗、注解
(一) 使用注解
1满力、注解的定義:注解(Annotation)是放在Java源碼的類、方法轻纪、字段油额、參數(shù)前的一種標(biāo)簽,是 java 語言用于工具處理的標(biāo)注刻帚。
2潦嘶、注解的作用
(1)注解本身對代碼邏輯沒有任何影響
(2)如何使用注解由工具(例如:編譯器)決定
public class Hello {
@Override
public String toString(){
return "Hello";
}
@Deprecated
public void hello(String name){
System.out.println(name);
}
@SuppressWarnings("unused");
public void hello(){
int n;
}
}
3、編輯器可以使用的注解:
(1)@Override:讓編輯器檢查該方法是否正確地實現(xiàn)了覆寫
(2)@Deprecated:告訴編譯器該方法已經(jīng)被標(biāo)記為"作廢"崇众,在其他地方引用將會出現(xiàn)編譯警告
(3)@SuppressWarnings:告訴編譯器忽略某種警告
(4)寫注解編譯器會幫助檢查問題衬以,不寫注解編譯器就不會檢查。
4校摩、注解可以定義配置參數(shù):
(1)配置參數(shù)由注解類型定義
(2)配置參數(shù)可以包括:
??(a)所有基本類型
??(b)String
??(c)枚舉類型
??(d)數(shù)組
(3)配置參數(shù)必須是常量
public class Hello{
int n = 100;
@Test(timeout=100)
public void test(){
System.out.println("Test");
}
}
5看峻、缺少某個配置參數(shù)將使用默認(rèn)值
(1)如果只寫常量,相當(dāng)于省略了 value 參數(shù)
(2)如果只寫注解衙吩,相當(dāng)于全部使用默認(rèn)值互妓,默認(rèn)值的定義是在注解定義時定義的
public class Hello{
@Check(min=0, max=100, value=55)
public int n;
@Check(value=99)
public int p;
@Check(99) // @Check(value=99)
public int x;
@Check
public int y;
}
(二) 定義注解
1、在 Java 中使用 @interface 來定義注解(Annotation)
(1)注解的參數(shù)類似無參數(shù)方法
(2)可以給注解設(shè)定一個 默認(rèn)值 (推薦)
(3)把最常用的參數(shù)命名為 value能(推薦)
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
2坤塞、元注解:
(1)有些注解可以修飾其他注解冯勉,稱為元注解。
(2)使用 @Target 定義Annotation 可以被應(yīng)用于源碼的哪些位置
??(a)類或接口:ElementType.TYPE
??(b)字段:ElementType.FIELD
??(c)方法:ElementType.METHOD
??(d)構(gòu)造方法:ElementType.CONSTRUCTOR
??(e)方法參數(shù):ElementType.PARAMETER
@Target(ElementType.METHOD) // 聲明 Resport 注解只能應(yīng)用于 方法 上
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
@Target({ElementType.METHOD, ElementType.FIELD}) // 聲明 Report 注解只能應(yīng)用于 方法摹芙、字段 上灼狰。
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
(3)使用 @Retention 定義 Annotation 的生命周期:
??(a)僅編譯期:RetentionPolicy.SOURCE 編譯器在編譯時直接丟棄,即編譯器編譯后該注解就不存在了
??(b)僅 class 文件:RetentionPolicy.CLASS 僅存儲在 class 文件中
??(c)運行期:RetentionPolicy.RUNTIME 在運行期可以讀取該Annotation注解
??(d)如果 @Retention 不存在浮禾,則該 Annotation 默認(rèn)為 CLASS交胚,通常自定義的注解 Annotation 都是 RUNTIME。
@Retention(RetentionPolicy.RUNTIME)
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
(4)使用 @Repeatable 定義 注解Annotation 是否可重復(fù) (JDK >= 1.8)
@Repeatable
@Target(ElementType.TYPE)
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
@Report(type=1, level="debug") // 可以多次使用 Report
@Report(type=2, level="warning")
public class Hello{
}
(5)使用 @Inherited 定義子類是否可以繼承父類定義的 注解
??(a)僅針對 @Target 為 TYPE 類型的 注解
??(b)僅針對 class 的繼承
??(c)對 interface 的繼承無效
@Inherited
@Target(ElementType.TYPE)
public @interface Report{
int type defaulet 0;
String level() default "info";
String value() default "";
}
@Report(type=1) // 可以多次使用 Report
public class Person{
}
public class Student extends Person{
}
3盈电、總結(jié)定義注解步驟:
(1)用 @interface 定義注解
(2)用 元注解(meta annotation)配置注解
??(a)Target:必須設(shè)置
??(b)Retention:一般設(shè)置為 RUNTIME
??(c)通常不必寫 @Inherited蝴簇、@Repeatable 等等
(3)定義注解參數(shù)和默認(rèn)值
package com.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.FIELD) // 1、定義一個應(yīng)用于字段 Field 的 NotNUll 空注解
// @Target({ElementType.FIELD, ElementType.PARAMETER}) // 1匆帚、如果還想要應(yīng)用于 函數(shù)的參數(shù)字段上 還需要添加 ElementType.PARAMETER
public @interface NotNull {
}
package com.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 1;
int max() default 100;
}
package com.test;
public class Person{
@NotNull // 說明 name 字段不允許為空
public String name;
@Range(max=20) // 說明 age 這個字段最大值是 20
public int age;
public Person(String name, int age){
// public Person(@NotNull String name, int age){ // 應(yīng)用方法參數(shù) name, 聲明 name 不可為空
this.name = name;
this.age = age;
}
}
4熬词、總結(jié):
(1)使用 @interface 定義注解
(2)可以定義多個參數(shù)和默認(rèn)值,核心參數(shù)使用 value 名稱
(3)必須設(shè)置 @Target 來指定 Annotation 可以應(yīng)用的范圍
(4)應(yīng)當(dāng)設(shè)置 @Retention 為 RUNTIME 便于運行期讀取 該 Annotation
(三) 處理注解
1、使用反射API讀取 RUNTIME類型 的注解 Annotation:
(1)Class.isAnnotationPresent(Class) :判斷 注解 是否存在
Class cls = Person.class;
// 判斷 @Report 是否存在
cls.isAnnotationPresent(Report.class);
(2)Field.isAnnotationPresent(Class)
(3)Method.isAnnotationPresent(Class)
(4)Constructor.isAnnotationPresent(Class)
(5)Class.getAnnotation(Class) :獲取 注解
Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
int type = report.type();
String level = report.level():
(6)Field.getAnnotation(Class)
(7)Method.getAnnotation(Class)
(8)Constructor.getAnnotation(Class)
(9)getParameterAnnotations()
2互拾、使用反射 API 讀取 class Annotation 常用的兩種方法:
// 方法一
Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)){
Report report = cls.getAnnotation(Report.class);
...
}
// 方法二
Class cls = Person.class;
Report report = cls.getAnnotation(Report,class);
if (report != null) {
...
}
3歪今、使用反射 API 讀取方法參數(shù)的注解 PARAMETER Annotation 常用的方法:
public String hello(@NotNull @Range(max=5) String name, @NotNull String prefix){
...
}
Mthod m = ...
Annotation[][] annos = m.getParameterAnnottations();
Annotation[] annosOfName = annos[0]; // name 參數(shù)的 Annotation
for (Annotation anno : annosOfName){
if (anno instanceof Range){
Range r = (Range) anno;
}
}
// 以下是 annos 的內(nèi)容
{
{@NotNull, @Range(max=5)},
{@NotNull}
}
4、可以通過工具處理注解來實現(xiàn)相應(yīng)的功能:
(1)對JavaBean的屬性值按規(guī)則進行檢查
(2)JUnit會自動運行@Test標(biāo)記的測試方法
(四) 注解的應(yīng)用:
1颜矿、編寫注解 @NotNull:檢查該屬性為非null
2寄猩、編寫注解 @Range:檢查整型介于min~max,或者檢查字符串長度介于min~max
3或衡、編寫注解 @ZipCode:檢查字符串是否全部由數(shù)字構(gòu)成焦影,且長度恰好為value
三车遂、泛型
(一) 泛型的定義
1封断、泛型就是定義一種模版,例如 ArrayList<T>
(1)在代碼中為用到的類創(chuàng)建對應(yīng)的 ArrayList<類型>:
?? ArrayList<String> strList = new ArrayList<String>();
(2)編譯器針對類型作檢查操作:
?? strList.add("hello");
?? strList.add(new Integer(123)); // complie error!
// 定義泛型
public class ArrayList<T>{
private T[] array;
public void add(T e){
}
public void remove(int index){
}
public T get(int index) {
}
}
// 調(diào)用泛型
ArrayList<String> strList = new ArrayList<String>();
ArrayLIst<Float> floatList = new ArrayList<Float>();
ArrayLIst<Person> psList = new ArrayList<Person>();
2舶担、泛型的繼承關(guān)系
(1)向上轉(zhuǎn)型
public class ArrayList<T> implements List<T> {
private T[] array;
public void add(T e) {
}
public void remove(int index){
}
public T get(int index){
}
}
List<String> list = new ArrayLIst<String>(); // 向上轉(zhuǎn)型為 List<String>
(2)注意泛型的繼承關(guān)系:
?? a. 可以把 ArrayLIst<Integer> 向上轉(zhuǎn)型為 List<Integer> (T 不能變)坡疼;
?? b. 不能把 ArrayLIst<Integer> 向上轉(zhuǎn)型為 ArrayLIst<Number> 或 List<Number>,因為 ArrayList<Number>衣陶、 List<Number> 和 ArrayList<Integer> 沒有繼承關(guān)系柄瑰;
(二) 泛型的使用
1、使用泛型時剪况,可以省略編譯器能自動推斷出的類型
// 可以省略后面的 Number教沾,編譯器可以自動推斷類型
List<Number> list = new ArrayList<>();
2、Arrays.sort() 可以對 Object[] 數(shù)組進行排序:
String[] strs = {"Apple", "Pear", "Orange"};
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));
// {"Apple", "Orange", "Pear"}
3译断、泛型的好處是在編譯的時候檢查類型安全授翻,并且所有的 強制轉(zhuǎn)換 都是自動和隱式的,提高代碼的重用率孙咪。
4堪唐、泛型的類型參數(shù)只能是類類型(包括自定義類),不能是簡單類型翎蹈。
(三) 泛型的編寫(不常用)
1淮菠、如何編寫一個泛型類:
(1)按照某種類型(例如 String)編寫類
(2)標(biāo)記所有的特定類型(例如 String)
(3)把特定類型替換為 T, 并申明 <T>
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last){
this.first = first;
this.last = last;
}
public T getFirst(){
return first;
}
public T getLast(){
return last;
}
}
2荤堪、靜態(tài)方法不能引用泛型類型<T>合陵,必須定義其他類型<K>來實現(xiàn)“泛型”,即:編譯器無法在 靜態(tài)字段 或者 靜態(tài)方法 中使用泛型類型<T>.
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last){
this.first = first;
this.last = last;
}
public T getFirst(){
return first;
}
public T getLast(){
return last;
}
// 編譯錯誤
public static Pair<T> create(T first, T last){
return new Pair<T>(first, last);
}
// 正確方法如下
public static <K> Pair<K> create(K first, K last){ // static 后也加了<K>
return new Pair<K>(first, last);
}
}
3澄阳、編寫泛型時可以定義多種類型
public class Pair<T, K>{
private T first;
private K last;
public Pair(T first, K last){
this.first = first;
this.last = last;
}
public T getFirst(){
}
public K getLast(){
}
}
Pair<String, Integer> p = new Pair<>("test", 123);
(四) 擦拭法
1曙寡、Java 的 泛型是采用擦拭法實現(xiàn)的,虛擬機其實對泛型一無所知寇荧,所有的泛型都是編譯器在操作举庶。
編譯器把類型 <T> 視為 Object
編譯器根據(jù) <T> 實現(xiàn)安全的強制轉(zhuǎn)型
2、擦拭法的局限
(1)<T>不能是基本類型揩抡,例如:int
(2)Object字段無法持有基本類型
(3)無法取得帶泛型的Class户侥,例如:Pair<String>.class, 即無論 T 是什么類型镀琉,Class 都是 Pair.class
Pair<String> s = ...;
Pair<Integer> i = ...;
Class c1 = s.getClass();
Class c2 = i.getClass();
System.out.println(c1 == c2); // true
System.out.println(c1 == Pair.class); // true
// Compile error;
if (s instanceof Pair<String>.class){
}
(4)無法判斷帶泛型的Class,例如:x instanceof Pair <String>
(5)不能實例化 T 類型蕊唐,因為擦拭之后實際上是 new Object()屋摔,例如:new T()。 實例化 T類型必須借助 Class<T>
// 錯誤的實例化
public class Pair<T>{
private T first;
private T last;
public Pair(){
// Compile error;
first = new T();
last = new T();
}
}
Pair<String> pair = new Pair<>();
// 正確的實例化
public class Pair<T>{
private T first;
private T last;
public Pair(Class<T> clazz){
first = clazz.newInstance();
last = clazz.newInstance();
}
}
Pair<String> pair = new Pair<> (String.class);
(6)泛型方法要防止重復(fù)定義方法替梨,例如:public boolean equals(T obj)
3钓试、子類可以獲取父類的泛型類型 <T>
public class IntPair extends Pair<Integer>{
}
Class<IntPair> clazz = IntPair.class;
Type t = clazz. ();
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
}
4、JDK 定義了 Type接口副瀑, 他的實現(xiàn)類包括:Class弓熏、ParameterizedType、GenericArrayType糠睡、WildcardType挽鞠。
(五) extends 通配符
1、使用 extends 通配符狈孔,可以接收本身類型及子類信认,例如:
案例一:使用 Pair<? extends Number> 可以使方法接收所有泛型類型為 Number 或 Number 子類的 Pair 類。
案例二:使用 Pair<? extends String> 可以使方法接收所有泛型類型為 String 或 String 子類的 Pair 類均抽。
public class Pair<T>{
}
public class PairHelper{
Static void add(Pair<? extends Number> p){
Number first = p.getFirst();
p.setFirst(first); // ERORR
p.setFirst(null)嫁赏; // OK
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}
PairHelper.add(new Pair<Integer>(1, 2));
PairHelper.add(new Pair<Number>(1,2));
void process(List<? extends Number> list){
}
public class List<T> {
T get(int index);
void add(T t); // ERORR 無法調(diào)用傳入 Integer 引用的方法, add 同 set 一樣
void remove(T t); // ERORR 無法調(diào)用傳入 Integer 引用的方法, remove 同 set 一樣
}
2、 使用類似<? super Integer> 通配符作為 方法參數(shù) 是表示(代碼如上):
(1)方法內(nèi)部可以調(diào)用獲取 Integer 引用的方法(get方法)油挥,例如:Number n = obj.getXxx()
(2)方法內(nèi)部無法調(diào)用傳入 Integer 引用的方法(set方法 null 除外)潦蝇,例如:obj.setXxx(Number n) // 報錯
3、extends 通配符作為 方法參數(shù) 實例
void someMethod(List<? extends Number> list) {
Number n = list.get(0);
list.add(n); // ERROR
}
以上實例:
(1)允許傳入List<Number>喘漏,List<Integer>护蝶,List<Double>
4、extends 通配符的另一個用法 -- 定義泛型類:<T extends Number>
(1)定義泛型時可以通過extends限定T 必須 是Number或Number的子類
(2)方法內(nèi)部可以調(diào)用獲取(get) Number 引用的方法翩迈,Number n = obj,getXxx()持灰。
(3)方法內(nèi)部無法調(diào)用傳入(set、add负饲、remove) Number 引用的方法(null 除外)堤魁,obj.setXxx(Number n)。
5返十、使用類似<T extends Number> 定義泛型類型時表示:泛型類型限定為 Number 或 Number 的子類
(六) super 通配符
1妥泉、使用 super 通配符,可以接收本身類型及超類洞坑,例如:
案例一:使用 Pair<? super Integer> 可使方法接受所有泛型類型為 Integer 或 Integer 超類的 Pair 類
案例二:使用 Pair<? super String> 可使方法接受所有泛型類型為 String 或 String 超類的 Pair 類
public class Pair<T> {
}
public class PairHelper{
Static void add(Pair<? super Integer> p, Integer first, Integer last){
p.setFirst(first);
p.setLast(last);
Integer i = p.getFirst(); // Error 無法調(diào)用獲取 Integer 引用的方法
Object o = p.getFirst(); // 獲取 object 的引用
}
}
PairHelper.add(new Pair<Integer>(1, 2)); // Integer 是 Integer 本身
PairHelper.add(new Pair<Number>(1, 2)); // Number 是 Integer 的父類
void process(List<? extends Number> list){
}
public class List<T> {
T get(int index); // ERORR
void add(T t);
void remove(T t);
}
2盲链、使用類似 <? super Integer> 通配符作為 方法參數(shù) 時表示(代碼如上):
(1)方法內(nèi)部可以調(diào)用傳入 Integer 引用的方法:obj.setXxx(Integer n)
(2)方法內(nèi)部無法調(diào)用獲取 Integer 引用的方法(Object 除外):Object o = p.getFirst() // 獲取 object 的引用
3、super 通配符作為 方法參數(shù) 實例
void someMethod(List<? super Integer> list) {
list.add(123);
Integer n = list.get(0); // ERROR
}
以上實例:
(1)允許傳入List<Integer>,List<Number>刽沾,List<Object>
(2)允許調(diào)用方法傳入Integer類型
(3)不允許調(diào)用方法獲取Integer類型(Object除外)
4本慕、supper 通配符的另一個用法 -- 定義泛型類:<T super Integer>
(1)使用 <T super Integer> 定義泛型類 時表示:泛型類限定為 Integer 或者 Integer 超類。
(2)實例:
public class Pair<T super Integer>{
}
Pair<Integer> ip = new Pair<>(1, 2); // OK
Pair<Number> dp = new Pair<>(1.2, 2.4); // OK
Pair<String> cp = new Pair<>("a", "2"); // error
5侧漓、extends 和 super 通配符的區(qū)別:
(1)<? extends T> 允許調(diào)用方法獲取 T 的引用
(2)<? super T> 允許調(diào)用方法傳入 T 的引用
public class Collections {
// 把 src 的每個元素復(fù)制到 dest 中
public static <T> void copy(List<? super T> dest, List<? extends T> src){
for (int i = 0; i < src.size(); i++){
T t = src.get(i);
dest.add(t);
}
}
}
6锅尘、無限定通配符 <?> 很少使用,只能獲取 Object 引用布蔗,只能傳入 null藤违,可以用 <T> 替換。
(七) 泛型和反射
1纵揍、部分反射 API 是泛型:
(1)Class<T> 是泛型顿乒,泛型.getSuperclass() 方法的返回結(jié)果也是泛型
// compile warning 編譯器警告
Class clazz = String.class;
String str = (String) clazz.newInstance();
// 正確使用 Class<T>
Class<String> clazz = String.class;
String str = clazz.newInstance(); // T.newInstance() 無需強制轉(zhuǎn)型
// getSuperclass()
Class <? super String> sup = clazz.getSuperclass();
(2)Constructor<T> 是泛型
Class<Integer> clz = Integer.class;
Constructor<Integer> cons = clz.getConstructor(int.class);
Integer i = cons.newInstance(123);
2、泛型數(shù)組
(1)可以聲明帶泛型的數(shù)組骡男,但是不能用 new 創(chuàng)建帶泛型的數(shù)組淆游,必須強制轉(zhuǎn)型:
Pair<String>[] ps = null;
Pair<String>[] ps = new Pair<String>[2]; // erorr
(2)必須通過強制轉(zhuǎn)型實現(xiàn)帶泛型的數(shù)組:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
(3)使用泛型數(shù)組時要注意安全地使用傍睹,因為數(shù)組在運行過程中是沒有泛型的隔盛。
// 錯誤用法
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr; // ps 和 arr 指向了同一個數(shù)組
ps[0] = new Pair<String>("a", "b");
arr[1] = new Pair<Integer>(1, 2); // 這里還不會報錯
// ClassCastException:
Pair<String> p = ps[1]; // 會報錯,因為 arr 對數(shù)組進行了改動拾稳,出入了 Integer 類型的參數(shù)吮炕,而這里返回值是 Pair<String> 類型,不是 Pair<Integer>
String s = p.getFirst();
// 正確用法
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
3访得、帶泛型的數(shù)組實際上是編譯器的類型擦除龙亲,所以對帶泛型的數(shù)組調(diào)用 getClass() 方法,返回的是 定義的類型.class
Pair [] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;
System.out.println(ps.getClass() == Pair[].class); // true getClass 的結(jié)果為 Pair[].class
String s1 = (String) arr[0].getFirst();
String s2 = ps[0].getFirst();
4悍抑、不能直接創(chuàng)建 T[] 數(shù)組(因為擦拭后代碼變?yōu)榱?new Object[])鳄炉,可以通過 Array.newInstance(Class<T>, int) 創(chuàng)建 T[] 數(shù)組,需要強制轉(zhuǎn)型搜骡。
(1)方法一:借助 Class<T>
public class Abc<T> {
T[] createArray(Class<T> cls){
return (T[]) Array.newInstance(cls, 5);
}
}
(2)方法二:利用可變參數(shù)(T...)創(chuàng)建 T[] 數(shù)組:
public class ArrayHelper{
@SafeVarargs //消除編譯警告
static <T> T[] asArray(T... objs) { // ...表示可變
retrun objs;
}
}
String[] as = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
(八) 泛型使用實例
1拂盯、案例一:
package com.test;
public class Pair<T>{
private T first;
private T last;
public Pair(T first, T last){
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
}
package com.test;
public class Main{
public static void main(String[] args){
Pair<Number> p = new Pair<>(123, 45.65);
int sum = add(p);
System.out.println(sum);
System.out.println(add(new Pair<Integer>(123, 456)));
System.out.println(add(new Pair<Double>(12.4, 23.4)));
}
static int add(Pair<? extends Number> p){
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}