Java进阶学习笔记(三) 反射教程详细笔记

本文详细介绍了Java反射机制,从获取类对象、创建对象、访问属性和方法,到利用反射实现动态配置服务切换和绕过泛型检查。反射机制在运行时获取类信息,用于动态调用对象方法,是理解依赖注入、控制反转和切面的基础。

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

主要参考HOW2J反射机制系列教程Java 反射 -超详细讲解(附源码)

一、反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

或许有人奇怪,我在编写代码时,也能够获取这个类的所有属性和方法,为何需要反射呢?
我们在编写代码的时候,是在编译前获取对象,而反射是在程序运行时获取对象。

结论: 反射是运行时获取对象用的
应用场景: 之后学习切面的时候,我们需要在运行中获取对象的信息,此时就需要反射,jdk动态代理也用到了反射


二、获取对象

首先,让我们先写一个class

public class Hero {
    public String name; //姓名
}

我们获取对象有三个方法(以Hero举例)

方法说明
Class.forName(“Hero”)通过类名获取class,若包不同则需要添加包名“demo.Hero”
Hero.class直接获取class,需要import
new Hero().getClass()创建对象后获取class

实际代码如下

	public static void main(String[] args) {
		String className = "pogo.Hero";
		try {
			//获取类对象的第一种方式
			Class copyClass1 = Class.forName(className);
			//获取类对象的第二种方式
			Class copyClass2 = Hero.class;
			//获取类对象的第三种方式
			Class copyClass3 = new Hero().getClass();
			System.out.println(copyClass1==copyClass2);//输出true
			System.out.println(copyClass2==copyClass3);//输出true
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

发现输出的结果均为true,这是因为在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的,或者说是同一个。

三、使用反射创建对象

  • 获取对象:我们需要先获取对象,通过所获取的对象再创建对象,获取对象的方法可参照上文,此处仅只用Class.forName的方式获取对象

  • 获取构造函数:若要创建对象,则需要使用调用构造函数,获取构造函数的方法有以下几种

方法返回值说明
getConstructors()Constructor[]所有public构造方法
getDeclaredConstructors()Constructor[]获取所有构造方法
getConstructor(Class… parameterTypes)Constructor获取指定参数类型public构造方法
getDeclaredConstructor(Class… parameterTypes)Constructor获取指定参数类型的构造方法
  • 创建对象则是:Constructor对象调用newInstance(Object... initargs)

我们修改我们的Hero类如下,添加多个构造方式

public class Hero {
    public String name; //姓名
    public float hp; //血量
    public float armor; //护甲
    public int moveSpeed; //移动速度

    //(默认的构造方法)
    Hero(String name){
        System.out.println("(默认)的构造方法 s = " + name);
        this.name = name;
    }

    //无参构造方法
    public Hero(){
        System.out.println("调用了公有、无参构造方法执行了。。。");
    }

    //有一个参数的构造方法
    public Hero(char name){
        System.out.println("姓名:" + name);
        this.name = String.valueOf(name );
    }

    //有多个参数的构造方法
    public Hero(String name ,float hp){
        System.out.println("姓名:"+name+"血量:"+ hp);
        this.name = name;
        this.hp = hp;
    }

    //受保护的构造方法
    protected Hero(boolean n){
        System.out.println("受保护的构造方法 n = " + n);
    }

    //私有构造方法
    private Hero(float hp){
        System.out.println("私有的构造方法   血量:"+ hp);
        this.hp = hp;
    }
}

主函数如下所示:

public class Main {
    public static void main(String[] args) {
        try {
//            若是包名不同则添加包名如demo.Hero
            Class copyClass = Class.forName("Hero");

            System.out.println("————————————————————所有公有构造方法");
            Constructor[] conArray = copyClass.getConstructors();
            for (Constructor constructor: conArray) {
                System.out.println(constructor);
            }


            System.out.println("————————————————————所有构造方法");
            Constructor[] conArray2 = copyClass.getDeclaredConstructors();
            for (Constructor constructor: conArray2) {
                System.out.println(constructor);
            }

            System.out.println("————————————————————获取共有,参数为null(即无参)的构造方法");
            Constructor conSingle = copyClass.getConstructor(null);
            System.out.println(conSingle);
            Object object = conSingle.newInstance();
            System.out.println(object);

            System.out.println("————————————————————获取参数为float类型的构造方法");
            Constructor conSingle2 = copyClass.getDeclaredConstructor(float.class);
            System.out.println(conSingle2);
            System.out.println("强行访问私有类");
            conSingle2.setAccessible(true);
            Object object2 = conSingle2.newInstance(100);
            System.out.println(object2);

        } catch (Exception  e) {
            e.printStackTrace();
        }
    }
}

若觉得catch太多,可直接使用即可

		catch (Exception  e) {
            e.printStackTrace();
        }

输出结果如下

————————————————————所有公有构造方法
public Hero(java.lang.String,float)
public Hero()
public Hero(char)
————————————————————所有构造方法
private Hero(float)
protected Hero(boolean)
public Hero(java.lang.String,float)
Hero(java.lang.String)
public Hero()
public Hero(char)
————————————————————获取共有,参数为null(即无参)的构造方法
public Hero()
调用了公有、无参构造方法执行了。。。
Hero(name=null, hp=0.0, armor=0.0, moveSpeed=0)
————————————————————获取参数为float类型的构造方法
private Hero(float)
强行访问私有类
私有的构造方法 血量:100.0
Hero(name=null, hp=100.0, armor=0.0, moveSpeed=0)

三、获取对象的属性并修改

步骤:

  • 获取对象
  • 获取成员变量
  • 修改成员变量

获取成员变量的方式有两种

方法说明
getField获取公有属性
getDeclaredField获取属性(不能获取继承来的字段)

这里所说的不能获取继承来的字段指的是:只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true)

且需要注意的事情是,getDeclaredField虽然可以获取私有属性,但是不能对其进行修改,除非加上setAccessible(true)

我们继续修改Hero类,添加get、set方法,去掉过多的构造方法

public class Hero {
    private  String name; //姓名
    private  float hp; //血量
    private  float armor; //护甲
    public int moveSpeed; //移动速度

    //无参的构造方法
    public Hero(){
        System.out.println("无参的构造方法");
    }
    
    public Hero(String name){
        System.out.println("一个参数的构造方法 name = " + name);
        this.name = name;
    }

    //有多个参数的构造方法
    public Hero(String name ,float hp){
        System.out.println("姓名:"+name+"血量:"+ hp);
        this.name = name;
        this.hp = hp;
    }
    public String getName() {
        return name;
    }

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

    public float getHp() {
        return hp;
    }

    public void setHp(float hp) {
        this.hp = hp;
    }

    public float getArmor() {
        return armor;
    }

    public void setArmor(float armor) {
        this.armor = armor;
    }

    public int getMoveSpeed() {
        return moveSpeed;
    }

    public void setMoveSpeed(int moveSpeed) {
        this.moveSpeed = moveSpeed;
    }
}

主函数如下所示:

    public static void main(String[] args) {
//        新建对象
        Hero hero = new Hero();
//        设置name
        hero.setName("盖伦");
        try {
            Field field = hero.getClass().getDeclaredField("name");
//            由于name 是私有属性,所以需要setAccessible(true);才可以进行修改
            field.setAccessible(true);
            field.set(hero, "提莫");
            System.out.println(hero.getName());
        } catch (Exception  e) {
            e.printStackTrace();
        }
    }

执行结果如下:

无参的构造方法
提莫

四、获取并使用对象的方法

步骤:

  • 获取对象
  • 获取对象方法
  • 调用方法

获取对象方法 的方法如下所示:

方法返回值说明
getMethod(String name ,Class<?>… parameterTypes)Method获取公有方法
getDeclaredMethods(String name ,Class<?>… parameterTypes)Method获取所有方法(不包括继承)

调用方法如下所示:
method.invoke(Object obj,Object… args)
obj : 要调用方法的对象;
args:调用方式时所传递的实参;

代码示例:

    public static void main(String[] args) {
//        新建对象
        Hero hero = new Hero();
//        设置name
        hero.setName("盖伦");
        try {
            Method method = hero.getClass().getMethod("setName", String.class);
            method.invoke(hero,"提莫");
            System.out.println(hero.getName());//提莫
        } catch (Exception  e) {
            e.printStackTrace();
        }
    }

五、获取并使用对象的main方法

我们修改Hero,在其中添加一个main方法

public class Hero {
    public static void main(String[] args) {
        System.out.println("执行main方法");
        for (String str : args) {
            System.out.println(str);
        }
    }
}

获取main的方式,与四、获取并使用对象的方法类似,只是main的参数需要注意,这边我就直接上代码了

public static void main(String[] args) {
    try {
        Class copyClass = Class.forName("Hero");
        Method method = copyClass.getMethod("main",String[].class);
        method.invoke(null,(Object) new String[]{"a","b"});
        // method.invoke(null, new Object[]{new String[]{"a","b"}});//方式二
    }catch (Exception e) {
        e.printStackTrace();
    }
}

六、反射的应用

反射在实际工程中有着很大的作用,理解反射是理解依赖注入、控制反转、切面的基础。
此处先用两个简单例子。

六、1 利用反射,通过修改配置文件切换服务

1、准备两个Service

package cn.demo.module.learn;

public class ServiceOne {
    public void doServiceOne() {
        System.out.println("执行业务1");
    }
}
package cn.demo.module.learn;

public class ServiceTwo {
    public void doServiceTwo() {
        System.out.println("执行业务2");
    }
}

准备好我们的配置文件springService.txt

class=cn.demo.module.learn.ServiceTwo
method=doServiceTwo

测试用函数如下所示:

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(3000);
                //springService.txt中获取类名称和方法名称
                File springConfigFile = new File("D:\\springService.txt");
                Properties springConfig= new Properties();
                springConfig.load(new FileInputStream(springConfigFile));
                String className = (String) springConfig.get("class");
                String methodName = (String) springConfig.get("method");

//            根据类名称获取类对象
                Class copyClass = Class.forName(className);
//            根据名称,获取方法对象
                Method method = copyClass.getMethod(methodName);
//            获取构造器
                Constructor constructor = copyClass.getConstructor();
//            通过构造器生成对象
                Object service = copyClass.newInstance();
//                调用指定的方法
                method.invoke(service);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

开始运行后,我们在中途修改springService.txt,改成如下所示内容:

class=cn.demo.module.learn.ServiceOne
method=doServiceOne

执行结果如下所示:

在这里插入图片描述

可发现,我们并没有修改代码,但是成功从执行serviceTwo切换为serviceOne

六、2 通过反射,绕过泛型

    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();
        list.add("this");
        list.add("is");

        //	list.add(5);报错

        try {
            /********** 越过泛型检查    **************/
            //获取ArrayList的Class对象,反向的调用add()方法,添加数据
            Class listClass = list.getClass();
            //获取add()方法
            Method m = listClass.getMethod("add", Object.class);
            //调用add()方法
            m.invoke(list, 5);

        } catch (Exception e) {
            e.printStackTrace();
        }
//       此处list.add(5);仍报错,即泛型并没有被修改
        //遍历集合
        for (Object obj : list) {
            System.out.println(obj + " is " + obj.getClass().toString());
        }
    }

结果如下所示:

this is class java.lang.String
is is class java.lang.String
5 is class java.lang.Integer

可见仅有5是Integer类型,在后续想要执行list.add(5)报错,是因为泛型并没有被修改,仍是String类型,所以是越过泛型检查。

这是因为 泛型是在编译期间起作用的。在编译后的.class文件中是没有泛型的。所有比如T或者E类型啊,本质都是通过Object处理的。

所以可以通过使用反射来越过泛型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值