Java反射机制

一、什么是反射机制

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 、clazz2clazz3为同一个对象,这里请记住一句话:所有的类都是 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 + "]";
	}<pre name="code" class="java">}
 
应用一:可以创建对应的运行时类的对象。 

  //调用空参的构造器创建运行时类对象
	@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 {
	<span style="white-space:pre">	</span>String className = "review.Animal";
	<span style="white-space:pre">	</span>Class clazz = Class.forName(className);
	<span style="white-space:pre">	</span>Constructor[] cons = clazz.getDeclaredConstructors();
	<span style="white-space:pre">	</span>for (Constructor c : cons) {
           System.out.println(c);
	<span style="white-space:pre">	</span>}
     } 

  //获取运行时类中所有的方法
     @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)根据上面的代码大家可以看到每次使用构造函数、成员方法、成员变量之前要判断是否要添加取消访问检查,其实我们不用管他们的权限修饰符,每次要使用这些之前都加上取消访问检查就可以了。

反射的基本知识点就介绍到这,上面列举的知识获取的运行时类的构造方法、成员方法、成员变量的信息,其实我们还可以获取到接口、泛型、内部内、异常、包路径、泛型等信息,只要学会了上面方法的使用,那么学习其他方法的使用也是同理的。大家赶紧动手试一下吧!



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值