java Class对象及反射

2018/02/04 Java 共 7645 字,约 22 分钟

反射

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

类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName(“com.mysql.jdbc.Driver”) 这种方式来控制类的加载,该方法会返回一个 Class 对象。

反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

  1. Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  2. Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
  3. Constructor :可以用 Constructor 创建新的对象。

IDE 使用反射机制获取类的信息,在使用一个类的对象时,能够把类的字段、方法和构造函数等信息列出来供用户选择。

Advantages of Using Reflection:

  • Extensibility Features : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.
  • Class Browsers and Visual Development Environments : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
  • Debuggers and Test Tools : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.

Drawbacks of Reflection:

Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.

  • Performance Overhead : Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
  • Security Restrictions : Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.
  • Exposure of Internals :Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.

Trail: The Reflection API </br> 深入解析 Java 反射(1)- 基础

Class对象

获取Class的方法有三种:

1.Class.forName(“类名”); 通过类名字符串获取Class对象。

2.通过类的对象调用getClass() 获取该类型的Class对象

3.通过类型直接获取Class对象。 类名.class (类字面常量)

Example:

// Class支持泛型
Class<Integer> integerClass = Integer.class;
// 注意这里能拿到的是Class<?>, 和Class.forName返回的一样
Class<?> c1 = Integer.valueOf(3).getClass();

Class类是什么?

  1. Class类也是类的一种,与class关键字是不一样的。
  2. 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。
  3. 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
  4. Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
  5. Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。

类加载的过程,如下:

  1. 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

  2. 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例1. 成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。

  3. 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

Note:

  • 使用字面常量的方式获取Class对象的引用不会触发类的初始化。 我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。
  • 实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段

关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化:

  1. 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。
  2. 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。
  3. 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
  4. 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类

参考我的另一个博客:深入理解Class对象

类型转换

在许多需要强制类型转换的场景,我们更多的做法是直接强制转换类型

之所可以强制转换,这得归功于RTTI(Run-Time Type Identification)运行时类型识别,要知道在Java中,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常ClassCastException。

关于instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例。如下,在强制转换前利用instanceof检测obj是不是Animal类型的实例对象,如果返回true再进行类型转换,这样可以避免抛出类型转换的异常(ClassCastException)。 注意子类对象也是父类类型的一个实例,返回true.

//判断这个对象是不是这种类型
obj.instanceof(class)
//判断这个对象能不能被转化为这个类
class.inInstance(obj)

功能上它们是等价的。

反射用法

Constructor类及其用法

        //获取Class对象的引用
        Class clazz = Class.forName("reflect.User");

        //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
        User user = (User) clazz.newInstance();

        //获取带String参数的public构造函数
        Constructor cs1 =clazz.getConstructor(String.class);
        //创建User
        User user1= (User) cs1.newInstance("xiaolong");

         //取得指定带int和String参数构造函数,该方法是私有构造private
        Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
        //由于是private必须设置可访问
        cs2.setAccessible(true);
        //创建user对象
        User user2= (User) cs2.newInstance(25,"lidakang");

        //获取所有构造包含private
        Constructor<?> cons[] = clazz.getDeclaredConstructors();
        // 查看每个构造方法需要的参数
        for (int i = 0; i < cons.length; i++) {
            //获取构造函数参数类型
            Class<?> clazzs[] = cons[i].getParameterTypes();
            System.out.println("构造函数["+i+"]:"+cons[i].toString() );
            System.out.print("参数类型["+i+"]:(");
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1)
                    System.out.print(clazzs[j].getName());
                else
                    System.out.print(clazzs[j].getName() + ",");
            }
            System.out.println(")");
        }

Field类及其用法

        Class<?> clazz = Class.forName("reflect.Student");

        //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
        // 否则抛NoSuchFieldException
        Field field = clazz.getField("age");
        System.out.println("field:"+field);

        //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
        Field fields[] = clazz.getFields();
        for (Field f:fields) {
            System.out.println("f:"+f.getDeclaringClass());
        }

                System.out.println("================getDeclaredFields====================");
        //获取当前类所字段(包含private字段),注意不包含父类的字段
        Field fields2[] = clazz.getDeclaredFields();
        for (Field f:fields2) {
            System.out.println("f2:"+f.getDeclaringClass());
        }
        //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
        Field field2 = clazz.getDeclaredField("desc");
        System.out.println("field2:"+field2);

其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值

Method类及其用法

        Class clazz = Class.forName("reflect.Circle");

        //根据参数获取public的Method,包含继承自父类的方法
        Method method = clazz.getMethod("draw",int.class,String.class);

        System.out.println("method:"+method);

        //获取所有public的方法:
        Method[] methods =clazz.getMethods();
        for (Method m:methods){
            System.out.println("m::"+m);
        }

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

        //获取当前类的方法包含private,该方法无法获取继承自父类的method
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        System.out.println("method1::"+method1);
        //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
        Method[] methods1=clazz.getDeclaredMethods();
        for (Method m:methods1){
            System.out.println("m1::"+m);
        }

在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:

        Class clazz = Class.forName("reflect.Circle");
        //创建对象
        Circle circle = (Circle) clazz.newInstance();

        //获取指定参数的方法对象Method
        Method method = clazz.getMethod("draw",int.class,String.class);

        //通过Method对象的invoke(Object obj,Object... args)方法调用
        method.invoke(circle,15,"圈圈");

        //对私有无参方法的操作
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        //修改私有方法的访问标识
        method1.setAccessible(true);
        method1.invoke(circle);

        //对有返回值得方法操作
        Method method2 =clazz.getDeclaredMethod("getAllCount");
        Integer count = (Integer) method2.invoke(circle);
        System.out.println("count:"+count);

反射包中的Array类

        int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        //获取数组类型的Class 即int.class
        Class<?> clazz = array.getClass().getComponentType();
        //创建一个具有指定的组件类型和长度的新数组。
        //第一个参数:数组的类型,第二个参数:数组的长度
        Object newArr = Array.newInstance(clazz, 15);
        //获取原数组的长度
        int co = Array.getLength(array);
        //赋值原数组到新数组
        System.arraycopy(array, 0, newArr, 0, co);
        for (int i:(int[]) newArr) {
            System.out.print(i+",");
        }

        //创建了一个长度为10 的字符串数组,
        //接着把索引位置为6 的元素设为"hello world!",然后再读取索引位置为6 的元素的值
        Class clazz2 = Class.forName("java.lang.String");

        //创建一个长度为10的字符串数组,在Java中数组也可以作为Object对象
        Object array2 = Array.newInstance(clazz2, 10);

        //把字符串数组对象的索引位置为6的元素设置为"hello"
        Array.set(array2, 6, "hello world!");

        //获得字符串数组对象的索引位置为5的元素的值
        String str = (String)Array.get(array2, 6);
        System.out.println();
        System.out.println(str);//hello

Search

    Table of Contents