Java的反射机制(类的加载过程、Class类&获取Class类实例(4种)、创建运行时类的对象&获取运行时类的完整结构)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/zxdspaopao/article/details/101911895

Java的反射机制

反射概述

  Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

​  加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息,这个过程叫做反射。

正常获得实例化对象的方式:

  1. 引入需要的“包类”名称。
  2. 通过new关键字实例化。
  3. 获得实例对象。

通过反射方式解析实例化对象的方式:

  1. 实例化对象。
  2. getClass()方法。
  3. 得到完整的“包类”名称。

Java反射机制提供的功能:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时获取泛型信息。
  • 在运行时调用任意一个对象的成员变量和方法。
  • 在运行时处理注解。
  • 生成动态代理。

Class类&获取Class的实例

Class 类

在Object类中定义了以下的方法,此方法将被所有子类继承:

public final Class getClass()

以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

Class可以获取获取当前类的属性、方法和构造器以及实现的接口&继承的父类。

关于对Class类的理解:

  • Class本身也是一个类。
  • Class对象只能由系统建立对象。
  • 一个加载的类在JVM中只会有一个Class实例。
  • 一个加载的类在 JVM 中只会有一个Class实例 。
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件。
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成。
  • 通过Class可以完整地得到一个类中的所有被加载的结构 。
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。

Class类的常用方法

方法名 功能说明
static Class forName(String name) 返回指定类名 name 的 Class 对象
Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例
getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称
Class getSuperClass() 返回当前Class对象的父类的Class对象
Class [] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体的超类的Class
Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
Field[] getDeclaredFields() 返回Field对象的一个数组
Method getMethod(String name,Class … paramTypes) 返回一个Method对象,此对象的形参类型为paramType

获取Class类的实例(四种方式):

  1. 通过类.class的方式获取.
  2. 调用Class的静态方法forName(String str)获取。
  3. 调用运行时类对象的getClass()。
  4. 调用类加载器(ClassLoader)。

通过实例看一下是怎么创建的:

import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {
	/*
	 * 如何获取Class的实例
	 */
	public static void main(String[] args){
		//1、通过类.class的方式获取
		Class clazz = Person.class;
		System.out.println(clazz);
		//2、调用Class的静态方法forName(String str)
		Class clazz2 = Class.forName("com.atguigureflection.Person");
		System.out.println(clazz2);
		System.out.println(clazz == clazz2); //true
		//3、调用运行时类对象的getClass()
		Person p = new Person();
		Class clazz3 = p.getClass();
		System.out.println(clazz3);
		
		//4、调用ClassLoader
		ClassLoader cl = this.getClass().getClassLoader();
		Class clazz4 = cl.loadClass("com.atguigureflection.Person");
		System.out.println(clazz4);
	}
}

那么另一个问题来了,哪些随想可以有Class对象呢?

看看下面的你应该就懂了哪些能.class了。

import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {
	/*
	 * ___.class
	 *注意:只要元素类型与维度一样,就是同一个Class
	 */
	public static void main(String[] args){
		Class c1 = Object.class;
		Class c2 = Comparable.class;
		Class c3 = String[].class;
		Class c4 = int[][].class;
		Class c5 = ElementType.class;
		Class c6 = Override.class;
		Class c7 = int.class;
		Class c8 = void.class;
		Class c9 = Class.class;				
	}
}

刚才在上面提到了一个ClassLoader,也就是类加载器。加载器肯定是跟加载有关,那就从加载说起吧。

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
在这里插入图片描述

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。

链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

  • 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
  • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化

  • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。

  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

  • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

通过一小段代码来理解一下

public class ClassLoadingTest {
    public static void main(String[] args) {
        System.out.println(A.m);//100
    }
}

class A {
    static {
        m = 300;
    }
    static int m = 100;
}
/*
第二步:链接结束后m=0
第三步:初始化后,m的值由<clinit>()方法执行决定
  这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于
  <clinit>(){
         m = 300;
         m = 100;
     }
*/

  
那么什么时候会发生类初始化呢?

主要分为两类:主动引用和被动引用

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类。
  • new一个类的对象。
  • 调用类的静态成员(除了final常量)和静态方法。
  • 使用java.lang.reflect包的方法对类进行反射调用。
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类。

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。
  • 当通过子类引用父类的静态变量,不会导致子类初始化。
  • 通过数组定义类引用,不会触发此类的初始化。
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)。
      

执行源文件的过程:

在这里插入图片描述
了解完类的加载过程,继续说说类加载器。

ClassLoader

类加载器作用是用来把类(class)装载进内存的。共有以下几种类加载器。

1、引导类加载器(bootstrap class loader):

  • 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容),是用原生代码(C/C++)来实现的,并不继承自java.lang.ClassLoder。
  • 加载扩展类和应用程序类加载器。并指定为他们的父类加载器。

2、扩展类加载器(extensions class loader)

  • 用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar或java.ext.dirs路径下的内容)。Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。
  • 由sun.misc.Launcher$ExtClassLoader实现。

3、应用程序类加载器(application class loader)

  • 它根据Java应用的类路径(classpath,java.class.path)的类。
  • 一般来说,Java应用的类都是由它来完成加载。
  • 由sun.misc.Launcher$AppClassLoader实现。

它们的装载/加载顺序:
在这里插入图片描述
实例:

import java.io.InputStream;

public class ClassLoaderTest1 {
	public static void main(String[] args) {
		//1.获取一个系统类加载器
		ClassLoader classloader = ClassLoader.getSystemClassLoader();
		System.out.println(classloader);
		//2.获取系统类加载器的父类加载器,即扩展类加载器
		classloader = classloader.getParent();
		System.out.println(classloader);
		//3.获取扩展类加载器的父类加载器,即引导类加载器
		classloader = classloader.getParent();
		System.out.println(classloader);
		//4.测试当前类由哪个类加载器进行加载
		try {
			classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();
			System.out.println(classloader);
			//5.测试JDK提供的Object类由哪个类加载器加载
			classloader = Class.forName("java.lang.Object").getClassLoader();
			System.out.println(classloader);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		//6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
		InputStream in = ClassLoaderTest1.class.getClassLoader().getResourceAsStream("exer2\\jdbc.properties");
		System.out.println(in);
	}
}


  

创建运行时类的对象

创建运行时类对象步骤

  1. 根据全类名获取对应的Class对象。
  2. 调用指定参数结构的构造器,生成Constructor的实例。
  3. 通过Constructor的实例创建对应类的对象,并初始化属性。

例如:

import java.lang.reflect.Constructor;

public class Test {
	public static void main(String[] args) {
		//1.根据全类名获取对应的Class对象
		String name = "atguigu.java.Person";
		Class clazz = null;
		try {
			clazz = Class.forName(name);
			//2.调用指定参数结构的构造器,生成Constructor的实例
			Constructor con = clazz.getConstructor(String.class,Integer.class);
			//3.通过Constructor的实例创建对应类的对象,并初始化类属性
			Person p2 = (Person) con.newInstance("Peter",20);
			System.out.println(p2);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

  

获取运行时类的完整结构

通过反射获取运行时类的完整结构

  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的Field

具体说一下方法吧:

1.实现的全部接口

public Class<?>[] getInterfaces()

确定此对象所表示的类或接口实现的接口。

2.所继承的父类

public Class<? Super T> getSuperclass()

返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。

3.全部的构造器

  • public Constructor[] getConstructors()
    返回此 Class 对象所表示的类的所有public构造方法。

  • public Constructor[] getDeclaredConstructors()

    返回此 Class 对象表示的类声明的所有构造方法。

Constructor类中:

  • 取得修饰符: public int getModifiers();
  • 取得方法名称: public String getName();
  • 取得参数的类型:public Class<?>[] getParameterTypes();

4.全部的方法

  • public Method[] getDeclaredMethods()

    返回此Class对象所表示的类或接口的全部方法

  • public Method[] getMethods()

    返回此Class对象所表示的类或接口的public的方法

Method类中:

  • public Class<?> getReturnType()取得全部的返回值
  • public Class<?>[] getParameterTypes()取得全部的参数
  • public int getModifiers()取得修饰符
  • public Class<?>[] getExceptionTypes()取得异常信息

5.全部的Field

  • public Field[] getFields()

    返回此Class对象所表示的类或接口的public的Field。

  • public Field[] getDeclaredFields()

    返回此Class对象所表示的类或接口的全部Field。

Field方法中:

  • public int getModifiers() 以整数形式返回此Field的修饰符。
  • public Class<?> getType() 得到Field的属性类型。
  • public String getName() 返回Field的名称。

6. Annotation相关

  • get Annotation(Class annotationClass)
  • getDeclaredAnnotations()

7.泛型相关

  • 获取父类泛型类型:Type getGenericSuperclass()
  • 泛型类型:ParameterizedType
  • 获取实际的泛型类型参数数组:getActualTypeArguments()

8.类所在的包 Package getPackage()

  

调用运行时类的指定结构

一、调用指定方法

通过反射,调用类中的方法,通过Method类完成。步骤:

  1. 通过Class类的getMethod****(String name,Class**…parameterTypes)**方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
  2. 之后使用Object invoke(Object obj**, Object[]** args**)**进行调用,并向方法中传递要设置的obj对象的参数信息。
二、调用指定属性

在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。

  • public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
  • public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。

在Field中:

  • public Object get(Object obj) 取得指定对象obj上此Field的属性内容。
  • public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容。

  

用一个案例测试获取&调用的方法:

用反射获取某个类的信息,并用反射使用某个类
1、声明一个类:com.homework.demo.Demo,
(1)包含静态变量:学校school(显式初始化为"一中")
(2)包含属性:班级名称className
(3)并提供构造器,get/set等
(4)实现Serializable和Comparable接口(按照班级名称排序)
2、把com.homework.demo.class导出为一个school.jar并放到D:\ProgramFiles\Java\jdk1.8.0_151\jre\lib\ext目录(注意,以你自己的JDK安装目录为准)
3、在测试类Test的test01()测试方法中,用反射获取Demo类的Class对象,并获取它的所有信息,包括类加载器、包名、类名、父类、父接口、属性、构造器、方法们等。
4、在测试类Test的test02()测试方法中,用反射获取school的值,并修改school的值,然后再获取school的值。
5、在测试类Test的test03()测试方法中,用反射创建Demo类的对象,并设置班级名称className属性的值,并获取它的值。
6、在测试类Test的test04()测试方法中,用反射获取有参构造创建2个Demo类的对象,并获取compareTo方法,调用compareTo方法,比较大小。

代码实现:

Demo类:

package com.homework.demo;

import java.io.Serializable;

public class Demo implements Serializable,Comparable<Demo>{
	
	private static final long serialVersionUID = 1L;
	private static String school = "一中";
	private String className;
		
	public Demo(String className) {
		super();
		this.className = className;
	}

	public Demo() {
		super();
	}
	

	public static String getScool() {
		return school;
	}

	public static void setScool(String scool) {
		Demo.school = scool;
	}

	public String getClassName() {
		return className;
	}

	public void setClassName(String className) {
		this.className = className;
	}

	
	@Override
	public String toString() {
		return "Demo [className=" + className + "]";
	}

	@Override
	public int compareTo(Demo o) {
		return this.className.compareTo(o.getClassName());
	}
	
}

ReflectTest类:

package com.homework.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

import org.junit.Test;
/*
	用反射获取Demo类的Class对象,并获取它的所有信息,包括类加载器、包名、类名、父类、父接口、属性、构造器、方法们等。
 */
public class Test {
	@Test
	public void test01() throws ClassNotFoundException{
		Class clazz = Class.forName("com.homework.demo.Demo");
		
		ClassLoader classLoader = clazz.getClassLoader();
		System.out.println("类加载器:" + classLoader);
		
		Package pkg = clazz.getPackage();
		System.out.println("包名:"+pkg.getName());
		
		int cMod = clazz.getModifiers();
		System.out.println("类的修饰符:"+Modifier.toString(cMod));
		
		System.out.println("类名:"+clazz.getName());
		System.out.println("父类:"+clazz.getSuperclass().getName());
		
		Class[] interfaces = clazz.getInterfaces();
		System.out.println("父接口:"+Arrays.toString(interfaces));
		
		Field[] dfs = clazz.getDeclaredFields();
		for(int i = 0 ; i < dfs.length; i++){
			System.out.println("第" + (i+1) + "个字段");
			int fMod = dfs[i].getModifiers();
			System.out.println("修饰符:"+Modifier.toString(fMod));
			System.out.println("数据类型:"+dfs[i].getType().getName());
			System.out.println("属性名:"+dfs[i].getName());
		}
		
		Constructor[] dcs = clazz.getDeclaredConstructors();
		for(int i = 0; i < dcs.length; i++){
			System.out.println("第" + (i+1) + "个构造器:");
			int csMod = dcs[i].getModifiers();
			System.out.println("修饰符:"+Modifier.toString(csMod));
			System.out.println("构造器名:"+dcs[i].getName());
			System.out.println("形参列表:"+Arrays.toString(dcs[i].getParameterTypes()));
		}
		
		Method[] dms = clazz.getDeclaredMethods();
		for(int i = 0; i < dms.length; i++){
			System.out.println("第" + (i+1) + "个成员方法:");
			int csMod = dms[i].getModifiers();
			System.out.println("修饰符:"+Modifier.toString(csMod));
			System.out.println("返回值类型:"+dms[i].getReturnType().getName());
			System.out.println("方法名:"+ dms[i].getName());
			System.out.println("形参列表"+Arrays.toString(dms[i].getParameterTypes()));
		}
	}
	//用反射获取school的值,并修改school的值,然后再获取school的值
	@Test
	public void test02() throws Exception{
		Class clazz = Class.forName("com.homework.demo.Demo");
		Field field = clazz.getDeclaredField("school");
		field.setAccessible(true);
		Object value = field.get(null);
		System.out.println("school = " +value);
		
		field.set(null, "宁夏大学");
		value = field.get(null);
		System.out.println("school = "+ value);
	}
	/*
	 * 用反射创建Demo类的对象,并设置班级名称className属性的值,并获取它的值
	 */
	@Test
	public void test03() throws Exception{
		Class clazz = Class.forName("com.homework.demo.Demo");
		Object object = clazz.newInstance();
		Field field = clazz.getDeclaredField("className");
		field.setAccessible(true);
		Object value = field.get(object);
		System.out.println("className = " + value);
		
		field.set(object, "20080408班");
		value = field.get(object);
		System.out.println("className = " + value);
	}
	
	/*
	 * 用反射获取有参构造创建2个Demo类的对象,并获取compareTo方法,调用compareTo方法,比较大小。
	 */
	@Test
	public void test04() throws Exception{
		Class<?> clazz = Class.forName("com.homework.demo.Demo");
		Constructor c = clazz.getDeclaredConstructor(String.class);
		Object obj1 = c.newInstance("1997正式版");
		Object obj2 = c.newInstance("1999克隆版");
		
		Method m = clazz.getDeclaredMethod("compareTo", Object.class);
		System.out.println("obj1与obj2比较结果:"+m.invoke(obj1, obj2));	
	}
}

运行结果:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

关于反射的知识基本上都说完了,当然还有一些反射的应用,比如动态代理,这块的知识会在设计模式统一汇总。看完觉得不错的关于以下呗,以后会持续更新的。

猜你喜欢

转载自blog.csdn.net/zxdspaopao/article/details/101911895