JavaSE(四)反射与泛型

反射

Class类

class本身是一种数据类型(Type),class/interface的数据类型是Class,JVM为每个加载的class创建唯一的Class实例。
Class实例包含该class的所有信息,通过Class实例获取class信息的方法称为反射(Reflection)

获取一个class的Class实例:
	1)Class cls = String.class;
	2)Class cls = "str".getClass();
	3)Class cls = Class.forName("java.lang.String");
注意 Class的==比较和instanceof的区别
Integer n = new Integer(123);

boolean b3 = n instanceof Integer; // true
boolean b4 = n instanceof Number; // true

// 精确判断时
boolean b1 = n.getClass() == Integer.class; // true
boolean b2 = n.getClass() == Number.class; // false
从Class实例获取class信息:
	1)getName():获取完整类名
	2)getSimpleName():获取简单的类名
	3)getPackage():获取包名
从Class实例判断class类型:
	1)isInterface():判断是否是接口类型
	2)isEnum():判断是否是枚举类型
	3)isArray():判断是否是数组类型
	4)isPrimitive():判断是否是基本类型
创建class实例:
	cls.newInstance()
	JVM总是动态加载class,可以在运行期根据条件控制加载class
// commons logging优先使用Log4j
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;
	}
}

访问字段

通过Class实例获取字段field信息:
	1)getField(name)——获取某个public的field(包括父类)
	2)getDeclaredField(name)——获取当前类的某个field(不包括父类)
	3)getFields()——获取所有public的field(包括父类)
	4)getDeclaredFields()——获取当前类的所有field(不包括父类)
Field对象包含一个field的所有信息
	1)getName():字段名称
	2)getType():字段定义的数据类型
	3)getModifiers():字段修饰符
获取和设置field的值
	1)get(Object obj)
	2)set(Object, Object)
通过反射访问Field需要通过SecurityManager设置的规则
通过设置setAccessible(true)来访问非public字段

调用方法

通过Class实例方法获取Method信息:
	1)getMethod(name, Class ...):获取某个public的method(包括父类)
	2)getDeclaredMethod(name, Class ...):获取当前类的某个method(不包括父类)
	3)getMethods():获取所有public的method(包括父类)
	4)getDeclaredMethods():获取当前类的所有method(不包括父类)
Method对象包含一个method的所有信息
	1)getName():获取方法名
	2)getReturnType():获取返回值类型
	3)getParameterTypes():获取方法参数类型
	4)getModifiers():获取方法修饰符
调用Method:
	Object invoke(Object obj, Object ...args)
通过设置setAccessible(true)来访问非public方法
反射调用Method也遵守多态的规则

调用构造方法

Class.newInstance()只能调用public无参数构造方法
	String s = (String) String.class.newInstance();	// ^_^
	Integer n = (Integer) Integer.class.newInstance();  // ERROR
通过Class实例获取Constructor信息:
	1)getConstructor(Class...):获取某个public 的 Constructor
	2)getDeclaredConstructor(Class...):获取某个Constructor
	3)getConstructors():获取所有public的Constructor
	4)getDeclareConstructors():获取所有的Constructor
通过Constructor实例可以创建一个实例对象
	newInstance(Object ... parameters)
通过设置setAccessible(true)来访问非public构造方法

获取继承关系

获取父类的Class:
	1)Class getSuperclass()
	2)Object的父类是null
	3)interface的父类是null
获取当前类直接实现的interface:
	1)Class[] getInterfaces()
	2)getInterfaces()获取的不包括间接实现的interface;如果想获取所有的,可以递归调用获取
	3)没有interface的class返回空数组
	4)interface返回继承的interface
判断一个向上转型是否成立:
	boolean isAssignableFrom(Class)

注解 Annotation

使用注解

注解是放在Java源码的类、方法、字段、参数前的一种标签
注解本身对代码逻辑没有任何影响,如何使用注解由工具决定
编译器可以使用的注解
	@Override:让编译器检查该方法是否正确实现了覆写
	@Deprecated:告诉编译器该方法已经标记为“作废”
	@SuppressWarnings:让编译器忽略警告
注解可以定义配置参数和默认值
	配置参数由注解类型定义
	配置参数可以包括:
		所有基本类型
		String
		枚举类型
		数组
	配置参数必须是常量

定义注解

使用@interface定义注解,例如
@Target(ElementType.METHOD)
public @interface Report {
	int type() default 0;
	String level() default "info";
	String value() default "";
}

使用元注解(meta annotation)定义注解
1)@Target
2)@Retention
3)@Repeatable
4)@Inherited

@Target

使用@Target定义Annotation可以被应用于源码的哪些位置:
	类或接口:ElementType.TYPE
	字段:ElementType.FIELD
	方法:ElementType.METHOD
	构造方法:ElementType.CONSTRUCTOR
	方法参数:ElementType.PARAMETER

@Retention

使用@Retention定义Annotation的生命周期
	仅编译器:RetentionRolicy.SOURCE
	仅class文件:RetentionRolicy.CLASS
	运行期:RetentionRolicy.RUNTIME
如果@Retention不存在,则该Annotation默认为CLASS
通常自定义的Annotation都是RUNTIME

@Repeatable

使用@Repeatable定义Annotation是否可重复
JDK>=1.8

@Inherited

使用@Inherited定义子类是否可继承父类定义的Annotation
	仅针对@Target为TYPE类型的Annotation
	仅针对class的继承
	对interface的继承无效

定义Annotation的步骤:

1)用@interface定义注解
2)用元注解配置注解
	Target:必须设置
	Retention:一般设置为RUNTIME
	通常不必写@Inherited,@Repeatable等
3)定义注解参数和默认值

处理注解

注解本身对代码逻辑没有任何影响,SOURCE类型的注解在编译期就被丢掉了,CLASS类型的注解仅保存在class文件中,RUNTIME类型的注解在运行期可以被读取,如何使用注解由工具决定
Q:如何读取RUNTIME类型的注解?

Annotation也是class,所有Annotation继承自java.lang.annotation.Annotation,可以使用反射API

使用反射API读取Annotation:isAnnotationPresent、getAnnotation

isAnnotationPresent

判断某个注解是否存在

	Class.isAnnotationPresent(Class)
	Field.isAnnotationPresent(Class)
	Method.isAnnotationPresent(Class)
	Constructor.isAnnotationPresent(Class)

getAnnotation

获取某个注解信息

	Class.getAnnotation(Class)
	Field.getAnnotation(Class)
	Method.getAnnotation(Class)
	Constructor.getAnnotation(Class)

getParameterAnnotations

获取参数类型的注解:getParameterAnnotations()

Annotation[][] annos = m.getParameterAnnotations();
可以在运行期通过反射读取RUNTIME类型的注解
	不要漏写@Retention(RetentionPolicy.RUNTIME)
可以通过工具处理注解来实现相应的功能:
	1)对JavaBean的属性值按规则进行检查
	2)JUnit会自动运行@Test标记的测试方法

练习

请根据注解;
	@NotNull:检查该属性为非null
	@Range:检查整型介于min~max,或者检查字符串长度介于min~max
	@ZipCode:检查字符串是否全部由数字构成,且长度恰好为value
实现对Java Bean的属性值检查。如果检查未通过,抛出异常

泛型

什么是泛型

泛型(Generic)就是定义一种模板,例如ArrayList<T>
在代码中为用到的类创建对应的ArrayList<类型>
	ArrayList<String> strList = new ArrayList<String>();
编译器会针对泛型作检查,要注意泛型的继承关系
	可以吧ArrayList<Integer>向上转型为List<Integer>(T不能变)
	不能把ArrayList<Integer>向上转型为ArrayList<Number>或List<Number>
	ArrayList<Number>和ArrayList<Integer>两者没有继承关系

使用泛型

定义泛型类型<Number>:
	List<T>的泛型接口为强类型
		void add(Number)
		Number get(int)
不指定泛型参数类型时,编译器会给出警告,且只能将<T>视为Object类型

编写泛型

泛型一般用在集合类中,编写类型时,需要定义泛型类型<T>
	public class Pair<T> {...}
静态方法不能引用泛型类型<T>,必须定义其他类型<K>来实现泛型
	public static <K> Pair<k> create(K first, K last){ ... }
泛型可以同时定义多种类型<T, K>

擦拭法

Java的泛型是采用擦拭法(Type Erasure)实现的,编译器把类型视为Object,再根据实现安全的强制转型

擦拭法是局限:
	1)<T>不能是基本类型,例如int
	2)Object字段无法持有基本类型
	3)无法取得带泛型的Class
	4)无法判断带泛型的Class
	5)不能实例化T类型,因为擦拭后实际上是new Object()

可以继承自泛型类

public class IntPair extends Pair<Integer>{ 
}
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
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
}

父类类型是Pair<Integer>;子类的类型是IntPair;子类可以获取父类的泛型类型
继承关系:
在这里插入图片描述

extends通配符

public class Pair<T> { ... }
public class PairHelper {
	static int get(Pair<Number> p) {
		Number first = p.getFirst();
		Number last = p.getLast();
		return first.intValue() + last.intValue();
	}
}
PairHelper.get(new Pair<Number>(1, 2));  // √
PairHelper.get(new Pair<Integer>(1, 2));  // ×

Pair<Integer>不是Pair<Number>的子类;get()不接受Pair<Integer>,所以可以使用<? extends Number>使方法接收所有泛型类型为Number或Number类的Pair类

public class PairHelper {
	static int get(Pair<? extends Number> p) {
		Number first = p.getFirst();
		Number last = p.getLast();
		return first.intValue() + last.intValue();
	}
}
PairHelper.get(new Pair<Number>(1, 2));  // √
PairHelper.get(new Pair<Integer>(1, 2));  // √
对Pair<? extends Number>调用getFirst()方法:
	方法签名:? extends Number getFirst()
	可以安全赋值给Number类型的变量:Number x = p.getFirst();
	不可预测实际类型就是Integer:Integer x = p.getFirst();
对Pair<? extends Number>调用setFirst()方法会报错:
	方法签名:void setFirst(? extends Number)
	无法传递任何Number类型给setFirst(? extends Number)

<? extends Number> 的通配符:
允许调用get方法获得Number的引用
不允许调用set方法传入Number的引用
唯一例外:可以调用setFirst(null)
<T extends Number> 的通配符
限定定义Pair<T>时只能是Number或Number的子类

super通配符

public class Pair<T> { ... }
public class PairHelper {
	static int set(Pair<Integer> p, Integer first, Integer last) {
		p.setFirst(first);
		p.setLast(last);
	}
}
PairHelper.set(new Pair<Integer>(1, 2), 3, 4);  // √
PairHelper.set(new Pair<Number>(1, 2), 3, 4);  // ×

Pair<Integer>不是Pair<Number>的子类;set()不接受Pair<Number>,所以可以使用<? super Integer>使方法接收所有泛型类型为Integer或Integer超类的Pair类

public class PairHelper {
	static int set(Pair<? super Integer> p, Integer first, Integer last) {
		p.setFirst(first);
		p.setLast(last);
	}
}
PairHelper.set(new Pair<Integer>(1, 2), 3, 4);  // √
PairHelper.set(new Pair<Number>(1, 2), 3, 4);  // √
对Pair<? super Integer>调用setFirst()方法:
	方法签名:void setFirst(? super Integer)
	可以安全传入Integer类型的变量:p.setFirst(new Integer(123));
对Pair<? super Integer>调用getFirst()方法会报错:
	方法签名:? super Integer getFirst()
	无法赋值给Integer类型的变量

<T super Integer> 的通配符
允许调用set方法传入Integer的引用
不允许调用get方法获得Integer的引用
唯一例外:可以获取Object引用Object o = p.getFirst();
<T extends Number> 的通配符
限定定义Pair时只能是Integer或Integer的超类
extends和super通配符的区别

<? extends T> 允许调用方法获取T的引用
<? super T> 允许调用方法传入T的引用
public class Collections{
	// 把src的每个元素复制到dest中
	public static <T> void copy(List<? super T> dest, List<? extends T> src){
		for(var i=0; i<src.size(); i++){
			T t = src.get(i);
			dest.add(t);
		}
	}
}

无限定通配符<?>

不允许调用set方法(null除外)
只能调用get方法获取Object引用
可以用<T>消除<?>

泛型与反射

部分反射API是泛型
	Class<T>
	Constructor<T>
可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型,例如
Pair<String>[] ps = null; // √
Pair<String>[] ps = new Pair<String>[2]; // ×
Pair<String>[] ps = (Pair<String>[])new Pair[2]; // √
// 不安全地使用带泛型的数组==>通过引用
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;

ps[0] = new Pair<String>("a", "b");
arr[1] = new Pair<Integer>(1, 2);

// ClassCastException
Pair<String> p = ps[1];
String s = p.getFirst();
// 安全地使用带泛型的数组 ==> 不要使用arr的引用
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
不能直接创建 T[] 数组:
	因为擦拭后变为new Object[5];必须借助Class<T>
可以通过Array.newInstance(Class<T>, int)创建T[]数组,需要强制转型
T[] createArray(Class<T> cls){
	return (T[]) Array.newInstance(cls, 5);
}
public class ArrayHelper{
	@SafeVarargs  // 消除编译器警告
	static <T> T[] asArray(T... objs){
		return objs;
	}
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);

猜你喜欢

转载自blog.csdn.net/yangwei234/article/details/84977315