Java语言学习之类加载机制与反射

本文详细解析了Java程序中类的加载过程,包括类的加载时机、类加载器的作用、类的连接与初始化,以及如何通过反射获取和操作类信息。重点讲解了自定义类加载器和使用反射创建对象的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.类的加载,连接和初始化
1.JVM和类
当调用java命令运行某个java程序时,该命令将会启动一个Java虚拟机进程,同一个JVM的所有线程,所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。
当系统出现以下几种情况时,JVM进程将被终止。
1>程序运行到最后正常结束;
2>程序运行到使用System.ext()或Runtime.getRuntime().exit()代码出结束程序;
3>程序执行过程中遇到未捕获的异常或错误而结束;
4>程序所在平台强制结束了JVM进程。

public class A
{
	// 定义该类的类变量
	public static int a = 6;
}

public class ATest1
{
	public static void main(String[] args)
	{
		// 创建A类的实例
		A a = new A();
		// 让a实例的类变量a的值自加
		a.a ++;
		System.out.println(a.a);//7
	}
}
public class ATest2
{
	public static void main(String[] args)
	{
		// 创建A类的实例
		A b = new A();
		// 输出b实例的类变量a的值
		System.out.println(b.a);//6
	}
}

先运行ATest1.java,类变量a自增1,可出看出输出是7,而运行ATest2.java结果是6,而不是7,因为第一次运行JVM结束后,它对A类所做的修改将全部丢失----第二次运行JVM时将再次初始化A类。

两次运行Java程序处于两个不同的JVM进程中,两个JVM之间并不会共享数据。

2.类的加载
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三个步骤来对该类进行初始化。
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:
1>从本地文件系统加载class文件,前面绝大部分演示程序都是采用类加载方式;
2> 从JAR包加载class文件,这种方式也很常见;
3>通过网络加载class文件;
4>把一个Java源文件动态编译并执行加载。

3.类的连接
4.类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化,在Java类中对类变量指定初始值有两种方式:
声明类变量时指定初始值;
使用静态初始化块为类变量指定初始值;

public class Test {
	//声明变量a时指定初始值
	static int a = 5;
	static int b;
	static int c;//默认初始值为0
	static {
		//使用静态初始化块为变量b指定初始值
		b = 6;
	}
}

5.类初始化的时机
在这里插入图片描述
当使用ClassLoader类的loadClass方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化,使用Class的forName静态方法才会导致强制初始化该类。

class Tester
{
	static
	{
		System.out.println("Tester类的静态初始化块...");
	}
}
public class ClassLoaderTest
{
	public static void main(String[] args)
		throws ClassNotFoundException
	{
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		// 下面语句仅仅是加载Tester类
		cl.loadClass("Tester");
		System.out.println("系统加载Tester类");
		// 下面语句才会初始化Tester类
		Class.forName("Tester");
	}
}

二.类加载器

类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象。

1.类加载器简介
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构
Bootstrap ClassLoader:根类加载器
他负责加载Java的核心类,

public class BootstrapTest
{
	public static void main(String[] args)
	{
		// 获取根类加载器所加载的全部URL数组
		URL[] urls = sun.misc.Launcher.
		getBootstrapClassPath().getURLs();
		// 遍历、输出根类加载器加载的全部URL
		for (int i = 0; i < urls.length; i++)
		{
			System.out.println(urls[i].toExternalForm());
		}
	}
}

Extension ClassLoader: 扩展类加载器
负责加载JRE的扩展目录

System ClassLoader:系统类加载器
负责在JVM启动时加载来自java命令的-classpath选项,java.class.path系统属性。

2.创建并使用自定义的类加载器
JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。

3.URLClassLoader类
Java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类,功能比较强大,可以从本地文件系统获取二进制文件来加载类,也可以从远处主机获取二进制文件来加载类。

三.通过反射查看类信息

1.获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类,Java程序
中获得Class对象通常有如下三种方式。
1>使用Class类的forName(String clazzName)静态方法,
2>调用某个类的class属性来获取该类对应的Class对象;如Person.class将会返回Person类对应的Class对象;
3>调用某个对象的getClass方法。

一旦获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象和该类的真实信息了。

2.从Class中获取信息

// 定义可重复注解
@Repeatable(Annos.class)
@interface Anno {}
@Retention(value=RetentionPolicy.RUNTIME)
@interface Annos {
    Anno[] value();
}
// 使用4个注解修饰该类
@SuppressWarnings(value="unchecked")
@Deprecated
// 使用重复注解修饰该类
@Anno
@Anno
public class ClassTest
{
	// 为该类定义一个私有的构造器
	private ClassTest()
	{
	}
	// 定义一个有参数的构造器
	public ClassTest(String name)
	{
		System.out.println("执行有参数的构造器");
	}
	// 定义一个无参数的info方法
	public void info()
	{
		System.out.println("执行无参数的info方法");
	}
	// 定义一个有参数的info方法
	public void info(String str)
	{
		System.out.println("执行有参数的info方法"
			+ ",其str参数值:" + str);
	}
	// 定义一个测试用的内部类
	class Inner
	{
	}
	public static void main(String[] args)
		throws Exception
	{
		// 下面代码可以获取ClassTest对应的Class
		Class<ClassTest> clazz = ClassTest.class;
		// 获取该Class对象所对应类的全部构造器
		Constructor[] ctors = clazz.getDeclaredConstructors();
		System.out.println("ClassTest的全部构造器如下:");
		for (Constructor c : ctors)
		{
			System.out.println(c);
		}
		// 获取该Class对象所对应类的全部public构造器
		Constructor[] publicCtors = clazz.getConstructors();
		System.out.println("ClassTest的全部public构造器如下:");
		for (Constructor c : publicCtors)
		{
			System.out.println(c);
		}
		// 获取该Class对象所对应类的全部public方法
		Method[] mtds = clazz.getMethods();
		System.out.println("ClassTest的全部public方法如下:");
		for (Method md : mtds)
		{
			System.out.println(md);
		}
		// 获取该Class对象所对应类的指定方法
		System.out.println("ClassTest里带一个字符串参数的info()方法为:"
			+ clazz.getMethod("info" , String.class));
		// 获取该Class对象所对应类的上的全部注解
		Annotation[] anns = clazz.getAnnotations();
		System.out.println("ClassTest的全部Annotation如下:");
		for (Annotation an : anns)
		{
			System.out.println(an);
		}
		System.out.println("该Class元素上的@SuppressWarnings注解为:"
			+ Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
		System.out.println("该Class元素上的@Anno注解为:"
			+ Arrays.toString(clazz.getAnnotationsByType(Anno.class)));
		// 获取该Class对象所对应类的全部内部类
		Class<?>[] inners = clazz.getDeclaredClasses();
		System.out.println("ClassTest的全部内部类如下:");
		for (Class c : inners)
		{
			System.out.println(c);
		}
		// 使用Class.forName方法加载ClassTest的Inner内部类
		Class inClazz = Class.forName("ClassTest$Inner");
		// 通过getDeclaringClass()访问该类所在的外部类
		System.out.println("inClazz对应类的外部类为:" +
			inClazz.getDeclaringClass());
		System.out.println("ClassTest的包为:" + clazz.getPackage());
		System.out.println("ClassTest的父类为:" + clazz.getSuperclass());
	}
}

3.Java8新增的方法参数反射
Java8在java.lang.reflect包下新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor,Method两个子类。

四.使用反射生成并操作对象

1.创建对象
通过反射来生成对象有如下两种方式
在这里插入图片描述

public class ObjectPoolFactory
{
	// 定义一个对象池,前面是对象名,后面是实际对象
	private Map<String ,Object> objectPool = new HashMap<>();
	// 定义一个创建对象的方法,
	// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
	private Object createObject(String clazzName)
		throws InstantiationException
		, IllegalAccessException , ClassNotFoundException
	{
		// 根据字符串来获取对应的Class对象
		Class<?> clazz = Class.forName(clazzName);
		// 使用clazz对应类的默认构造器创建实例
		return clazz.newInstance();
	}
	// 该方法根据指定文件来初始化对象池,
	// 它会根据配置文件来创建对象
	public void initPool(String fileName)
		throws InstantiationException
		, IllegalAccessException ,ClassNotFoundException
	{
		try(
			FileInputStream fis = new FileInputStream(fileName))
		{
			Properties props = new Properties();
			props.load(fis);
			for (String name : props.stringPropertyNames())
			{
				// 每取出一对key-value对,就根据value创建一个对象
				// 调用createObject()创建对象,并将对象添加到对象池中
				objectPool.put(name ,
					createObject(props.getProperty(name)));
			}
		}
		catch (IOException ex)
		{
			System.out.println("读取" + fileName + "异常");
		}

	}
	public Object getObject(String name)
	{
		// 从objectPool中取出指定name对应的对象。
		return objectPool.get(name);
	}

	public static void main(String[] args)
		throws Exception
	{
		ObjectPoolFactory pf = new ObjectPoolFactory();
		pf.initPool("obj.txt");
		System.out.println(pf.getObject("a"));      // ①
		System.out.println(pf.getObject("b"));      // ②
	}
}

如果不想利用默认的构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器,为了利用指定的构造器来创建Java对象,需要
1>获得该类的Class对象;
2>利用Class对象的getConstructor方法来获取指定的构造器;
3>调用Constructor的newInstance方法来创建Java对象;

public class CreateJFrame
{
	public static void main(String[] args)
		throws Exception
	{
		// 获取JFrame对应的Class对象
		Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
		// 获取JFrame中带一个字符串参数的构造器
		Constructor ctor = jframeClazz
			.getConstructor(String.class);
		// 调用Constructor的newInstance方法创建对象
		Object obj = ctor.newInstance("测试窗口");
		// 输出JFrame对象
		System.out.println(obj);
	}
}

2.调用方法
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods方法或者getMethod方法来获取全部方法或指定方法。这两个方法的返回值是Method数组,或Method对象。

每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它对应的方法,在Method里包含一个invoke方法。

public class ExtendedObjectPoolFactory
{
	// 定义一个对象池,前面是对象名,后面是实际对象
	private Map<String ,Object> objectPool = new HashMap<>();
	private Properties config = new Properties();
	// 从指定属性文件中初始化Properties对象
	public void init(String fileName)
	{
		try(
			FileInputStream fis = new FileInputStream(fileName))
		{
			config.load(fis);
		}
		catch (IOException ex)
		{
			System.out.println("读取" + fileName + "异常");
		}
	}
	// 定义一个创建对象的方法,
	// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
	private Object createObject(String clazzName)
		throws InstantiationException
		, IllegalAccessException , ClassNotFoundException
	{
		// 根据字符串来获取对应的Class对象
		Class<?> clazz =Class.forName(clazzName);
		// 使用clazz对应类的默认构造器创建实例
		return clazz.newInstance();
	}
	// 该方法根据指定文件来初始化对象池,
	// 它会根据配置文件来创建对象
	public void initPool()throws InstantiationException
		,IllegalAccessException , ClassNotFoundException
	{
		for (String name : config.stringPropertyNames())
		{
			// 每取出一对key-value对,如果key中不包含百分号(%)
			// 这就标明是根据value来创建一个对象
			// 调用createObject创建对象,并将对象添加到对象池中
			if (!name.contains("%"))
			{
				objectPool.put(name ,
					createObject(config.getProperty(name)));
			}
		}
	}
	// 该方法将会根据属性文件来调用指定对象的setter方法
	public void initProperty()throws InvocationTargetException
		,IllegalAccessException,NoSuchMethodException
	{
		for (String name : config.stringPropertyNames())
		{
			// 每取出一对key-value对,如果key中包含百分号(%)
			// 即可认为该key用于控制调用对象的setter方法设置值,
			// %前半为对象名字,后半控制setter方法名
			if (name.contains("%"))
			{
				// 将配置文件中key按%分割
				String[] objAndProp = name.split("%");
				// 取出调用setter方法的参数值
				Object target = getObject(objAndProp[0]);
				// 获取setter方法名:set + "首字母大写" + 剩下部分
				String mtdName = "set" +
				objAndProp[1].substring(0 , 1).toUpperCase()
					+ objAndProp[1].substring(1);
				// 通过target的getClass()获取它实现类所对应的Class对象
				Class<?> targetClass = target.getClass();
				// 获取希望调用的setter方法
				Method mtd = targetClass.getMethod(mtdName , String.class);
				// 通过Method的invoke方法执行setter方法,
				// 将config.getProperty(name)的值作为调用setter的方法的参数
				mtd.invoke(target , config.getProperty(name));
			}
		}
	}
	public Object getObject(String name)
	{
		// 从objectPool中取出指定name对应的对象。
		return objectPool.get(name);
	}
	public static void main(String[] args)
		throws Exception
	{
		ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
		epf.init("extObj.txt");
		epf.initPool();
		epf.initProperty();
		System.out.println(epf.getObject("a"));
	}
}

3.访问成员变量值
通过Class对象的getFields或getField方法可以获取该类所包括的全部成员变量或指定成员变量。
在这里插入图片描述

class Person
{
	private String name;
	private int age;
	public String toString()
	{
		return "Person[name:" + name +
		" , age:" + age + " ]";
	}
}
public class FieldTest
{
	public static void main(String[] args)
		throws Exception
	{
		// 创建一个Person对象
		Person p = new Person();
		// 获取Person类对应的Class对象
		Class<Person> personClazz = Person.class;
		// 获取Person的名为name的成员变量
		// 使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
		Field nameField = personClazz.getDeclaredField("name");
		// 设置通过反射访问该成员变量时取消访问权限检查
		nameField.setAccessible(true);
		// 调用set()方法为p对象的name成员变量设置值
		nameField.set(p , "Yeeku.H.Lee");
		// 获取Person类名为age的成员变量
		Field ageField = personClazz.getDeclaredField("age");
		// 设置通过反射访问该成员变量时取消访问权限检查
		ageField.setAccessible(true);
		// 调用setInt()方法为p对象的age成员变量设置值
		ageField.setInt(p , 30);
		System.out.println(p);
	}
}

4.操作数组

在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组,
在这里插入图片描述

public class ArrayTest1
{
	public static void main(String args[])
	{
		try
		{
			// 创建一个元素类型为String ,长度为10的数组
			Object arr = Array.newInstance(String.class, 10);
			// 依次为arr数组中index为5、6的元素赋值
			Array.set(arr, 5, "疯狂Java讲义");
			Array.set(arr, 6, "轻量级Java EE企业应用实战");
			// 依次取出arr数组中index为5、6的元素的值
			Object book1 = Array.get(arr , 5);
			Object book2 = Array.get(arr , 6);
			// 输出arr数组中index为5、6的元素
			System.out.println(book1);
			System.out.println(book2);
		}
		catch (Throwable e)
		{
			System.err.println(e);
		}
	}
}
public class ArrayTest2
{
	public static void main(String args[])
	{
		/*
		  创建一个三维数组。
		  根据前面介绍数组时讲的:三维数组也是一维数组,
		  是数组元素是二维数组的一维数组,
		  因此可以认为arr是长度为3的一维数组
		*/
		Object arr = Array.newInstance(String.class, 3, 4, 10);
		// 获取arr数组中index为2的元素,该元素应该是二维数组
		Object arrObj = Array.get(arr, 2);
		// 使用Array为二维数组的数组元素赋值。二维数组的数组元素是一维数组,
		// 所以传入Array的set()方法的第三个参数是一维数组。
		Array.set(arrObj , 2 , new String[]
		{
			"疯狂Java讲义",
			"轻量级Java EE企业应用实战"
		});
		// 获取arrObj数组中index为3的元素,该元素应该是一维数组。
		Object anArr  = Array.get(arrObj, 3);
		Array.set(anArr , 8  , "疯狂Android讲义");
		// 将arr强制类型转换为三维数组
		String[][][] cast = (String[][][])arr;
		// 获取cast三维数组中指定元素的值
		System.out.println(cast[2][3][8]);
		System.out.println(cast[2][2][0]);
		System.out.println(cast[2][2][1]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值