Java的反射

我们要了解Java中的反射就要了解基础类Class,一切反射的基石,它也是一个类,类名就是Class,不同于我们自定义类使用的小写class。Java中所有的类都属于同一种事物那就是Class类,就像所有的人一样都属人类,当然一说人那么它就必须满足人类所固有的一些特性,Java中的类也是一样,而用来描述所有Java类的特性的就是Class,所以通过一个Class就可以获取到Java类的所有信息。下面是它的定义,它里面提供了很多方法来获取类的信息,大家可以打开源代码自己看一下。


public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {

    /*
     * Constructor. Only the Java Virtual Machine creates Class
     * objects.
     */
    private Class() {}
}

值得注意一点就是这个类的构造方法是私有的,也就是不能new Class();注释说了这个类的对象只能由Java虚拟机进行创建,那么怎么获取到这个类的对象呢???接着往下看。。。

其实我们平常写的.java文件都是经过了Java 编译器生成.class文件(字节码文件),再由JVM将.class加载到内存,然后我们才可以运行。这里其实每个.class文件就对应一个Class的实例对象。

举个例子:

我们创建一个Person.java文件,把它编译成Person.class文件以后,就可以使用Class classPerson  = Person.class 来获取它的字节码文件。

或者我们创建一个Person对象Person person = new Person();然后使用Class classPerson = person.getClass()也可以获取Person的字节码对象。

另外我们还有一种获取字节码的方式就是通过Class类提供一个方法Class classPerson = Class.forName("com.lp.ref.Person");在实际应用中我们使用这种方式也最多(其他两种也用的不少),使用这种方式有两个步骤,当代码运行到这个一行的时候类加载器会检查JVM内存中是否存在Person的字节码文件如果存在就会返回Person的字节码类,如果不存在就会检查硬盘中是否存在是否存在com.lp.ref.Person的Java文件,如果存在则类加载器就会把它加载到JVM内存中,然后返回Person的字节码文件,如果还不存在就会抛出ClassNotFoundException异常

以上就是反射获取类字节码的三种方式,下面我们看个小实验

public class Person {
}
public class RefClass {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        Class personClass = person.getClass();
        Class personClass1 = Person.class;
        Class personClass2 = Class.forName("com.lp.ref.Person");
        System.out.println(personClass == personClass1);
        System.out.println(personClass == personClass2);
    }
}

 

我们定义了一个Person类,什么内容都没有,通过三种方式来获取它的字节码,我们打印出来结果为true true,这就说明了不管用哪种方式我们都是获取的同一份字节码对象

接下来我们再看,Class中提供了一个isPrimitive()的方法,看它的注释我们了解到,它是用来判断一个字节码类型是否属于原始类型的Class,在JVM中为我们提供了九种原始类型的Class对象,包括Java的八种基本类型和Void类型,这些JVM提供对象都可以通过他们的包装类.TYPE来获取

 /**
     * Determines if the specified {@code Class} object represents a
     * primitive type.
     *
     * <p> There are nine predefined {@code Class} objects to represent
     * the eight primitive types and void.  These are created by the Java
     * Virtual Machine, and have the same names as the primitive types that
     * they represent, namely {@code boolean}, {@code byte},
     * {@code char}, {@code short}, {@code int},
     * {@code long}, {@code float}, and {@code double}.
     *
     * <p> These objects may only be accessed via the following public static
     * final variables, and are the only {@code Class} objects for which
     * this method returns {@code true}.
     *
     * @return true if and only if this class represents a primitive type
     *
     * @see     java.lang.Boolean#TYPE
     * @see     java.lang.Character#TYPE
     * @see     java.lang.Byte#TYPE
     * @see     java.lang.Short#TYPE
     * @see     java.lang.Integer#TYPE
     * @see     java.lang.Long#TYPE
     * @see     java.lang.Float#TYPE
     * @see     java.lang.Double#TYPE
     * @see     java.lang.Void#TYPE
     * @since JDK1.1
     */
    public native boolean isPrimitive();

介绍完反射的基本类Class之后,我们再来看看什么是反射?偶尔看到一句经典——反射就是把Java类中的各种成份映射成相应的Java类,类的各种成份包括 成员变量,方法,构造方法,包等等,他们也都可以使用相应的Java类来表示,分别对应Filed,Method,Constructor,Package。表示Java类的Class类中提供了一系列的方法来获取这些成员属性。

        Class personClass = Person.class;
        Method[] methods = personClass.getMethods();
        Annotation[] annotations = personClass.getAnnotations();
        Constructor[] constructors = personClass.getConstructors();
        Field[] fields = personClass.getFields();
        Package personPackage = personClass.getPackage();

上面的代码段就是通过Class提供的方法来获取Person中相应的成份,获取需要的类成份以及使用他们就是反射的要点,也是反射的价值

我们把新增一个Animal类并把 Person类加上几个成员,一下代码希望自己动手在编译器里面运行一下,由更深的体会

public class Animal {
    public String head;
    private String body;
    public void run(){
        System.out.println("animal run");
    }
}
public class Person extends Animal{
    private int age;
    public String name;

    public static void staticMethod(String flag){
        System.out.println("我是静态方法:"+flag);
    }

    public Person() {
    }

    public Person(int age) {
        this.age = age;
    }

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

    private void privateMethod(){
        System.out.println("我是私有方法");
    }
    public void speak(){
        System.out.println("你好,我是"+name+" 我的年龄是"+age);
    }
    public int getAge(){
        return this.age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

构造函数的反射,及创建对象

public class RefClass {
    public static void main(String[] args) throws Exception {
        //反射获取获取Person类的字节码对象
        Class personClass = Class.forName("com.lp.ref.Person");
        //通过字节码对象获取Person类的所有构造函数
        Constructor[] constructors = personClass.getConstructors();
        //打印出Person有几个构造函数,打印结果
        System.out.println(constructors.length);


        //通过字节码对象,获取Person的无参构造函数
        Constructor constructor = personClass.getConstructor(null);
        //通过获取的无参构造函数构造器来创建Person对象
        Person instancePerson = (Person) constructor.newInstance();
        //测试 打印出 你好,我是null 我的年龄是0
        instancePerson.speak();


        //通过字节码对象,获取Person的构造函数,其中构造函数的参数有一个并且是int类型
        Constructor constructor1 = personClass.getConstructor(int.class);
        //传递一个参数给构造器来创建Person对象
        Person instancePerson1 = (Person) constructor1.newInstance(8);
        //测试 打印出 你好,我是null 我的年龄是8
        instancePerson1.speak();

        //通过字节码对象,获取Person的构造函数,其中构造函数的参数有两个,且第一个参数是int类型,第二个参数是String类型
        Constructor constructor2 = personClass.getConstructor(int.class,String.class);
        //传递一个参数给构造器来创建Person对象
        Person instancePerson2 = (Person) constructor2.newInstance(8,"lisi");
        //测试 打印出 你好,我是lisi 我的年龄是8
        instancePerson2.speak();
    }
}

成员变量的反射及使用

public class RefClass {
    public static void main(String[] args) throws Exception {
        //通过new 的方式创建一个对象,传入两个参数,第一个参数的修饰符在Person定义中是私有的,第二个是公有的
        Person person = new Person(12, "wang");
        //反射获取Person类的字节码对象
        Class personClass = Class.forName("com.lp.ref.Person");
        //获取该对象中的所有public成员变量,包括继承的父类的public成员变量
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            //打印出成员变量的名称,并不打印变量的值   打印结果 name head 并没有获取age和父类body 因为age body 是private的
            //field.getName() 获取字段的名称
            System.out.println(field.getName());
        }
        //通过成员属性的名字获取成员属性
        //这一行会报错:Exception in thread "main" java.lang.NoSuchFieldException: age
        //Field age = personClass.getField("age");
        Field name = personClass.getField("name");
        //给属性绑定对象,获取绑定的对象相对应的属性值  打印结果 wang
        System.out.println((String)name.get(person));

        //获取所有声明的成员变量,包括private,不包括父类属性
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            //打印结果 age name
            System.out.println("declaredField:"+declaredField.getName());
        }
        //不会报错
        Field age = personClass.getDeclaredField("age");
        // Exception in thread "main" java.lang.IllegalAccessException: Class com.lp.ref.RefClass
        // can not access a member of class com.lp.ref.Person with modifiers "private"
        // 如果不加下面这句会报异常,不能访问 private 的成员变量,加上下面这句 暴力反射 就可以访问private
        age.setAccessible(true);
        //打印结果 12
        System.out.println((int)age.get(person));

        for (Field declaredField : declaredFields) {
            //获取字段的类型,返回的是类型的字节码对象
            if (declaredField.getType() == String.class){
                //改变某个对象的某个属性的值
                declaredField.set(person,"zhang");
            }
            //打印结果  zhang
            System.out.println((String)name.get(person));
        }
    }
}

反射方法及调用

public class RefClass {
    public static void main(String[] args) throws Exception {
        //通过new 的方式创建一个对象,传入两个参数,第一个参数的修饰符在Person定义中是私有的,第二个是公有的
        Person person = new Person(12, "wang");
        //反射获取获取Person类的字节码对象
        Class personClass = Class.forName("com.lp.ref.Person");
        //获取所有的public方法,包括继承的父类的public方法,不包括构造方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            //method.getModifiers() 获取修饰符类型  修饰符类型在java.lang.reflect.Modifier 定义 如下,是十六进制
//            public static final int PUBLIC           = 0x00000001; 转为十进制是 1
//            public static final int PRIVATE          = 0x00000002; 转为十进制是 2
//            public static final int PROTECTED        = 0x00000004; 转为十进制是 4
//            public static final int STATIC           = 0x00000008; 转为十进制是 8
//            public static final int FINAL            = 0x00000010; 转为十进制是 16
//            public static final int SYNCHRONIZED     = 0x00000020; 转为十进制是 32
//            public static final int VOLATILE         = 0x00000040; 转为十进制是 64
//            public static final int TRANSIENT        = 0x00000080; 转为十进制是 128
//            public static final int NATIVE           = 0x00000100; 转为十进制是 256
//            public static final int INTERFACE        = 0x00000200; 转为十进制是 512
//            public static final int ABSTRACT         = 0x00000400; 转为十进制是 1024
//            public static final int STRICT           = 0x00000800; 转为十进制是 2048
            /**
             * getModifiers() 方法返回的值会把所有的修饰符的值都加起来,举个例子
             *  public final native Class<?> getClass();
             *  这个方法前面被 public final  native 三种修饰,那么返回的值就是 1 + 16 + 256 = 273
             */
            // method.getReturnType() 方法的返回值类型
            // method.getName() 获取方法名称
            /**
             * 打印结果  这里的Person类虽然没有显式使用extends 继承Object 但是JVM 编译的时候会让它继承Object
             * 方法的修饰符类型9 返回值类型:void 方法名称:staticMethod
             * 方法的修饰符类型1 返回值类型:void 方法名称:setAge
             * 方法的修饰符类型1 返回值类型:void 方法名称:speak
             * 方法的修饰符类型1 返回值类型:int 方法名称:getAge
             * 方法的修饰符类型1 返回值类型:void 方法名称:run
             * 方法的修饰符类型17 返回值类型:void 方法名称:wait
             * 方法的修饰符类型17 返回值类型:void 方法名称:wait
             * 方法的修饰符类型273 返回值类型:void 方法名称:wait
             * 方法的修饰符类型1 返回值类型:boolean 方法名称:equals
             * 方法的修饰符类型1 返回值类型:class java.lang.String 方法名称:toString
             * 方法的修饰符类型257 返回值类型:int 方法名称:hashCode
             * 方法的修饰符类型273 返回值类型:class java.lang.Class 方法名称:getClass
             * 方法的修饰符类型273 返回值类型:void 方法名称:notify
             * 方法的修饰符类型273 返回值类型:void 方法名称:notifyAll
             */
            System.out.println("方法的修饰符类型"+method.getModifiers()+" 返回值类型:"+method.getReturnType()+" 方法名称:"+method.getName());
        }

        //获取Person所有的方法,包括private 但是不包括父类的任何方法
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            /** 打印结果
             * declared 方法的修饰符类型9 返回值类型:void 方法名称:staticMethod
             * declared 方法的修饰符类型2 返回值类型:void 方法名称:privateMethod
             * declared 方法的修饰符类型1 返回值类型:void 方法名称:setAge
             * declared 方法的修饰符类型1 返回值类型:void 方法名称:speak
             * declared 方法的修饰符类型1 返回值类型:int 方法名称:getAge
             */
            System.out.println("declared 方法的修饰符类型"+method.getModifiers()+" 返回值类型:"+method.getReturnType()+" 方法名称:"+method.getName());
        }

        //通过方法名获取public方法对象也可以获取父类中的public方法对象,获取Person类中的speak方法,没有参数
        Method speakMethod = personClass.getMethod("speak");
        //绑定person对象执行speak方法,执行结果 你好,我是wang 我的年龄是12
        speakMethod.invoke(person);

        //获取父类public方法对象
        Method getClassMethod = personClass.getMethod("getClass");
        //绑定person对象执行speak方法,执行结果 你好,我是wang 我的年龄是12
        Object invoke = getClassMethod.invoke(person);
        //执行结果 class com.lp.ref.Person
        System.out.println(invoke);

        //获取声明的私有方法
        Method privateMethod = personClass.getDeclaredMethod("privateMethod");
        /**如果不加下面代码会有如下异常,加上之后,就是我们所说的暴力反射
         * Exception in thread "main" java.lang.IllegalAccessException: Class com.lp.ref.RefClass can not access a member of class com.lp.ref.Person with modifiers "private"
         * 打印结果 我是私有方法
         */
        privateMethod.setAccessible(true);
        privateMethod.invoke(person);

        //获取带有参数的方法
        Method setAgeMethod = personClass.getDeclaredMethod("setAge",int.class);
        setAgeMethod.invoke(person,20);
        speakMethod.invoke(person);//打印结果 你好,我是wang 我的年龄是20

        //获取对象中的静态方法
        Method staticMethod = personClass.getDeclaredMethod("staticMethod",String.class);
        //静态方法的调用可以不通过对象 打印 我是静态方法:staticMethod
        staticMethod.invoke(null,"staticMethod");

        //调用程序入口函数main
        Class mainClass = Class.forName("com.lp.ref.MainClass");
        Method mainMethod = mainClass.getMethod("main", String[].class);
        //这个地方有个特殊,需要大家记住,传参的时候需要使用new Object[] 把String[] 包裹一下,这是因为javac会兼任1.5以前的语法,
        // 在jdk1.5以前的语法中,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会把数组打散成为若干个单独的参数
        //就会报错 java.lang.IllegalArgumentException: wrong number of arguments
        /** 打印结果
         * MainClass:aa
         * MainClass:bb
         * MainClass:cc
         */
//        mainMethod.invoke(null,new Object[]{new String[]{"aa","bb","cc"}});
        //或者使用下面这种方式 明确告诉编译器,这个数组是一个参数
        mainMethod.invoke(null, (Object)new String[]{"aa","bb","cc"});
    }
}
class MainClass{
    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println("MainClass:"+arg);
        }
    }
}

题外话

在Java源码中Class类的注释中有这么一句话

Every array also belongs to a class that is reflected as a {@code Class} object that is shared by all arrays with the same element type and number of dimensions 具有相同元素类型并且维度相同的数组属于同一种Class
/**
         * Class 的getName 方法上面有这样的注释  题外话,如果有反编译过 jar 包的话,会看到编译过的代码种会经常出现这种编码符号
         *  If this class object represents a class of arrays, then the internal form of the name
         *  consists of the name of the element type preceded by one or more '{@code [}' characters
         *  representing the depth of the array nesting.  The encoding of element type names is as follows:
         *
         * Element Type         Encoding
         * boolean              Z
         * byte                 B
         * char                 C
         * class or interface   L<i>classname</i>;
         * double               D
         * float                F
         * int                  I
         * long                 J
         * short                S
         */

        int[] intArr = new int[4];
        int[] intArr2 = new int[5];
        int[][] intArr3 = new int[5][6];
        String[] strArr = new String[7];

        //打印结果  intArr.getClass()==intArr2.getClass()  true
        System.out.println("intArr.getClass()==intArr2.getClass()  "+(intArr.getClass() == intArr2.getClass()));
        System.out.println(intArr.getClass().getName());//打印结果 [I  根据打印结果可以很明显的看出是否属于同一种Class
        System.out.println(intArr2.getClass().getName());//打印结果 [I
        System.out.println(intArr3.getClass().getName());//打印结果 [[I
        System.out.println(strArr.getClass().getName());//打印结果 [Ljava.lang.String;

反射介绍到此结束

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值