【学习笔记】Java中常用的反射操作

一、反射简介

1.1 先验知识

class也是一个对象

在面向对象的世界里,**java中的类(就是我们定义的那个class)也是一个实例对象,**是Class类的实例对象,这个对象在官网被称为(class type)。

类的加载信息

在这里插入图片描述

1.2 反射是什么?

在程序运行状态中:

  • 对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意一个方法和属性;

这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

1.3 反射能干什么?

反射机制主要提供了以下功能:

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时判断任意一个类所具有的成员变量和方法;
  3. 在运行时构造任意一个类的对象;
  4. 在运行时调用任意一个对象的方法;
  5. 生成动态代理

【注意】:是运行时获取而不是编译时获取。

举例:很多时候我们在用ide 或eclipse等编译器写代码时,当我们输入一个点的时候(比如 student.) 编译器就会自动列出它的属性和方法,这里就会用到反射。

二、常用的反射操作

java的反射机制的实现要借助于4个类: Class、Constructor、Field、Method,常用的操作基本都围绕这四个类来。

下面以需求demo的方式来展示一些常用的操作。在展示之前,我们要做些准备工作。

2.1 准备工作

构造一个 Student 类,用于测试:

package reflectDemo;

public class Student {
	//3个私有属性
    private String name;
    private Integer age;
    private String sex;
	
	//2个构造方法:无参 + 有参
    public Student(){}
    public Student(String name ,Integer age, String sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

	//2个public函数:无参 + 有参
    public String getNameWithAge(){
        return this.name +"的年龄是" + this.age + "岁.";
    }
    public String getNameWithKeyInfo(String keyInfo){
        return this.name +"的座右铭是" + keyInfo + ".";
    }
    
	//1个带参私有函数
    private String getAllInfo(String keyInfo){
        return  this.name + "年龄是 " + this.getAge() 
        +", 性别是 " + this.getSex() + ", 座右铭是:" + keyInfo;
    }

	// 其他工具函数
    public String getName(){
        return this.name;
    }
    public String getSex(){
        return this.sex;
    }
    public Integer getAge(){
        return  this.age;
    }
    @Override
    public String toString(){
        return  this.name + "年龄是 " + this.getAge() 
        +", 性别是 " + this.getSex();
    }
}

2.2 获取 class 对象

class对象是反射中最常用的,获取class对象的方式的主要有三种

  • 根据类名:类名.class
  • 根据对象:对象.getClass()
  • 根据全限定类名:Class.forName(全限定类名)

demo如下:

public class ReflectStudent {

	Student bigBear = new Student("BigBear", 29,"男");

    public static void main(String[] args){
        //1、通过全限定类名获取类信息
        Class<?> class1 = Class.forName("reflectDemo.Student");
        System.out.println("1、通过[全限定类名]获取类信息:" + class1.getName());
        
        //2、通过对象获取类信息
        Class<?> class2 = bigBear.getClass();
        System.out.println("2、通过[对象]获取类信息:" + class2.getName());
        
        //3、通过类名获取类信息
        Class<?> class3 = Student.class;
        System.out.println("3、通过[类名]获取类信息" + class3.getName());
	}
}

输出结果:

1、通过[全限定类名]获取类信息:reflectDemo.Student

2、通过[对象]获取类信息:reflectDemo.Student

3、通过[类名]获取类信息reflectDemo.Student

获取到class对象用来干什么?能干的事情多了,可以获取 构造方法Constructor、方法Method、属性Field、创建对象等,下面分别举例说明。

2.3 获取构造方法(Constructor)

常用的获取构造方法的方式:

// 返回指定参数类型、具有public访问权限的构造函数对象(若无参数,可不填)
Constructor getConstructor(Class<?>... parameterTypes)

// 返回所有具有public访问权限的构造函数的Constructor对象数组
Constructor<?>[] getConstructors()

下面demo:

  • step1:先通过 “对象.getClass() ” 方式获得Student的 class对象,
  • step2:再通过class对象获取对应的 无参 和 带参 构造方法;
  • step3: 使用构造方法来获取student对象实例。
public class ReflectStudent {

	Student bigBear = new Student("BigBear", 29,"男");

    public static void main(String[] args){
         //case1: 获取无参构造方法
        Constructor<?> constructor1 = bigBear.getClass().getConstructor();
        // 使用无参构造方法创建对象
        Student studentByConstructor1 = (Student)constructor1.newInstance();
        System.out.println("studentByConstructor1 = " + studentByConstructor1.toString());
        
        //case2: 获取带参构造函数
        Constructor<?> constructor2 = bigBear.getClass().getConstructor(String.class, Integer.class, String.class);
        //使用带参构造方法创建对象
        Student studentByConstructor2 = (Student)constructor2.newInstance("王铁柱", 17 , "女");
        System.out.println("studentByConstructor2 = " + studentByConstructor2.toString());
	}
}

对应的输出结果:

studentByConstructor1 = null年龄是 null, 性别是 null

studentByConstructor2 = 王铁柱年龄是 17, 性别是 女

2.4 获取方法(Method)

一个类里的方法,按照访问限制分类,可以分为 public型 和 非public型;按照继承关系分类,可以分为本类自己的 和 从父类继承的。通过反射获取方法时,这4种情况可以归纳到两种case里:

case1: 使用:

//获取单个指定的方法
Method method = clazz.getMethod(String methodName, Class<?>... parameterTypes);

//获取所有方法
Method[] methods = clazz.getMethods()

可以获取类中有访问权限的方法(仅public方法,包括父类中继承的方法)

case2:使用

//获取单个指定的方法
Method method = clazz.getDeclaredMethod(String methodName, Class<?>... parameterTypes)

//获取所有方法
Method[] methods = clazz.getDeclaredMethods()

可以获取类中的所有方法(public方法 + 非public方法,不包括父类中继承的方法)

对应的demo:

public class ReflectStudent {

	Student bigBear = new Student("BigBear", 29,"男");

    public static void main(String[] args){
       // case1: 调用无参public方法:getNameWithAge()
        Method method = Student.class.getMethod("getNameWithAge");
        System.out.println("method_反射测试_通过【类名】获取无参public方法: " + (String)method.invoke(bigBear));
        Method method01 = bigBear.getClass().getMethod("getNameWithAge");
        System.out.println("method_反射测试_通过【对象】获取无参public方法: " + method01.invoke(bigBear));
        
        // case2: 调用带参public方法: getNameWithKeyInfo(String keyInfo)
        Method method1 = Student.class.getMethod("getNameWithKeyInfo", String.class);
        String result2 = (String)method1.invoke(bigBear,"踏实做人, 认真做事");
        System.out.println("反射测试_调用带参public方法: " + result2);
		
		
		//case3: 调用带参的private方法:
		try {
            //case3.1 使用 getMethod:  会报 java.lang.NoSuchMethodException 异常,无法取到private方法
            Class<?> classForPrivate = bigBear.getClass();
            Method methodForPrivate = classForPrivate.getMethod("getAllInfo", String.class);
            String resultForPrivate1 = (String)methodForPrivate.invoke(bigBear, "踏实做人, 认真做事");
            System.out.println("resultForPrivate1 = " + resultForPrivate);
        }catch (Exception e){
            System.out.println("resultForPrivate1 error! " + e);
        }
        try {
        	 //case3.2 使用 getDeclaredMethod :会报 java.lang.IllegalAccessException 异常,能取到private方法,但不能调用
             Class<?> classForPrivate2 = bigBear.getClass();
             Method methodForPrivate2 = classForPrivate2.getDeclaredMethod("getAllInfo", String.class);
             String resultForPrivate2 = (String)methodForPrivate2.invoke(bigBear, "踏实做人, 认真做事 ");
             System.out.println("resultForPrivate2 = " + resultForPrivate2);
         }catch (Exception e){
             System.out.println("resultForPrivate2 error! " + e);
         }
         
         //case3.3 使用 getDeclaredMethod + setAccessible 设置,能取到private方法,也能调用该方法
         Class<?> classForPrivate3 = bigBear.getClass();
         Method methodForPrivate3 = classForPrivate3.getDeclaredMethod("getAllInfo", String.class);
         methodForPrivate3.setAccessible(true);
         String resultForPrivate3 = (String)methodForPrivate3.invoke(bigBear, "踏实做人, 认真做事 ");
         System.out.println("resultForPrivate3 = " + resultForPrivate3);
	}
}

对应的输出结果:

// case1的结果
method_反射测试_通过【类名】获取无参public方法: BigBear的年龄是29.
method_反射测试_通过【对象】获取无参public方法: BigBear的年龄是29.

// case2的结果
反射测试_调用带参public方法: BigBear的座右铭是:踏实做人, 认真做事.

// case3的结果
// case3.1 的结果
resultForPrivate1 error! java.lang.NoSuchMethodException: reflectDemo.Student.getAllInfo(java.lang.String)
// case3.2 的结果
resultForPrivate2 error! java.lang.IllegalAccessException: Class reflectDemo.ReflectStudent can not access a member of class reflectDemo.Student with modifiers "private"
// case3.3 的结果
resultForPrivate3 = BigBear年龄是 29, 性别是 男, 座右铭是:踏实做人, 认真做事: 

在case3中:

  • 使用 calzz.getMethod(methodName) 方式获取私有方法,会报 java.lang.NoSuchMethodException 异常;
  • 使用了 calzz.getDeclaredMethod(methodName) 方式获取私有方法,在invoke该私有方法时,会报 java.lang.IllegalAccessException 异常;
  • 只有使用 calzz.getDeclaredMethod(methodName) 方式获取私有方法,并且使用method.setAccessible(true)取消访问检查,才能达到访问私有对象的目的。

对于“私有方法的访问“一个场景是写单测的时候,可以直接测试指定的私有方法逻辑,比较方便。

2.5 获取属性(Filed)

一个类里的属性字段,按照访问限制分类,可以分为 public型 和 非public型;按照继承关系分类,可以分为本类自己的 和 从父类继承的。通过反射获取方法时,这4种情况可以归纳到两种case里:

case1:

//获取单个指定的属性
Field field = clazz.getField(String name);

//获取所有的属性
Field[] fields = clazz.getFields();	

获取指定name名称的属性字段(仅public字段,包括父类中继承的属性)。

case2:

//获取单个指定的属性
Field field = clazz.getDeclaredField(String name)

//获取所有的属性
Field[] fields = clazz.getDeclaredFields();	

可以获取类中的所有属性字段(public + 非public字段,不包括父类中继承的属性字段)

在Student里,我们的属性都是private类型,所以这里使用getDeclaredField方法来获取对于的属性,并赋值。相应的demo:

public class ReflectStudent {

	Student bigBear = new Student("BigBear", 29,"男");

    public static void main(String[] args){
    	//使用反射的方式,给这个 happyBird 对象属性赋值
       Student happyBird = new Student();
       Field nameField = happyBird.getClass().getDeclaredField("name");
       nameField.setAccessible(true);
       nameField.set(happyBird, "HappyBird");
       
       Field ageField =  happyBird.getClass().getDeclaredField("age");
       ageField.setAccessible(true);
       ageField.set(happyBird, 28);
       
       Field sexField =   Student.class.getDeclaredField("sex");
       sexField.setAccessible(true);
       sexField.set(happyBird, "女");
       
       System.out.println("通过反射给所有字段赋值后的结果:" + happyBird.toString() );

       //通过反射获取字段信息
       Field nameField2 = happyBird.getClass().getDeclaredField("name");
       nameField2.setAccessible(true);
       Object nameValue = nameField2.get(happyBird);
       System.out.println("通过反射取得的name结果:name info :" + nameValue);
	}
}

输出结果是:

通过反射给所有字段赋值后的结果:HappyBird年龄是 28, 性别是 女

通过反射取得的name结果:name info :HappyBird

当我们拿到一个没有set方法的对象时,需要对其相关属性赋值,可以使用上面这种方式来搞。同样的,需要注意使用method.setAccessible(true)取消访问检查,才能达到访问私有对象的目的。

2.6 通过反射创建对象

有两种方法,一种是上面提到的通过 Constructor 来创建,一种是通过class对象来创建,先看下demo:

public class ReflectStudent {

	Student bigBear = new Student("BigBear", 29,"男");

    public static void main(String[] args){
    	// case1: 通过Class对象来创建, 只能使用无参构造函数
       Class<?> studentClass = bigBear.getClass();
       Student studentByClassNewInstance = (Student)studentClass.newInstance();
       System.out.println("studentByClassNewInstance = " + studentByClassNewInstance.toString());
       
       // case2: 通过 Constructor 来创建, 可以使用 无参或 带参 构造函数
       Constructor<?> studentConstructorWithoutPar = bigBear.getClass().getConstructor();
       Student studentByConstructorWithoutPar = (Student)studentConstructorWithoutPar.newInstance();
       System.out.println("studentByConstructorWithNoPra = " + studentByConstructorWithoutPar.toString());
       
       Constructor<?> studentConstructorWithPar = bigBear.getClass()
           .getConstructor(String.class, Integer.class, String.class);
       Student studentByConstructorWithPar = (Student)studentConstructorWithPar.newInstance("翠花", 17 , "男");
       System.out.println("studentByConstructorWithPra = " + studentByConstructorWithPar.toString());
	}
}

对应的结果:

//case1
studentByClassNewInstance = null年龄是 null, 性别是 null
//case2
studentByConstructorWithNoPra = null年龄是 null, 性别是 null
studentByConstructorWithPra = 翠花年龄是 17, 性别是 男

这里有2个问题可以简单探究下:
Q1、初始化一个类,生成一个实例的时候,new与newInstance() 有什么区别?
A1: 区别在于创建对象的方式不一样,前者是使用的是类加载机制。
此外,从可伸缩、可扩展,可重用等软件思想上看:

  • newInstance: 弱类型,低效率,且只能调用无参构造;不过却是实现IOC、反射、面对接口编程 和 依赖倒置 等技术方法的必然选择;
  • new: 强类型,相对高效,能调用任何public构造;不过却只能实现具体类的实例化,不适合于接口编程。

三、参考文档

ref1.Java 基础 - 反射机制详解

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值