Java基础之反射

框架都要用到反射技术,反射都要用到一个类Class.

java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class.

得到字节码的方式有三种:

Date.class;new Date().getClass();Class.forName("java.lang.String");最后一种是有直接返回,没有则从硬盘加载。

有9个预定义的Class实例对象,8种基本数据类型加void.class:参考Class.isPrimitive方法doc;

public class ReflectTest {
	public static void main(String[] args) throws Exception {// args:javaplay.TestArguments
		String str1 = "abc";
		Class<?> cls1 = str1.getClass();
		Class<?> cls2 = String.class;
		Class<?> cls3 = Class.forName("java.lang.String");
		System.out.println(cls1 == cls2);// true
		System.out.println(cls1 == cls3);// true

		System.out.println(cls1.isPrimitive());// false
		System.out.println(int.class.isPrimitive());// true
		System.out.println(int.class == Integer.class);// false
		// Integer.TYPE代表它所包装的基本类型字节码,请查看doc
		System.out.println(int.class == Integer.TYPE);// true
		System.out.println(int[].class.isPrimitive());// false
		System.out.println(int[].class.isArray());// true
		System.out.println(void.class == Void.class);// false
		// 总之,在源程序中出现的类型,都有各自的Class实例对象,例如int[],void

		// 通常方式:String str = new String(new StringBuffer("abc"));
		// jdk5之前只能接受数组,不接受可变参数
		// Constructor constructor = String.class.getConstructor(new Class<?>[]
		// { StringBuffer.class, int.class });
		// 得到所有public构造方法
		Constructor[] constructors = Class.forName("java.lang.String").getConstructors();
		// 得到一个构造方法
		Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
		// 反射方式(详见doc 一目了然,不能是"abc",要用到与StringBuffer相同类型的对象)
		String str2 = (String) constructor1.newInstance(/* "abc" */new StringBuffer("abc"));
		System.out.println(str2.charAt(2));// c
		// Class.newInstance()方法,可省去class->constructors->new object中间获取构造方法的环节
		// 从而直接创建对象,但它只能调用默认的无参构造方法,该方法内部先得到默认构造方法,然后用该构造方法创建对象,其中
		// 用到了缓存机制来保存默认构造方法的实例,这样能省点事,直接调用newInstance而不用获取构造方法的反射
		String o = (String) Class.forName("java.lang.String").newInstance();

		// 成员变量的反射
		ReflectPoint pt1 = new ReflectPoint(3, 5);
		Field fieldY = pt1.getClass().getField("y");
		// fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值
		System.out.println(fieldY.get(pt1));

		Field fieldX = pt1.getClass().getDeclaredField("x");// 私有成员要用Declared
		fieldX.setAccessible(true);// 设置true才可以访问,暴力反射
		System.out.println(fieldX.get(pt1));

		// 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"
		changeStringValue(pt1);
		System.out.println(pt1);

		// 成员方法的反射
		// 普通方式:str.charAt(1),反射方式如下:
		Method methodCharAt = String.class.getMethod("charAt", int.class);
		System.out.println(methodCharAt.invoke(str1, 1));
		// System.out.println(methodCharAt.invoke(null, 1));//静态方法第一个参数才为null
		// 1.4还有可变参数,只能用数组,又因为参数五花八门,所以用Object,1.4的调用语法如下:
		System.out.println(methodCharAt.invoke(str1, new Object[] { 1 }));

		// 对接受数组参数的成员方法进行反射
		// 写程序的时候不知道要执行哪个类的main
		TestArguments.main(new String[] { "111", "222", "333" });
		String startingClassName = args[0];
		Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
		mainMethod.invoke(null, new Object[] { new String[] { "111", "222", "333" } });// 原因在于1.5要兼容1.4
		mainMethod.invoke(null, (Object) new String[] { "111", "222", "333" });// 也行,总之,只能接受一个参数
		// mainMethod.invoke(null, (Object) new String[] { "111" }, (Object) new
		// String[] { "222" });// 不行
		// mainMethod.invoke(null, new String[] { "111" });// 不行
		/**
		 * 启动java程序的main方法的参数是一个字符串数组,通过反射方式调用时,如何传递参数呢,按1.5的语法,整个数组是一个参数,
		 * 而按1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会按照哪种方法进行
		 * 处理呢?1.5肯定要兼容1.4,会按1.4的语法来处理,即把数组打散成若干个单独的参数,所以,在给main方法传递参数时,不能使用
		 * mainMethod.invoke(null,new
		 * String[]{"xxx"}),javac会把它当作1.4语法来理解,会把里面的"xxx"当作需要传递的String[]{},所以
		 * 参数类型不对。
		 */

		/*
		 * 数组与Object的关系及其反射类型 . 1.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
		 * 2.代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class
		 * 3.基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既
		 * 可以当做Object类型使用,又可以当作Object[]类型使用
		 * 4.Arrays.asList()方法处理int[]和String[]时的差异
		 */
		int[] a1 = new int[] { 1, 2, 3 };
		Integer[] a11 = new Integer[] { 1, 2, 3 };
		int[] a2 = new int[4];
		int[][] a3 = new int[2][3];
		String[] a4 = new String[] { "a", "b", "c" };
		System.out.println(a1.getClass() == a2.getClass());// true 数组的类型和维度相同就一样
		// System.out.println(a1.getClass() == a4.getClass());//false
		// System.out.println(a1.getClass() == a3.getClass());//false
		System.out.println(a1.getClass().getName());// [I Class的getName方法有详细说明[I
		System.out.println(a1.getClass().getSuperclass().getName());// java.lang.Object
		System.out.println(a4.getClass().getSuperclass().getName());// java.lang.Object
		System.out.println(a4.getClass().getName());// [Ljava.lang.String;
		System.out.println(String.class.getName());// java.lang.String
		System.out.println(String.class.getSuperclass().getName());// java.lang.Object
		System.out.println(a11.getClass().getName());// [Ljava.lang.Integer;
		System.out.println(a11.getClass().getSuperclass().getName());// java.lang.Object
		Object aObj1 = a1;
		Object bObj2 = a4;
		Object aObj11 = a11;
		// Object[] aObj3 = a1;// 不能赋值,int不是Object
		Object[] aObj4 = a3;// a3是一维数组的数组,Object相当于一个一维int数组,此时Object[]表示一维数组(int[])的数组
		Object[] aObj5 = a4;// String是Object
		Object[] aObj6 = a11;

		// 由此引申的一个问题
		System.out.println(a1);// [I@5fb7a531
		System.out.println(a4);// [Ljava.lang.String;@11be650f
		// 确实转换成了数组,只是数组里只有一个元素,这个元素也是数组
		System.out.println(Arrays.asList(a1));// [[I@5fb7a531]
		// 字符串却可以转换,原因在于asList接受的是Object[](1.4)和T...(1.5),如果是String[]就按
		// 1.4的形式(因为符合1.4的形式,要向下兼容)转换成了list了,
		// 如果是int[]就不符合Object[]的形式,就会按1.5的形式T...来处理,也即当成一个int[](即T)类型的元素
		System.out.println(Arrays.asList(a4));// [a, b, c]

		// 数组的反射
		printObject(a1);// 1 2 3
		printObject(a4);// a b c
		printObject("xyz");// xyz
		// 有没有办法得到数组的类型?
		// 目前没有 Object[] a = new Object[]{"a",1};
		// a[0].getClass();
		// 即只能得到某个具体元素的类型,不能得到整个数组的元素类型
		// 写框架这些东西是必备,不写框架,翻某些砖头似的书像翻小说一样了,this is 进步。
	}

	public static void changeStringValue(Object obj) throws Exception {
		Field[] fields = obj.getClass().getFields();
		for (Field field : fields) {
			if (field.getType() == String.class) {// 同一份字节码,推荐用==
				String oldValue = (String) field.get(obj);
				String newValue = oldValue.replace("b", "a");
				field.set(obj, newValue);
			}
		}

	}

	public static void printObject(Object obj) {
		Class clazz = obj.getClass();
		if (clazz.isArray()) {
			// 对数组进行反射的类Array
			int len = Array.getLength(obj);
			for (int i = 0; i < len; i++) {
				System.out.println(Array.get(obj, i));
			}
		} else {
			System.out.println(obj);
		}
	}

}

class TestArguments {
	public static void main(String[] args) {
		for (String arg : args) {
			System.out.println(arg);
		}
	}
}
public class ReflectPoint {
	private int x;
	public int y;

	public String str1 = "ball";
	public String str2 = "basketball";
	public String str3 = "itcast";

	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "ReflectPoint [x=" + x + ", y=" + y + ", str1=" + str1 + ", str2=" + str2 + ", str3=" + str3 + "]";
	}

}

反射就是把java类中的各种成分映射成相应的java类,例如,一个java类用一个Class类的对象表示,一个类中的组成部分:成员变量、方法、构造方法、包等信息也用一个个的java类来表示,它们是Field、Method、Constructor、Package等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。

ArrayList_HashSet比较及hashcode分析:

public class ReflectTest2 {
	public static void main(String[] args) {
		Collection<ReflectPoint> collections = new HashSet<>();
		ReflectPoint pt1 = new ReflectPoint(3, 3);
		ReflectPoint pt2 = new ReflectPoint(5, 5);
		ReflectPoint pt3 = new ReflectPoint(3, 3);

		collections.add(pt1);
		collections.add(pt2);
		collections.add(pt3);
		collections.add(pt1);
		pt1.y = 7;// 修改后输出2,不修改,输出1
		collections.remove(pt1);
		// new ArrayList<>();输出4,添加时不比较直接按顺序存储引用变量、
		// new HashSet<>();输出3,存放时会先判断集合里有没有这个对象,有就不放不是覆盖,想覆盖要先remove再放
		// HashSet比较对象时调用对象默认equals是比较内存地址(通常是根据内存地址换算出来的,效果与==等价)
		// 实现equals和hashcode方法后输出2,如果此时注释hashcode或者equals其中任意一个则又输出3了
		// 原因在于它会先根据hashcode的值,算出自己要存放的最终位置所在的区域,如果只注释hashcode是有可能输出2的,因为存放的区域可能相同,也可能不同
		// 再调用equals进行比较是否有相等的对象(hashcode只在hash算法的集合中才有意义,其它集合没有任何价值),
		// 这就是输出3的原因,pt1和pt3存放的区域不一样,原因是没有实现hashcode则根据内存地址进行计算存放的区域,
		// 为了让相等的对象也肯定放在相同的区域,就有一个说法,如果两个对象equals相等,则应该也要让hashcode相等(不是hash集合就不要搞hashcode)
		// 注意:当一个对象被存储进hashset集合中,就不能修改这个对象中那些参与计算哈希值的字段了。否则就删除不了了,日积月累,
		// 不断的添加、修改、删除对象,而程序却一直使用这个hash集合就会造成内存泄漏!因为hash集合中有很多对象已经没用了却没释放
		// 由hashcode的作用讲到内存泄漏,由内存泄漏讲到hashcode的作用!
		System.out.println(collections.size());

	}

}

反射的作用->实现框架的功能

框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

用反射技术开发框架的原理:简单框架,把要使用的类放到配置文件里面

在工程根目录下创建config.properties文件:

className=java.util.HashSet
public class ReflectTest2 {
	public static void main(String[] args) throws Exception {
		InputStream ips = new FileInputStream("config.properties");
		Properties props = new Properties();
		props.load(ips);
		ips.close();// 跟操作系统说,把window干掉,把自己关联的系统/物理资源释放,自己则由垃圾回收器管理
		String className = props.getProperty("className");
		Collection<ReflectPoint> collections = (Collection<ReflectPoint>) Class.forName(className).newInstance();

		// Collection<ReflectPoint> collections = new HashSet<>();
		ReflectPoint pt1 = new ReflectPoint(3, 3);
		ReflectPoint pt2 = new ReflectPoint(5, 5);
		ReflectPoint pt3 = new ReflectPoint(3, 3);

		collections.add(pt1);
		collections.add(pt2);
		collections.add(pt3);
		collections.add(pt1);
		// pt1.y = 7;// 修改后输出2,不修改,输出1
		// collections.remove(pt1);

		System.out.println(collections.size());// config.properties文件内容是HashSet:2,ArrayList:4

	}

}

用类加载器的方式管理资源和配置文件

public class ReflectTest2 {
	public static void main(String[] args) throws Exception {
		// eclipse会自动把包下面的java文件或者普通文件编译后拷贝到classpath路径下
		// InputStream ips = new FileInputStream("config.properties");
		// 类加载器能加载.class文件,那加载普通文件也是顺带的事,下表示在classpath指定的根目录下去找指定的文件
		// 根目录下肯定没有,只是根目录下的javaplay目录下才有,此时不能定写成/javaplay
		// ssh框架内部用的就是用的类加载器加载的配置文件,所以ssh的配置文件必须放在classpath指定的目录下,原因就是它
		// 用的是类加载器读取的配置文件,而第一种不仅可以读还可以写即保存,类加载器的方式只能读不能写
		InputStream ips11 = ReflectTest2.class.getClassLoader().getResourceAsStream("javaplay/config.properties");
		// 类提供了一个便捷方法不用获取类加载器就可以加载配置文件,而且默认加载与自己同一个包内的配置文件
		InputStream ips2 = ReflectTest2.class.getResourceAsStream("config.properties");
		// 也可以相对路径
		InputStream ips3 = ReflectTest2.class.getResourceAsStream("resource/config.properties");
		// 也可以绝对路径,此时必须要以/开头,不管相对还是绝对内部都是调用
		InputStream ips = ReflectTest2.class.getResourceAsStream("/javaplay/resource/config.properties");
		Properties props = new Properties();
		props.load(ips);
		ips.close();// 跟操作系统说,把window干掉,把自己关联的系统/物理资源释放,自己则由垃圾回收器管理
		String className = props.getProperty("className");
		Collection<ReflectPoint> collections = (Collection<ReflectPoint>) Class.forName(className).newInstance();

		// Collection<ReflectPoint> collections = new HashSet<>();
		ReflectPoint pt1 = new ReflectPoint(3, 3);
		ReflectPoint pt2 = new ReflectPoint(5, 5);
		ReflectPoint pt3 = new ReflectPoint(3, 3);

		collections.add(pt1);
		collections.add(pt2);
		collections.add(pt3);
		collections.add(pt1);
		System.out.println(collections.size());// config.properties文件内容是HashSet:2,ArrayList:4

	}

}


 

 

 


 

 

posted @ 2016-10-12 00:28  john8169  阅读(74)  评论(0编辑  收藏  举报