--反射机制和类加载器--java学习日记12(高新技术)

 

           根据面向对象的思想,任何事物都可以抽象成类,都能用类来描述。想到java类也是一个事物,它是用什么来描述的呢?答案是Class,即类对应的字节码文件。Class是一个用来描述java类的类,它包含类的名字、成员变量、所属包、成员方法、构造方法、注解和泛型类型等信息。要得到一个java类的字节码对象,有三种方法:1.类名.class    2.对象名.getClass();   3.Class.forName("类名");  我们已经知道,假如有一个Person类对象p1,可以通过它提供的方法获得p1 这个人的姓名年龄等信息,如果已经有了一个类的Class对象,如何从中获取该类的成员信息呢?

           这就要用到java的反射机制,反射并不是jdk5的新特性。反射就是将java类中各个成分映射成相应的java类。反射的基础就是得到类的字节码文件,方法是上面提到的三种。用Class.forName()方法得到字节码文件的过程是:先在jvm中查找,如果已经存在一份字节码则直接返回,否则会先将该类的字节码文件加载进内存然后再返回。

 

通过反射机制获取指定类的构造方法,字段和方法:

package enhancedDay123;
import java.lang.reflect.*;

public class ReflectTest {

	/**
	 * @param args
	 * 反射就是将java类中的各种成分映射成相应的java类,它不是jdk5的新特性
	 * 需求:通过反射机制获得ReflectPoint类中的域变量x和y
	 * 并通过get和set方法设置并改变其值
	 * 思路:反射的前提是先得到目标类的class文件,有三种方法:
	 * 1.类名.class 2.对象.getClass() 3.Class.forName(类名)
	 * 三种方式得到的结果一样。类加载器会先在虚拟机中查找有没有指定的类文件,如果有就直接返回,否则将类文件加载到虚拟机中再返回
	 */
	public static void main(String[] args) throws Exception{// 这里为了简化代码,并不抛出具体的异常
		// 用类加载器获得ReflectPoint的类对应的class文件
		Class classFile = Class.forName("cn.it.reflectTest.ReflectPoint");
		
		// 首先,要得到一个类对象,首先要获得其构造方法
		Constructor cons = classFile.getConstructor(int.class, int.class);
		ReflectPoint rp = (ReflectPoint) cons.newInstance(4, 6);// 通过构造器创建一个实例对象
		
		// 有了对象就可以通过反射机制获取对象内部的域变量和域方法
		Field fieldX = classFile.getDeclaredField("x");// 由于x是ReflectPoint的私有域,所以要用暴力反射方式获得
		fieldX.setAccessible(true);                  // 并设置其可获得性为真,这样就可以获得并修改x的值了
		Field fieldY = classFile.getField("y");
		System.out.println("x:"+fieldX.get(rp)+"  y:"+fieldY.get(rp));// 获取rp这个对象的x和y的值并打印
		
		// 进一步获得ReflectPoint的设值和获取值的方法,改变rp的x和y
		Method methodGetX = classFile.getMethod("getX", null);// 用来获得getX方法,参数分别为要获得的方法名和参数类型
		int rpX = (int)methodGetX.invoke(rp, null);// 调用rp这个对象的getX方法获得rp的x值
		
		// 用来获得setX方法,参数分别为要获得的方法名和参数类型
		Method methodSetX = classFile.getDeclaredMethod("setX", int.class);
		methodSetX.invoke(rp, 18);// 调用rp这个对象的getX方法获得rp的x值
		
		// 重新设置了x的值后再打印一下
		System.out.println("改变后:x:"+fieldX.get(rp));// 获取rp这个对象的x和y的值并打印    
		// 程序运行结果为:  x:4  y:6     改变后:x:18
	}
}

 

这里用到的ReflectPoint类的定义如下:

package enhancedDay123;
//这是一个用于测试反射机制的类
public class ReflectPoint {
	private int x;   // 注意,这里的字段x为私有的,反射的方式和y不同
   	public int y;
	
	public ReflectPoint(int x, int y){  // 构造方法
  		this.x = x;
		this.y = y;
	}
	
	@Override
	public boolean equals(Object arg0) {   // 覆盖equals方法
  		if(!(arg0 instanceof ReflectPoint))   // 如果要比较的对象根本就不是ReflectPoint类型就直接返回false,不用再比较
 			return false;
		ReflectPoint rp = (ReflectPoint)arg0;
		return this.x == rp.x && this.y == rp.y;
	}

	@Override
	public int hashCode() {  // 覆写hashCode方法,使对象的哈希值和字段有关
 		return (this.x*43 + this.y);
	}
        // 字段的getter和setter方法
	public int getX() {
		return x;
	}

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

	public int getY() {
		return y;
	}

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


 

              值得注意的是,如果要得到一个类的私有字段,需要通过getDeclaredField的暴力反射方法获得,并通过setAccessable(true)的方式使其值可获得。

          由于获得构造方法比较费时,反射设有缓存,将构造的对象缓存起来,用的时候直接拿来用就可以了,但比较浪费内存,会导致程序性能严重下降。

 

           类加载器用来加载一个类的字节码文件,它是反射的基础。上面已经说过类加载的方法,下面通过一个例子实现用Class的getResourceAsStream方法加载配置文件信息:

package enhancedDay123;

import java.io.InputStream;
import java.util.*;

public class ClassLoaderTest {

	/**
	 * @param args
	 * 类加载器
	 * java提供的三个基本的类加载器:
	 * 1,BootStrap   它不是java类,用来加载其他类。加载JRE/lib/rt.jar
	 * 2.ExtClassLoader    加载JRE/lib/ext/*.jar
	 * 3.AppClassLoader    加载CLASSPATH指定的所有jar或目录
	 * 类加载器委托机制:当要加载一个类时,当前加载器并不加载,而是让上级加载,接着向上传递,直到BootStrap
	 * 没有上级加载器,就由BootStrap加载,如果没找到就再向下一级一级找,如果到发起者也找不到就抛类找不到异
	 * 常。这个机制可以保证加载的类优先使用java提供的,便于集中管理,防止内存中存在多份相同的类文件。
	 * @throws IOException 
	 */
	public static void main(String[] args) throws Exception {
		Properties prop = new Properties();  // 获取系统属性
		InputStream is = ReflectPoint.class.getResourceAsStream("config.properties"); // 通过Class提供的方法得到指定文件输入流
		prop.load(is); // 加载配置文件
		is.close();// 关闭输入流
		String clazzName = prop.getProperty("className");// 得到配置文件中的指定键对应的值
		Class clazz = Class.forName("java.util." + clazzName);// 加载字节码文件
		Collection coll = (Collection)clazz.newInstance();// 创建一个实例
		
		ReflectPoint rp1 = new ReflectPoint(4, 8);// 创建ReflectPoint的对象
		ReflectPoint rp2 = new ReflectPoint(65, 8);
		ReflectPoint rp3 = new ReflectPoint(4, 3);
		ReflectPoint rp4 = new ReflectPoint(4, 8);// 根据ReflectPoint的hashCode和equals方法知rp1和rp4相等
		coll.add(rp4);// 向集合中添加元素
		coll.add(rp3);
		coll.add(rp2);
		coll.add(rp1);
		System.out.println(coll.size());// 打印集合中元素的个数
		/*
		 * 若配置文件中className节点配置的是ArrayList,打印4
		 * 若配置文件中className节点配置的是HashSet,打印3,因为hashSet集合中不能存放相同的元素
		 * */
	}
}

 

Java中的内存泄露和hashCode的作用:

如果改变了参与hashCode计算的成员,则可能会造成内存泄露。

       比如,往HashSet中添加一个person对象后(对象的name和age参与hashCode的计算),自动根据计算的hashCode值为把象存放到指定的区域,如果改变这个对象的name或age,计算的哈希值就与存进集合时的哈希值不同,此时即使在contains方法使用该对象的当前引用作为参数区HashSet集合中检索对象也将返回找不到对象的结果,这也会导致无法从HashSet集合中remove这个对象,从而造成内存泄露。上面这个例子中,如果修改了集合中的元素的x和y 的值,再用remove移除元素,会发现元素还存在在集合中,即已经找不到指定的对象了,造成内存泄露。

hashCode的作用:(哈希值是根据内存地址计算出来的)

      哈希算法将集合分成许多区域,当一个对象要存进来时,根据其哈希值决定存放到哪个区域(用哈希值对某个数取模),hashSet表中存储的元素保证唯一性也是根据hashCode去指定区域寻找是否有相同对象,提高了查找效率。

如果两个对象的equals方法返回相等,则hashCode也要相等,只有当对象要存入按哈希算法进行存取的对象的集合时,覆盖hashCode方法才有意义。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值