反射Reflect

反射Reflect

反射的定义:反射就是把Java类中的各种成分映射成相应的java类。

例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

一、Class.forName()

Class.forName() 的作用是用来返回字节码。

1)返回字节码的方式有两种:

1、这份字节码曾经被加载过,已经存在于 java 虚拟机中。

2、虚拟机中还没有字节码,需要使用类加载器加载,把加载的字节码放在 java 虚拟机中。

2)获取字节码对应的实例对象( Class类型)获取字节码的三种方式

1、类名.class,例如,System.class虚拟中已经存在该类的字节码,只有用类名.class 就可以得到

2、对象.getClass()例如,new Date().getClass()用这个方式去获取一个类的字节码

3、Class.forName("类名")例如,Class.forName("java.util.Date");主要用于反射。

3)一个奇怪的问题:加载了字节码,并调用了其getMethods之类的方法,但是没有看到类的静态代码块被执行,只有在第一个实例对象被创建时,这个静态代码才会被行。准确的说,静态代码块不是在类加载时被调用的,而是第一个实例对象被创建时才执行的。

4)isPrimitive()判断是否是基本数据类型的字节码。不是,返回 false是 ,返回 true

5)字节码的使用注意:

1、int 的字节码不等于 Integer 的字节码 ,返回 false

如:System.out.println(int.class == Integer.class);

2、int是 Integer 的包装类型中的,所以是同一份字节码,返回 true

如:System.out.println(int.class == Integer.TYPE);

3、数组类型的Class 实例对象Class.isArray();

如:System.out.println(int[].class.isArray());

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void

二、Constructor类:构造器

Constructor代表某个类中的一个构造方法

1、得到某个类的所有构造方法有如下两种写法,他们的作用是一样的。

Constructor[] cons1 = String.class.getConstructors();
Cosntructor[] cons2 = Class.forName("java.lang.String").getConstructors();

2、得到某一个构造方法:

Constructor cons3 = String.class.getConstructor(StringBuilder.class);
Constructor cons4 = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

3、创建实例对象

常用方式:cons3对应的初始化的 Constructor 对象。

String str1 = new String(new StringBuffer("abc"));

反射的方式:

String str2 = (String) cons4.newInstance(new StringBuffer("abc"));

4、Class.newInstance() 方法

String obj = (String) Class.forName("java.lang.String").newInstance();

三、Field类

Field 类代表某个类中的一个成员变量

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。

得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,那么久会报错,java.lang.NoSuchFieldException: x

注意:

fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值。

想要访问私有成员变量,就得使用getDeclaredField 和 setAccessible。这就是暴力反射,不管是否私有化,都能通过该方法获取。

Code:

package study.part3.day018.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectDemo4Field {
	public static void main(String[] args) throws Exception {
		ReflectPoint rp = new ReflectPoint(3,5);
		//得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,
		//那么久会报错,java.lang.NoSuchFieldException: x
		Field fieldy = rp.getClass().getField("y");
		//那 fieldy 的值是多少呢? 5?错
		//fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值
		System.out.println(fieldy.get(rp));
		//想要访问私有成员变量,就得使用getDeclaredField 和 setAccessible
		Field fieldx = rp.getClass().getDeclaredField("x");
		//暴力反射,不管是否私有化,都能通过该方法获取
		fieldx.setAccessible(true);
		System.out.println(fieldx.get(rp));		changeStringValue(rp);
		System.out.println("rp-->"+rp);		
	}
	/**
	 * 
	 * @param obj 传入所得的字节码参数
	 * @throws Exception
	 */
	public static void changeStringValue(Object obj) throws Exception{
		Field[] fields = obj.getClass().getFields();
		for(Field field : fields){
			//if(field.getType().equals(String.class)){}
			//字节码用 == 比较,不要用 equals 比较
			System.out.println(field.getType() == String.class);
			if(field.getType() == String.class){
				String oldVal = (String) field.get(obj);
				//System.out.println("old:"+oldVal);
				String newVal = oldVal.replace('a', 'x');
				field.set(obj, newVal);
			}
		}
	}
}
class ReflectPoint{
	private int x;
	public int y;
	public String stra = "ball";
	public String strb = "basketball";
	public String strc = "itcast";
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
	//覆盖重写,toString 方法
	@Override
	public String toString() {
		return "ReflectPoint [x=" + x + ", y=" + y + ", stra=" + stra
				+ ", strb=" + strb + ", strc=" + strc + "]";
	}
	@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;
	}
}<span style="font-family: Arial, Helvetica, sans-serif;"> </span>

四、Method类

Method 类代表某个类中的一个成员方法

1、得到类中的某一个方法:

Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);

2、调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式: System.out.println(charAt.invoke(str, 1)); 

3、jdk1.4和jdk1.5的invoke()方法的区别:

Jdk1.5:public Object invoke(Object obj,Object... args)

Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。

4、使用注意:

如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法。

Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。

5、Method Code Demo:

package study.part3.day018.reflection;
import java.lang.reflect.Method;
public class ReflectDemo5Method {
	public static void main(String[] args) throws Exception {
		Method getChatAtFunction = String.class.getMethod("charAt", int.class);
		System.out.println(getChatAtFunction);
		//getChatAtFunction
	}
}
class TestMain{
	public static void main(String[] args) {
		//处理高级 for 循环
		for(String arg : args){
			System.out.println("-->"+arg);
		}
	}
}

五、ArrayReflect数组的反射

1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

2、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

3、Arrays.asList()方法处理int[]和String[]时的差异。

4、Array工具类用于完成对数组的反射操作。

5、CodeDemo:

package study.part3.day018.reflection;
import java.lang.reflect.Array;
public class ReflectDemo7Array {
	public static void main(String[] args) {
		int[] a1 = new int[3];
		int[] a2 = new int[4];
		int[][] a3 = new int[3][4];
		String[] s1 = new String[3];
		System.out.println(a1==a2);
		//相同数据类型的数组,当维数相同时,他们的字节码是相同的
		System.out.println(a1.getClass()==a2.getClass());
		//System.out.println(a1.getClass()==a3.getClass());
		System.out.println(a1.getClass().getName()+"=="+a2.getClass().getName());
		System.out.println(a1.getClass().getSuperclass().getName());
		System.out.println(a3.getClass().getSuperclass().getName());
		Object o1 = a1;
		Object o2 = a2;
		Object o3 = a3;
		Object o4 = s1;
		Object[] o5 = a3;
		Object[] o6 = s1;
		System.out.println(o3);
		System.out.println("-----------------------------");
		printObject(a1);
		//Object 数组的反射
		Object[] oo = new Object[]{"a",1,'a'};
		String claoo = oo[1].getClass().getName();
		System.out.println(claoo);
	}
	public static void printObject(Object obj){
		Class clazz = obj.getClass();
		if(clazz.isArray()){
			//获取array 的长度
			int len = Array.getLength(obj);
			//for 循环
			for(int i = 0;i<len;i++){
				System.out.println((i+1)+":"+Array.get(obj, i));
			}
		}else{
			System.out.println("OBJ:"+obj);
		}
	}
}

六、CollectionReflect:集合反射

1、一句话总结:配置文件的加载往往是用类加载器的方式加载。以后学到的框架都是这么加的。

2、类加载器:用类加载器加载配置文件

-->类.class.getClassLoader().getResourceAsStream

说明:就是在classPath 下逐一的去查找你要加载的那个文件

3、扩展知识: eclipse 会把源目录(src)下的所有的 .java 文件,自动编译成 .class 你一保存它就编译了,然后存放到 classPath 指定的目录下去。同时,它也把所有的非 .java 文件,原封不动的再保存一份到 classPath 下,那么你就会发现,config.properties存在与两个不同的文件目录下。但是当你真正在获取配置文件的时候,它其实是在classPath下找的。

4、使用注意:创建一个文件读取流,读取配置文件中的字符流数据。一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。

5、CodeDemo:

package study.part3.day018.reflection;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class ReflectDemo8Collection {
	public static void main(String[] args) throws IOException, Exception, IllegalAccessException, ClassNotFoundException{
		//创建一个文件读取流,读取配置文件中的字符流数据
		//那么,读取properties 配置文件的路径怎么取呢?
		//一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。
		//InputStream ips = new FileInputStream("config.properties");
		//在 classPath 根目录下逐一的找 config.properties 文件
		//那么,如果把配置文件放在包内,那么就要加上。。
		//InputStream ips = ReflectDemo8Collection.class.getClassLoader().getResourceAsStream("study/day018/part3/reflection/config.properties");
		System.out.println("-------");
		//直接获取配置文件资源,并未使用类加载器,这种方法:只需要写上配置文件名,不需要写上目录名
		//InputStream ips = ReflectDemo8Collection.class.getResourceAsStream("config.properties");
		//当把配置文件整合到一个 resources 资源包下,那么只需要在获取的路径上加上当前的包名(下级目录)
		InputStream ips = ReflectDemo8Collection.class.getResourceAsStream("resources/config.properties");
		//初始化一个配置文件对象properties 。
		Properties prop = new Properties();
		//加载文件读取流
		prop.load(ips);
		//马上关闭文件读取流,这是一个习惯。如果没有及时关闭,那么会造成内存泄漏
		//并不是因为 ips 对象不被释放,而是这个对象关联的系统资源没有被释放。这是两个概念
		//总结:释放资源
		ips.close();
		String className = prop.getProperty("className");
		System.out.println("className的值是:"+className);
		//newInstance():不带参数的构造方法
		Collection cols = (Collection) Class.forName(className).newInstance();
		//Collection cols = new HashSet();		//得到的集合长度是3
		//Collection cols = new ArrayList();	//得到的集合长度是4
		ReflectPoint pt1 = new ReflectPoint(3, 4);
		ReflectPoint pt2 = new ReflectPoint(2, 5);
		ReflectPoint pt3 = new ReflectPoint(3, 4);
		//给集合添加了两次的pt1。
		Cols.add(pt1);
		Cols.add(pt1);
		Cols.add(pt1);
		//如果重新给pt1的变量 y 赋值,那么当 remove 了pt1后,就会造成 hashCode 的内存泄露的现象
		//pt1.y = 7;
		//cols.remove(pt1);
		System.out.println(cols.size());
		//某个对象,我不要了,但是却一直在用,没有被释放掉。这就是内存泄漏。
	}
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值