zoukankan      html  css  js  c++  java
  • Java反射总结

     

     

    能够分析类能力的程序称为反射。对于给定的Java类名,可以通过反射获取类的信息、将类的各成分映射出相应的Java类。

    Class类

        在程序运行期间,Java运行时系统始终对所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。可以通过专门的Java类访问这些信息。保存这些信息的类被称为Class

    创建Class类对象的三种方法:

    1. 通过getClass方法

        Object中的getClass方法将返回一个Class类型的实例。

    复制代码
    复制代码
    class Person {
       //...
    }
    
    Person p = new Person();
    Class c = p.getClass();
    复制代码
    复制代码

    2. forName方法

        可以通过静态方法forName获得类名对应的Class对象。

    String className = "java.util.Date";
    Class c = Class.forName(className);

        如果需要在运行过程中需要改变类名,就可以使用这种方法。当然,字符串className必须是一种类名或接口(包括包名),否则会出现异常。

    3. T.class

    Class c1 = Date.class;
    Class c2 = int.class;
    Class c3 = Double[].class;

        T表示任意的Java类型。通过T.class获取匹配的类对象。

    注意:一个Class对象实际表示一种类型,而这种类型未必是一种类。

    虚拟机为每个类型管理一个Class对象。因此可以用==运算符来实现两个类对象的比较

    if (p.getClass() == Person.class) {...}

    newInstance方法

        通过newInstance方法可以创建一个类的实例。

    Person person = p.getClass().newInstance();

        创建了一个与p具有相同类类型的实例。newInstance调用默认的构造器初始化创建的对象。如果这个类没有默认的构造器,就会抛出异常。

    利用反射解析类结构

    获取包名+类名(包括父类名)

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    class Person {
        //...
    }
    
    class Student extends Person{
    
    }
    
    public class ReflectionDemo1 {
        public static void main(String[] args) {
            Student s = new Student();
            Class cl = s.getClass();
            Class superCl = cl.getSuperclass();
            System.out.println("获取类名:" + cl.getName());
            System.out.println("获取父类名:" + superCl.getName());
        }
    }
    复制代码
    复制代码

    【运行结果】:
    获取类名:com.xiaoxiaoyihan.reflection.Student
    获取父类名:com.xiaoxiaoyihan.reflection.Person

    说明:如果类在一个包中,包的名字也作为类名的一部分。

    一点扩展:

    首先看一个例子:

    复制代码
    复制代码
    class Person {
        private String name = "萧萧弈寒";
        // 省略setter和getter
    }
    class Animal {
        private String name = "paqi";
        // 省略setter和getter
    }
    
    Person p;
    Animal a;
    Class cPerson = p.getClass();
    Class cAnimal = a.getClass();
    // cPerson.getName()获取的是类名、p.getName()是Person实例的name属性值
    System.out.println(cPerson.getName() + "<--->" + p.getName());
    System.out.println(cAnimal.getName() + "<--->" + p.getName());
    复制代码
    复制代码

    【运行结果】:
    com.xxyh.reflec.Person<--->萧萧弈寒
    com.xxyh.reflec.Animal<--->paqi

    由此说明,一个Person对象p表示一个特定人的属性,一个Animal对象a表示一个特定动物的属性,一个Class对象表示一个特定类(Person或Animal)的属性。从这点看,Class的实例是一种对象

    解析构造函数(Constructor)

    Class类中的getConstructors方法将返回公共构造器数组。Class类中的getDeclaredConstructors将返回类中声明的构造器数组。

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Constructor;
    class Person {
        private String name;
        private int age;
    
        private Person() {}
    
        protected Person(String name) {
            this.name = name;
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    public class ReflectionDemo1 {
        public static void main(String[] args) throws ClassNotFoundException {
            Class cl = Class.forName("com.xiaoxiaoyihan.reflection.Person");  // 直接抛出异常以简化代码 
    
            Constructor[] constructors = cl.getDeclaredConstructors();
            //Constructor[] constructors = cl.getConstructors();
            for (Constructor c :constructors) {
                System.out.println(c);
            }
        }
    }
    复制代码
    复制代码

    【运行结果】:

    private com.xiaoxiaoyihan.reflection.Person()
    protected com.xiaoxiaoyihan.reflection.Person(java.lang.String)
    public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int)

    // public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int)// 本部分为运行getConstructors方法输出结果

    对结果加以分析,会发现通过System.out.println(c);直接打印的构造函数是由几部分组成的,其中包括了修饰符(public/protected/private)、类名以及构造函数的参数,那么,这些部分是如何获取的呢?

    Modifier类中包含静态方法getModifiers方法,它返回一个整数i,用不同的数值表示public、static、final这样的修饰符。Modifier中的静态方法toString(i)返回对应的修饰符。Constructor类中包含由静态方法getParameterTypes,它返回一个描述参数类型的Class对象数组

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Modifier;
    
    class Person {
        private String name;
        private int age;
    
        private Person() {}
    
        protected Person(String name) {
            this.name = name;
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    public class ReflectionDemo1 {
        public static void main(String[] args) throws ClassNotFoundException {
            Class cl = Person.class;
    
            Constructor[] constructors = cl.getDeclaredConstructors();
            for (Constructor c :constructors) {
                // 获取包名和类名
                String name = c.getName();
    
                // 获取修饰符
                String modifiers = Modifier.toString(c.getModifiers());
                if (modifiers.length() > 0) {   //如果有修饰符
                    System.out.print(modifiers + " ");
                }
    
                System.out.print(name + "(");
    
                // 获取构造函数的参数类型
                Class[] paramTypes = c.getParameterTypes();
    
                for (int i = 0; i < paramTypes.length; i++) {
                    if (i > 0) {    // 如果不止一个参数,使用","将参数类型分割
                        System.out.print(",");
                    }
                    System.out.print(paramTypes[i].getName());
                }
                System.out.println(");");
            }
    
        }
    }
    
    复制代码
    复制代码

    【运行结果】:

    private com.xiaoxiaoyihan.reflection.Person();
    protected com.xiaoxiaoyihan.reflection.Person(java.lang.String);
    public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int);

    解析方法(Method)

    Class类中的getMethods将返回方法的数组,其中包括本类的所有公共方法、从接口实现的方法、父类的公共(public)方法、父类的父类的公共方法……一直延伸到Object的公共方法getDeclaredMethods方法将返回本类声明的方法、从接口实现的方法

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    
    
    class Person/* extends Object */ {
        private void privateMethodPerson() {
            //...
        }
    
        protected void protectedMethodPerson() {
            // ...
        }
    
        public void publicMethodPerson() {
            //...
        }
    }
    
    interface Smoke {
        void smoking();
    }
    
    class Student extends Person implements Smoke{
        @Override
        public void smoking() {
            // ...
        }
    
        private void privateMethodStudent() {
            //...
        }
    
        protected void protectedMethodStudent() {
            // ...
        }
    
        public void publicMethodStudent() {
            //...
        }
    }
    
    
    public class ReflectionDemo1 {
        public static void main(String[] args) {
            Student s = new Student();
            Class cl = s.getClass();
            Method[] methods = cl.getDeclaredMethods();
    //        Method[] methods = cl.getMethods();
    
            for (Method m : methods) {
                System.out.println(m);
            }
        }
    }
    复制代码
    复制代码

    【运行结果】:

    public void com.xiaoxiaoyihan.reflection.Student.smoking()
    protected void com.xiaoxiaoyihan.reflection.Student.protectedMethodStudent()
    private void com.xiaoxiaoyihan.reflection.Student.privateMethodStudent()
    public void com.xiaoxiaoyihan.reflection.Student.publicMethodStudent()

    上面的例子故意给出一种继承结构,是为了查看getMethods的结果,在此就不给出结果了。注意Person默认继承了Object类。顺便说一句,笔者(非科班自学菜鸟)曾在面试的时候被问到Object中有哪些方法,估计他是想问有哪些常用方法吧,我没答全~2333,不知道读者您能一眼看出结果吗??。

    类似地,我们看到上面的System.out.println(m);打印出方法由修饰符返回值类名方法名以及参数组成。那么这些部分是如果获取的呢? 与Construction类相似,Method类中也提供了静态方法getParamTypes,该方法返回描述参数类型的Class对象数组。此外,Method还提供了getReturnType方法,用于获取返回类型的Class对象。

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    
    class Person {
    
        private String name = "萧萧弈寒";
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public static void speak() {
            // ...
        }
    
        public final void eat() {
            // ...
        }
    }
    
    
    public class ReflectionDemo1 {
        public static void main(String[] args) {
    
            Class cl = Person.class;
    
            Method[] methods = cl.getDeclaredMethods();
            for (Method m : methods) {
                // 返回类型的Class对象
                Class retType = m.getReturnType();
                //
                String retTypeName = retType.getName();
    
                // 获取方法名
                String name = m.getName();
    
                String modifiers = Modifier.toString(m.getModifiers());
                if (modifiers.length() > 0) // 如果有修饰符
                    System.out.print(modifiers + " ");
    
                // 返回值名
                System.out.print(retType.getName() + " ");
    
                System.out.print(name + "(");
    
                Class[] paramTypes = m.getParameterTypes();
                for (int i = 0; i < paramTypes.length; i++) {
                    if (i > 0) { // 如果不止一个参数,用","分割
                        System.out.print(paramTypes[i].getName());
                    }
                }
                System.out.println(");");
            }
    
        }
    }
    复制代码
    复制代码

    【运行结果】:
    public java.lang.String getName();
    public void setName();
    public static void speak();
    public final void eat();

    解析域(Field)

    Class类中的getDeclaredFields方法返回类中声明的域数组,getFields方法返回类中的公有域、接口中的域所组成的Field对象数组。

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Field;
    import java.util.Date;
    
    class Person {
        private String name;
        protected int age;
        public Date birthday;
    }
    
    class Student extends Person implements Smoke{
        private float score;
    }
    
    interface Smoke {
        String brand = "大中华";
    }
    
    public class ReflectionDemo1 {
        public static void main(String[] args) {
            Class cl = Student.class;
    
            Field[] fields = cl.getFields();
            for (Field f : fields) {
                System.out.println(f);
            }
        }
    }
    复制代码
    复制代码

    【运行结果】:
    public static final java.lang.String com.xiaoxiaoyihan.reflection.Smoke.brand
    public java.util.Date com.xiaoxiaoyihan.reflection.Person.birthday

        结果显示了字段由修饰符类型类名字段名构成。这里需要引入Field类中的getType方法,它返回字段声明类型的Class对象。下面同样作出解析:

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.util.Date;
    
    class Person {
        private String name;
        protected int age;
        public Date birthday;
    }
    
    public class ReflectionDemo1 {
        public static void main(String[] args) {
            Class cl = Person.class;
    
            Field[] fields = cl.getDeclaredFields();
            for (Field f : fields) {
                // 属性类型的Class对象
                Class type = f.getType();
    
                // 属性类型名
                String name = type.getName();
                System.out.print(" ");
    
                // 修饰符
                String modifiers = Modifier.toString(f.getModifiers());
    
                if (modifiers.length() > 0) {   // 如果有修饰符
                    System.out.print(modifiers + " ");
                }
    
                System.out.println(type.getName() + " " + name + ";");
            }
        }
    }
    复制代码
    复制代码

    【运行结果】:

    private java.lang.String java.lang.String;
    protected int int;
    public java.util.Date java.util.Date;

    反射的应用

    操作对象属性(域)的值

        前面已经知道如何查看任意对象的数据域名称和类型。在编写程序时,如果想要查看域名和类型是很简单的事情,而反射机制可以查看在编译时还不清楚的对象域。

    利用get方法获取域(属性)值

        查看对象域的关键方法是Field类的get方法。如果f是一个Field类型的对象,obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,类型为Object,其值为obj域的当前值。示例:

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Field;
    
    class Person {
        private String name;
    
        public Person(String name) {
            this.name = name;
        }
    }
    public class ReflectionDemo2 {
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            Person p = new Person("萧萧弈寒");
    
            Class clz = p.getClass();
            Field f = clz.getDeclaredField("name");
            //f.setAccessible(true);
            Object name = f.get(p);
    
            System.out.println(name);
        }
    }
    复制代码
    复制代码

    【运行结果】:

    Exception in thread "main" java.lang.IllegalAccessException: Class com.xiaoxiaoyihan.reflection.ReflectionDemo2 can not access a member of class com.xiaoxiaoyihan.reflection.Person with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:109) ……

    说明:因为反射机制的默认行为受限于Java的访问控制。如果一个Java程序没有受限于安全管理器的控制,就可以覆盖访问控制。Field,Method或Constructor对象提供了setAccessible方法用于覆盖访问控制。

        get方法还需要解决另一个问题,get方法的返回值是Object类型的,上面的name是String类型的,将String类型值,赋给Object对象没有问题,但是如果出现double类型的域呢?Java中的数值类型不是对象。可以通过Field类的getDouble方法,也可以调用get方法然后进行强制类型转换。反射机制会自动地将这个域值打包到相应的对象包装器中,对于double类型,将打包成Double。

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Field;
    
    class Person {
        private double salary;
    
        public Person(double salary) {
            this.salary = salary;
        }
    }
    public class ReflectionDemo2 {
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            Person p = new Person(100);
    
            Class clz = p.getClass();
    
            Field f = clz.getDeclaredField("salary");
            f.setAccessible(true);
    //        double salary = (double) f.get(p);
            double salary = f.getDouble(p);
            System.out.println("薪水:" + salary);
        }
    }
    复制代码
    复制代码
    
    【运行结果】: 薪水:100.0
    

    利用set方法设置域(属性)值

       当然也可以使用set方法,对obj对象的f域设置新值。set方法的签名是:

    void set(obj, value)    // obj:操作的对象;value:新值
    

    示例:

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.Field;
    
    class Person {
        private String name;
        private double salary;
    
        public Person(String name, double salary) {
            this.name = name;
            this.salary = salary;
        }
    
        public double getSalary() {
            return salary;
        }
    
        public String getName() {
            return name;
        }
    }
    public class ReflectionDemo2 {
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            Person p = new Person("张三", 100);
    
            System.out.println(p.getName() + "-----" + p.getSalary());
    
            Class clz = Person.class;
    
            Field f = clz.getDeclaredField("name");
            f.setAccessible(true);
            f.set(p, "萧萧弈寒");
    
            f = clz.getDeclaredField("salary");
            f.setAccessible(true);
            f.set(p, 200);
    
            System.out.println(p.getName() + "-----" + p.getSalary());
    
        }
    }
    复制代码
    复制代码

    调用任意方法

    invoke方法

        与Field类的get方法查看对象的域相似,Method类提供了一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:

    Object invoke(Object obj, Object...args)
    

    第一个参数是隐式参数,其余的对象提供了显示参数。

    例如,m1代表Person的getName方法,那么通过invoke方法获取name的形式为:

    String n = m1.invoke(p);
    

    如果返回的是基本类型,invoke方法会返回其包装器类型。例如,m2表示getSalary方法,那么返回的对象实际是一个Double,必须响应的完整类型转换。

    double s = m2.invoke(p);
    

    根据前面的介绍,可以通过Class类的静态方法getDeclaredMethods获取Method对象数组,然后对返回的数组进行遍历,找到指定的方法。与getField方法类似,getField方法通过表示域名的字符串,返回一个Field对象。然而,由于方法存在重载的情况,有可能获得若干个相同名字的方法。因此,还必须提供方法的参数类型。getMethod方法的签名:

    Method getMethod(String name, Class...paramTypes)
    

    示例:

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    class Person {
        private String name;
        private double salary;
    
        public String getName() {
            return name;
        }
    
        public double getSalary() {
            return salary;
        }
    
        public void raiseSalary(double raise) {
            salary += raise;
        }
    
        public Person(String name, double salary) {
            this.name = name;
            this.salary = salary;
        }
    
    }
    
    public class ReflectionDemo2 {
        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            Person p = new Person("萧萧弈寒", 100);
    
            // 获取getName方法对象
            Method m1 = p.getClass().getMethod("getName");
            // 调用getName方法
            String name = (String)m1.invoke(p);
            System.out.println("我的名字是:" + name);
    
            // 获取raiseSalary方法对象,需要传double类型的参数
            Method m2 = p.getClass().getMethod("raiseSalary", double.class);
            // 调用raiseSalary方法,并传入参数值
            m2.invoke(p, 200);
            System.out.println("加薪后:salary=" + p.getSalary());
        }
    }
    复制代码
    复制代码

    【运行结果】:
    我的名字是:萧萧弈寒
    加薪后:salary=300.0

    对于静态方法,invoke的第一个参数可以被忽略,既可以将它设置为null。

    复制代码
    复制代码
    package com.xiaoxiaoyihan.reflection;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    class Person {
        public static void speak(String name) {
            System.out.println("木头!" + name + ",你倒是说话啊!");
        }
    
    }
    
    public class ReflectionDemo2 {
        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            Method m = Person.class.getMethod("speak", String.class);
            m.invoke(null, "萧萧弈寒");
        }
    }
    复制代码
    复制代码

    【运行结果】: 木头!萧萧弈寒,你倒是说话啊!

    注意:invoke的参数和返回值必须是Object类型的。这就意味着必须进行多次的类型转换。这样做将会是编译器错过检查代码的机会。此外,通过反射获得方法指针的代码比直接调用方法效率低。

  • 相关阅读:
    关于java.lang.OutOfMemoryError: Java heap space的错误分析
    对TCP/IP网络协议的深入浅出归纳
    leetcode面试准备:Contains Duplicate I && II
    leetcode面试准备:Count Complete Tree Nodes
    leetcode面试准备: Jump Game II
    leetcode面试准备: Jump Game
    LeetCode解题报告:Linked List Cycle && Linked List Cycle II
    最小栈的实现与优化
    面试:归并排序
    leetcode面试准备:Decode Ways
  • 原文地址:https://www.cnblogs.com/csk001/p/14226355.html
Copyright © 2011-2022 走看看