黑马程序员-JAVA-反射初探

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


JAVA的反射(java.lang.reflect)机制提供了在运行时加载类,取得类信息,构造类实例对象,取得和设置静态和实例字段,取得和引发(invoke)类的静态和实例方法的一系列工具,为各种框架和应用带来了极高的动态扩展能力,并且提供了运行时改变业务逻辑的可能,这个特性不能不了解一下。

下面我分别作一些简单的例子来试试这些功能

为了方便测试 做一个测试的目标和简单输出语句


class ReflectPoint extends Rflt implements Point {
	int x, y;
	String comment;
	int[] val;

	private static int rect(int xl, int yl) {
		return xl * yl;
	}

	@Override
	public String toString() {
		return "ReflectPoint [x=" + x + ", y=" + y + ", comment=" + comment
				+ ", val=" + Arrays.toString(val) + "]";
	}

	public ReflectPoint(int x, int y, String comment, int[] val) {
		super();
		this.setX(x);
		this.y = y;
		this.comment = comment;
		this.val = val;
	}

	@Override
	public int getX() {
		return x;
	}

	private void setX(int x) {
		this.x = x;
	}

	@Override
	public int getY() {
		return y;
	}

	void setY(int y) {
		this.y = y;
	}

	private static boolean not(boolean b) {
		return !b;
	}

	@Override
	protected String getComment() {
		return comment;
	}
}

abstract class Rflt {
	abstract String getComment();
}

interface Point {
	int getX();

	int getY();
}


	public static void sp(Object o) {
		if (o == null) {
			System.out.println("null");
		}
		if (o.getClass().isArray()) {
			StringBuilder sb = new StringBuilder();
			int len = Array.getLength(o);
			sb.append('[');
			for (int i = 0; i < len; i++) {
				if (i != 0) {
					sb.append(':');
				}
				sb.append(Array.get(o, i));
			}
			sb.append(']');
			System.out.println(sb);
		} else {
			System.out.println(o);
		}
	}



首先试试取得类:

	private static void testClass() {
		// 构造一个样例
		int[] val={3,4,7};
		ReflectPoint rp=new ReflectPoint(12, 25, "ref test", val);
		
		//取得类的3种常见方式 1.直接用类名.class 2.用已知类实例的.getClass() 
		//3.用Class的静态方法forName(String className) !!注意这种方式如果在classpath下找不到类会引发ClassNotFoundException  
		Class c1=ReflectPoint.class,c2=rp.getClass(),c3=null;
		try {
			c3=Class.forName("com.itheima.ReflectPoint");
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("ClassNotFound");
		}
		//以下三个输出都是class com.itheima.ReflectPoint
		sp(c1);
		sp(c2);
		sp(c3);
		
		//类路径错误的例子
		try {
			Class.forName("com.test.IdontExist");
		} catch (ClassNotFoundException e) {
			//throw new RuntimeException("ClassNotFound");
			sp("com.test.IdontExists not found");
		}
		
		//三种方式取得的Class实例在同一个类加载器下是单例,所以可以用==来比较,当然用.equals也行
		sp(c1==c2);//true
		sp(c1==c3);//true
	}
	


看来取得类很容易,值得注意的是Class.forName(String className)方法,这里加载的类依赖于字符串输入,而将它绑定到一个类似properties的配置文件或者绑定到命令行参数便轻松实现了启动应用时配置加载的类。再进一步,用一个驻留的后台线程监听配置文件改变,如果发生变化就重新配置或者重新启动应用服务,便达到了在线改变配置的效果,当然要达到这样的效果应该有良好的框架设计以免出现冲突和漏洞。


那么有了类,试试构建实例对象

	private static void testConstructer() {
		// 构造必要的数据
		int[] val = { 3, 4, 7 };
		int x = 12, y = 25;
		String comm = "cons test";

		// 取得Constructor
		// 这里使用了直接指定参数列表取得指定的构造函数的方式,当然如果参数不匹配会导致NoSuchMethodException
		// 而当前运行时上下文无权限访问(修饰符限定)时,会导致SecurityException 就要改用暴力反射才能继续了
		Constructor<ReflectPoint> constructor=null;
		try {
			constructor= ReflectPoint.class.getConstructor(int.class,int.class,String.class,int[].class);
		} catch (NoSuchMethodException | SecurityException e) {
			throw new RuntimeException("Constructor can not get");
		}
		
		//构造实例
		//这里直接用.newInstance()方法即可,当然:参数列表必须匹配
		ReflectPoint rp=null;
		if(constructor!=null){
			try {
				rp=constructor.newInstance(x,y,comm,val);
			} catch (InstantiationException | IllegalAccessException
					| IllegalArgumentException | InvocationTargetException e) {
				throw new RuntimeException("Construct Instance fail");
			}
		}
		
		//测试一下实例
		if(rp!=null){
			sp(rp);
			sp(rp.x);
			sp(rp.y);
			sp(rp.comment);
			sp(rp.val);//OK 构造成功 字段都取得了
		}
	}

取得构造方法还有一个途径就是Class实例方法.getConstructors() 它返回类的所有公共构造方法数组,然后可以遍历整个结果,利用.getGenericParameterTypes()分析参数类型,最后选择适合的构造方法。这种方法适用于黑箱操作,毕竟开发的时候不大可能完全了解可能加载的构造方法列表。


取得字段的例子,这里使用了暴力反射,而且逐个修改了传入的对象的字段:


	/*
	 * 测试时传入的对象 int[] val = { 3, 4, 7 }; ReflectPoint rp = new ReflectPoint(12,
	 * 25, "ref test", val); testFields(rp);
	 */
	private static void testFields(Object o) {
		// 现在,传入了一个Object类型对象 显然信息是极少的
		// 那么就必须用反射来获取他的各种信息
		// 这里测试获取他的字段并作修改

		// 修改前
		sp(o);
		// ReflectPoint [x=12, y=25, comment=ref test, val=[3, 4, 7]]

		// 这里为了取得所有的成员字段,使用了getDeclared系列方法和setAccessible强制访问,所谓的暴力反射
		Field[] fields = o.getClass().getDeclaredFields();
		for (Field field : fields) {
			if (!field.isAccessible()) {
				field.setAccessible(true);
			}
			// 可以根据字段类型判断
			if (field.getType().isPrimitive()) {
				// 也可以根据字段名称判断
				switch (field.getType().getName()) {
				// 简单修改一下整形内容
				case "int":
					int mod = 0;
					switch (field.getName()) {
					case "x":
						mod = 7;
						break;
					case "y":
						mod = -10;
						break;
					default:
						break;
					}
					try {
						// 用get方法取,用set方法写入
						field.set(o, (int) (field.get(o)) + mod);
					} catch (IllegalArgumentException | IllegalAccessException e) {
						throw new RuntimeException("Field " + e.getMessage());
					}
					break;
				default:
					break;
				}
			} else {
				switch (field.getType().getName()) {
				// 修改String 这里类型名是完整的限定名
				case "java.lang.String":
					try {
						field.set(o, (String) (field.get(o)) + "edited");
					} catch (IllegalArgumentException | IllegalAccessException e) {
						throw new RuntimeException("Field " + e.getMessage());
					}
					break;
				// 数组类型名为'['+元素类型名 基本类型用一个大写字符表示, I表示int
				case "[I":
					try {
						Object arr = field.get(o);
						int[] val = new int[Array.getLength(arr) + 2];
						int sum = 0;
						for (int i = 0; i < Array.getLength(arr); i++) {
							int intValue = Array.getInt(arr, i);
							sum += intValue;
							val[i] = intValue;
						}
						val[val.length - 2] = 787;
						val[val.length - 1] = sum + 787;
						field.set(o, val);
					} catch (IllegalArgumentException | IllegalAccessException e) {
						throw new RuntimeException("Field " + e.getMessage());
					}
					break;
				default:
					break;
				}
			}
		}
		// 修改后
		sp(o);
		// ReflectPoint [x=19, y=15, comment=ref testedited, val=[3, 4, 7, 787,
		// 801]]
		// 可以看到修改成功了
	}



可以看到 字段的取得主要靠Field类 每一个Field类实例代表一个类中的字段,而不是字段的实例,要取得字段的实例,需要传入对象实例,或者传入null来取得静态字段。

最后 测试一下方法的反射


	private static void testMethod() {
		// 还是先准备一个对象
		int[] val = { 3, 4, 7 };
		ReflectPoint rp = new ReflectPoint(12, 25, "ref test", val);

		// 还是暴力一下,拿到所有方法
		Method[] mths = rp.getClass().getDeclaredMethods();
		for (Method method : mths) {
			if (!method.isAccessible()) {
				method.setAccessible(true);
			}
			// 先看看相关的信息
			sp(method.getName());
			sp(method.getModifiers());
			sp(method.getParameterTypes());
			sp(method.getReturnType());

			// 调用一下试试看
			Object result = null;
			Class<?>[] paraType = method.getParameterTypes();
			int paraCount = paraType.length;
			Object[] paras = new Object[paraCount];
			for (int i = 0; i < paraCount; i++) {
				paras[i] = defaultValue(paraType[i]);
			}
			switch (method.getParameterTypes().length) {
			case 0:
				try {
					result = method.invoke(rp);
				} catch (IllegalAccessException | IllegalArgumentException
						| InvocationTargetException e) {
					throw new RuntimeException("Method " + e.getMessage());
				}
				break;
			case 1:
				try {
					result = method.invoke(rp, paras[0]);
				} catch (IllegalAccessException
						| IllegalArgumentException | InvocationTargetException e) {
					throw new RuntimeException("Method " + e.getMessage());
				}
				break;
			case 2:
				try {
					result = method.invoke(rp, paras[0], paras[1]);
				} catch (IllegalAccessException
						| IllegalArgumentException | InvocationTargetException e) {
					throw new RuntimeException("Method " + e.getMessage());
				}
				break;
			// 再多参数就不管了
			default:
				break;
			}
			sp(method);
			if (result != null) {
				sp(result);
			}else{
				sp("null|void");
			}
		}
	}

	private static Object defaultValue(Class<?> cls) {
		if (cls.isPrimitive()) {
			switch (cls.getName()) {
			case "byte":
			case "char":
			case "short":
			case "int":
			case "long":
				return (byte) 0;
			case "float":
			case "double":
				return 0.0f;
			case "boolean":
				return false;
			case "void":
				return null;
			}
		} else {
			try {
				return cls.newInstance();
			} catch (InstantiationException | IllegalAccessException e) {
				sp("unable to init default instance");
				return null;
			}
		}
		// TODO Auto-generated method stub
		return null;
	}



可以看到 方法的反射依靠Method的实例方法invoke,而且和字段类似,可以暴力反射拿到没有权限访问的方法。而且,静态方法的invoke与传入的对象无关,一般来说,可以传入null。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值