Java 學(xué)習(xí)基礎(chǔ)篇 ---- Java反射與泛型

一泉褐、反射

通過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(){}
}
創(chuàng)建Class.png

(2)JVM 持有的每個 Class 實例都指向一個數(shù)據(jù)類型(class 或 interface,如下圖:”Class實例.png“)雕薪。

Class實例.png

(3)一個 Class 實例包含了該 class 的完整信息谦去,例如下圖是指向 String 類(class)的 Class 實例:它包含了 String 這個類的 name、package蹦哼、super鳄哭、interface、field纲熏、method 等信息妆丘。

String類.png

(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();
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市记靡,隨后出現(xiàn)的幾起案子谈竿,更是在濱河造成了極大的恐慌,老刑警劉巖摸吠,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件空凸,死亡現(xiàn)場離奇詭異,居然都是意外死亡寸痢,警方通過查閱死者的電腦和手機呀洲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人道逗,你說我怎么就攤上這事丛楚。” “怎么了憔辫?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵趣些,是天一觀的道長。 經(jīng)常有香客問我贰您,道長坏平,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任锦亦,我火速辦了婚禮舶替,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杠园。我一直安慰自己顾瞪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布抛蚁。 她就那樣靜靜地躺著陈醒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞧甩。 梳的紋絲不亂的頭發(fā)上钉跷,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音肚逸,去河邊找鬼爷辙。 笑死,一個胖子當(dāng)著我的面吹牛朦促,可吹牛的內(nèi)容都是我干的膝晾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼务冕,長吁一口氣:“原來是場噩夢啊……” “哼血当!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洒疚,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤歹颓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后油湖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巍扛,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年乏德,在試婚紗的時候發(fā)現(xiàn)自己被綠了撤奸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吠昭。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胧瓜,靈堂內(nèi)的尸體忽然破棺而出矢棚,到底是詐尸還是另有隱情,我是刑警寧澤府喳,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布蒲肋,位于F島的核電站,受9級特大地震影響钝满,放射性物質(zhì)發(fā)生泄漏兜粘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一弯蚜、第九天 我趴在偏房一處隱蔽的房頂上張望孔轴。 院中可真熱鬧,春花似錦碎捺、人聲如沸路鹰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晋柱。三九已至,卻和暖如春帽氓,著一層夾襖步出監(jiān)牢的瞬間趣斤,已是汗流浹背俩块。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工黎休, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人玉凯。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓势腮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親漫仆。 傳聞我的和親對象是個殘疾皇子捎拯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359