Java反射详解,还有什么理由学不会?

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

简述

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

反射基础

RTTI(Run-Time Type Indentification) 运行时类型识别,就是在运行时识别使用一个对象/类的信息,不同于编译时已经确定好的类型

反射就是将一个类中的各种属性和方法等解剖为一个个对象

Class类

Class(java.lang)类 Java应用里每一个类或接口都是Class类的实例,每个Java类在jvm都表现为一个Class对象,基本数据类型和void数组也是Class类的对象

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }
复制代码

阅读源码可得:

  • Class也是类,区别于class关键字
  • Class类构造方法是private,仅由JVM调用创建和加载

类加载

类加载机制图解 image-1654589184721

  • Java文件编译后会生成对应的class文件,里面记录着类一切的信息,jvm创建类都是依据Class对象来创建的
  • class文件在加载到内存中后,会生成唯一的Class对象,一个类全局仅存一个对应的Class对象

反射的基本使用

在Java中,Class类和java.lang.reflect类库共同支持反射技术 image-1654589918641

其中,Constructor类表示Class对象所表示类的构造方法,Field类表示成员变量,Method类表示成员方法等

方法介绍

方法 说明
forName() (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
(2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass() 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName() 取全限定的类名(包括包名),即类的完整名字。
getSimpleName() 获取类名(不包括包名)
getCanonicalName() 获取全限定规范 的类名(包括包名,一般在log时使用)
isXXX() 判断是否为某一类型,比如isInterface()是否为接口,isEnum()等
getXXX() 获取类的某一个信息,比如getFiled(String name)获取名字为name的属性,getMethod(),getAnnotation()等
getXXXs() 获取某个信息的数组,比如getInterfaces(),getMethods,getFileds() 注意:后两个方法会获取到包括父类所拥有的全部信息
getDeclredXXX/s() 获取已经声明的信息,仅包含本类,不包含父类 ,比如:getDeclaredAnnotations() ,getDeclaredMethod()
newInstance() 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器

Class对象的读取

在类加载的时候,JVM会创建一个class对象 class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种

  • 根据类名:类名.class
  • 根据对象:对象.getClass()
  • 根据全限定类名:Class.forName(全限定类名)
    tips:如果是加载内部类,格式为package.ClassName$InnerClass
        logger.info("根据类名: \t"+test.class);   //根据类名: 	class zjamss.test
        logger.info("根据对象: \t"+this.getClass());   //根据对象: 	class zjamss.test
        logger.info("根据全限定类名: \t"+Class.forName("zjamss.test"));    //根据全限定类名: 	class zjamss.test
        Class test = zjamss.test.class;
        // 常用的方法
        logger.info("获取全限定类名:\t" + test.getName());   //获取全限定类名:	zjamss.test
        logger.info("获取类名:\t" + test.getSimpleName());   //获取类名:	test
        logger.info("实例化:\t" + test.newInstance());   //实例化:	zjamss.test@17d10166
复制代码

Construct类及使用方法

Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法

扫描二维码关注公众号,回复: 14462971 查看本文章
    //Constructor使用
    @Test
    public void t2() throws Exception {
        Class<?> clazz = Class.forName("zjamss.reflect.User");
        User user = (User) clazz.newInstance();  //调用无参构造函数
        System.out.println(user);   //User{name='null', age=0}

        Constructor cs1 = clazz.getConstructor(int.class);
        User user1 = (User) cs1.newInstance(10);  //调用一个参数的构造函数
        System.out.println(user1);   //User{name='null', age=10}

        Constructor cs2 = clazz.getDeclaredConstructor(String.class,int.class);
        User user2 = (User) cs2.newInstance("12", 12);  //调用两个参数的构造函数
        System.out.println(user2);   //User{name='12', age=12}

        Constructor[] constructors = clazz.getDeclaredConstructors();  //获取User类里所有的构造方法
        for(Constructor constructor : constructors){
            System.out.print(constructor.toGenericString+":\t");
            Class[] parameterTypes = constructor.getParameterTypes();   //获取构造方法的所有形参类型
            for(Class param : parameterTypes){
                System.out.print(param.getCanonicalName()+"\t");
            }
            System.out.println();
        }
    }
    
                    //public zjamss.reflect.User(int):	int	
					//public zjamss.reflect.User(java.lang.String,int):	java.lang.String	int	
					//public zjamss.reflect.User():	
复制代码
        
public class User{
    private String name;
    public int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public User(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
复制代码

Field类及其用法

Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类静态字段或实例字段

    //Field
    @Test
    public void t3() throws Exception{
        Class clazz = User.class;
        //获取指定字段名称且修饰符为public的字段  包括父类
        Field age = clazz.getField("age");
        System.out.println("age: "+age);   //age: public int zjamss.reflect.User.age

        //获取所有修饰符为public的字段  包括父类
        Field[] fields = clazz.getFields();
        for(Field field : fields){
            System.out.println(field.getName()+": "+field.getDeclaringClass());
             //age: class zjamss.reflect.User
			//score: class zjamss.reflect.User
        }

        //获取当前类所有字段
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field field : declaredFields){
            System.out.println(field.getName()+": "+field.getDeclaringClass());
            	//name: class zjamss.reflect.User
				//tel: class zjamss.reflect.User
                //age: class zjamss.reflect.User
				//score: class zjamss.reflect.User
        }

        //获取当前类指定字段,任意修饰符都可
        Field name = clazz.getDeclaredField("name");
        System.out.println(name.getName()+": "+name);   //name: private java.lang.String zjamss.reflect.User.name
    }
复制代码

上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。

设置字段
        Class clazz = User.class;
        final Field name = clazz.getDeclaredField("name");
        final Constructor constructor = clazz.getConstructor(int.class);
        final User user = (User) constructor.newInstance(10);
        System.out.println(user);  //User{name='null', age=10}
        name.setAccessible(true); //解除private
        name.set(user,"name");
        System.out.println(user);  //User{name='name', age=10}
        System.out.println(name.get(user));  //name
复制代码

Method类及其用法

        Class clazz = User.class;
        //获取所有public的方法,包括父类
        Method[] methods =clazz.getMethods();
        for (Method m:methods){
            System.out.println("m::"+m);
        } 
        //m::public int zjamss.reflect.User.getAge()
        //m::public java.lang.String zjamss.reflect.User.toString()
        //m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
        //m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
        //m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
        //m::public boolean java.lang.Object.equals(java.lang.Object)
        //m::public native int java.lang.Object.hashCode()
        //m::public final native java.lang.Class java.lang.Object.getClass()
        //m::public final native void java.lang.Object.notify()
        //m::public final native void java.lang.Object.notifyAll()
        //获取当前类的方法包含private,该方法无法获取继承自父类的method
        Method method1 = clazz.getDeclaredMethod("display");
        System.out.println("method1::"+method1);  //method1::private void zjamss.reflect.User.display()
        method1.setAccessible(true);  //修改访问权限
        method1.invoke(clazz.newInstance());  //调用方法  User{name='null', age=0}  12

        Method method2 = clazz.getDeclaredMethod("getAge");
        System.out.println("method2:"+method2);  //method2:public int zjamss.reflect.User.getAge()
        int age = (int) method2.invoke(new User(12));  //获取方法返回值
        System.out.println(age);
        //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
        Method[] methods1=clazz.getDeclaredMethods();
        for (Method m:methods1){
            System.out.println("m1::"+m);
        } 
        //m1::private void zjamss.reflect.User.display()
        //m1::public int zjamss.reflect.User.getAge()
        //m1::public java.lang.String zjamss.reflect.User.toString()
复制代码

反射的使用案例

Spring框架想必大家都耳熟能详了,是一个提供IOC和AOP支持等等的框架,那里面的IOC又是怎么实现的呢?其实就是反射,比如最使用的@Autowired注解,自动类型注入

简单来说,当spring扫描bean的时候,会获取一个bean的Class对象,然后获取所有的field,遍历查找属性上是否有@Autowired注解,如果有,则先设置Accessiletrue,接着就将字段赋值为对应的bean对象

猜你喜欢

转载自juejin.im/post/7130080507530280973