Java 学习基础篇 ---- Java反射与泛型

一、反射

通过Class实例获取 class 信息的方法称为反射(Reflection)。

(一) Class 类

Java 除基本类型外其他都是class类型(包括 interface,例如:String、Object、Runnable、Exception),class 的本质就是一种数据类型;
1、Class 的简介
(1)class/interface 的数据类型是 Class(如下代码),每加载一个 class,JVM 就为其创建了一个 Class 类型的实例,并关联起来(如下图:“创建class.png”)

public final class Class{
    private Class(){}
}
创建Class.png

(2)JVM 持有的每个 Class 实例都指向一个数据类型(class 或 interface,如下图:”Class实例.png“)。

Class实例.png

(3)一个 Class 实例包含了该 class 的完整信息,例如下图是指向 String 类(class)的 Class 实例:它包含了 String 这个类的 name、package、super、interface、field、method 等信息。

String类.png

(4)JVM 为每个加载的 class 创建对应的 Class 实例,并在实例中保存该 class 的所有信息。

2、反射:通过Class实例获取class信息的方法称为反射。如果获取了某个 Class 实例,则可以获取到该实例对应的 class 的所有信息。

3、获取一个 class 的 Class 实例有三种方法:
(1)使用 Type.class :

// 获取 String 类的 Class 实例
Class cls = String.class;      

(2)对实例变量调用 getClass 方法:

// 通过 String 类的实例 s 来获取 String 类的 Class 实例
String s = "hello";
Class cls = s.getClass();

(3)使用 Class 提供的 静态方法 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 可以判断是否相等,判断子类关系。
(2)Class 实例比较符 "==",只能判断是否相等,而不能判断是否是子类关系。

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 创建 class 实例

Class cls = String.class;
// new String();
String s = (String) cls.newInstance();

9、利用 JVM 动态加载 class 的特性,可以在运行期根据条件加载不同的实现类(例如 Commons Logging 的使用):

// Commons Logging 优先使用 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 总是动态加载 class,所以我们可以在运行期根据条件控制加载 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):获取当前类的某个field(不包括父类)
(3)getFields():获取所有 public 的field(包括父类)
(4)getDeclaredFields():获取当前类的所有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、获取和设置field的值:
(1)get(Object obj) 获取一个实例的该字段 field 的值

Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("value");
f.get(n);     // 123  相当于 n.value

(2)set(Object, Object) 设置 一个实例的该字段 field 的值

Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("value");
f.set(n, 234);      // 相当于 n.value = 234

4、通过设置 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);           //  设置为 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会有一个规则,如果这个规则阻止对 Field 设置 accessible(例如:规则应用于所有 java 和 javax 开头的 package 的类,则就不能访问 private 字段)。通常情况下我们自己编写的类 和 第三方类没有这个限制。

(三) 调用方法

1、通过Class实例获取方法Method信息:
(1)getMethod(name, Class...):获取某个public的method(包括父类)
(2)getDeclaredMethod(name, Class...):获取当前类的某个method(不包括父类)
(3)getMethods():获取所有public的method(包括父类)
(4)getDeclaredMethods():获取当前类的所有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、调用Method:
(1)调用无参数的 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"  相当于 String s = n.toString();

(2)调用带参数的 Method 方法:Object invoke(Object obj, Object...args),即第一个参数是实例变量,第二个参数是方法参数

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, 相当于 int r = n.compareTo(456)

4、通过设置setAccessible(true)来访问非public方法。

5、反射调用Method也遵守 多态 的规则。
例:从 Person.class 获取的 Method,作用于 Student 实例时,实际调用的 方法是 Student 覆写的方法, 保证了多态的正确性

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,这里实现了多态   相当于 Person p = new Student();  p.hello();

(四) 调用构造方法

1、调用 public 无参数构造方法:
(1)使用 Class.newInstance() 调用
(2)newInstance() 只能调用无参数的构造方法

String s = (String) String.class.newInstance();      // String.class 是 Class 对象,String 类定义的了一个 无参数构造方法,所以可以使用 newInstance()
Integer n = (Integer) Integer.class.newInstance();   // error  Integer 类没有无参数的构造方法,无法使用 newInstance()

2、调用带参数的 构造方法
(1)调用带参数的构造方法,不能直接使用 Class 的 newInstance 方法。需要获取 Constructor 对象,Constructor 对象包含一个构造方法的所有信息,通过 Constructor 对象的 newInstance() 方法来调用。(即Constructor实例使用 newInstance 可以创建一个实例对象)
(2)实例:

Class cls = Integer.class;
// 调用构造方法 Integer(int)
Constructor cons1 = cls.getConstructor(int.class);            // 获取一个 Constructor 对象
Integer n1 = (Integer) cons1.newInstance(123);             // 通过 Constructor 对象的 newInstance 方法来调用有参数的构造方法
// 调用构造方法 Integer(String)
Constructor cons2 = cls.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("123");

(3)通过Class实例获取 构造方法 的信息:
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 方法创建一个 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)通过设置setAccessible(true)来访问非public构造方法。

3、注意区分 Class.newinstance 和 Constructor.newinstance 的区别

(五) 获取继承关系

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、获取当前类直接实现的 interface(不包括间接实现的interface) :
(1)使用 Class[] getInterfaces() 来获取实现的 interface
(2)没有interface的class返回空数组
(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、判断一个向上转型是否成立:bool isAssignableFrom(Class)

// Integer i = ...
// Number x = i ?
Number.class.isAssignableFrom(Integer.class);   // true     Integer 向上转型为 Number 成立
// Number n = ...
// Integer i = n ?
Integer.class.isAssignableFrom(Number.class);   // false

4、总结
(1)getSuperclass() // 获取类的父类
(2)getInterfaces() // 获取类直接实现的接口
(3)isAssignableFrom() // 判断向上转型是否正确

二、注解

(一) 使用注解

1、注解的定义:注解(Annotation)是放在Java源码的类、方法、字段、参数前的一种标签,是 java 语言用于工具处理的标注。

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:让编辑器检查该方法是否正确地实现了覆写
(2)@Deprecated:告诉编译器该方法已经被标记为"作废",在其他地方引用将会出现编译警告
(3)@SuppressWarnings:告诉编译器忽略某种警告
(4)写注解编译器会帮助检查问题,不写注解编译器就不会检查。

4、注解可以定义配置参数:
(1)配置参数由注解类型定义
(2)配置参数可以包括:
  (a)所有基本类型
  (b)String
  (c)枚举类型
  (d)数组
(3)配置参数必须是常量

public class Hello{
    int n = 100;
    @Test(timeout=100)
    public void test(){
        System.out.println("Test");
    }
}

5、缺少某个配置参数将使用默认值
(1)如果只写常量,相当于省略了 value 参数
(2)如果只写注解,相当于全部使用默认值,默认值的定义是在注解定义时定义的

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)注解的参数类似无参数方法
(2)可以给注解设定一个 默认值 (推荐)
(3)把最常用的参数命名为 value能(推荐)

public @interface Report{
    int type() default 0;
    String level() default "info";
    String value() default "";
}

2、元注解:
(1)有些注解可以修饰其他注解,称为元注解。
(2)使用 @Target 定义Annotation 可以被应用于源码的哪些位置
  (a)类或接口:ElementType.TYPE
  (b)字段:ElementType.FIELD
  (c)方法:ElementType.METHOD
  (d)构造方法:ElementType.CONSTRUCTOR
  (e)方法参数:ElementType.PARAMETER

@Target(ElementType.METHOD)          // 声明 Resport 注解只能应用于 方法  上
public @interface Report{
    int type() default 0;
    String level() default "info";
    String value() default "";
}
@Target({ElementType.METHOD, ElementType.FIELD})       // 声明 Report 注解只能应用于 方法、字段 上。
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 默认为 CLASS,通常自定义的注解 Annotation 都是 RUNTIME。

@Retention(RetentionPolicy.RUNTIME)
public @interface Report{
    int type() default 0;
    String level() default "info";
    String value() default "";
}

(4)使用 @Repeatable 定义 注解Annotation 是否可重复 (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、总结定义注解步骤:
(1)用 @interface 定义注解
(2)用 元注解(meta annotation)配置注解
  (a)Target:必须设置
  (b)Retention:一般设置为 RUNTIME
  (c)通常不必写 @Inherited、@Repeatable 等等
(3)定义注解参数和默认值

package com.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)         // 1、定义一个应用于字段 Field 的 NotNUll 空注解
// @Target({ElementType.FIELD, ElementType.PARAMETER})    // 1、如果还想要应用于 函数的参数字段上 还需要添加 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){     // 应用方法参数 name, 声明 name 不可为空
        this.name = name;
        this.age = age;
    }
}

4、总结:
(1)使用 @interface 定义注解
(2)可以定义多个参数和默认值,核心参数使用 value 名称
(3)必须设置 @Target 来指定 Annotation 可以应用的范围
(4)应当设置 @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 读取方法参数的注解 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 参数的 Annotation
for (Annotation anno : annosOfName){
     if (anno instanceof Range){
         Range r = (Range) anno;
    }
}
// 以下是 annos 的内容
{
    {@NotNull, @Range(max=5)},
    {@NotNull}
}

4、可以通过工具处理注解来实现相应的功能:
(1)对JavaBean的属性值按规则进行检查
(2)JUnit会自动运行@Test标记的测试方法

(四) 注解的应用:

1、编写注解 @NotNull:检查该属性为非null
2、编写注解 @Range:检查整型介于min~max,或者检查字符串长度介于min~max
3、编写注解 @ZipCode:检查字符串是否全部由数字构成,且长度恰好为value

三、泛型

(一) 泛型的定义

1、泛型就是定义一种模版,例如 ArrayList
(1)在代码中为用到的类创建对应的 ArrayList<类型>:
   ArrayList strList = new ArrayList();
(2)编译器针对类型作检查操作:
   strList.add("hello");
   strList.add(new Integer(123)); // complie error!

// 定义泛型
public class ArrayList{
    private T[] array;
    public void add(T e){
    }
    public void remove(int index){
    }
    public T get(int index) {
    }
}

// 调用泛型
ArrayList strList = new ArrayList();
ArrayLIst floatList  = new ArrayList();
ArrayLIst psList  = new ArrayList();

2、泛型的继承关系
(1)向上转型

public class ArrayList implements List {
    private T[] array;
    public void add(T e) {
    }
    public void remove(int index){
    }
    public T get(int index){
    }
}

List list = new ArrayLIst();      // 向上转型为 List

(2)注意泛型的继承关系:
   a. 可以把 ArrayLIst 向上转型为 List (T 不能变);
   b. 不能把 ArrayLIst 向上转型为 ArrayLIst 或 List,因为 ArrayList、 List 和 ArrayList 没有继承关系;

(二) 泛型的使用

1、使用泛型时,可以省略编译器能自动推断出的类型

// 可以省略后面的 Number,编译器可以自动推断类型
List list = new ArrayList<>();       

2、Arrays.sort() 可以对 Object[] 数组进行排序:

String[] strs = {"Apple", "Pear", "Orange"};
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));
// {"Apple", "Orange", "Pear"}

3、泛型的好处是在编译的时候检查类型安全,并且所有的 强制转换 都是自动和隐式的,提高代码的重用率。

4、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。

(三) 泛型的编写(不常用)

1、如何编写一个泛型类:
(1)按照某种类型(例如 String)编写类
(2)标记所有的特定类型(例如 String)
(3)把特定类型替换为 T, 并申明

public class Pair {
    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、静态方法不能引用泛型类型,必须定义其他类型来实现“泛型”,即:编译器无法在 静态字段 或者 静态方法 中使用泛型类型.

public class Pair {
    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 create(T first, T last){
        return new Pair(first, last);
    }

    // 正确方法如下
    public static  Pair create(K first, K last){        // static 后也加了
        return new Pair(first, last);
    }
}

3、编写泛型时可以定义多种类型

public class Pair{
    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 p = new Pair<>("test", 123);

(四) 擦拭法

1、Java 的 泛型是采用擦拭法实现的,虚拟机其实对泛型一无所知,所有的泛型都是编译器在操作。
编译器把类型 视为 Object
编译器根据 实现安全的强制转型

2、擦拭法的局限
(1)不能是基本类型,例如:int
(2)Object字段无法持有基本类型
(3)无法取得带泛型的Class,例如:Pair.class, 即无论 T 是什么类型,Class 都是 Pair.class

Pair s = ...;
Pair 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.class){
}

(4)无法判断带泛型的Class,例如:x instanceof Pair
(5)不能实例化 T 类型,因为擦拭之后实际上是 new Object(),例如:new T()。 实例化 T类型必须借助 Class

// 错误的实例化
public class Pair{
    private T first;
    private T last;
    public Pair(){
        // Compile error;
        first = new T();
        last = new T();
    }
}

Pair pair = new Pair<>();
// 正确的实例化
public class Pair{
    private T first;
    private T last;
    public Pair(Class clazz){
        first = clazz.newInstance();
        last = clazz.newInstance();
    }
}

Pair pair = new Pair<> (String.class); 

(6)泛型方法要防止重复定义方法,例如:public boolean equals(T obj)

3、子类可以获取父类的泛型类型

public class IntPair extends Pair{
}

Class 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接口, 他的实现类包括:Class、ParameterizedType、GenericArrayType、WildcardType。

(五) extends 通配符

1、使用 extends 通配符,可以接收本身类型及子类,例如:
案例一:使用 Pair 可以使方法接收所有泛型类型为 Number 或 Number 子类的 Pair 类。
案例二:使用 Pair 可以使方法接收所有泛型类型为 String 或 String 子类的 Pair 类。

public class Pair{
}

public class PairHelper{
    Static void add(Pair 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(1, 2));
PairHelper.add(new Pair(1,2));
void process(List list){
}

public class List {
    T get(int index);
    void add(T t);             // ERORR        无法调用传入 Integer 引用的方法,   add 同 set 一样
    void remove(T t);            // ERORR       无法调用传入 Integer 引用的方法,   remove 同 set 一样
}

2、 使用类似 通配符作为 方法参数 是表示(代码如上):
(1)方法内部可以调用获取 Integer 引用的方法(get方法),例如:Number n = obj.getXxx()
(2)方法内部无法调用传入 Integer 引用的方法(set方法 null 除外),例如:obj.setXxx(Number n) // 报错

3、extends 通配符作为 方法参数 实例

void someMethod(List list) { 
    Number n = list.get(0); 
    list.add(n);   // ERROR 
} 

以上实例:
(1)允许传入List,List,List

4、extends 通配符的另一个用法 -- 定义泛型类:
(1)定义泛型时可以通过extends限定T 必须 是Number或Number的子类
(2)方法内部可以调用获取(get) Number 引用的方法,Number n = obj,getXxx()。
(3)方法内部无法调用传入(set、add、remove) Number 引用的方法(null 除外),obj.setXxx(Number n)。

5、使用类似 定义泛型类型时表示:泛型类型限定为 Number 或 Number 的子类

(六) super 通配符

1、使用 super 通配符,可以接收本身类型及超类,例如:
案例一:使用 Pair 可使方法接受所有泛型类型为 Integer 或 Integer 超类的 Pair 类
案例二:使用 Pair 可使方法接受所有泛型类型为 String 或 String 超类的 Pair 类

public class Pair {
}

public class PairHelper{
    Static void add(Pair p, Integer first, Integer last){
        p.setFirst(first);
        p.setLast(last);
        Integer i = p.getFirst();        // Error 无法调用获取 Integer 引用的方法
        Object o = p.getFirst();          // 获取 object 的引用
    }
}

PairHelper.add(new Pair(1, 2));          // Integer 是 Integer 本身
PairHelper.add(new Pair(1, 2));        // Number 是 Integer 的父类
void process(List list){
}

public class List {
    T get(int index);                // ERORR 
    void add(T t);                    
    void remove(T t);            
}

2、使用类似 通配符作为 方法参数 时表示(代码如上):
(1)方法内部可以调用传入 Integer 引用的方法:obj.setXxx(Integer n)
(2)方法内部无法调用获取 Integer 引用的方法(Object 除外):Object o = p.getFirst() // 获取 object 的引用

3、super 通配符作为 方法参数 实例

void someMethod(List list) {
    list.add(123); 
    Integer n = list.get(0);    // ERROR 
}

以上实例:
(1)允许传入List,List,List
(2)允许调用方法传入Integer类型
(3)不允许调用方法获取Integer类型(Object除外)

4、supper 通配符的另一个用法 -- 定义泛型类:
(1)使用 定义泛型类 时表示:泛型类限定为 Integer 或者 Integer 超类。
(2)实例:

public class Pair{
}
Pair ip = new Pair<>(1, 2);          // OK
Pair dp = new Pair<>(1.2, 2.4);         // OK
Pair cp = new Pair<>("a", "2");        // error

5、extends 和 super 通配符的区别:
(1) 允许调用方法获取 T 的引用
(2) 允许调用方法传入 T 的引用

public class Collections {
    // 把 src 的每个元素复制到 dest 中
    public static  void copy(List dest, List src){
        for (int i = 0; i < src.size(); i++){
            T t = src.get(i);
            dest.add(t);
        }
    }
}

6、无限定通配符 很少使用,只能获取 Object 引用,只能传入 null,可以用 替换。

(七) 泛型和反射

1、部分反射 API 是泛型:
(1)Class 是泛型,泛型.getSuperclass() 方法的返回结果也是泛型

// compile warning 编译器警告
Class clazz = String.class;
String str = (String) clazz.newInstance();

// 正确使用 Class
Class clazz = String.class;
String str = clazz.newInstance();         // T.newInstance()   无需强制转型

// getSuperclass()
Class  sup = clazz.getSuperclass();

(2)Constructor 是泛型

Class clz = Integer.class;
Constructor cons = clz.getConstructor(int.class);
Integer i = cons.newInstance(123);

2、泛型数组
(1)可以声明带泛型的数组,但是不能用 new 创建带泛型的数组,必须强制转型:

Pair[] ps = null;
Pair[] ps = new Pair[2];        // erorr

(2)必须通过强制转型实现带泛型的数组:

@SuppressWarnings("unchecked")
Pair[] ps = (Pair[]) new Pair[2];

(3)使用泛型数组时要注意安全地使用,因为数组在运行过程中是没有泛型的。

// 错误用法
Pair[] arr = new Pair[2];
Pair[] ps = (Pair[]) arr;         // ps 和 arr 指向了同一个数组

ps[0] = new Pair("a", "b"); 
arr[1] = new Pair(1, 2);      // 这里还不会报错

// ClassCastException:          
Pair p = ps[1];          // 会报错,因为 arr 对数组进行了改动,出入了 Integer 类型的参数,而这里返回值是 Pair 类型,不是 Pair
String s = p.getFirst();
// 正确用法
@SuppressWarnings("unchecked")
Pair[] ps = (Pair[]) new Pair[2];

3、带泛型的数组实际上是编译器的类型擦除,所以对带泛型的数组调用 getClass() 方法,返回的是 定义的类型.class

Pair [] arr = new Pair[2];
Pair[] ps = (Pair[]) arr;
System.out.println(ps.getClass() == Pair[].class);       // true     getClass 的结果为 Pair[].class
String s1 = (String) arr[0].getFirst();
String s2 = ps[0].getFirst();

4、不能直接创建 T[] 数组(因为擦拭后代码变为了 new Object[]),可以通过 Array.newInstance(Class, int) 创建 T[] 数组,需要强制转型。
(1)方法一:借助 Class

public class Abc {
    T[] createArray(Class cls){
        return (T[]) Array.newInstance(cls, 5);
    }
}

(2)方法二:利用可变参数(T...)创建 T[] 数组:

public class ArrayHelper{
    @SafeVarargs         //消除编译警告
    static  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{
    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 p = new Pair<>(123, 45.65);
        int sum = add(p);
        System.out.println(sum);
        System.out.println(add(new Pair(123, 456)));
        System.out.println(add(new Pair(12.4, 23.4)));
    }
    static int add(Pair p){
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

你可能感兴趣的:(Java 学习基础篇 ---- Java反射与泛型)