Java基础(二十三):反射机制


一、反射(Reflection)的概念

  • Reflection(反射)是被视为动态语言的关键
    • 反射机制允许程序在运行期间借助于Reflection API取得任何类的内部信息
    • 并能直接操作任意对象的内部属性及方法
  • 类加载完之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象)
    • 这个对象就包含了完整的类的结构信息
    • 可以通过这个对象看到类的结构
    • 这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射

在这里插入图片描述

  • 从内存加载上看反射:

在这里插入图片描述

二、理解Class类

  • 要想解剖一个类,必须先要获取到该类的Class对象
  • 而剖析一个类或用反射解决具体的问题就是使用相关API
    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器

理论上:

  • 在Object类中定义了以下的方法,此方法将被所有子类继承:
public final native Class<?> getClass();
  • 以上的方法返回值的类型是一个Class类,此类是Java反射的源头
  • 可以通过对象反射求出类的名称

在这里插入图片描述

  • 对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口
  • 对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象
  • 一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息
    • Class本身也是一个类
    • Class 对象只能由系统建立对象
    • 一个加载的类在 JVM 中只会有一个Class实例
    • 一个Class对象对应的是一个加载到JVM中的一个.class文件
    • 每个类的实例都会记得自己是由哪个 Class 实例所生成
    • 通过Class可以完整地得到一个类中的所有被加载的结构
    • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

内存结构上:

在这里插入图片描述

说明:上图中字符串常量池在JDK6中存储在方法区;JDK7及以后,存储在堆空间

哪些类型可以有Class对象:

简言之,所有Java类型!

(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举在这里插入代码片
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void

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;

三、获取Class类的实例(四种方法)

  • 调用运行时类的静态属性:class
Class clazz1 = User.class;
  • 调用运行时类的对象的getClass()
User u1 = new User();
Class clazz2 = u1.getClass();
  • 调用Class的静态方法forName(String className)
String className = "com.xc.User"; //全类名
Class clazz3 = Class.forName(className);
  • 使用类的加载器的方式 (了解)
Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("com.xc.User");

四、反射的基本应用

1、应用1:创建运行时类的对象

  • 方式1:直接调用Class对象的newInstance()方法
    • 1)类必须有一个无参数的构造器,否则InstantiationException实例化异常
    • 2)类的构造器的访问权限需要足够,否则IllegalAccessException非法访问异常
    • 因为限制要求多,从jdk9开始不建议使用,@Deprecated(since=“9”)
  • 方式2:通过获取构造器对象来进行实例化
    • 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
    • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
    • 通过Constructor.newInstance实例化对象

示例代码:

public class TestCreateObject {
    @Test
    public void test1() throws InstantiationException, IllegalAccessException {
        // 1、没有空参构造抛出:InstantiationException实例化异常
        // 2、权限不够抛出:IllegalAccessException非法访问异常
        Class clazz = Person.class;
        //创建Person类的实例
        Person per = (Person) clazz.newInstance();
        System.out.println(per);
    }

    @Test
    public void test2()throws Exception{
        //(1)获取Class对象
        Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguDemo");
        /*
         * 获取AtGuiguDemo类型中的有参构造
         * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
         * 例如:public AtGuiguDemo(String title, int num)
         */
        //(2)获取构造器对象
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class);

        //(3)创建实例对象
        // T newInstance(Object... initargs)  这个Object...是在创建对象时,给有参构造的实参列表
        Object obj = constructor.newInstance("尚硅谷",2022);
        System.out.println(obj);
    }
}

2、应用2:获取运行时类的完整结构

2.1、相关API

//1.实现的全部接口
public Class<?>[] getInterfaces()   
//确定此对象所表示的类或接口实现的接口。 

//2.所继承的父类
public Class<? Super T> getSuperclass()
//返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。

//3.全部的构造器
public Constructor<T>[] getConstructors()
//返回此 Class 对象所表示的类的所有public构造方法。
public Constructor<T>[] 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<T> annotationClass) 
getDeclaredAnnotations() 

//7.泛型相关
//获取父类泛型类型:
Type getGenericSuperclass()
//泛型类型:ParameterizedType
//获取实际的泛型类型参数数组:
getActualTypeArguments()

//8.类所在的包
Package getPackage() 

2.2、获取所有的属性及相关细节

public class FieldTest {
	
	@Test
	public void test1(){
		
		Class clazz = Person.class;
		//getFields():获取到运行时类本身及其所有的父类中声明为public权限的属性
//		Field[] fields = clazz.getFields();
//
//		for(Field f : fields){
//			System.out.println(f);
//		}
		
		//getDeclaredFields():获取当前运行时类中声明的所有属性
		Field[] declaredFields = clazz.getDeclaredFields();
		for(Field f : declaredFields){
			System.out.println(f);
		}
	}
	
	//权限修饰符  变量类型  变量名
	@Test
	public void test2(){
		Class clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            //1.权限修饰符
            /*
         	* 0x是十六进制
         	* PUBLIC           = 0x00000001;  1    1
         	* PRIVATE          = 0x00000002;  2	10
         	* PROTECTED        = 0x00000004;  4	100
         	* STATIC           = 0x00000008;  8	1000
         	* FINAL            = 0x00000010;  16	10000
         	* ...
         	*
         	* 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
         	*
         	* mod = 17          0x00000011
         	* if ((mod & PUBLIC) != 0)  说明修饰符中有public
         	* if ((mod & FINAL) != 0)   说明修饰符中有final
         	*/
            int modifier = f.getModifiers();
            System.out.print(Modifier.toString(modifier) + "\t");

//            //2.数据类型
            Class type = f.getType();
            System.out.print(type.getName() + "\t");
//
//            //3.变量名
            String fName = f.getName();
            System.out.print(fName);
//
            System.out.println();
        }
	}
}

2.3、获取所有的方法及相关细节

public class MethodTest {

	@Test
	public void test1() {

		Class clazz = Person.class;
		// getMethods():获取到运行时类本身及其所有的父类中声明为public权限的方法
		// Method[] methods = clazz.getMethods();
		//
		// for(Method m : methods){
		// System.out.println(m);
		// }

		// getDeclaredMethods():获取当前运行时类中声明的所有方法
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method m : declaredMethods) {
			System.out.println(m);
		}
		//
	}

	// 注解信息
	// 权限修饰符 返回值类型 方法名(形参类型1 参数1,形参类型2 参数2,...) throws 异常类型1,...{}
	@Test
	public void test2() {
		Class clazz = Person.class;
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method m : declaredMethods) {
			// 1.获取方法声明的注解
			Annotation[] annos = m.getAnnotations();
			for (Annotation a : annos) {
				System.out.println(a);
			}

			// 2.权限修饰符
			System.out.print(Modifier.toString(m.getModifiers()) + "\t");

			// 3.返回值类型
			System.out.print(m.getReturnType().getName() + "\t");

			// 4.方法名
			System.out.print(m.getName());
			System.out.print("(");
			// 5.形参列表
			Class[] parameterTypes = m.getParameterTypes();
			if (!(parameterTypes == null && parameterTypes.length == 0)) {
				for (int i = 0; i < parameterTypes.length; i++) {

					if (i == parameterTypes.length - 1) {
						System.out.print(parameterTypes[i].getName() + " args_" + i);
						break;
					}

					System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
				}
			}

			System.out.print(")");

			// 6.抛出的异常
			Class[] exceptionTypes = m.getExceptionTypes();
			if (exceptionTypes.length > 0) {
				System.out.print("throws ");
				for (int i = 0; i < exceptionTypes.length; i++) {
					if (i == exceptionTypes.length - 1) {
						System.out.print(exceptionTypes[i].getName());
						break;
					}
					System.out.print(exceptionTypes[i].getName() + ",");
				}
			}
			System.out.println();
		}
	}
}

2.4、获取其他结构(构造器、父类、接口、包、注解等)

public class OtherTest {

    /*
    	获取当前类中的所有的构造器
     */
    @Test
    public void test1(){
        Class clazz = Person.class;
        Constructor[] cons = clazz.getDeclaredConstructors();
        for(Constructor c :cons){
            System.out.println(c);
        }
    }
    /*
    	获取运行时类的父类
     */
    @Test
    public void test2(){
        Class clazz = Person.class;
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);//class com.atguigu.java1.Creature
    }
    /*
    	获取运行时类的所在的包
     */
    @Test
    public void test3(){
        Class clazz = Person.class;
        Package pack = clazz.getPackage();
        System.out.println(pack);

    }
    /*
    	获取运行时类的注解
     */
    @Test
    public void test4(){
        Class clazz = Person.class;
        Annotation[] annos = clazz.getAnnotations();
        for (Annotation anno : annos) {

            System.out.println(anno);
        }

    }

    /*
    	获取运行时类所实现的接口
     */
    @Test
    public void test5(){
        Class clazz = Person.class;
        Class[] interfaces = clazz.getInterfaces();
        for (Class anInterface : interfaces) {

            System.out.println(anInterface);
        }

    }
    /*
    	获取运行时类的父类(带泛型)
     */
    @Test
    public void test6(){
        Class clazz = Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);//com.atguigu.java1.Creature<java.lang.String>
    }
}

2.5、获取运行时类的父类的泛型

@Test
public void test5() throws ClassNotFoundException {
    Class clazz = Class.forName("com.xc.data.Person");
    //获取带泛型的父类(Type是一个接口,Class实现了此接口
    Type superclass = clazz.getGenericSuperclass();
    //如果父类是带泛型的,则可以强转为ParameterizedType
    ParameterizedType paramType = (ParameterizedType) superclass;
    //调用getActualTypeArguments()获取泛型的参数,结果是一个数组,因为可能有多个泛型参数。
    Type[] arguments = paramType.getActualTypeArguments();
    //获取泛型参数的名称
    System.out.println(((Class)arguments[0]).getName()); // java.lang.String
}

3、应用3:调用运行时类的指定结构

3.1、调用指定的属性

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

(1)获取该类型的Class对象

Class clazz = Class.forName(“包.类名”);

(2)获取属性对象

Field field = clazz.getDeclaredField(“属性名”);

(3)如果属性的权限修饰符不是public,那么需要设置属性可访问

field.setAccessible(true);

(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象

Object obj = clazz.newInstance(); //有公共的无参构造

Object obj = 构造器对象.newInstance(实参…);//通过特定构造器对象创建实例对象

(5)设置指定对象obj上此Field的属性内容

field.set(obj,“属性值”);

如果操作静态变量,那么实例对象可以省略,用null表示(field.set(null,"属性值");)

(6)取得指定对象obj上此Field的属性内容

Object value = field.get(obj);

如果操作静态变量,那么实例对象可以省略,用null表示(Object value = field.get(null);)

示例:

public class TestField {
    public static void main(String[] args)throws Exception {
        //1、获取Student的Class对象
        Class clazz = Class.forName("com.atguigu.reflect.Student");

        //2、获取属性对象,例如:id属性
        Field idField = clazz.getDeclaredField("id");

        //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作
        idField.setAccessible(true);

        //4、创建实例对象,即,创建Student对象
        Object stu = clazz.newInstance();

        //5、获取属性值
        /*
         * 以前:int 变量= 学生对象.getId()
         * 现在:Object id属性对象.get(学生对象)
         */
        Object value = idField.get(stu);
        System.out.println("id = "+ value);

        //6、设置属性值
        /*
         * 以前:学生对象.setId(值)
         * 现在:id属性对象.set(学生对象,值)
         */
        idField.set(stu, 2);

        value = idField.get(stu);
        System.out.println("id = "+ value);
    }
}

关于setAccessible方法的使用:

  • Method和Field、Constructor对象都有setAccessible()方法
  • setAccessible启动和禁用访问安全检查的开关
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查

3.2、调用指定的方法

在这里插入图片描述

(1)获取该类型的Class对象

Class clazz = Class.forName(“包.类名”);

(2)获取方法对象

Method method = clazz.getDeclaredMethod(“方法名”,方法的形参类型列表);

(3)创建实例对象

Object obj = clazz.newInstance();

(4)调用方法

Object result = method.invoke(obj, 方法的实参值列表);

如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
如果方法是静态方法,实例对象也可以省略,用null代替(Object result = method.invoke(null, 方法的实参值列表);)

示例代码:

public class TestMethod {
    @Test
    public void test()throws Exception {
        // 1、获取Student的Class对象
        Class<?> clazz = Class.forName("com.atguigu.reflect.Student");

        //2、获取方法对象
        /*
         * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载
         *
         * 例如:void setName(String name)
         */
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);

        //3、创建实例对象
        Object stu = clazz.newInstance();

        //4、调用方法
        /*
         * 以前:学生对象.setName(值)
         * 现在:方法对象.invoke(学生对象,值)
         */
        Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三");

        System.out.println("stu = " + stu);
        //setName方法返回值类型void,没有返回值,所以setNameMethodReturnValue为null
        System.out.println("setNameMethodReturnValue = " + setNameMethodReturnValue);

        Method getNameMethod = clazz.getDeclaredMethod("getName");
        Object getNameMethodReturnValue = getNameMethod.invoke(stu);
        //getName方法返回值类型String,有返回值,getNameMethod.invoke的返回值就是getName方法的返回值
        System.out.println("getNameMethodReturnValue = " + getNameMethodReturnValue);//张三
    }

    @Test
    public void test02()throws Exception{
        Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguClass");
        Method printInfoMethod = clazz.getMethod("printInfo", String.class);
        //printInfo方法是静态方法
        printInfoMethod.invoke(null,"尚硅谷");
    }
}

4、应用4:读取注解信息

添加注解的类:

@Table("t_stu")
public class Student {
    @Column(columnName = "sid",columnType = "int")
    private int id;
    @Column(columnName = "sname",columnType = "varchar(20)")
    private String name;
}

读取和处理自定义注解:

public class AnnotationTest {
    //获取类声明上的注解
    @Test
    public void test1(){
        Class clazz = Customer.class;

        Table annotation = (Table) clazz.getDeclaredAnnotation(Table.class);

        System.out.println(annotation.value());
    }

    //获取属性声明的注解
    @Test
    public void test2() throws Exception {
        Class clazz = Customer.class;

        Field nameField = clazz.getDeclaredField("name");

        //获取属性声明上的注解
        Column nameColumn = nameField.getDeclaredAnnotation(Column.class);
        System.out.println(nameColumn.columnName());//sname
        System.out.println(nameColumn.columnType()); //varchar(20)
    }
}
  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值