一、什么是反射机制
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
二、Java反射机制提供的功能
(1)在运行时判断任意一个对象所属的类。
(2)在运行时构造任意一个类的对象。
(3)在运行时判断任意一个类所具有的成员变量和方法。
(4)在运行时调用任意一个对象的成员变量和方法。
(5)生成动态代理。
三、反射机制的优点与缺点
优点:
(1)能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
(2)与Java动态编译相结合,可以实现无比强大的功能。
缺点:
(1)使用反射的性能较低
(2)使用反射相对来说不安全
(3)破坏了类的封装性,可以通过反射获取这个类的私有方法和属性。
四、如何创建Class实例
要想明白如何创建Class实例需要掌握以下4个知识点
1.创建Class实例的过程:源文件经过编译(javac.exe)以后,得到一个或多个.class文件。.class文件经过运行(java.exe)这步,就需要进行类的加载(通过JVM的类的加载器),记载到内存中的缓存,每一个放入缓存中的.class文件就是一个Class的实例。
2.Class的一个对象,对应着一个运行时类。相当于一个运行时类本身充当了Class的一个实例。
3.java.lang.Class是反射的源头。接下来涉及到反射的类都在java.lang.reflect子包下。
4.实例化Class的方法(三种)
// 1.调用运行时类本身的.class属性
Class clazz1 = Person.class;
System.out.println(clazz1.getName());
// 2.通过运行时类的对象获取
Person p = new Person();
Class clazz2 = p.getClass();
System.out.println(clazz2.getName());
// 3.通过Class的静态方法来获取,通过此方式,体会一下反射的动态性。
String className = "cn.net.ecode.Person";
Class clazz3 = Class.forName(className);
System.out.println(clazz3.getName());
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz1 == clazz3);//true
注:
(1)结果是 clazz1 、clazz2、clazz3为同一个对象,这里请记住一句话:所有的类都是 Class 类的对象。即类也是一个对象,它是Class的对象,它的类型称为类类型。
(2)对于开发而言,我更推荐使用第三种,原因很简单:因为第三种是一个字符串,而不是一个具体的类名,这样的话我们可以把这样的字符串配置在配置文件中去了,这样更方便后期代码的维护。
在这里大家可以根据下面这张图体会一下Java的反射机制。
五、有了Class实例以后可以做什么?
这个知识点我用一个实例来给大家分析一下,这里新建一个 Animal类,在整个案例中,我们都用到这个 Animal类来讲解。
Animal.java
public class Animal {
private String name;
public int age;
static String desc = "我是一个动物";
public Animal() {
super();
}
private Animal(String name, int age) {
super();
this.name = name;
this.age = age;
}
private int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getDesc() {
return desc;
}
public static void setDesc(String desc) {
Animal.desc = desc;
}
public static void info() {
System.out.println("动物");
}
public void show(String desc) {
System.out.println("我是一个:" + desc);
}
@Override
public String toString() {
return "Animal [name=" + name + ", age=" + age + "]";
}
应用一:可以创建对应的运行时类的对象。
//调用空参的构造器创建运行时类对象
@Test
public void test1() throws Exception {
//获取Class对象,字符串为包名+类名
Class clazz = Class.forName("review.Animal");
// 创建运行时类的对象。使用newInstance(),实际上是调用了运行时类的空参的构造器。
// 要想能够创建成功,①要求对应的运行时类要有空参的构造器。②构造器的权限修饰符的权限要足够。
Object obj = clazz.newInstance();
Animal animal = (Animal) obj;
System.out.println(animal);
}
//调用指定构造器创建运行时类对象
@Test
public void test2() throws Exception {
//获取Class对象
Class clazz = Animal.class;
//获取两参的构造方法,参数是个可变参数
Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);
//设置取消访问检查,因为权限修饰符为private
cons.setAccessible(true);
//创建对象,参数为构造方法的实参
Animal animal = (Animal)cons.newInstance("Tom",10);
System.out.println(animal);
}
此处有两个异常需要注意:
(1)由于Animal类的两参构造函数的权限修饰符为private,所以需要使用getDeclaredConstructor()来获取。否则会报java.lang.NoSuchMethodException异常。
(2)因为权限修饰符为private,要想访问这个构造函数,需要取消访问检查。否则会报java.lang.IllegalAccessException异常。
应用二:获取对应的运行时类的完整的类的结构。(我们可以获取到类的属性,方法,构造器,接口,泛型,异常,包,父类,内部类等结构,此处我们就用类的属性,方法,构造器来做讲解)
//获取运行时类的构造器
@Test
public void test5() throws Exception {
String className = "review.Animal";
Class clazz = Class.forName(className);
Constructor[] cons = clazz.getDeclaredConstructors();
for (Constructor c : cons) {
System.out.println(c);
}
}
//获取运行时类中所有的方法
@Test
public void test3() {
Class clazz = Animal.class;
Method[] m2 = clazz.getDeclaredMethods();
for (Method m : m2) {
//1.获取方法的权限修饰符
String str = Modifier.toString(m.getModifiers());
System.out.print(str + " ");
//2.获取方法的返回值类型
Class returnType = m.getReturnType();
System.out.print(returnType.getName() + " ");
//3.获取方法的方法名
System.out.print(m.getName() + " ");
System.out.println();
}
}
//获取属性的各个部分的内容
@Test
public void test4() {
Class clazz = Animal.class;
Field[] fields1 = clazz.getDeclaredFields();
for (Field f : fields1) {
//1.获取每个属性的权限修饰符
int i = f.getModifiers();
String str1 = Modifier.toString(i);
System.out.print(str1 + " ");
//2.获取每个属性的变量类型
Class type = f.getType();
System.out.print(type.getName() + " ");
//3.获取属性名
System.out.print(f.getName());
System.out.println();
}
}
由此可以看出,有了Class实例后,我们可以获取到运行时类的完整结构。
应用三:可以调用对应的运行时类中指定的结构(某个指定的方法,属性,构造器)。
//1.调用非public的属性
Field f1 = clazz.getDeclaredField("name");
f1.setAccessible(true);
f1.set(animal, "Jerry");
System.out.println(animal);
//调用public的属性
Field f2 = clazz.getField("age");
f2.set(animal, 9);
System.out.println(animal);
//调用静态属性
Field f3 = clazz.getDeclaredField("desc");
System.out.println(f3.get(animal));
//2.调用非public的方法
Method m1 = clazz.getDeclaredMethod("getAge");
m1.setAccessible(true);
int age = (Integer) m1.invoke(animal);
System.out.println(age);
//调用public方法
Method m2 = clazz.getDeclaredMethod("show",String.class);
Object returnValue = m2.invoke(animal, "金毛");
System.out.println(returnValue);
//调用static方法
Method m3 = clazz.getDeclaredMethod("info");
m3.setAccessible(true);
//对于调用静态方法,一下三种方式都可以。
m3.invoke(Animal.class);
//m3.invoke(animal);
//m3.invoke(null);
//3.调用指定构造器创建运行时类对象
Class clazz = Animal.class;
Constructor cons = clazz.getDeclaredConstructor(String.class, int.class);
cons.setAccessible(true);
Animal animal = (Animal) cons.newInstance("Tom", 10);
System.out.println(animal);
注:
(1)在我们获取运行时类的构造函数、成员方法、成员变量时,我们要根据权限去使用对用的方法。在这里我们做一下区分:
①getXXX()方法是获取运行时类及其父类中所有的声明为public方法
②getDeclaredXXX()方法是获取运行时类本身生声明的所有方法,不用去管权限修饰符是什么
如果我们只需要获取运行时类自身的方法,我们直接使用getDeclaredXXX()方法是最为方便、有效的。
(2)根据上面的代码大家可以看到每次使用构造函数、成员方法、成员变量之前要判断是否要添加取消访问检查,其实我们不用管他们的权限修饰符,每次要使用这些之前都加上取消访问检查就可以了。
反射的基本知识点就介绍到这,上面列举的知识获取的运行时类的构造方法、成员方法、成员变量的信息,其实我们还可以获取到接口、泛型、内部内、异常、包路径、泛型等信息,只要学会了上面方法的使用,那么学习其他方法的使用也是同理的。大家赶紧动手试一下吧!