【Java SE】反射
Java Reflection
1 Java 反射机制概述
Reflection反射被视为动态语言的关键,反射机制允许在运行期间借助于Reflection取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。
加载完类之后,在堆内存的方法区中就生成了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。对象就像是一个镜子,通过对象得到类的结构称之为“反射”。
| 反射相关的主要API | |
|---|---|
| java.lang.class | 代表一个类 | 
| java.lang.reflect.Method | 代表类的方法 | 
| java.lang.reflect.Field | 代表类的成员变量 | 
| java.lang.reflect.Constructor | 代表类的构造器 | 
        Class clazz = Person.class;
        Constructor cons = clazz.getConstructor(int.class, String.class, String.class);
        Object obj = cons.newInstance(25, "lxg", "china");
        Person p = (Person) obj;
        System.out.println(p.toString());
        Field age = clazz.getField("age");
        age.set(p, 10);
        System.out.println(p);
        Method method = clazz.getMethod("show");
        method.invoke(p);
反射类的私有结构
        //私有构造器
        Constructor<Person> cons1 = clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true);
        Person p1 = cons1.newInstance("ryuu");
        System.out.println(p1.getName());
        //私有属性
        Field nation = clazz.getDeclaredField("nation");
        nation.setAccessible(true);
        nation.set(p1, "china");
        System.out.println(p1.getNation());
        //私有方法
        Method method1 = clazz.getDeclaredMethod("showNation");
        method1.setAccessible(true);
        method1.invoke(p1);
1.1 对于反射机制的疑问
类的公共结构可以通过new的方式和反射的方式进行调用,开发中一般使用哪个?
直接new的方式,在不确定new哪个类的对象的时候采用放射的方式动态性地调用类的公共结构,比如前端页面的login和register传入后端解析,服务器启动时不能确定即将传入的是哪一个类,因此需要动态地判断。
反射机制和面向对象的封装性矛盾吗?
不矛盾,封装性对于结构的封装体现的是建议性的屏蔽使用,而反射机制是能够使用封装性不建议使用的类结构。
2 Class类的理解
2.1 类的加载过程
java程序编写完成后,经过javac.exe(编译)生成一个或多个字节码文件.class,再经java.exe命令对某个字节码文件解释运行,将其加载到内存中,这个过程称作类的加载。加载到内存中的类称为运行时类,就作为Class的一个实例:类本身也是一个Class的实例。换句话说,Class实例对应着一个运行时类,可以直接通过运行时类直接赋值。
当程序主动使用某个类时,如果类还没有被加载到内存,就会通过如下三个步骤对类进行初始化:
①类的加载(Load)
将类的Class文件加载到内存,并为之创建一个java.lang.Class对象与之对应。此过程由类的加载器完成。
②类的链接(Link)
将类的二进制数据合并到JRE中。
验证: 确保加载的类信息符合JVM规范。
准备: 正式为类变量(static)分配内存并设置变量初始化值的阶段,这些内存都在方法区中进行分配。
解析: 执行类构造器方法
在对一个类初始化时,如果父类还没有初始化,应该先初始化父类
虚拟机 会保证一个类的类构造器方法在多线程环境中,被正确加锁和同步
③类的初始化(Initialize)
JVM负责对类进行初始化。
2.2 如何获取Class的实例
//      方式一:调用运行时类的class属性
        Class clazz1 = Person.class;
        System.out.println(clazz1);
//      方式二:通过运行时类的对象,调用Object的getClass()方法
        Person person = new Person();
        Class clazz2 = person.getClass();
        System.out.println(clazz2);
//      方式三:调用Class的静态方法
        Class clazz3 = Class.forName("com.hikaru.java.Person");
        System.out.println(clazz3);
//      本质都是指向了Person的运行时类,故地址相同
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz2 == clazz3);
第三种使用的最多,编译时不确定加载类,更好地体现了反射的动态性,第二种由于反射本来就是为了通过运行时类不使用new的方式而创建对象因此使用得最少,第一种则相对于第三种写死了类名。
方式四:使用类加载器(了解)
2.2 哪些类可以有Class对象
| class | 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类 | 
| interface | 接口 | 
| [] | 数组 | 
| enum | 枚举类 | 
| annotation | 注解 | 
| primitive type | 基本数据类型 | 
| void | 
只要数组的类型和维度相同就是一个CLass
        int[] a = new int[10];
        int[] b = new int[100];
        Class clazz1 = a.getClass();
        Class clazz2 = b.getClass();
//        true
        System.out.println(clazz1 == clazz2);
2.3 ClassLoader类加载器
| 类加载器分类 | |
|---|---|
| 引导类加载器 | 负责Java核心库的加载,如String,该加载器无法直接获取 | 
| 扩展类加载器 | jar包装入工作库 | 
| 系统类加载器 | 最常用的家加载器 | 
        ClassLoader classLoader1 = ReflectionTest.class.getClassLoader();
        System.out.println(classLoader1);
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);
        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);
//        sun.misc.Launcher$AppClassLoader@18b4aac2
//        sun.misc.Launcher$ExtClassLoader@28a418fc
//        null
Java核心类库的类加载器无法直接获取
通过类加载器读取properties,区别于File读取此时默认路径为module下的src。注意:开发时module下的配置文件不起作用。
        Properties properties = new Properties();
//        FileInputStream fis = new FileInputStream("\\jdbc.properties");
//        properties.load(fis);
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc.properties");
        properties.load(is);
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
        System.out.println(username + password);
Class实例创建对象:newInstance(),创建对应运行时类的对象
        Class<Person> clazz = Person.class;
        Person person = clazz.newInstance();
实质还是通过构造器创建的对象,并且要求:
①类提供空参构造器,框架底层经常使用反射构建对象
②访问权限足够
在javabean中要求提供一个空参构造器的原因:
①便于通过反射,创建运行时的对象
②便于子类继承父类,调用super()方法时,保证父类有此构造器
2.4 体会反射机制的动态性
在编译时不确定类的类别,而在运行时才确定。
    @Test
    public void test6() {
        int choice =new Random().nextInt(3);
        String class_path = null;
        switch (choice) {
            case 0:
                class_path = "java.lang.Object";break;
            case 1:
                class_path = "java.util.Date";break;
            case 2:
                class_path = "com.hikaru.java.Person";break;
        }
        try {
            Object obj = getInstance(class_path);
            System.out.println(obj);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
    public Object getInstance(String class_path) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = Class.forName(class_path);
        return clazz.newInstance();
    }
2.5 获取运行时类的完整属性及内部结构
2.5.1 获取运行类属性
| Class对象 | |
|---|---|
| Field[] getFields() | 获取当前类及父类中声明为public访问权限的属性 | 
| Field[] getDeclaredFields() | 获取当前运行类中声明的所有属性 | 
| Field对象获取属性的权限修饰符、数据类型、变量名 | |
|---|---|
| int getModifiers() | |
| Class getType() | |
| String getName() | 
        Class clazz = Person.class;
        Field[] fields = clazz.getFields();
        for(Field f : fields) {
            int modifier = f.getModifiers();
            System.out.print(Modifier.toString(modifier));
            Class type = f.getType();
            System.out.print(" " + type);
            String name = f.getName();
            System.out.println(" " + name);
        }
        Class clazz = Person.class;
        Method[] methods = clazz.getMethods();
        for(Method m : methods) {
            System.out.println(m);
        }
        Method[] methods1 = clazz.getDeclaredMethods();
        for(Method m : methods1) {
            System.out.println(m);
        }
2.5.2 获取运行类方法
| Class对象方法 | |
|---|---|
| Method[] getMethods() | 获取当前类及父类中声明为public访问权限的方法 | 
| Method[] getDeclaredMethod() | 获取当前运行类中声明的所有方法 | 
| Method对象方法 | |
|---|---|
| Annotation[] getAnnotations() | |
| Modifer getModifers() | |
| String getName() | |
| Class<?> getParamterType() | 获取形参列表 | 
获取方法的注解:getAnnotations()
    @Test
    public void test8() {
        Class clazz = Person.class;
//        Method[] methods = clazz.getMethods();
//
//        for(Method m : methods) {
//            System.out.println(m);
//        }
        Method[] methods1 = clazz.getDeclaredMethods();
        for(Method m : methods1) {
//            System.out.println(m);
            Annotation[] annotations = m.getAnnotations();
            for(Annotation annotation : annotations)
                System.out.println(annotation);
        }
    }
| 注解生命周期参数 | |
|---|---|
| RetentionPolicy.SOURCE | 源文件保留 | 
| RetentionPolicy.CLASS | class保留,不会加载到内存中 | 
| RetentionPolicy.RUNTIME | 运行时保留,只有声明为RUNTIME生命周期的注解,才能通过反射获取。 | 
因此注解中需要声明为RUNTIME
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello";
}
2.5.3 获取运行时类的构造器:获取当前运行类中声明为public的构造器
        Class clazz = Person.class;
        Constructor[] constructors = clazz.getConstructors();
2.5.4 获取父类以及带泛型的父类
| Class getSuperClass() | 获取父类 | 
| Type getGenericSuperClass() | 获取带泛型的父类 | 
        Class clazz = Person.class;
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
2.5.5 获取带泛型父类的泛型
        ParameterizedType genericSuperclass1 = (ParameterizedType) genericSuperclass;
        Type[] actualTypeArguments = genericSuperclass1.getActualTypeArguments();
        System.out.println(((Class)actualTypeArguments[0]).getTypeName());
genericSuperclass1.getActualTypeArguments():获取泛型类型数组
获取运行时类实现的接口
//        获取运行时类的接口
        Class[] interfaces = clazz.getInterfaces();
        for(Class i : interfaces)
            System.out.println(i);
//        获取运行时类的父类的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for(Class i : interfaces1)
            System.out.println(i);
2.5.6 获取运行时类的包
        Class clazz = Person.class;
        Package aPackage = clazz.getPackage();
        System.out.println(aPackage);
2.5.7 获取运行时类的注解
        Class clazz = Person.class;
        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annotation : annotations) {
            System.out.println(annotation);
        }
2.6 调用运行时类的指定结构
2.6.1 调用类的指定属性
        Class clazz = Person.class;
        Field field = clazz.getField("age");
        Person person = new Person();
        field.set(person, 18);
        int age = (int) field.get(person);
        System.out.println(age);
        Class clazz = Person.class;
        Field field = clazz.getDeclaredField("name");
//        保证当前属性可访问
        field.setAccessible(true);
        Person person = new Person();
        field.set(person, "mike");
        String name = (String) field.get(person);
        System.out.println(name);
第一种只能访问当前运行类的public属性,第二种可以访问所有属性比较常用
2.6.2 调用类的指定方法
2.6.3 调用运行时类的指定构造器
        Class<Person> clazz = Person.class;
        Person person1 = clazz.newInstance();
        Constructor<Person> constructor = clazz.getConstructor();
        Person person2 = constructor.newInstance();
        Method display = clazz.getMethod("display", String.class);
        String interest = (String) display.invoke(person1, "reading");
        System.out.println(interest);
        Method show = clazz.getDeclaredMethod("show", String.class);
        show.setAccessible(true);
        show.invoke(person2, "CHN");
 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号