Java反射基础

一般在开发中很少自己刻意去使用反射,但是在某些情况下反射却显得异常有用。在Java开发中反射几乎可以称之为万能公式,因为在某些时候它确实是万能的。一些常用的开发框架如Gson以及Web开发的SSH框架底层几乎无一例外采用反射机制,再如目前比较流行的使用注解的方式底层解析也是使用反射,所以反射几乎是学习Java进阶的不二之路。

反射简介

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

那么反射可以做什么呢?

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。

在Java中Object类是所有类的父类,Object提供了一个很多非常有用的方法如equals()、notify()、wait()、clone()等等,但是还有一个很少用却非常有用的方法getClass()方法,getClass()方法返回的是一个Class对象,Class对象是执行反射操作的关键所在。更多有关Class的介绍会在后续文章中进一步深入探讨,本篇文章重点介绍一下反射的基础知识点。

Class位于java.lang包下面,但是所有反射相关的API基本位于java.lang.reflect子包下面。先看一下获取java.lang.Class对象的常用方式。

  • Class类的静态方法forName(String className)。其中传入的className参数为类的全类名(包含具体包名的类名)。
  • 调用类的class属性来获取该类的Class对象。如Class strClass = String.class。
  • 调用对象的getClass()方法。

反射部分常用方法介绍

在进行介绍之前,先看一下反射中我们使用的一个测试类User。

public class User implements Serializable{
	
	private static final long serialVersionUID = 1L;
	
	private String id;
	public String username;
	private int age;
	
	public User(){}
	
	private User(String id){
		this.id=id;
	}
	
	public User(String id,String username, int age) {
		this.id = id;
		this.username = username;
		this.age = age;
	}
	
	private void printId(){
		System.out.println("this is private method");
	}
	
	public static void printName(){
		System.out.println("this is static method");
	}
	
	public void sayHello(){
		System.out.println("hello world");
	}
	
	public void sayHello(String str){
		System.out.println("hello:"+str);
	}

	public String toString() {
		return "User [username=" + username + ", age=" + age + ", id=" + id
				+ "]";
	}
}

获取类的Class实例

上面已经介绍了可以通过三种方式获取Class实例,事实上三种方式获取到的Class实例是同一个。

User user = new User();
System.out.println(User.class);
System.out.println(user.getClass());
System.out.println(Class.forName("com.yimi.demo.bean.User"));
System.out.println(User.class == user.getClass());
System.out.println(User.class == Class.forName("com.yimi.demo.bean.User"));
//class com.yimi.demo.bean.User
//class com.yimi.demo.bean.User
//class com.yimi.demo.bean.User
//true
//true

获取构造方法

一般最常用的就是下面对应的四种方式,带有declared关键字的可以获取到所有的构造方法,private修饰符修饰的也不例外,反之,只能获取public修饰符修饰非构造方法。

  • Constructor<T> getConstructor(Class<?>... parameterTypes)
  • Constructor<?>[] getConstructors()
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
  • Constructor<?>[] getDeclaredConstructors()
Class clazz = User.class;
Constructor[] constructors = clazz.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
	System.out.print(Modifier.toString(constructors[i].getModifiers()) + " ");
	System.out.print(constructors[i].getName() + "(");
	Class[] paramTypes = constructors[i].getParameterTypes();
	for (int j = 0; j < paramTypes.length; j++) {
		if (j < paramTypes.length - 1) {
			System.out.print(paramTypes[j].getSimpleName() + " args_" + j + ",");
		} else {
			System.out.print(paramTypes[j].getSimpleName() + " args_" + j);
		}
	}
	System.out.println(")");
}
//public com.yimi.demo.bean.User(String args_0,String args_1,int args_2)
//private com.yimi.demo.bean.User(String args_0)
//public com.yimi.demo.bean.User()

打印出来的方法可以看出,在获取所有Constructor数组列表中,构造方法的打印顺序是参数最多的索引最靠前。在获取权限修饰符的时候constructors[i].getModifiers(),默认返回的是一个整形数字,从方法名getModifiers也可以看出来这个值不单单是一个修复符这么简单,所以末尾加了一个s,实际上获取到的返回值是所有权限修饰符之和,如果是public static final,返回值就是这三个修饰符对应的整数之和,当我们调用Modifier.toString()方法的时候回自动返回对应字符串修饰符。

constructors[i].getParameterTypes()会返回构造方法中所有参数对应的Class数组,这样就可以获取到所有参数对应的数据类型。

如果想要获取带有某种参数类型的构造方法可以使用传入不定长参数的方法getConstructor(Class<?>... parameterTypes),该方法返回的是单个构造方法。

获取属性

类似于上面的获取构造方法,包括后面获取方法也是采用同样的命名方式,只要带有declared关键字的方法都是获取所有在类中声明的相关属性,否则获取到的是只有使用public修饰符修饰的属性。

  • Field getField(String name); //返回对应name的public属性
  • Field[] getFields();  //所有public属性
  • Field getDeclaredField(String name);
  • Field[] getDeclaredFields();
Class clazz=User.class;
Field[] fields=clazz.getDeclaredFields();
for(int i=0;i<fields.length;i++){
	System.out.print(Modifier.toString(fields[i].getModifiers())+" ");
	System.out.print(fields[i].getType().getSimpleName()+" ");
	System.out.println(fields[i].getName());
}
//private static final long serialVersionUID
//private String id
//public String username
//private int age

获取方法

使用反射获取方法跟上面两种类似。

  • Method getMethod(String name, Class<?>... parameterTypes);
  • Method[] getMethods(); //所有public方法
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes);
  • Method[] getDeclaredMethods(); //所有方法
Class clazz=User.class;
Method[] methods=clazz.getDeclaredMethods();
for(int i=0;i<methods.length;i++){
	System.out.print(Modifier.toString(methods[i].getModifiers())+" ");
	System.out.print(methods[i].getReturnType().getSimpleName()+" ");
	System.out.print(methods[i].getName()+"(");
	Class[] paramTypes = methods[i].getParameterTypes();
	for (int j = 0; j < paramTypes.length; j++) {
		if (j < paramTypes.length - 1) {
			System.out.print(paramTypes[j].getSimpleName()+" args_"+j+ ",");
		} else {
			System.out.print(paramTypes[j].getSimpleName()+" args_"+j);
		}
	}
	System.out.println(")");
}
//public String toString()
//public void sayHello(String args_0)
//public void sayHello()
//public static void printName()
//private void printId()

反射简单使用

创建对象

如果创建一个默认无参的对象一般直接使用Class实例的newInstance()方法就可以了,创建过程如下:

Class clazz=Class.forName("com.yimi.demo.bean.User");
User user=(User) clazz.newInstance();

但是如果需要创建一个带有构造参数的对象,使用Class来创建就不够用了,这时候我们还需要使用Constructor构造方法来进行创建。

Class clazz=Class.forName("com.yimi.demo.bean.User");
Constructor constructor=clazz.getConstructor(new Class[]{String.class,String.class,int.class});
User user=(User) constructor.newInstance(new Object[]{"1001","admin",new Integer(20)});

如果需要调用私有构造方法,一定要使用带有declared关键字的方法getDeclaredConstructor(),同时记住必须使用setAccessible(true),否则就会抛出 java.lang.IllegalAccessException异常。一般情况下调用带有declared关键字方法,都要随后设置一下setAccessible(true),这几乎就是使用反射的模板语句。

setAccessible()方法提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor对象来设置或获取字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。

创建方法调用

首先看一个调用私有无参方法,使用反射真正调用一个方法实际上是用的invoke()方法,下面是使用反射的方式调用User类的私有printId()方法。

Class clazz=Class.forName("com.yimi.demo.bean.User");
Method method=clazz.getDeclaredMethod("printId",null);
method.setAccessible(true);
method.invoke(clazz.newInstance(),null);

invoke(Object obj, Object... args)有两个参数。第一个参数是方法定义所在类的实例,第二个是方法的入参。

下面是使用反射调用需要传参的方法sayHello()。

Class clazz=Class.forName("com.yimi.demo.bean.User");
Method method=clazz.getMethod("sayHello",new Class[]{String.class});
method.invoke(clazz.newInstance(),new String[]{"admin"});

创建属性调用

这种情况一般是某个类的属性在外部不可访问,但是某些情况下有需要重新设定一下,这时候就可以使用反射来进行重新赋值。

Class clazz=User.class;
Object obj=clazz.newInstance();
Field field=clazz.getDeclaredField("id");
field.setAccessible(true);
field.set(obj, "1001");

System.out.println(field.get(obj));

反射中Field相对还是比较常见的一种使用,所以它还提供了许多针对基本数据类型的方法如setDouble()、setBoolean()、setInt()等等。

本文针对反射只是一些很基础的部分,有关反射更多内容后续会继续介绍如操作泛型以及注解。

评论

您确定要删除吗?删除之后不可恢复