欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java学习笔记(四)——反射与泛型

程序员文章站 2022-03-08 18:50:03
...

一、反射

(一)类Class

1 . Class的概念

我们知道在Java中除了基本类型之外的其他类型都是class(包括interface),例如:String,Object,Runnable,Exception等。class的本质其实就是数据类型,Java规定class/interface的数据类型是Class。
当我们把一个类型的实例赋值给一个数据类型的变量时,必须严格按照数据类型进行赋值,无继承关系的数据类型无法赋值,编写不符合数据类型要求的代码,编译器就会发生报错。
(1)JVM持有的每一个Class类型都指向一个数据类型(class或interface)
例如,JVM在加载String类时,首先会读取String.class文件,然后为String类创建一个Class实例(这个过程是JVM在其内部实现)
(2)JVM为每个加载的class创建对应的Class实例,并在实例中保存该class的所有信息
(3)如果获取了某个Class实例,则可以获取到该实例对应的class的所有信息
例如:name,package,super,interface,field,method等
(4)通过Class实例获取class信息的方法称为反射(Reflection)

2 . 如何获取class的Class实例

(1)Type.class

Class cls = String.class;
//返回String类对应的Class实例

(2)getClass()

String s = "Hello";
Class cls = s.getClass();
//对实例变量调用getClass方法来返回Class实例

(3)Class.forName()

Class cls = Class.forName("java.lang.String");
//利用Class提供的静态方法forName()传入完整类名得到对应的Class

2 . 比较Class实例

在JVM中,一个class只对应唯一的一个Class实例,可以使用==操作符比较两个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

Class实例比较和instanceof比较的差别:
(1)当我们使用instanceof进行比较时,不但匹配当前的类型,还匹配当前类型的子类。例如:

Integer n = new Integer(123);

boolean b3 = n instanceof Integer;//true
boolean b4 = n instanceof Number;//true
//因为Integer是Number的子类,所以判断结果都为true

(2)当我们使用==操作符对Class实例进行比较时,由于Class实例只能精确的判断数据类型,不能进行子类的比较。例如:

boolean b1 = n.getClass() == Integer.class;//true
boolean b2 = n.getClass() == Number.class;//false

3 . 反射的目的

反射的目的是当获得某个Object实例时,我们可以获取该Object的class信息

(1)从Class实例获取class信息:

①获取完整类名:getName()
②获取简单类名:getSimpleName()
③获取包名:getPackage()

Class cls = String.class;

String fname = cls.getName();//"java.lang.String"
String sname = cls.getSimpleName();//"String"
String pkg = cls.getPackage();//"java.lang"

(2)从Class实例判断class类型:

①判断数据类型:isinterface()
②判断枚举类型:isEnum()
③判断数组类型:isArray()
④判断基本类型:isPrimitive()

Runnable.class.isinterface();//true
java.time.Month.class.isEnum();//true
String[].class.isArray();//true
int.classisPrimitive();//true

(3)通过Class创建class实例:

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

注意:在这里我们只能调用默认构造方法,带有参数的构造方法无法通过此方式进行调用

(4)动态加载

利用JVM动态加载class的特性:
可以在运行期间根据条件加载不同的类
例如:
如果我们要实现Commons Logging这个功能
首先我们判断org.apache.logging.log4j.Logger这个类是否存在
利用if-else语句,如果这个类存在就优先使用Log4j,如果不存在则使用JVM自带的JDKLog

//Commons Logging优先使用Log4j
LogFactory factory;
if(isClassPresent("org.apache.logging.log4j.Logger")){
    factory = createLog4j;
} else {
    factory = createJDKLog();
}
//在JVM执行这段代码时,并没有引用Log4j的Class本身,在这种情况下即使没有放入Log4j的jar包,代码也依然会执行

而判断一个Class是否存在,我们只需要使用Class.forName()方法,然后捕获Class Not Found异常就可以

boolean isClassPresent(String name) {
    try {
        Class.forName(name);
        return true;
    } catch (Exception e) {
        return false;
    }
}

这就是我们只需要把Log4j的jar包放入classpath中,Commons Logging就可以直接使用Log4j的原因。

(二)访问字段

1 . 通过Class实例获取field信息

(1)获取某个public的field(包括父类):getField(Name)
(2)获取当前某各类的field(不包括父类):getDeclaredField(Name)
(3)获取所有public的field(包括父类):getFields()
(4)获取当前类的所有field(不包括父类):getDeclaredFields()

2 . Field对象包含一个field的所有信息

(1)获得field字段的名称:getName()
(2)获得field字段定义的类型:getType()
(3)获得field字段定义的修饰符: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)

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

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

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

4 . 访问非public字段

通过反射访问Field需要通过SecurityManager设置的规则。
通过设置setAccessible(true)来访问非public字段。
setAccessible(true)
如果定义了SecurityManager,SecurityManager的规则将会阻止对该field设置Accessible

(三)调用方法

1 . 通过Class实例获取method信息

(1)获取某个public的method(包括父类):getMethod(name,Class…)
(2)获取当前类的某个method(不包括父类):getDeclaredMethod(Name,Class…)
(3)获取所有public的method(包括父类):getMethods()
(4)获取当前类的所有的method(不包括父类):getDeclaredMethods()

2 . Method对象包含一个method的所有信息

(1)返回method的名称:getName()
(2)返回method的返回类型:getReturnType()
(3)返回method的参数类型:getParamaterType()
(4)返回method的修饰符: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.getParamaterType();
    m.Modifiers();//modifiers
}

3 . 调用Method

(1)调用无参数的Method:Object invoke(Object obj):

Integer n = new Integer(123);
Class cls = n.getClass();
Method m = cls.getMethod("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);
int r = (Integer) m.invoke(n,456);
//( -1 ),相当于int r = n.compareTo(456);

4 . 访问非public方法

使用setAccessible(true)来访问非public方法
如果定义了SecurityManager,SecurityManager的规则将会阻止对该method设置Accessible

5 . 多态

从Person.class获取的Method作用于Student实例时,实际调用方法是Student覆写的方法,保证了多态的多样性特点

(四)调用构造方法

1 . 调用无参数构造方法

使用调用无参数构造方法:Class.newInstance()

String s = (String)String.class.newInstance();

2 . 调用带参数构造方法

想要调用带参数的构造方法, 需要获取一个Constructor对象。
Constructor对象包含一个构造方法所有的信息,可以创建一个实例,通过Class实例获取Constructor信息:

Class cls = Integer.class;
//Integer(int)
Constructor con1 = cls.getConstructor(int.class);
Integer n1 = (Integer) con1.newInstance(123);
//Integer(String)
Constructor con2 = cls.getConstructor(String.class);
Integer n2 = (Integer) con2.newInstance("123");

①获取某个public的Constructor:getConstructor(Class…)
②获取某个Constructor:getDeclaredConstructor(Class…)
③获取所有public的Constructors:getConstructors()
④获取所有Constructor:getDeclaredConstructors()
注意:Constructor总是代表当前类的构造方法,与继承关系无关,所以我们无法获取父类的构造方法

3 . 访问非public方法

使用setAccessible(true)访问非public的构造方法
如果定义了SecurityManager,SecurityManager的规则将会阻止对该Constructor设置Accessible

(五)获取继承关系

1 . 获取父类的Class

当我们得到一个Class实例时,可以获取其父类的Class:
(1)通过Class getSuperclass()方法返回父类的Class对象
(2)如果当前Class是Object,那么返回的父类是null(Object是所有类的根类)
(3)如果当前Class是Interface,那么返回的的父类是null(interface没有Class)

Class sup = Integer.class.getSuperclass();//Number.class
Objert.class.getSuperclass();//null
Runnable.class.getSuperclass();//null

2 . 获取当前类直接实现的Interface

使用Class[] getInterfaces()方法:

Class[] ifs = Integer.class.getInterfaces();//[Comparable.class]
Class[] ifs = java.util.ArrayList.class.getInterfaces();//[List.class, RandomAccess.class, Cloneable.class, Serializable.class]
Class[] ifs = Math.class.getInterfaces();//[]
Class[] ifs = java.util.List.getInterfaces();//[Collection.class]

(1)不包括间接实现的interface
(2)没有interface的class返回空数组
(3)interface返回继承的interface

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
//相当于判断Number类型的变量能否赋值给Integer类型的变量

二、注解Annotation

(一)使用注解

1 . 注解的概念

(1)注解(Annotation)是放在Java源码的类、方法、字段、参数前的一种标签
(2)注解本身对于代码的逻辑没有任何影响
(3)如何使用注解由工具决定

@Resource("hello")
public class Hello {
    @Inject
    int n;

    @PostConstruct
    public void hello(@Param String name) {
        System.out.println(name);
    }
    @Override
    public String toString() {
        return "Hello";
    }
}

2 . 注解的作用

编译器可以使用的注解:
(1)@Override:让编译器检查该方法是否正确的进行了覆写
(2)@Deprecated:告知编译器该方法已经被标记为“作废”,在其他地方引用将出现编译警告
(3)@SuppressWarning:告知编译器忽略出现的编译警告

public class Hello {
    @Override
    public String toString() {
        return "Hello";
    }

    @Deprecated
    public void hello(String name) {
        System.out.println(name);
    }

    @SuppressWarning("unused")
    public void hello() {
        int n;//local variable n is not used
    }
}

3 . 定义配置参数

注解可以定义配置参数
(1)配置参数由注解类型定义
(2)配置参数可以包括:
①基本类型
②String
③枚举类型
④数组:基本类型数组,String数组,枚举类型数组
(3)配置参数必须是常量

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

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

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 . 使用@interface定义注解

在Java中我们使用@interface来定义注解(这里的@interface与接口interface不同):

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

(1)注解的参数类似于无参数的方法
(2)推荐设置一个默认值
(3)推荐将最常用的参数命名为value

2 . 元注解

有些注解可以用来修饰其他的注解,我们称这些注解为元注解。JDK已经定义了一些元注解,我们可以直接使用,通常情况下不必自行定义元注解
(1)使用@Target定义Annotation可以被应用于:
①类或接口:ElementType.TYPE
②字段:ElementType.FIELD
③方法:ElementType.METHOD
④构造方法:ElementType.CONSTRUCTOR
⑤方法参数:ElementType.PARAMETER

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

(2)使用@Retention定义Annotation的生命周期:
①编译期:
RetentionPolicy.SOURCE
编译器在编译时直接丢弃,例如:@Override
②class文件:
RetentionPolicy.CLASS
该Annotation仅储存在class文件中,但不会被读取
③运行期:
RetentionPolicy.RUNTIME
在运行期可以读取该Annotation
如果Retention不存在,则该Annotation默认为CLASS
通常情况下自定义的Annotation为RUNTIME

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

(3)使用@Repeatable定义Annotation是否可以重复:

@Repeatable
@Target(ElementType.METHOD)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {

}

(4)使用@Inherited定义子类是否可以继承父类定义的Annotation:

@Inherited
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

@Report(type=1)
public class Person {
}

public class Student extends Person {
}

①仅针对@Target为TYPE类型的Annotation
②仅针对class的继承
③对于interface的继承无效

3 . 定义Annotation的步骤

(1)用@interface定义注解
(2)用元注解(meta annotation)配置注释
①Target:必须设置来指定Annotation的应用范围
②Retention:一般设置为RUNTIME
③通常不写@Repertable、@Inherited等
定义注解参数和默认值

@Retention(RetentionPolicy.RUNTIME)//第二步:一般设置为RUNTIME
@Target(ElementType.TYPE)//第二步:必须设置@Target
public @interface Report {//第一步:用@interface定义注解
    int type() default 0;//第三步:定义参数和默认值
    String level() default "info";//第三步:定义参数和默认值
    String value() default "";//第三步:定义参数和默认值
}

(三)处理注解

通过前面的内容我们已经了解到:
(1)注解本身对于代码的逻辑没有任何影响
(2)SOURCE类型注解在编译期就被丢掉
(3)CLASS类型的注解仅仅保存在class文件中
(4)RUNTIME类型的注解在运行期间可以被读取
(5)如何使用注解由工具来决定
所以我们讨论如何处理注解只针对RUNTIME类型的注解

1 . 如何获取RUNTIME类型的注解

(1)Annotation也是class
(2)所有Annotation继承自java.lang.annotation.Annotation
(3)使用反射API

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

@Report(type=1, level="error")
public class Person {
}

2 . 判断某个Annotation是否存在

(1)Class.isAnnotationPresent(Class)
(2)Field.isAnnotationPresent(Class)
(3)Method.isAnnotationPresent(Class)
(4)Constructor.isAnnotationPresent(Class)

Class cls = Person.class;
//判断@Report是否存在
cls.isAnnotationPresent(Report.class);

3 . 获得一个Annotation

(1)Class.getAnnotation(Class)
(2)Field.getAnnotation(Class)
(3)Method.getAnnotation(Class)
(4)Constructor.getAnnotation(Class)

Class cls = Person.class;
Report report = cls.getAnnotation(Report.class)
int type = report.type();
String level = report level();

4 . 读取方法参数的Annotation

使用getParameterAnnotations()

5 . 可以通过工具注释来实现相应的功能:

(1)对JavaBean的属性值按规则进行检查
(2)JUnit会自动运行@Test注解的测试方法


三、泛型

(一)泛型的概念

1 . 为什么需要泛型

首先我们来看一下JDK提供的ArrayList,这是一种可变长度的数组,因此使用起来比数组更加方便。
如果我们要用ArraysList存储String类型的数据,我们就需要进行强制转型,这样在编写代码过程中就很不方便并且容易出错

public class ArrayList {
    private Object[] Array;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}

ArrayList list = new ArrayList();
list.add("Hello");
String first = (String) list.get(0);

list.add(new Integer(123));
//ERROR:ClassCastException
String second = (String) list.get(1);

这时我们可以为String单独编写一种ArrayList:不需要强制转型,编译器会强制检查放入的类型,这样问题就能得到暂时的解决,例如:

public class StringArrayList {
    private String[] array;
    public void add(String e) {...}
    public void remove(int index) {...}
    public String get(int index) {...}
}

StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
//编译错误:不允许放入非String类型
list.add(new Integer(123));

但是新的问题产生了:我们还需要为Integer类型也单独编写一种ArrayList,还需要为其他所有class单独编写一种ArrayList,而JDK提供了上千种class,我们不可能去为每一种class都单独编写ArrayList
所以,我们可以为ArrayList创建一种模板:ArrayList< T >这里的T可以表示任意一种class,这样我们就能够实现:编写一次模板就能够创建任意类型的ArrayList。例如:

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

ArrayList<String> strList = new ArrayList<String>();
ArrayList<Float> floatList = new ArrayList<Float>();
ArrayList<Person> psList = new ArrayList<Person>();

所以,泛型的意义就是定义一种模板,以适应任何类型,例如ArrayList< T >
(1)在代码中为用到的类创建对应的ArrayList<类型>:
ArrayList< String > strList = new ArrayList< String >();
(2)编译器会针对泛型类型作检查:

strList.add("Hello");
strList.add(new Integer(123));//compile error!

2 . 泛型的继承关系

在JDK中,ArrayList< T >实现了List< T >接口,可以进行向上转型,将ArrayLIst< String >转化为List< String >:

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

不能把ArrayList< Integer >向上转型为ArrayList< Number >或List< Number >
ArrayList< Number >和ArrayList< Integer >两者没有继承关系

(二)使用泛型

当我们不定义泛型的时候,例如:

List list = new ArrayList();

这时List接口变为Object类型,当我们使用void add(Object)和Object get(int)等方法时,编译器会给出警告,此时只能把< T >当做Object来使用:

List list = new ArrayList();
list.add("Hello");
list.add("World");
Object first = list.get(0);
Object second = list.get(1);

当使用泛型时,把泛型参数< T >替换为需要的class类型,List< T >的泛型接口变为强类型

List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
Object first = list.get(0);
Object second = list.get(1);

可以省略编译器可以自动推断出的类型:

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

(三)编写泛型

编写泛型类比普通类要复杂,泛型类一般用在集合类中,幸运的是我们很少需要编写泛型类\O.O/

1 . 如何编写泛型类

(1)首先,按照某种类型编写类(例如String):

public class Pair {
    private String first;
    private String last;
    public Pair(String first, String last) {
        this.first = first;
        this.last = last;
    }
    public String getFirst() {
        return first;
    }
    public String getLast() {
        return last;
    }

(2)然后,标记所有的特定类型(例如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;
    }
}

通过以上3个步骤我们就完成了泛型的简单编写,当然如果大家编写熟练之后就可以直接编写< T >了,而不需要使用其他类型替换 \O.O/

2 . 泛型与静态方法

(1)泛型类型< T >不能用于静态方法,对静态方法使用泛型< T >将会出现编译错误,编译器无法在静态字段或静态方法中使用泛型类型< T >
(2)可以将静态方法改写为泛型方法< K >:

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

Pair<String, Integer> p = new Pair<>("test", 123);

(四)擦拭法

1 . 泛型的实现方式

擦拭法(Type Erasure)是Java中泛型的实现方式,
在代码编译的时候,编译器实际上是把所有类型< T >都视为Object类型,虚拟机对于泛型并不知情,所有的工作都是由编译器来完成的。在需要的时候,编译器根据< T >实现安全的强制转型

//我们所编写的泛型代码:
public static void main(String[] args) {
    Pair<String> pair = new Pair<>("Zero", "One");
    String first = pair.getFirst();
}

//编译器实际上处理的代码:
public static void main(String[] args) {
    Pair pair = new Pair("Zero", "One");
    String first = (String)pair.getFirst();
}

2 . 擦拭法的局限

(1)< T >不能是基本类型(例如int):
由于编译器将< T >类型视为Object,而Object是无法持有基本类型的
(2)无法取得带泛型的Class:
由于编译器将< T >类型视为Object,所以我们获取到的不同泛型的Class永远都是同一个Class,也就是Object
(3)无法判断带泛型的Class
(4)不能实例化< T >类型:
因为擦拭后new < T >实际上是new Object(),所以实例化T类型必须借助Class< T >,因为Class< T >传入了String.class,我们就可以实例化String类型

3 . 泛型的继承

(1)可以继承自泛型类

public class IntPair extends Pair<Integer> {
}

父类的类型是Pair< Integer >
子类的类型是IntPair
子类可以获取父类的泛型类型Integer

(2)JDK定义的Type接口的实现类包括:
①Class
②ParameterizedType
③GenericArrayType
④WildcardType

(五)extends通配符

1 . extends的作用

我们在之前了解到泛型的继承关系,Pair< Integer >并不是Pair< Number >的子类,例如:

public class Pair<T> {...}

public class PairHelper {
    static int add(Pair<Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

PairHelper.add(new Pair<Number>(1, 2));
PairHelper.add(new Pair<Integer>(1, 2));//编译错误:add()不接受Integer

如果我们既要传入Number类又要传入Number的子类的时候,就会变得很麻烦。
所以我们就需要使用泛型的extends通配符来完成实现这个功能
使用

public class Pair<T> {...}

public class PairHelper {
    static int add(Pair<?extends Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

PairHelper.add(new Pair<Number>(1, 2));//编译通过
PairHelper.add(new Pair<Integer>(1, 2));//编译通过

2 . 调用getFirst()方法

如果对Pair< ? extends Number >调用getFirst()方法
(1)方法签名会变为:? extends Number getFirst()
(2)可以安全赋值给Number类型的变量:Number x = p.getFirst();
(3)不可预测实际类型就是Integer:Integer x = p.getFirst();

3 . 调用setFirst()方法

对Pair< ? extends Number >调用setFirst()方法
(1)方法签名:void setFirst(? extends Number)
(2)无法传递任何Number类型给etFirst(? extends Number)

所以,如果我们调用了< ? extends Number >
(1)允许调用get方法获取Number的引用
(2)不允许调用set方法传入Number的引用
(3)唯一例外:可以调用setFirst(null)

4 . < T extends Number >

extends还有另外一种用法:
当我们定义class时,可以编写< T extends Number >,限定定义Pair< T >时只能是Number或Number的子类,例如:

public class Pair<T extends Number> {...}

Pair<Number> ip = new Pair<>(1, 2);
Pair<Double> ip = new Pair<>(1.2, 3.4);

(六)super通配符

1 . super通配符的作用

使用< ? super Integer >使方法接受所有泛型类型为Integer或Integer超类的Pair类

2 . 调用setFirst()方法

对Pair

3 . 调用getFirst()方法

对Pair

4 . < ? super Integer >

(1)允许调用set方法传入对Integer的引用
(2)不允许调用get方法获得Integer的引用
(3)唯一例外:可以获取Object引用,Object o = p.getFirst()

5 . < T super Number >

限定定义Pair时只能是Integer或Integer的超类

(七)extends和super通配符的区别

方法参数为< ? extends T >和方法参数为< ? super T >的区别:
(1)< ? extends T >允许调用方法获取T的引用
(2)< ? super T >允许调用方法传入T的引用

public class Collections {
    //把src的每一个元素复制到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);
        }
    }
}

(八)无限定通配符< ?>

使用无限定通配符时,既包含了super通配符的限制也包含了extends通配符的限制,因此:
(1)不允许调用set方法(null除外)
(2)只能调用get方法获取Object引用
(3)Pair< ? >和Pair不同
(4)< ? >很少使用,可以用< T >替换

public class Pair<T> {...}

public class PairHelper {
    static boolean isNull(Pair<?> p) {
        return p.getFirst() == null || p.getList() == null;
    }
}

(九)泛型和反射

1 . 部分反射API是泛型

(1)Class< T >是泛型:

//compile warning:
Class clazz = String.class;
String str = (String) clazz.newInstance();

Class<String> clazz = String.class;
String str = clazz.newInstance();

Class<? super String> sup = clazz.getSuperclass();

(2)Constructor< T >是泛型:

Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);

2 . 泛型数组

(1)可以声明带泛型的数组:

Pair<String>[] ps = null;

(2)但不能用new创建带泛型的数组:

Pair<String>[] ps = newPair<String>[2];

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

Pair<String>[] ps = (Pari<String>[] ) new Pair [2]

(4)不能直接创建T[]数组:
①擦拭后代码变为new Object[5]
②必须借助Class< T >

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

③利用可变参数:@SafeVarargs消除编译器警告

定义Main class:

package com.feiyangedu.sample;

import java.lang.reflect.Constructor;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) throws Exception {
        Class<String> clazz = String.class;
        String s1 = clazz.newInstance();
        System.out.println(s1);

        Constructor<String> cons = clazz.getConstructor(String.class);
        String s2 = cons.newInstance("Hello");
        System.out.println(s2);

        @SuppressWarnings("unchecked")
        Pair<String>[] ps = (Pair<String>[]) new Pair[2];
        ps[0] = new Pair<>("a", "b");
        ps[1] = new Pair<>("x", "y");
        System.out.println(Arrays.toString(ps));
    }
}

定义Pair class:

package com.feiyangedu.sample;

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 void setFirst(T first) {
        this.first = first;
    }

    public T getLast() {
        return last;
    }

    public void setLast(T last) {
        this.last = last;
    }

    public String toString() {
        return "Pair(" + first + ", " + last + ")";
    }
}
相关标签: Java 学习心得