在之前有写了关于JAVA反射的理解以及几种用法,今天主要想记一下常用一些功能以及一些注意的地方,为了使功能尽量全面,我们以一个包含比较丰富的实体类为例:
首先是新建一个自定义的注解、以及接口:
接下来新建一个Creature类:
接下来新建一个Person类,继承自Creature类,实现上面的自定义接口和一个自带的Comparable接口,以及引用自定义注解:
其中Person类属性有姓名name以及年龄age,分别用不同的访问修饰符:
有参无参构造器使用不同的访问修饰符:
setName方法传入多个变量并有输出语句:
声明一个无返回值和参数的eat()方法:
声明一个带自定义注解并且返回值为String的sleep()方法,并且抛出运行时异常:
再声明两个内部类Computer以及Mobile:
以及重写tostring方法等等,就不一一展示了。
首先来看在运行时创建一个类的实例,首先是创建Class实例,上一次已经说过:
Class<Person> clazz=Person.class;
使用一个带Person泛型的Class类接收,再调用该对象的.newInstance()方法创建一个Person实例:
Person person=clazz.newInstance();
System.out.println(person);
运行结果:
同样我们也可以使用forName()加载全类名创建一个Class实例:
Class<Person> clazz2= (Class<Person>) Class.forName("com.lya.Reflection.Person");
Person person=clazz2.newInstance();
System.out.println(person);
从结果我们不难看出,newInstance()方法默认调用无参构造器。
接下来看在运行时获取类的构造器,注意在上面我们声明Person构造器时,两个有参与无参的构造器访问修饰符不同。
首先同样是获取Class实例:
Class clazz=Person.class;
接下来使用reflect包中的Constructor类,声明一个该类型的数组,调用class实例的getConstructors()方法对所有构造器进行获取:
Constructor[] cons=clazz.getConstructors();
for (int i = 0; i <cons.length ; i++) {
System.out.println(cons[i]);
}
运行结果:
只打印出了修饰符为public的无参构造器,说明另一构造器没有获取到,此时我们调用另一方法getDeclaredConstructors()即可:
Constructor[] cons1=clazz.getDeclaredConstructors();
for (int i = 0; i <cons1.length ; i++) {
System.out.println(cons1[i]);
}
运行结果:
若此时我们将有参构造器改为private修饰,再次使用两种方法分别运行:
getConstructors():
getDeclaredConstructors():
由此说明,getConstructors()方法可以获取所有public修饰的构造器,而getDeclaredConstructors()方法可以获取所有已经声明的构造器,包括私有的。
类似地,我们还可以获取运行时类的属性、方法等:
获取属性:
Class clazz=Person.class;
Field[] fields=clazz.getFields();
for (int i = 0; i <fields.length ; i++) {
System.out.println(fields[i].getName());
}
System.out.println("------------------");
Field[] fields1=clazz.getDeclaredFields();
for (int i = 0; i <fields1.length; i++) {
System.out.println(fields1[i].getName());
}
运行结果:
这里值得注意的是:
Person类分别有两个属性name和age,而其继承的父类Creatrue还有两个属性color和legs,当我们调用getFields()方法,获取到的是name以及color两个属性,因为这两个属性都是用public修饰,而调用getDeclaredFields()方法时,获取到的是name以及age两个属性,也就是Person类本身的属性,这也就是说明getDeclaredFields()可以获取的是不包括父类的所有已声明的属性
获取方法:
@Test
public void test1(){
Class clazz=Person.class;
Method[] methods=clazz.getMethods();
for (int i = 0; i <methods.length ; i++) {
System.out.println(methods[i].getName());
}
System.out.println("-------------------------------");
Method[] methods1=clazz.getDeclaredMethods();
for (int i = 0; i <methods1.length; i++) {
System.out.println(methods1[i].getName());
}
}
通过调用getName()方法可以直接获取到方法名,运行结果:
通过仔细比较,发现getMethods()方法是获取了所有public修饰的方法,并且其父类以及更上一级的父类public方法全都获取到了,而getDeclaredMethods()方法则是获取Person本类中所有声明的方法,不包括父类的。
同样,我们还可以获取运行时类的其他元素,像是父类、注解、接口、内部类等等,这些东西我们在Person类中都有写好,所以可以看一看,使用方法与上面类似:
@Test
public void test3(){
Class clazz=Person.class;
//1.获取带泛型父类的类型
Type type = clazz.getGenericSuperclass();
System.out.println(type);
//2.参数化类型
ParameterizedType pt=(ParameterizedType)type;
//3.获取真实参数类型
Type[] types=pt.getActualTypeArguments();
Class ptClazz=(Class)types[0];
System.out.println(ptClazz.getName());
}
这里说一下获取带泛型父类的类型以及该泛型类型,首先先看获取泛型父类的类型,我们调用getGenericSuperclass()方法即可,而该方法的返回值类型为一个名为Type的接口:
该接口包括所有数据类型,运行结果:
因为Creatrue类泛型为自定义T,而默认会调用toString方法,所以打印出该结果,在获取了带泛型的父类类型后,我们再看如何获取该父类泛型的类型,在上面的打印结果中,我们不难看出,获取泛型类型实际上就是获取泛型中的java.lang.String,即参数,这里我们需要将类型Type进行参数化:
ParameterizedType pt=(ParameterizedType)type;
ParameterizedType 也是一个接口,其中包含了三个方法,而我们需要调用的是getActualTypeArguments()方法,该方法返回一个Type类型的数组,我们再新建一个Type数组进行接收:
Type[] types=pt.getActualTypeArguments();
由于该数组中只有一个类型,也就不必要遍历,我们直接输出它:
发现其结果是class,即输出的是修饰String的Class实例,而我们需要的只是该类型,这里可以将其强转成Class类型,再调用getName()方法进行打印,或者直接调用type接口中的getTypeName()方法,都可以获取成功:
其他的获取都非常类似,也就不一一演示,最后我们看一看在运行时获取并且调用运行时类的属性,比如我们要获取Person类中的name属性,首先先指定我们需要操作的属性:
Class clazz=Class.forName("com.lya.Reflection.Person");
Person p=(Person)clazz.newInstance();
Field name = clazz.getField("name");
接下来我们对其进行设置:
name.set(p,"张三");
接下来我们要获取该值:
String str=(String) name.get(p);
结果:
接下来我们看看操作另一属性age,按照上面的方法:
Field age = clazz.getDeclaredField("age");
age.set(p,18);
System.out.println(age.get(p));
但运行后报错:
错误告诉我们不能访问,因为该属性由private修饰,此时我们需要对该属性进行一个权限设置即可:
age.setAccessible(true);//忽略访问权限
再次运行:
类似的,我们也可以获取并且调用运行时类的方法,比如我们要调用eat方法:
Class clazz=Class.forName("com.lya.Reflection.Person");
Person person=(Person)clazz.newInstance();
Method m1=clazz.getMethod("eat");
m1.invoke(person);
这里需要注意getMethod()方法以及invoke()方法:
getMethod()方法需要传入一个我们指定的方法名,以及每该方法每个参数的class实例,没有时则不传
invoke方法传入一个指定的对象,以及我们需要传入的实际参数,依照我们调用的方法实际情况进行传参,没有参数则不传,有参数时,比如我们调用setName()方法,而该方法返回值为Object,我们就新建一个Object对象进行接收,与上面类似,sleep方法由private修饰,若我们想调用,忽略其访问权限即可:
Method m2=clazz.getMethod("setName", String.class, double.class, int.class);
Object obj=m2.invoke(person,"张三",99,18);
System.out.println(obj);
System.out.println("-----------------------");
Method m3=clazz.getDeclaredMethod("sleep");
m3.setAccessible(true);
Object obj2=m3.invoke(person);
System.out.println(obj2);
运行结果:
打印后我们发现,接收setName方法的object对象打印出来为空,而接收sleep的方法打印出来为我们在方法中写好的返回值,这也就是说明,invoke方法的返回值,实际上就是我们需要操作的方法的返回值,有则存在,没有则是空值