Java学习笔记(四)——反射与泛型
一、反射
(一)类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 + ")";
}
}