Java反射机制详解(四)

Java反射机制详解(四)

4.反射的基本应用第一节

有了Class对象,能做些什么?

4.1 应用1:创建运行时类

这是反射机制应用最多的地方。创建运行时类的对象有两种方式:

方式1:直接调用Class对象的newInstance()方法

注:这种方法在jdk9及以后的版本里面,官方不推荐使用!

要求:

  1. 类必须有一个无参数的构造器;
  2. 类的构造器的访问权限必须要支持访问。(权限足够指:1、构造器的权限为public;2、和类在同一个model包下,且该类的权限不低于默认权限)

代码演示:

Class clazz = Person.class;

//创建Person类的实例
Person per = (Person) clazz.newInstance();

System.out.println(per);

方式2:通过获取构造器对象来进行实例化

这种方式作为方式1的迭代版本,更具有通用性。

代码演示:

//(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("测试1",2023);
System.out.println(obj);

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

可以获取:所在包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。

4.2.1 获取运行时类的属性、方法以及其他结构
  • 获取运行时类的属性:

代码演示:

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);
}
  • 获取运行时类的权限修饰符、变量类型、变量名

代码演示:

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
    * ...
    */
    int modifier = f.getModifiers();
    System.out.print(modifier + ":" + 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();
}
  • 获取运行时类的方法

代码演示:

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);
}
  • 获取运行时类的构造器:

代码演示:

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();
}
4.2.2 获取其他结构(构造器、父类、接口、包、注解等)
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>
    }
}

4.2.3 获取泛型父类信息

代码演示:

/* Type:
 * (1)Class
 * (2)ParameterizedType   
 * 		例如:Father<String,Integer>
 * 			ArrayList<String>
 * (3)TypeVariable
 * 		例如:T,U,E,K,V
 * (4)WildcardType
 * 		例如:
 * 		ArrayList<?>
 * 		ArrayList<? super 下限>
 * 		ArrayList<? extends 上限>
 * (5)GenericArrayType
 * 		例如:T[]
 * 	
 */
public class TestGeneric {
	public static void main(String[] args) {
		//需求:在运行时,获取Son类型的泛型父类的泛型实参<String,Integer>
		
		//(1)还是先获取Class对象
		Class clazz = Son.class;//四种形式任意一种都可以
		
		//(2)获取泛型父类
        //Class sc = clazz.getSuperclass();
        //System.out.println(sc);
		/*
		 * getSuperclass()只能得到父类名,无法得到父类的泛型实参列表
		 */
		Type type = clazz.getGenericSuperclass();
		
		// Father<String,Integer>属于ParameterizedType
		ParameterizedType pt = (ParameterizedType) type;
		
		//(3)获取泛型父类的泛型实参列表
		Type[] typeArray = pt.getActualTypeArguments();
		for (Type type2 : typeArray) {
			System.out.println(type2);
		}
	}
}
//泛型形参:<T,U>
class Father<T,U>{
	
}
//泛型实参:<String,Integer>
class Son extends Father<String,Integer>{
	
}
4.2.4 获取内部类或外部类信息

public Class<?>[] getClasses():返回所有公共内部类和内部接口。包括从超类继承的公共类和接口成员以及该类声明的公共类和接口成员。

public Class<?>[] getDeclaredClasses():返回 Class 对象的一个数组,这些对象反映声明为此 Class 对象所表示的类的成员的所有类和接口。包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口。

public Class<?> getDeclaringClass():如果此 Class 对象所表示的类或接口是一个内部类或内部接口,则返回它的外部类或外部接口,否则返回null。

Class<?> getEnclosingClass() :返回某个内部类的外部类

@Test
public void test5(){
	Class<?> clazz = Map.class;
	Class<?>[] inners = clazz.getDeclaredClasses();
	for (Class<?> inner : inners) {
		System.out.println(inner);
	}

	Class<?> ec = Map.Entry.class;
	Class<?> outer = ec.getDeclaringClass();
	System.out.println(outer);
}

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

4.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(实参…);//通过特定构造器对象创建实例对象

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

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

如果操作静态变量,那么实例对象可以省略,用null表示

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

Object value = field.get(obj);

如果操作静态变量,那么实例对象可以省略,用null表示

示例代码:

package com.atguigu.reflect;

public class Student {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

package com.atguigu.reflect;

import java.lang.reflect.Field;

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语言访问检查。
4.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代替

示例代码:

package com.atguigu.reflect;

import org.junit.Test;

import java.lang.reflect.Method;

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,"尚硅谷");
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java丶Sunday

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值