我们应用会用到反射这个知识点,肯定是想要在运行时得到类的信息,根据类的那些信息去做一些特定的操作。那么,首先无疑就是得到类的信息,在JDK中提供了Class对象来保存类的信息。所以,反射的第一步就是得到Class对象。在JDK中提供了两种方式得到Class对象。第一种,如果编写代码的时候,就知道Class的名字,可以直接用如下方式得到Class对象:
//第一种,如果编写代码的时候,就知道Class的名字,可以直接用如下方式得到Class对象:
Class exampleObjectClass = ExampleObject.class;
//第二种,如果在编写代码的时候,不知道类的名字,但是在运行时的时候,可以得到一个类名的字符串,可以用如下的方式获取Class对象:
//注意,此方法需要有2个条件,第一,forName中的字符串必须是全限定名,第二,
//这个Class类必须在classpath的路径下面,因为该方法会抛出ClassNotFoundException的异常。
Class exampleObjectClass2 = Class.forName("com.team.frame.fanshe.ExampleObject");
下面介绍通过反射可以获取那些信息:
1. 得到类的名字
/**
* 得到类的名字
* 类的名字有两种方式得到,一种是getName(),一种是getSimpleName()。
* 第一种得到的是全限定名,第二种得到的是这个类的名字,不带包名。看下面的例子:Class对象,已经通过上面的代码得到了。
*/
String fullClassName = exampleObjectClass.getName();
String simpleClassName = exampleObjectClass.getSimpleName();
System.out.println(fullClassName);
System.out.println(simpleClassName);
2. 得到类的包名、父类和实现的接口
//得到包信息
Package aPackage = exampleObjectClass.getPackage();
System.out.println(aPackage);
//得到父类
Class superClass = exampleObjectClass.getSuperclass();
System.out.println(superClass.getSimpleName());
//判断父类是否是抽象类
boolean abstract1 = Modifier.isAbstract(superClass.getModifiers());
//我们还可以得到父类实现的接口
Class[] interfaces = superClass.getInterfaces();
System.out.println("父类的接口" + interfaces[0]);
3. 利用Java反射可以得到一个类的构造器,并根据构造器,在运行时动态的创建一个对象
Constructor[] constructors = exampleObjectClass.getConstructors();
for(Constructor cos:constructors){
System.out.println(cos.toString());
}
//如果,事先知道要访问的构造方法的参数类型,可以利用如下方法获取指定的构造方法,例子如下:
Constructor constructor = exampleObjectClass.getConstructor(String.class);
System.out.println(constructor);
//此外,如果我们不知道构造器的参数,只能得到所有的构造器对象,那么可以用如下方式得到每一个构造器对想的参数:
Constructor[] constructors2 = exampleObjectClass.getConstructors();
for(Constructor cos2:constructors2){
Class[] parameterTypes = cos2.getParameterTypes();
for(Class cla:parameterTypes){
System.out.println("参数类型"+cla.toString());
}
}
//根据构造器创建一个对象
//这个创建对象的方式有2个条件,第一是通过有参构造器创建的,第二,构造器对象必须通过传入参数信息的getConstructor得到。
//第一个条件,对于无参构造方法就可以创建的对象,不需要得到构造器对象,直接Class对象调用newInstance()方法就直接创建对象。
//第二个条件,构造器对象必须通过exampleObjectClass.getConstructor(String.class);这种形式得到。如果通过getConstructors得到构造器数组
//,然后调用指定的构造器对象去创建对象在JDK1.8是会错的。但是JDK1.6是正常的
Constructor constructor2 = exampleObjectClass.getConstructor(int.class,Integer.class);
Object newInstance = constructor2.newInstance(1,100);
System.out.println(newInstance.toString());
4. 利用Java反射可以在运行时得到一个类的变量信息
/**
* 变量
* 利用Java反射可以在运行时得到一个类的变量信息,并且可以根据上面讲的方式,创建一个对象,设置他的变量值。首先,通过如下方法,得到所有public的变量:
*/
Field[] fields = exampleObjectClass.getFields();
for(Field fi:fields){
System.out.println("变量为"+fi.toString());
}
//和构造器一样的得到方式一样,我们可以指定一个参数名,然后得到指定的变量:
Field field = exampleObjectClass.getField("age");
System.out.println(field.toString());
//更改变量的值
Constructor constructor1 = exampleObjectClass.getConstructor(String.class);
ExampleObject newInstance2 = (ExampleObject)constructor1.newInstance("heihei");
System.out.println("原来的age"+newInstance2.getAge());
field.set(newInstance2, 10);
System.out.println("修改后age"+newInstance2.getAge());
5. Java反射给我们除了给我们提供类的变量信息之外,当然也给我们提供了方法的信息
/**
* 方法
* Java反射给我们除了给我们提供类的变量信息之外,当然也给我们提供了方法的信息,
* 反射可以让我们得到方法名,方法的参数,方法的返回类型,以及调用方法等功能。
*/
Method[] methods = exampleObjectClass.getMethods();
for(Method me:methods){
System.out.println("method="+me.getName());
}
//根据参数获得具体方法
Method method = exampleObjectClass.getMethod("setAge", int.class);
System.out.println(method.getName());
//获取方法的参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
for(Class clz:parameterTypes){
System.out.println("参数名"+clz.getName());
System.out.println("类型"+clz.getTypeParameters());
}
//获得方法返回类型的
Class<?> returnType = method.getReturnType();
//此外,Java反射支持通过invoke调用得到的方法。例子如下:
//nvoke第一个参数是这个对象,第二个参数是变长数组,传入该方法的参数。和Field对象同样,对于静态方法同样,可以传入null,调用静态方法。
method.invoke(exampleObjectClass.newInstance(), 1);
6. Java给我们提供了在运行时获取类的注解信息,可以得到类注解,方法注解,参数注解,变量注解
/**
* 注解
* Java给我们提供了在运行时获取类的注解信息,可以得到类注解,方法注解,参数注解,变量注解。
* 与上面获取方式一样,Java提供了2种获取方式,一种是获取全部的注解,返回一个数组,第二种是指定得到指定的注解。
* 我们以一个类注解为例,讲解以下这两种获取方式。
*/
//获取类上的注解
Class clz = AnnotationObject.class;
Annotation[] annotations = clz.getAnnotations();
Annotation annotation = clz.getAnnotation(AnnotationObject.class);
//根据注解进行后续处理
for(Annotation ano:annotations){
if(ano instanceof MyAnnotationFanshe){
MyAnnotationFanshe annoFanshe=(MyAnnotationFanshe)ano;
System.out.println(annoFanshe.name());
System.out.println(annoFanshe.value());
}
}
//获取参数上的注解
Method method2 = clz.getMethod("doOtherThing",String.class);
Class[] params = method2.getParameterTypes();
Annotation[][] annotationInParam = method2.getParameterAnnotations();
int i = 0;
for (Annotation[] annos: annotationInParam){
Class para = params[i++];
for (Annotation anno : annotations){
if(annotation instanceof MyAnnotationFanshe){
MyAnnotationFanshe myAnnotation = (MyAnnotationFanshe) annotation;
System.out.println("param: " + para.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value :" + myAnnotation.value());
}
}
}
7. 获取泛型
/**
* 泛型
* 因为Java泛型是通过擦除来实现的,很难直接得到泛型具体的参数化类型的信息,
* 但是我们可以通过一种间接的形式利用反射得到泛型信息。比如下面这个类:
* 如果一个方法返回一个泛型类,我们可以通过method对象去调用getGenericReturnType来得到这个泛型类具体的参数化类型是什么。
*/
Class generObject = GenericObject.class;
//反射得到返回类型为泛型类的方法
Method method3 = generObject.getMethod("getLists");
//调用getGenericReturnType得到方法返回类型中的参数化类型
Type genericType = method3.getGenericReturnType();
//判断该type对象能不能向下转型为ParameterizedType
if(genericType instanceof ParameterizedType){
//转型成功,调用getActualTypeArguments得到参数化类型的数组,因为有的泛型类,不只只有一个参数化类型如Map<K,V>
ParameterizedType parameterizedType = ((ParameterizedType) genericType);
//取出数组中的每一个的值,转型为Class对象输出
Type[] types = parameterizedType.getActualTypeArguments();
for (Type type : types){
Class actualClz = ((Class) type);
System.out.println("参数化类型为 : " + actualClz);
}
}
//因为方法的参数为泛型类型的可能不止一个,所以通过getGenericParameterTypes得到是一个数组,
//我们需要确定每一个元素,是否是具有参数化类型。后续的步骤与上面类似,就不多说了。
//如果连方法参数都不带泛型类,那么只剩下最后一种情况,通过变量类型,即用Field类。例子如下:
Field field1 = generObject.getField("lists");
Type type = field1.getGenericType();
if (type instanceof ParameterizedType){
ParameterizedType parameterizedType = ((ParameterizedType) type);
Type [] types = parameterizedType.getActualTypeArguments();
for (Type type1 : types) {
System.out.println("参数化类型 : " + ((Class) type1).getTypeName());
}
}
8. Java反射可以对数组进行操作
/**
* 数组
* Java反射可以对数组进行操作,包括创建一个数组,访问数组中的值,以及得到一个数组的Class对象。
* 下面,先说简单的,创建数组以及访问数组中的值:在反射中使用Array这个类,是reflect包下面的。
*/
//创建一个int类型的数组,长度为3
int[] intArray = (int[])Array.newInstance(int.class, 3);
//通过反射形式给数组赋值
for(int a=0;a<intArray.length;a++){
Array.set(intArray, a, a+2);
}
//通过反射形式获取数组中的值
for(int b=0;b<intArray.length;b++){
Array.get(intArray, b);
}
//上述就是创建数组,访问数组中的值利用反射方式
//对于得到一个数组的Class对象,简单的可以用int[].class,或者利用Class.forName的形式得到,写法比较奇怪
Class clz2 = Class.forName("[I");
System.out.println(clz2.getTypeName());
//这个forName中的字符串,[表示是数组,I表示是int,float就是F,double就是D等等,如果要得到一个普通对象的数组
Class stringClz = Class.forName("[Ljava.lang.String;");
//[表示是数组,L的右边是类名,类型的右边是一个;;
//这种方式获取数组的Class对象实在是太繁琐了。
//在得到数组的Class对象之后,就可以调用他的一些独特的方法,比如调用getComponentType来得到数组成员的类型信息,如int数组就
//是成员类型就是int。
System.out.println(clz2.getComponentType().getTypeName());