一、什么是java反射

        java的反射机制是指在运行状态中,对于任意的一个类,都能获取到这个类的属性和方法,无论是private还是public。对于任意的一个对象,都能调用到它的任意一个方法和属性,无论是private还是public。这种动态获取类的信息和动态调用对象的方法,我们叫做java的反射机制。当然这种机制如果自己使用不当,在一定程度上破坏了java面向对象的封装性。

二、java反射实例和用法

        下面有个person的bean对象,我们就用这个简单的例子来学习,要想使用反射,首先得获取对应类的class对象。

package com.chendsir.exercisejava;

public  class Person {
	private int id;
	private String name;
	public  String age;

	public Person(String age) {
		this.age = age;
	}

	public int getId() {
		return id;
	}
	
	public String getName() {
		return name;
	}
	

	public String getAge() {
		return age;
	}
}

(1) :获取class对象。

   我们给这个例子加一个man函数,在man函数里面我们有三种方式来获取这个Person类的class对象。如果通过第一种方式Class.forName没有获取到指定类会报ClassNotFoundException 异常。

public static void main(String[] args) throws ClassNotFoundException {
       // 第一种方式,使用Class类的forName,传入的参数是想要获取类的完整包名加类名
        Class c1 =Class.forName("com.chendsir.exercisejava.Person");
        // 第二种方式,直接使用想要获取类的class属性来获取Class对象。
        Class c2 =Person.class;
        // 第三种方式,通过实例对象的getClass方法获取
        Person person = new Person();
        Class<?> c3 = person.getClass();
		
	}

(2): 获取类的属性。

如下可通过默认获取所有属性以及指定属性的两种方式获取。如果没有获取到指定属性会报NoSuchFieldException异常。

Class c4 = Person.class;//上面说的通过类的class属性获取Class对象
        // getDeclaredFields根据名字就可以知道,获取所有声明了的属性,无论什么访问权限。
        Field[] allFileds = c4.getDeclaredFields(); // id ,name,age都能获取到

        // getFields是访问该Class的所有public属性
        Field[] pubFileds = c4.getFields();   // 只能访问到age属性,因为age属性是public

        // 通过指定参数名id,访问Person类的id属性,找不到会报NoSuchFieldException异常
        Field idField = c4.getDeclaredField("id");
        
        // 通过指定参数吗age,访问Person类的公有age属性,找不到会报NoSuchFieldException异常
        Field ageField = c4.getField("age");

(3):获取类方法

获取方法和获取属性一样,可以默认获取所有,以及指定形参列表的方法。如果没有获取到指定的方法会报NoSuchMethodException 异常。

public void setAge(String tempAge) {    // 添加一个方法
		this.age = tempAge;
	}

   	public static void main(String[] args) throws NoSuchMethodException {
		Class c5 =Class.forName("com.chendsir.exercisejava.Person");
		// 获取Class对象所有声明的方法,getId,getName,getAge
		Method[] methods = c5.getDeclaredMethods();
		// 获取Class对象所有的public方法,假如把上例子的getId,getName改为private,这里只能获取到getAge
		Method[] allpubmethods = c5.getMethods();

		// 此处获得的是setAge() 没有形参的方法,且权限是public
		Method method = c5.getMethod("setAge");

		// 此处获得的是setAge(String tempAge) 方法,而且形参为String类型
		Method method2 = c5.getMethod("setAge",String.class);
        
        // 同上,不过无论是public还是private都能获取到
		Method method3 = c5.getDeclaredMethod("setAge",String.class);
	}

(4) 获取类的构造函数

   获取类的构造函数也可以获取所有的构造函数,和指定的方式。和上面是一个套路。相信大家已经熟悉了。

public Person(String age) {  // 添加一个带参数的构造函数
		this.age = age;
	}
    
    	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class c5 =Class.forName("com.chendsir.exercisejava.Person");
		
        // 获取所有的构造函数
        Constructor<?>[]  constructors = c5.getDeclaredConstructors();

        // 获取public的构造函数
        Constructor<?> [] constructors1 = c5.getConstructors();  

        // 获取带形参String参数的构造函数 
	Constructor<?>  constructor  = c5.getDeclaredConstructor(String.class);

        // 获取带形参String参数的Public构造函数
        Constructor<?>  constructor1 = c5.getConstructor(String.class);
	}

(5) 获取类的注解

获取注解也可以直接获取类中的所有注解或者指定一个注解,同样的套路,Retrofit的自定义ConvertFactory的来定义请求格式和返回格式的时候,经常用到Annotation这个。

// 获取所有注解,retorfit自定义的话,通过遍历可以来判断你的请求或者返回格式是xml还是json
		Annotation[] annotations = c5.getAnnotations();

       // 获取注解为Override的注解
		Annotation annotation =c5.getAnnotation(Override.class);

(6) 通过反射来生成对象

如何得到一个类的实例对象呢,在第(4) 步中我们已经知道怎么获取构造函数了,既然构造函数都有了,直接调用构造函数的newInstance方法就可以直接生成一个实例了。也可以直接通过Class对象的newInstance()来生成一个对象,调用的是无参构造函数,此处原生native方法会抛出InstantiationException, IllegalAccessException两个异常。

Class c5 =Class.forName("com.chendsir.exercisejava.Person");
                // 第一种方式,直接通过Class对象的newInstance方法来生成对象
		Object obj1 = c5.newInstance();
		// 第二种方式,通过构造函数来生成对象
		Constructor<?>  constructor  = c5.getDeclaredConstructor(String.class);
		Object obj2 = constructor.newInstance("18");// 我们获取的是有参构造函数,这里传入一个参数。

(7) 如何调用类的方法以及修改其属性

通过以上我们已经拿到了对象以及得到了方法,那么一个方法如何取调用以及传参呢。我们一般是通过invoke的方式去调用方法并传入相应参数。

...省略

   public void setAge(String tempAge) {
		this.age = tempAge;
	}

   ...省略
    
  Class c5 =Class.forName("com.chendsir.exercisejava.Person");
  
  Object obj1 = c5.newInstance();
   // 获取setAge方法
  Method method = c5.getDeclaredMethod("setAge", String.class);
  // 第一个参数是生成的实例对象obj1,第二个参数是我们调用setAge传入的参数名
  method.invoke(obj1,"18");

假如我们的Class中没有setAge这个方法,但是我们又想要修改age的值,该如何做呢。我们通过第(2)步可以直接获取一个类的属性,Filed类已经给我们提供了get和set方法了,如果是基本类型,用setInt、setChar、setBoolean来改,如果是引用类型直接用set方法,通过这个可以直接改,非常强大。

Object obj1 = c5.newInstance();
         // 我们先获取id这个属性
	Field field = c5.getField("id");
         // 通过setInt的方法来修改这个对象的id值为2
	field.setInt(obj1,2);
         
         // 如果要修改age呢?因为我定义的age是String类型的,不是基本类型,而是引用。
        Field field2 = c5.getField("age");
         // 通过set的方法来修改这个对象的age值为19
        field2.set(obj1,"19");
        //  想要获取的话,用get方法
        field2.get(obj1)  //来获得这个String类型的age值

(8) 反射的其他扩展常用方法

//一些常用判断,为真返回true,否则false   
               Class c5 =Class.forName("com.chendsir.exercisejava.Person");

		boolean isPrimitive = c5.isPrimitive(); //是否是基本类型,
		boolean isArray = c5.isArray();// 是否是集合类
		boolean isAnnotation = c5.isAnnotation();// 是否是注解类
		boolean isInterface = c5.isInterface(); // 是否是接口类
		boolean isEnum = c5.isEnum();	// 是否是枚举类
		boolean isAnonymousClass = c5.isAnonymousClass();//isAnonymousClass; 判断是否是匿名内部类
		boolean isAnonationPresent = c5.isAnnotationPresent(Deprecated.class); //判断是否被Deprecated这个注解类修饰
		String className = c5.getName(); // 这个getName包含了包名的路径
		Package apackage = c5.getPackage(); // 获取包的信息
		String classSimpleName = c5.getSimpleName(); //获取class类名
		int modfiers = c5.getModifiers();//获取class访问权限
		Type genericSuperclass = c5.getGenericSuperclass();// 获得class对象的直接超类type
		Type[]  interfaceTypes = c5.getGenericInterfaces(); // 获取class 对象的所有接口的type集合
		Class<?> []  classes = c5.getDeclaredClasses();//获取内部类
		Class<?>   classes1 = c5.getDeclaringClass();//获取外部类

三、java反射的一些应用场景

    1: Retofit这个框架,注解很强大,也是利用反射和注解结合起来的。

     2: EventBus 单纯的反射机制应用框架

     3: 反编译 

     4:JDBC的利用反射加载数据库驱动

     5: Spring框架用反射加载xml配置信息

     6: 动态生成类框架Gson

     7:  动态代理模式

     8:某些带有@hide注解的变量或者类,我们无法直接获取,通过反射就可以拿到

四、总结

通过本篇文章学会了如何通过反射,获取一个类,获取该类的属性、方法,构造函数、注解,以及如何调用这些方法,通过反射的方式生成一个对象,赋值,修改某个属性的值。本文讲的都是反射的一些基础,希望能对正在学习java反射的童鞋们有一个很好的引导。深入学习的话得建议去了解一下反射的动态代理模式,通过反射来获取泛型、以及泛型擦除的一些原理。