Java反射笔记

393 阅读8分钟

这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

反射

反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制范围内使用反射的字段、方法和构造函数对其底层副本进行操作。

每个类都有一个class对象,包含了与类有关的信息。当编译一个新类是,会产生一个同名的.class文件,该文件内保存着Class对象。

类加载相当于Classs对象的加载,类在第一次使用时才能动态加载到JVM中。

文件名说 明
getFields()获得类的public类型的属性。
getDeclaredFields()获得类的所有属性
getField(String name)获得类的指定属性
getMethods()获得类的public类型的方法
getMethod (String name,Class [] args)获得类的指定方法
getConstrutors()获得类的public类型的构造方法
getConstrutor(Class[] args)获得类的特定构造方法
newInstance()通过类的无参构造方法创建对象
getName()获得类的完整名字
getPackage()获取此类所属的包
getSuperclass()获得此类的父类对应的Class对象

反射的作用

​ 在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中

​ Class类:字节码对象类,每个类的字节码就是该类的一个对象

​ Constructor 类:构造法方法类,任何一个构造方法就是该类的一个对象

​ Field 类:属性类,任何一个属性都是该类的一个对象

​ Method类:方法类,任何一个方法都是该类的一个对象

Class

​ Class类:字节码对象类,每个类的字节码就是该类的一个对象

使用反射 获取类的类对象

public class testGetClass {
    public static void main(String[] args) throws ClassNotFoundException {
       /*
       * 通过类的全路径名获取
       * 一般结合配置文件 将类的名字放入配置文件中
       * */
        Class clazz =Class.forName("com.test.demo1.Person");
        /*通过类名获取
        * 一般明确类名的时候使用
        * */
        Class clazz2= Person.class;
        /*通过对象获取一个类的字节码
        * 一般是在多态的时候使用
        * 当子类对象指向父类引用,通过对象获取的Class字节码对象
        * */
        Person person= new Person();
        Class clazz3=person.getClass();
        
        System.out.println(clazz==clazz2&&clazz3==clazz2);
        //获取实现接口
        System.out.println(Arrays.toString(clazz.getInterfaces()));
        //获取父类字节码
        Class clazz4=clazz3.getSuperclass();
        System.out.println(clazz4);
        //获取修饰符
        System.out.println(clazz.getModifiers());
        System.out.println(Modifier.toString(clazz.getModifiers()));
        //获取基本数据类型 Class对象
        Class clazz2 = int.class;
        
        //获取数组 Class对象
        int[] arr = new int[10];
        Class clazz3 =arr.getClass();
        Class clazz4 = int[].class;
        
        Integer i =10;
        System.out.println(i.getClass());
        System.out.println(Class.forName("java.lang.Integer"));
        System.out.println(Integer.class);

        //包装类.TYPE 获取的是基本数据类型的字节码
        System.out.println(Integer.TYPE);//Integer包封装的int的类的对象
    }
}

Constructor(构造方法)

​ Constructor 类:构造法方法类,任何一个构造方法就是该类的一个对象

获取一个类的构造方法

public class TestGetConstructor {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class clazz = Class.forName("com.test.demo1.Person");
        //通过参数列表获取指定的构造方法
        Constructor cons = clazz.getDeclaredConstructor(int.class, String.class, String.class);
        
        //获取构造方法参数个数
        System.out.println(cons.getParameterCount());

        //获取构造方法参数类型
        Class[] parameterTypes = cons.getParameterTypes();
        System.out.println(Arrays.toString(parameterTypes));
        
        //获取一个类中的全部构造方法
        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (Constructor c:constructors){
            System.out.print("构造方法名:"+c.getName()+ "构造方法参数个数:"+c.getParameterCount()
                    +" 构造方法参数类型:"+Arrays.toString(c.getParameterTypes()));
            System.out.println();
        }

       System.out.println(Arrays.toString(constructors));
    }
}

使用反射创建对象

public class TestNewInstance {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取一个类的类对象
        Class clazz = Class.forName("com.test.demo1.Person");
        //获取类的构造方法
        Constructor cons = clazz.getDeclaredConstructor(int.class, String.class, String.class);
        //使用反射创建对象1
        Person person = (Person)cons.newInstance(1,"小白","男");
        System.out.println(person);

        //使用反射创建对象2,必须保证类中有无参构造方法
        Person person2 = (Person)clazz.newInstance();
    }
}

Field(属性)

​ Field 类:属性类,任何一个属性都是该类的一个对象

使用反射获取属性(Field)

​ 1.功能强大

​ 编译的时候不知道类的具体信息,可以使用反射根据运行时获取路径信息来操作

		可以突破封装性的限制,即使是**private**的也可以操作

​ 缺点

​ 1.代码可读性操,不好理解

​ 2.执行速度慢

​ 3突破了封装的限制(不遵守规范)

public class testGetField {
    public static void main(String[] args) throws NoSuchFieldException {
        //获得一个类的类对象
        Class clazz = Person.class;

        //获得public属性  getField 只能获取public修饰属性
        Field field = clazz.getField("lastName");
        //获得属性 无论是什么访问修饰符修饰的属性
        Field field2 = clazz.getDeclaredField("firstName");
        //查看属性名称
        System.out.println(field.getName());
        System.out.println(field2.getName());

        //获得属性的数据类型 属性的数据类型字节码
        System.out.println(field.getType());
        System.out.println(field2.getType());
        
         //获得全部属性
        Field [] fields = clazz.getDeclaredFields();
        System.out.println(fields.length);

        for(Field f:fields){
            System.out.println(f.getName()+"  "+f.getType());
        }
    }
}

使用反射给对象属性赋值

public class TestFieldSet {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
        //获取一个类的类对象
        Class clazz = Class.forName("com.test.demo1.Person");
        //使用反射创建对象,必须保证类中有无参构造方法
        Person person = (Person)clazz.newInstance();

        /**
         * 通过Field 对象赋值
         * */
        Field id =clazz.getDeclaredField("id");
        Field firstName  =clazz.getDeclaredField("firstName");
        Field sex  =clazz.getDeclaredField("sex");

        /*private修饰的属性不能直接访问 如果要使用先设置属性可以访问
        * 使用反射操作私有属性 突破封装性的限制,即使private、默认 的也可以访问
        * */
        id.setAccessible(true);
        firstName.setAccessible(true);
        sex.setAccessible(true);
        id.set(person,10);
        firstName.set(person,"小红");
        sex.set(person,"男");

        //静态成员变量赋值 ,不需要对象就可以赋值
        firstName.set(null,"小红");

        System.out.println(person);
    }
}

Method(方法)

​ Method类:方法类,任何一个方法都是该类的一个对象

使用反射获取类的方法

public class TestGetMethod {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //获取一个类的类对象
        Class clazz = Class.forName("com.test.demo1.Person");
        /*
        * 获取一个类的方法
        * 根据方法名和参数列表获得指定的一个方法
        * */
        Method method=clazz.getMethod("sum",int.class,double.class);
        // 获取方法名
        System.out.println(method.getName());
        //获得方法的参数列表
        System.out.println(method.getParameterCount());

        //获取方法所有参数
        Class[] parameterTypes = method.getParameterTypes();
        System.out.println(Arrays.toString(parameterTypes));

        //获取方法的返回值类型
        System.out.println(method.getReturnType());
        
        //获取一个类的所有方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m:declaredMethods){
            System.out.print("方法名:"+m.getName());
            System.out.print("参数个数:"+m.getParameterCount());
            System.out.print("方法返回值类型::"+m.getReturnType());
            System.out.println();
        }
    }
}

使用反射执行对象方法

public class TestInvoke {
    public static void main(String[] args) throws Exception {
        //获取一个类的类对象
        Class clazz = Class.forName("com.test.demo1.Person");
        //获取类的构造方法
        Constructor cons = clazz.getDeclaredConstructor(int.class, String.class, String.class);
        //使用反射创建对象
        Person person = (Person) cons.newInstance(1, "xiaobai", "男");


        //1.使用反射执行一个无参数无返回值的方法
        //获取要调用方法Method对象
        Method showNameMethod = clazz.getDeclaredMethod("showName");

       /* 调用invoke方法代表让当前方法执行
        如果是实例方法,在方法执行时, 定需要一个对象才行
        如果该方法执行需要参数,那么还要传入实参*/
        showNameMethod.invoke(person);

        //2.使用反射执行有返回值 有参数方法
        Method sumMethod = clazz.getDeclaredMethod("sum", int.class, double.class);
        //设置private方法可以访问的
        sumMethod.setAccessible(true);
        double sum =  (double) sumMethod.invoke(person,1,10.11);
        System.out.println(sum);

        //3.使用反射执行执行静态方法
        Method setFirstName = clazz.getDeclaredMethod("setFirstName");
        setFirstName.invoke(null,"小白");
    }
}

反射和泛型

没有出现泛型之前,Java中的所有数据类型包括:

​ 1.primitive types:基本类型

​ 2.raw type:原始类型。不仅仅指平常所指的类,还包括数组、接口、注解、枚举等结构。

​ Class类的一个具体对象代表一个指定的原始类型和基本类型。

泛型出现之后,也就扩充了数据类型:

​ 1.parameterized types(参数化类型): 就是我们平常所用到的泛型List、Map<K,V>的List和Map

​ 2.type variables(类型变量): 比如List中的T等。(注意和参数化类型的区别)

​ 3.array types(数组类型):并不是我们工作中所使用的数组String[] 、byte[](这种都属于Class),

​ 而是带 泛型的数组,比如List [ ],T [ ]

​ 4.WildcardType(泛型表达式类型 通配符类型): 例如List< ? extends Number>

Java采用泛型擦除机制来引入泛型。但是擦除的是方法体中局部变量上定义的泛型,在泛型类、泛型接口中定义的泛型,在成员变量、成员方法上定义的泛型,依旧会保存(可以理解为定义泛型信息保留,使用泛型信息擦除)。保留下来的信息可以通过反射获取。

另外一方面,Class类的一个具体对象代表一个指定的原始类型和基本类型,和泛型相关的新扩充进来的类型不好被统一到Class类中,否则会涉及到JVM指令集的修改,是很致命的。

为了能通过反射操作泛型,但是实现扩展性而不影响之前操作,Java就新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型

使用反射获取泛型类型

public class TestGeneric {
    public static void main(String[] args) throws NoSuchMethodException {
        Class clazz = Student.class;
        Method declaredMethod = clazz.getDeclaredMethod("getData");
        //获取返回值类型
        Class returnType = declaredMethod.getReturnType();
        System.out.println(returnType);
        //获取返回值类型 带泛型
        Type genericReturnType = declaredMethod.getGenericReturnType();
        System.out.println(genericReturnType);
        //  获取返回值类型 泛型参数
        Type[] aType = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        for (Type t:aType){
            System.out.println(t);
        }

        System.out.println("--------------------------");

        //获取泛型参数列表
        Method method2 = clazz.getDeclaredMethod("setDate", List.class, Map.class,String.class);
        Type[] gPTypes = method2.getGenericParameterTypes();
        for (Type type:gPTypes){
            System.out.println(type);
            if(type instanceof ParameterizedType){
                //获取泛型中参数列表
                Type[] act = ((ParameterizedType) type).getActualTypeArguments();
                for (Type t:act){
                    System.out.println("\t"+t);
                }
            }
        }
    }
}

使用反射突破泛型的限制

public class TestGeneric2 {
    public static void main(String[] args) throws Exception {
       List<String> list =new ArrayList<>();
        Class aClass = list.getClass();
        list.add("Java");
        list.add("MySQL");
        list.add("MyBatis");
        System.out.println(list);

        Method add = aClass.getDeclaredMethod("add", Object.class);
        add.invoke(list,123);
        add.invoke(list,"aa");
        add.invoke(list,111);

        System.out.println(list);
    }
}

使用反射创建数组

Array类为java.lang.reflect.Array

import java.lang.reflect.Array;
public class TestArray {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls,25);
        //往数组里添加内容
        Array.set(array,0,"hello");
        Array.set(array,1,"Java");
        Array.set(array,2,"fuck");
        Array.set(array,3,"Scala");
        Array.set(array,4,"Clojure");
        //获取某一项的内容
        System.out.println(Array.get(array,3));
    }
}

使用反射注意事项

​ 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就 不需要用反射。

另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题