Java高质量代码之反射

反射是Java的一个非常重要但又很容易被忽略的知识点,下面先让我们来看看反射的基本使用:

a/新建Person类

public class Person {

	public String name;
	private int age;

	public void setAge(int age) {
		this.age = age;
	}
	
	// 无参数
	public void show() {
		System.out.println("这是一个人");
	}

	// 一个参数
	public void showNation(String nation) {
		System.out.println("国籍是------->" + nation);
	}
	
	// 多个参数
	public void showNation(String name, String nation, int age) {
		this.name = name;
		this.age = age;
		System.out.println(name + "的国籍是------->" + nation + ",年龄是------>" + age);
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

}

b/执行测试代码

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.junit.Test;

public class TestReflection {

	@Test
	public void test1() throws Exception {
		// 1、获取运行时类
		Class clazz = Class.forName("com.test.Person");
		
		// 2、创建运行时类
		// Person必须要有空参的构造函数,否则用newInstance实例化会报错,构造器修饰符权限要足够,最好要给类留个空参构造器、
		Person person1 = (Person) clazz.newInstance();
		
		// 3、操作属性
		//    3.1、公共属性
		Field field1 = clazz.getField("name");
		field1.set(person1, "刘德华");
		System.out.println(person1);
		//    3.2、操作私有属性
		Field field2 = clazz.getDeclaredField("age");
		field2.setAccessible(true);
		field2.set(person1, 30);
		
		// 4、操作方法。
		//    4.1、方法无参数
		Method method1 = clazz.getMethod("show");
		method1.invoke(person1);
		//    4.2、方法一个参数
		Method method2 = clazz.getMethod("showNation", String.class);  // (方法名,参数类型)
		method2.invoke(person1, "中国"); // (对象,实参)
		//    4.3、方法多个参数
		Method method3 = clazz.getMethod("showNation", String.class, String.class, int.class);
		method3.invoke(person1, "张学友", "中国香港", 40);

	}
}

结果输出:

Person [name=刘德华, age=0]

这是一个人

国籍是------->中国

张学友的国籍是------->中国香港,年龄是------>40



1.注意Class类的特殊性
      Java语言是先把Java源文件编译成后缀为class的字节码文件,然后通过ClassLoader机制把类文件加载到内存当中,最后生成实例执行的,在描述一个类是,Java使用了一个元类来对类进行描述,这就是Class类,他是一个描述类的类,所以注定Class类是特殊的 
      1.Class类无构造函数,Class对象加载时由JVM通过调用类加载器的 
        defineClass方法来构造Class对象 
      2.Class类还可以描述基本数据类型,由于基本类型并不是Java中的对象,它们 
        一般存在于栈,但Class仍然可以对它们进行描述,例如使用int.class 
      3.其对象都是单例模式,一个Class的实现对象描述一个类,并且只描述一个类 
        所以只要是该被描述的类所有对象都只有一个Class实例 
      4.Class类是Java反射的入口 


2.适时选择getDeclaredXXX和getXXX
      Class类中提供了很多getDeclaredXXX和getXXX的方法,请看以下例子 

public static void main(String[] args) {
		Class cls = User.class;
		// 获取类方法
		cls.getDeclaredMethods();
		cls.getMethods();
		// 获取类构造函数
		cls.getDeclaredConstructors();
		cls.getConstructors();
		// 获取类属性
		cls.getDeclaredFields();
		cls.getFields();
	}

      getXXX的方式是获取所有公共的(public)级别的,包括从父类继承的方法,而getDeclaredXXX的方式是获取所有的,包括公共的(public),私有的(private),不受限与访问权限, 

3.反射访问属性或方法时将Accessible设置为true
      在调用构造函数或方法的invoke前检查accessible已经是公认的写法,例如以下代码 

public static void main(String[] args) throws Exception {
		Class cls = User.class;
		// 创建对象
		User user = cls.newInstance();

		// 获取test方法
		Method method = cls.getDeclaredMethod("test");

		// 检查Accessible属性
		if (!method.isAccessible()) {
			method.setAccessible(true);
		}
		method.invoke(user);
	}

      读者可以尝试获取Class的getMethod,也就是公开的方法,再输出isAccessible,可以看到输出的其实也是false,其实因为accessible属性的语义并不是我们理解的访问权限,而是指是否进行安全检查,而安全监察是非常消耗资源的,所以反射提供了Accessible可选项,让开发者逃避安全检查,有兴趣的读者可以查看AccessibleObject类观察其源码了解安全检查. 

4.使用forName动态加载类
      forName相信各位读者不会陌生,在使用JDBC时要动态加载数据库驱动就是使用forName的方式进行加载,同时亦可以从外部配置文件中读取类的全路径字符串进行加载,在使用forName时,被加载的类就会被加载到内存当中,只会加载类,并不会执行任何代码,而我们的数据库驱动就是利用static代码块来执行操作的,因为当类被加载到内存中时,会执行static代码块 

5.使用反射让模板方法更强大
      模板方法的定义是,定义一个操作的算法骨架,将某些步骤延迟到子类当中实现,而实现细节由子类决定,父类只决定骨架,以下是一个传统模板方法的事例 

public abstract class Test {

		public final void doSomething() {
			System.out.println("start...");
			doInit();
			System.out.println("end.....");
		}

		protectedabstractvoid doInit();
	} 

    此时子类只需要继承Test类实现doInit()方法即可嵌入到doSomething中,现在我们有一个需求,若我在doSomething中需要调用一系列的方法才能完成doSomething呢?而且我调用方法的数量并不确定,只需遵从某些规则则可将方法添加到doSomething方法当中.请看以下代码 

	public abstract class Test {

		public final void doSomething() throws Exception {

			Method[] methods = this.getClass().getDeclaredMethods();
			System.out.println("start...");
			for (Method method : methods) {
				if (this.checkInitMethod(method)) {
					method.invoke(this);
				}
			}
			System.out.println("end.....");
		}

		private boolean checkInitMethod(Method method) {
			// 方法名初始是否为init
			return method.getName().startsWith("init")
					// 是否为public修饰
					&& Modifier.isPublic(method.getModifiers())
					// 返回值是否为void
					&& method.getReturnType().equals(Void.TYPE)
					// 是否没有参数
					&& !method.isVarArgs()
					// 是否抽象类型
					&& !Modifier.isAbstract(method.getModifiers());
		}
	}  
  

      看到上面的代码,读者是否有似曾相识的感觉?在使用Junit3时是不是只要遵守方法的签名约定,就能成为测试类?使用这种反射可以让模板方法更强大,下次需要使用多个方法在模板方法中时,不要创建多个抽象方法,尝试使用以上方式 

6.不要过分关注反射的效率
      反射的效率是一个老生常谈的问题,普通的调用方法,创建类,在反射的情况下需要调用诸多API才能实现,效率当然要比普通情况下低下,但在项目当中真正引起性能问题的地方,绝大数不会是由反射引起的,而反射带给我们的却是如此的美妙,当今的各系列开源框架,几乎都存在反射的身影,而且大量存在更比比皆是,让Java这个沉睡的美女活起来的,非反射莫属 


部分资料源自互联网

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值