美文网首页
细说Java反射1

细说Java反射1

作者: as_pixar | 来源:发表于2020-03-10 11:00 被阅读0次

    让我们一起认真对待之前可能没有多在意的基础知识之一 Java 反射。

    注意,这篇文章因为内容太多,所以篇幅非常长。中途受不了的同学可以回到目录跳转到感兴趣的小节进行学习。

    向一个门外汉介绍反射

    反射是什么?

    官方文档上有这么一段介绍:

    Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

    我来翻译一下:反射技术通常被用来检测和改变应用程序在 Java 虚拟机中的行为表现。它是一个相对而言比较高级的技术,通常它应用的前提是开发者本身对于 Java 语言特性有很强的理解的基础上。值得说明的是,反射是一种强有力的技术特性,因此可以使得应用程序突破一些藩篱,执行一些常规手段无法企及的目的。

    我再通俗概括一下:反射能够在程序运行时修改程序的行为。反射是非常规手段,反射有风险,应用需谨慎。]

    如果是你在写这篇博客,如何向一个刚有一点点 Java 基础的初学者,或者是说毫无 Java 基础的门外汉解释清楚反射这样一种东西?

    直接翻译官方文档,显然是不太行。因为那仍然是抽象的,所以,最好的方法仍然是通过类比或者是拟人,用生活场景中具体的事物与抽象的概念建立相关性。

    把程序代码比作一辆车,因为 Java 是面向对象的语言,所以这样很容易理解,正常流程中,车子有自己的颜色、车型号、品牌这些属性,也有正常行驶、倒车、停泊这些功能操作。

    正常情况下,车子需要一个司机来驾驶,然后按照行为准则规范行驶。

    那么反射是什么呢?反射是非常规手段,正常行驶的时候,车子需要司机驾驶,但是,反射却不需要,因为它就是车子的——自动驾驶。

    又因为反射非常规,所以,它风险未知,需要开发者极强的把控力。而汽车动驾驶技术现在是热门,但是特斯拉都出过故障,所以同样在汽车领域,自动驾驶技术也需要车厂家有超强的风险把控能力,这个基础就是要遵从汽车本身的结构与交通规则,不能因为运用了自动驾驶技术的汽车就不叫做汽车了,应用了反射技术的代码就不叫做代码了。

    自动驾驶需要遵守基础规则,同样反射也需要,下面的文章就是介绍反射技术应该遵守的规格与限制。

    反射入口

    我们试想一下,如果自动驾驶要运用到一辆汽车之上,研发人员首先要拿到的是什么?

    肯定是汽车的规格说明书。

    同样,反射如果要作用于一段 Java 代码上,那么它也需要拿到一本规格说明书,那么对于反射而言,这本规格说明书是什么呢?

    Class

    因为 Java 是面向对象的语言,基本上是以类为基础构造了整个程序系统,反射中要求提供的规格说明书其实就是一个类的规格说明书,它就是 Class。

    注意的是 Class 是首字母大写,不同于 class 小写,class 是定义类的关键字,而 Class 的本质也是一个类,因为在 Java 中一切都是对象。

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

    Class 就是一个对象,它用来代表运行在 Java 虚拟机中的类和接口。

    把 Java 虚拟机类似于高速公路,那么 Class 就是用来描述公路上飞驰的汽车,也就是我前面提到的规格说明书。

    Class 的获取

    反射的入口是 Class,但是反射中 Class 是没有公开的构造方法的。Java 反射中 Class 的获取可以通过下面 3 种方式。

    1. 每个对象都有所属的类型

    对于一个对象而言,如果这个对象可以访问,那么调用 getClass()方法就可以获取到了它的相应的 Class 对象。

    public class Car {}
    
    public class Test {
    
        public static void main(String[] args) {
            
            Car car = new Car();
            
            Class clazz = car.getClass();
        }
    
    }
    

    值得注意的是,这种方法不适合基本类型如 int、float 等等。

    2. 通过 .class 标识

    上面的例子中,Car 是一个类,car 是它的对象,通过 car.getClass() 就获取到了 Car 这个类的 Class 对象,也就是说通过一个类的实例的 getClass() 方法就能获取到它的 Class。如果不想创建这个类的实例的话,就需要通过 ```.class`` 这个标识。

    public class Test {
    
        public static void main(String[] args) {
            
            Class clazz = Car.class;
            Class cls1 = int.class;
            Class cls2 = String.class;
    
        }
    }
    

    3. 通过 Class.forName() 方法

    有时候,我们没有办法创建一个类的实例,甚至没有办法用 Car.class 这样的方式去获取一个类的 Class 对象。

    这在 Android 开发领域很常见,因为某种目的,Android 工程师把一些类加上了 @hide 注解,所示这些类就没有出现在 SDK 当中,那么,我们要获取这个并不存在于当前开发环境中的类的 Class 对象时就没有辙了吗?答案是否定的,Java 给我们提供了 Class.forName() 这个方法。

    只要给这个方法中传入一个类的全限定名称就好了,那么它就会到 Java 虚拟机中去寻找这个类有没有被加载。

    try {
        Class clz = Class.forName("com.wwj.test.Car");
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    

    “com.wwj.test.Car” 就是 Car 这个类的全限定名称,它包括包名+类名。

    如果找不到时,它会抛出 ClassNotFoundException 这个异常,这个很好理解,因为如果查找的类没有在 JVM 中加载的话,自然要告诉开发者。

    所以,上面 3 节讲述了如何拿到一个类的 Class 对象。

    Class 内容清单

    仅仅拿到 Class 对象还不够,我们感兴趣的是它的内容。

    在正常的代码编写中,我们如果要编写一个类,一般会定义它的属性和方法,如:

    public class Car {
        
        private String mBand;
        private Color mColor;
        
        public enum Color {
            RED,
            WHITE,
            BLACK,
            BLUE,
            YELLOR
        }
    
        public Car() {
            super();
        }
    
        public Car(String mBand) {
            this.mBand = mBand;
        }
    
        public void drive() {
            System.out.println("di di di,开车了!");
        }
    
        @Override
        public String toString() {
            return "Car [mBand=" + mBand + ", mColor=" + mColor + "]";
        }
    
    }
    

    现在我们来一一分解它。

    Class 的名字

    Class 对象也有名字,涉及到的 API 有:

    Class.getName();
    
    Class.getSimpleName();
    
    Class.getCanonicalName();
    

    现在,说说它们的区别。

    因为 Class 是一个入口,它代表引用、基本数据类型和数组对象,所以获取它们的方式又有一点不同。

    先从 getName() 说起。

    当 Class 代表一个引用时

    getName() 方法返回的是一个二进制形式的字符串,比如“com.wwj.test.Car”。

    当 Class 代表一个基本数据类型,比如 int.class 的时候

    getName() 方法返回的是它们的关键字,比如 int.class 的名字是 int。

    当 Class 代表的是基础数据类型的数组时 比如 int[][][] 这样的 3 维数组时

    getName() 返回 [[[I 这样的字符串。

    Java 本身对于这一块制定了相应规则,在元素的类型前面添加相应数量的 [ 符号,用 [ 的个数来提示数组的维度,并且值得注意的是,对于基本类型或者是类,都有相应的编码,所谓的编码大多数是用一个大写字母来指示某种类型,规则如下:

    Element Type       Encoding  
    boolean               Z  
    byte                  B  
    char                  C  
    class or interface       Lclassname;  
    double                D  
    float                 F  
    int                   I  
    long                  J  
    short                 S  
    

    需要注意的是类或者是接口的类型编码是 L类名; 的形式,后面有一个分号。

    比如 String[].getClass().getName() 结果是 [Ljava.lang.String;。

    我们来测试一下代码:

    public class Test {
    
        public static void main(String[] args) {
            
            try {
                Class clz = Class.forName("com.wwj.test.Car");
                    
                Class clz1 = float.class;
                
                Class clz2 = Void.class;
                
                Class clz3 = new int[]{}.getClass();
                
                Class clz4 = new Car[]{}.getClass();
                
                System.out.println(clz.getName());
                System.out.println(clz1.getName());
                System.out.println(clz2.getName());
                System.out.println(clz3.getName());
                System.out.println(clz4.getName());
                
                
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        
        }
    
    }
    

    上面代码的打印结果如下:

    com.wwj.test.Car
    float
    java.lang.Void
    [I
    [Lcom.wwj.test.Car;
    

    刚刚介绍的都是 getName() 的情况,那么 getSimpleName() 和 getCaninolName() 呢?

    getSimpleName() 自然是要去获取 simplename 的,那么对于一个 Class 而言什么是 SimpleName 呢?我们先要从嵌套类说起

    public class Outter {
        static class Inner {}
    }
    

    Outter 这个类中有一个静态的内部类。

    Class clz = Outter.Inner.class;         
    System.out.println(" Inner Class name:"+clz.getName());
    System.out.println(" Inner Class simple name:"+clz.getSimpleName());
    

    我们分别打印 Inner 这个类的 Class 对象的 name 和 simplename。

     Inner Class name:com.wwj.test.Outter$Inner
     Inner Class simple name:Inner
    

    可以看到,因为是内部类,所以通过 getName() 方法获取到的是二进制形式的全限定类名,并且类名前面还有个 $ 符号。
    getSimpleName() 则直接返回了 Inner,去掉了包名限定。
    打个比方,我的全名叫做 wwj as,而我的 simplename 就叫做 wwj,simplename 之于 name 也是如此。

    simplename 的不同

    需要注意的是,当获取一个数组的 Class 中的 simplename 时,不同于 getName() 方法,simplename 不是在前面加 [,而是在后面添加对应数量的 [] 。

    上面代码打印结果是:

     Inner Class name:[[[Lcom.wwj.test.Outter$Inner;
     Inner Class simple name:Inner[][][]
    

    还需要注意的是,对于匿名内部类,getSimpleName() 返回的是一个空的字符串。

    Runnable run = new Runnable() {
                    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            
        }
    };
    
    System.out.println(" Inner Class name:"+run.getClass().getName());
    System.out.println(" Inner Class simple name:"+run.getClass().getSimpleName());
    

    打印结果是:

    Inner Class name:com.wwj.test.Test$1
    Inner Class simple name:
    

    最后再来看 getCanonicalName()。

    Canonical 是官方、标准的意思,那么 getCanonicalName() 自然就是返回一个 Class 对象的官方名字,这个官方名字 canonicalName 是 Java 语言规范制定的,如果 Class 对象没有 canonicalName 的话就返回 null。

    getCanonicalName() 是 getName() 和 getSimpleName() 的结合。

    • getCanonicalName() 返回的也是全限定类名,但是对于内部类,不用 $ 开头,而用 .。
    • getCanonicalName() 对于数组类型的 Class,同 simplename 一样直接在后面添加 [] 。
    • getCanonicalName() 不同于 simplename 的地方是,不存在 canonicalName 的时候返回 null 而不是空字符串。
    • 局部类和匿名内部类不存在 canonicalName。
    Class clz = new Outter.Inner[][][]{}.getClass();
                
    System.out.println(" Inner Class name:"+clz.getName());
    System.out.println(" Inner Class simple name:"+clz.getSimpleName());
    System.out.println(" Inner Class canonical name:"+clz.getCanonicalName());
    
    
    //run 是匿名类
    Runnable run = new Runnable() {
        
        @Override
        public void run() {
            // TODO Auto-generated method stub
            
        }
    };
    
    System.out.println(" anonymous Class name:"+run.getClass().getName());
    System.out.println(" anonymous Class simple name:"+run.getClass().getSimpleName());
    System.out.println(" anonymous Class canonical name:"+run.getClass().getCanonicalName());
    
    // local 是局部类
    class local{};
    
    System.out.println("Local a name:"+local.class.getName());
    System.out.println("Local a simplename:"+local.class.getSimpleName());
    System.out.println("Local a canonicalname:"+local.class.getCanonicalName());
    

    打印结果如下:

    Inner Class name:[[[Lcom.wwj.test.Outter$Inner;
    Inner Class simple name:Inner[][][]
    Inner Class canonical name:com.wwj.test.Outter.Inner[][][]
    
    anonymous Class name:com.wwj.test.Test$1
    anonymous Class simple name:
    anonymous Class canonical name:null
    
    Local a name:com.wwj.test.Test$1local
    Local a simplename:local
    Local a canonicalname:null
    

    Class 去获取相应名字的知识内容就讲完了,仔细想一下,小小的一个细节,其实蛮有学问的。

    好了,我们继续往下。

    Class 获取修饰符

    通常,Java 开发中定义一个类,往往是要通过许多修饰符来配合使用的。它们大致分为 4 类。

    • 用来限制作用域,如 public、protected、priviate。
    • 用来提示子类复写,abstract。
    • 用来标记为静态类 static。
    • 注解。

    Java 反射提供了 API 去获取这些修饰符。

    package com.wwj.test;
    
    public abstract class TestModifier {
    
    }
    

    我们定义了一个类,名字为 TestModifier,被 public 和 abstract 修饰,现在我们要提取这些修饰符。我们只需要调用 Class.getModifiers() 方法就是了,它返回的是一个 int 数值。

    System.out.println("modifiers value:"+TestModifier.class.getModifiers());
    System.out.println("modifiers :"+Modifier.toString(TestModifier.class.getModifiers()));
    
    modifiers value:1025
    modifiers :public abstract
    

    大家肯定会有疑问,为什么会返回一个整型数值呢?

    这是因为一个类定义的时候可能会被多个修饰符修饰,为了一并获取,所以 Java 工程师考虑到了位运算,用一个 int 数值来记录所有的修饰符,然后不同的位对应不同的修饰符,这些修饰符对应的位都定义在 Modifier 这个类当中。

    public class Modifier {
    
        public static final int PUBLIC           = 0x00000001;
    
    
        public static final int PRIVATE          = 0x00000002;
    
    
        public static final int PROTECTED        = 0x00000004;
    
    
        public static final int STATIC           = 0x00000008;
    
    
        public static final int FINAL            = 0x00000010;
    
    
        public static final int SYNCHRONIZED     = 0x00000020;
    
    
        public static final int VOLATILE         = 0x00000040;
    
    
        public static final int TRANSIENT        = 0x00000080;
    
    
        public static final int NATIVE           = 0x00000100;
    
     
        public static final int INTERFACE        = 0x00000200;
    
    
        public static final int ABSTRACT         = 0x00000400;
    
    
        public static final int STRICT           = 0x00000800;
    
        public static String toString(int mod) {
            StringBuilder sb = new StringBuilder();
            int len;
    
            if ((mod & PUBLIC) != 0)        sb.append("public ");
            if ((mod & PROTECTED) != 0)     sb.append("protected ");
            if ((mod & PRIVATE) != 0)       sb.append("private ");
    
            /* Canonical order */
            if ((mod & ABSTRACT) != 0)      sb.append("abstract ");
            if ((mod & STATIC) != 0)        sb.append("static ");
            if ((mod & FINAL) != 0)         sb.append("final ");
            if ((mod & TRANSIENT) != 0)     sb.append("transient ");
            if ((mod & VOLATILE) != 0)      sb.append("volatile ");
            if ((mod & SYNCHRONIZED) != 0)  sb.append("synchronized ");
            if ((mod & NATIVE) != 0)        sb.append("native ");
            if ((mod & STRICT) != 0)        sb.append("strictfp ");
            if ((mod & INTERFACE) != 0)     sb.append("interface ");
    
            if ((len = sb.length()) > 0)    /* trim trailing space */
                return sb.toString().substring(0, len-1);
            return "";
        }
    
    }
    

    调用 Modifier.toString() 方法就可以打印出一个类的所有修饰符。

    当然,Modifier 还提供了一系列的静态工具方法用来对修饰符进行操作。

    public static boolean isPublic(int mod) {
            return (mod & PUBLIC) != 0;
        }
    
    
    public static boolean isPrivate(int mod) {
        return (mod & PRIVATE) != 0;
    }
    
    
    public static boolean isProtected(int mod) {
        return (mod & PROTECTED) != 0;
    }
    
    
    public static boolean isStatic(int mod) {
        return (mod & STATIC) != 0;
    }
    
    
    public static boolean isFinal(int mod) {
        return (mod & FINAL) != 0;
    }
    
    
    public static boolean isSynchronized(int mod) {
        return (mod & SYNCHRONIZED) != 0;
    }
    
    
    public static boolean isVolatile(int mod) {
        return (mod & VOLATILE) != 0;
    }
    
    
    public static boolean isTransient(int mod) {
        return (mod & TRANSIENT) != 0;
    }
    
    
    public static boolean isNative(int mod) {
        return (mod & NATIVE) != 0;
    }
    
    
    public static boolean isInterface(int mod) {
        return (mod & INTERFACE) != 0;
    }
    
    
    public static boolean isAbstract(int mod) {
        return (mod & ABSTRACT) != 0;
    }
    
    
    public static boolean isStrict(int mod) {
        return (mod & STRICT) != 0;
    }
    

    这些代码的作用,一看就懂,所以不再多说。

    获取 Class 的成员

    一个类的成员包括属性(有人翻译为字段或者域)、方法。对应到 Class 中就是 Field、Method、Constructor。

    获取 Filed

    获取指定名字的属性有 2 个 API

    public Field getDeclaredField(String name)
                           throws NoSuchFieldException,
                                  SecurityException;
    
    public Field getField(String name)
                   throws NoSuchFieldException,
                          SecurityException
    

    两者的区别就是 getDeclaredField() 获取的是 Class 中被 private 修饰的属性。 getField() 方法获取的是非私有属性,并且 getField() 在当前 Class 获取不到时会向祖先类获取。

    获取所有的属性。

    //获取所有的属性,但不包括从父类继承下来的属性
    public Field[] getDeclaredFields() throws SecurityException {}
    
    //获取自身的所有的 public 属性,包括从父类继承下来的。
    public Field[] getFields() throws SecurityException {
    

    可以用一个例子,给大家加深一下理解。

    public class Farther {
        
        public int a;
        
        private int b;
    
    }
    
    public class Son extends Farther {
        int c;
        
        private String d;
        
        protected float e;
    }
    
    
    package com.wwj.test;
    import java.lang.reflect.Field;
    
    public class FieldTest {
        public static void main(String[] args) {
            Class cls = Son.class;
            try {
                Field field = cls.getDeclaredField("b");
                
            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.out.println("getDeclaredField "+e.getMessage());
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.out.println("getDeclaredField "+e.getMessage());
            }
            
            try {
                Field field = cls.getField("b");
                
            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.out.println("getField "+e.getMessage());
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.out.println("getField "+e.getMessage());
            }
            
            Field[] filed1 = cls.getDeclaredFields();
            
            for ( Field f : filed1 ) {
                System.out.println("Declared Field :"+f.getName());
            }
            
            Field[] filed2 = cls.getFields();
            
            for ( Field f : filed2 ) {
                System.out.println("Field :"+f.getName());
            }
    
        }
    
    }
    

    代码打印结果:

    java.lang.NoSuchFieldException: b
        at java.lang.Class.getDeclaredField(Unknown Source)
        at com.wwj.test.FieldTest.main(FieldTest.java:29)
    getDeclaredField b
    
    
    java.lang.NoSuchFieldException: b
        at java.lang.Class.getField(Unknown Source)
        at com.wwj.test.FieldTest.main(FieldTest.java:40)
    getField b
    
    
    
    Declared Field :c
    Declared Field :d
    Declared Field :e
    
    Field :a
    

    大家细细体会一下,不过需要注意的是 getDeclaredFileds() 方法可以获取 private、protected、public 和 default 属性,但是它获取不到从父类继承下来的属性。

    获取 Method

    类或者接口中的方法对应到 Class 就是 Method。
    相应的 API 如下:

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    
    public Method getMethod(String name, Class<?>... parameterTypes)
    
    public Method[] getDeclaredMethods() throws SecurityException
    
    
    public Method getMethod(String name, Class<?>... parameterTypes) 
    

    因为跟 Field 类似,所以不做过多的讲解。parameterTypes 是方法对应的参数。

    获取 Constructor

    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
    
    public Constructor<T> getConstructor(Class<?>... parameterTypes)
    
    public Constructor<?>[] getDeclaredConstructors() throws SecurityException 
    
    public Constructor<?>[] getConstructors() throws SecurityException 
    

    仍然以前面的 Father 和 Son 两个类为例。

    public class Farther {
        
        public int a;
        
        private int b;
    
        public Farther() {
            super();
            // TODO Auto-generated constructor stub
        }
        
    
    }
    
    public class Son extends Farther {
        int c;
        
        private String d;
        
        protected float e;
        
        
    
        private Son() {
            super();
            // TODO Auto-generated constructor stub
        }
    
    
    
        public Son(int c, String d) {
            super();
            this.c = c;
            this.d = d;
        }
        
    }
    
    public class ConstructorTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            
            Class clz = Son.class;
            
            Constructor[] constructors = clz.getConstructors();
            
            for ( Constructor c : constructors ) {
                System.out.println("getConstructor:"+c.toString());
            }
            
            constructors = clz.getDeclaredConstructors();
            
            for ( Constructor c : constructors ) {
                System.out.println("getDeclaredConstructors:"+c.toString());
            }
    
        }
    
    }
    

    测试程序代码的打印结果如下:

    getConstructor:public com.wwj.test.Son(int,java.lang.String)
    
    getDeclaredConstructors:private com.wwj.test.Son()
    getDeclaredConstructors:public com.wwj.test.Son(int,java.lang.String)
    

    因为,Constructor 不能从父类继承,所以就没有办法通过 getConstructor() 获取到父类的 Constructor。

    我们获取到了 Field、Method、Constructor,但这一是终点,相反,这正是反射机制中开始的地方,我们运用反射的目的就是为了获取和操控 Class 对象中的这些成员。

    Field 的操控

    我们在一个类中定义字段时,通常是这样。

    public class Son extends Farther {
        int c;
        
        private String d;
        
        protected float e;
        
        Car car;
    
    }
    

    像 c、d、e、car 这些变量都是属性,在反射机制中映射到 Class 对象中都是 Field,很显然,它们也有对应的类别。

    它们要么是 8 种基础类型 int、long、float、double、boolean、char、byte 和 short。或者是引用,所有的引用都是 Object 的后代。

    Field 类型的获取

    获取 Field 的类型,通过 2 个方法:

    public Type getGenericType() {}
    
    public Class<?> getType() {}
    

    注意,两者返回的类型不一样,getGenericType() 方法能够获取到泛型类型。大家可以看下面的代码进行理解:

    public class Son extends Farther {
        int c;
        
        private String d;
        
        protected float e;
        
        public List<Car> cars;
        
        public HashMap<Integer,String> map;
        
        private Son() {
            super();
        }
    
        public Son(int c, String d) {
            super();
            this.c = c;
            this.d = d;
        }
        
    }
    
    public class FieldTest {
    
        public static void main(String[] args) {
            Class cls = Son.class;
            
            Field[] filed2 = cls.getFields();
            
            for ( Field f : filed2 ) {
                System.out.println("Field :"+f.getName());
                System.out.println("Field type:"+f.getType());
                System.out.println("Field generic type:"+f.getGenericType());
                System.out.println("-------------------");
            }
    
        }
    
    }
    
    

    打印结果:

    Field :cars
    Field type:interface java.util.List
    Field generic type:java.util.List<com.wwj.test.Car>
    -------------------
    Field :map
    Field type:class java.util.HashMap
    Field generic type:java.util.HashMap<java.lang.Integer, java.lang.String>
    -------------------
    Field :a
    Field type:int
    Field generic type:int
    -------------------
    

    可以看到 getGenericType() 确实把泛型都打印出来了,它比 getType() 返回的内容更详细。

    Field 修饰符的获取

    同 Class 一样,Field 也有很多修饰符。通过 getModifiers() 方法就可以轻松获取。

    public int getModifiers() {}
    

    这个与前面 Class 获取修饰符一致,所以不需要再讲,不清楚的同学翻看前面的内容就好了。

    Field 内容的读取与赋值

    这个应该是反射机制中对于 Field 最主要的目的了。

    Field 这个类定义了一系列的 get 方法来获取不同类型的值。

    
    public Object get(Object obj);
    
    public int getInt(Object obj);
            throws IllegalArgumentException, IllegalAccessException;
    
    public long getLong(Object obj)
            throws IllegalArgumentException, IllegalAccessException;
    
    public float getFloat(Object obj)
            throws IllegalArgumentException, IllegalAccessException;
    
    public short getShort(Object obj)
            throws IllegalArgumentException, IllegalAccessException;
    
    public double getDouble(Object obj)
            throws IllegalArgumentException, IllegalAccessException;
    
    public char getChar(Object obj)
            throws IllegalArgumentException, IllegalAccessException;
    
    public byte getByte(Object obj)
            throws IllegalArgumentException, IllegalAccessException;
    
    public boolean getBoolean(Object obj)
            throws IllegalArgumentException, IllegalAccessException
    
    

    Field 又定义了一系列的 set 方法用来对其自身进行赋值。

    public void set(Object obj, Object value);
    
    ppublic void setBoolean(Object obj, boolean z)
    
     public void setLong(Object obj, long l)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void setFloat(Object obj, float f)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void setShort(Object obj,short value)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void setDouble(Object obj, double d)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void getChar(Object obj,char value)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void setByte(Object obj,byte b)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void setBoolean(Object obj,boolean b)
            throws IllegalArgumentException, IllegalAccessException
    

    可能有同学会对方法中出现的 Object 参数有疑问,它其实是类的实例引用,这里涉及一个细节。

    Class 本身不对成员进行储存,它只提供检索,所以需要用 Field、Method、Constructor 对象来承载这些成员,所以,针对成员的操作时,一般需要为成员指定类的实例引用。

    下面用代码来说明:

    A testa = new A();
    testa.a = 10;
    
    System.out.println("testa.a = "+testa.a);
    
    Class c = A.class;
    
    try {
        Field fielda = c.getField("a");
    
        int ra = fielda.getInt(testa);
        
        System.out.println("reflection testa.a = "+ra);
        
        fielda.setInt(testa, 15);
        
        System.out.println("testa.a = "+testa.a);
        
    } catch (NoSuchFieldException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (SecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    

    打印结果如下:

    testa.a = 10
    reflection testa.a = 10
    testa.a = 15
    

    我们再来看看 Field 被 private 修饰的情况

    public class A {
        
        public int a;
        
        private int b;
    
        public int getB() {
            return b;
        }
    
        public void setB(int b) {
            this.b = b;
        }
        
    }
    

    再编写测试代码

    A testa = new A();
    testa.setB(3);
    
    System.out.println("testa.b = "+testa.getB());
    
    Class c = A.class;
    
    try {
        Field fieldb = c.getDeclaredField("b");
        int rb = fieldb.getInt(testa);
        
        System.out.println("reflection testa.b = "+rb);
        
        fieldb.setInt(testa, 20);
        
        System.out.println("testa.b = "+testa.getB());
        
    } catch (NoSuchFieldException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (SecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    
    

    打印的结果如下:

    testa.b = 3
    java.lang.IllegalAccessException: Class com.wwj.test.FieldTest can not access a member of class com.wwj.test.A with modifiers "private"
        at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
        at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source)
        at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
        at java.lang.reflect.Field.getInt(Unknown Source)
        at com.wwj.test.FieldTest.main(FieldTest.java:20)
    

    抛异常了。这是因为在反射中访问了 private 修饰的成员,如果要消除异常的话,需要添加一句代码。

    fieldb.setAccessible(true);
    

    再看打印结果

    testa.b = 3
    reflection testa.b = 3
    testa.b = 20
    

    Method 的操控

    Method 对应普通类的方法。
    我们看看一般普通类的方法的构成。

    public int add(int a,int b);
    

    方法由下面几个要素构成:

    • 方法名
    • 方法参数
    • 方法返回值
    • 方法的修饰符
    • 方法可能会抛出的异常

    很显然,反射中 Method 提供了相应的 API 来提取这些元素

    Method 获取方法名

    通过 getName() 这个方法就好了。

    以前面的 Car 类作为测试对象。

    public class MethodTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Car car = new Car();
            
            Class clz = car.getClass();
            
            Method methods[] = clz.getDeclaredMethods();
            
            for ( Method m : methods ) {
                System.out.println("method name:"+m.getName());
            } 
        }
    
    }
    

    打印结果如下:

    method name:toString
    method name:drive
    

    Method 获取方法参数

    涉及到的 API 如下:

    public Parameter[] getParameters() {}
    

    返回的是一个 Parameter 数组,在反射中 Parameter 对象就是用来映射方法中的参数。经常使用的方法有:

    Parameter.java

    // 获取参数名字
    public String getName() {}
    
    // 获取参数类型
    public Class<?> getType() {}
    
    // 获取参数的修饰符
    public int getModifiers() {}
    

    当然,有时候我们不需要参数的名字,只要参数的类型就好了,通过 Method 中下面的方法获取。

    // 获取所有的参数类型
    public Class<?>[] getParameterTypes() {}
    
    // 获取所有的参数类型,包括泛型
    public Type[] getGenericParameterTypes() {}
    
    

    下面,同样进行测试。

    public class Car {
        
        private String mBand;
        
        private Color mColor;
        
        public enum Color {
            RED,
            WHITE,
            BLACK,
            BLUE,
            YELLOR
        }
        
        
    
        public Car() {
            super();
            // TODO Auto-generated constructor stub
        }
    
    
        public Car(String mBand) {
            this.mBand = mBand;
        }
        
        
        public void drive() {
            System.out.println("di di di,开车了!");
        }
    
        @Override
        public String toString() {
            return "Car [mBand=" + mBand + ", mColor=" + mColor + "]";
        }
        
        public void test(String[] paraa,List<String> b,HashMap<Integer,Son> maps) {}
        
    
    }
    
    public class MethodTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Car car = new Car();
            
            Class clz = car.getClass();
            
            Method methods[] = clz.getDeclaredMethods();
            
            
            
            for ( Method m : methods ) {
                System.out.println("method name:"+m.getName());
                
                Parameter[] paras = m.getParameters();
                
                for ( Parameter p : paras ) {
                    System.out.println(" parameter :"+p.getName()+" "+p.getType().getName());
                }
                
                Class[] pTypes = m.getParameterTypes();
                
                System.out.println("method para types:");
                for ( Class type : pTypes ) {
                    System.out.print(" "+ type.getName());
                }
                System.out.println();
                
                Type[] gTypes = m.getGenericParameterTypes();
                System.out.println("method para generic types:");
                for ( Type type : gTypes ) {
                    System.out.print(" "+ type.getTypeName());
                }
                System.out.println();
                System.out.println("==========================================");
                
            } 
        }
    
    }
    
    
    

    打印结果如下:

    method name:toString
    method para types:
    
    method para generic types:
    
    ==========================================
    method name:test
     parameter :arg0 [Ljava.lang.String;
     parameter :arg1 java.util.List
     parameter :arg2 java.util.HashMap
    method para types:
     [Ljava.lang.String; java.util.List java.util.HashMap
    method para generic types:
     java.lang.String[] java.util.List<java.lang.String> java.util.HashMap<java.lang.Integer, com.wwj.test.Son>
    ==========================================
    method name:drive
    method para types:
    
    method para generic types:
    
    ==========================================
    

    Method 获取返回值类型

    // 获取返回值类型
    public Class<?> getReturnType() {}
    
    
    // 获取返回值类型包括泛型
    public Type getGenericReturnType() {}
    

    Method 获取修饰符

    public int getModifiers() {}
    

    这部分内容前面已经讲过。

    Method 获取异常类型

    public Class<?>[] getExceptionTypes() {}
    
    public Type[] getGenericExceptionTypes() {}
    

    Method 方法的执行

    这是整个反射机制的核心内容,很多时候运用反射目的其实就是为了以常规手段执行 Method。

    public Object invoke(Object obj, Object... args) {}
    

    Method 调用 invoke() 的时候,存在许多细节:

    • invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 ojb 为 null,后面的可变参数 Object 对应的自然就是参数。

    • invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。

    • 在对 Method 调用 invoke() 的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由 Method 统一抛出 InvocationTargetException。而通过 InvocationTargetException.getCause() 可以获取真正的异常。

    下面同样通过例子来说明,我们新建立一个类,要添加一个 static 修饰的静态方法,一个普通的方法和一个会抛出异常的方法。

    public class TestMethod {
        
        public static void testStatic () {
            System.out.println("test static");
        }
        
        private  int add (int a,int b ) {
            return a + b;
        }
        
        public void testException () throws IllegalAccessException {
            throw new IllegalAccessException("You have some problem.");
        }
    
    }
    

    我们编写测试代码:

    public class InvokeTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Class testCls = TestMethod.class;
            
            try {
                Method mStatic = testCls.getMethod("testStatic",null);
                // 测试静态方法
                mStatic.invoke(null, null);
            } catch (NoSuchMethodException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            TestMethod t = new TestMethod();
            
            try {
                Method mAdd = testCls.getDeclaredMethod("add",int.class,int.class);
                // 通过这句代码才能访问 private 修饰的 Method
                mAdd.setAccessible(true);
                int result = (int) mAdd.invoke(t, 1,2);
                System.out.println("add method result:"+result);
            } catch (NoSuchMethodException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            try {
                Method testExcep = testCls.getMethod("testException",null);
                try {
                    testExcep.invoke(t, null);
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    //e.printStackTrace();
    
                    // 通过 InvocationTargetException.getCause() 获取被包装的异常
                    System.out.println("testException occur some error,Error type is :"+e.getCause().getClass().getName());
                    System.out.println("Error message is :"+e.getCause().getMessage());
                }
            } catch (NoSuchMethodException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    

    打印结果如下:

    test static
    add method result:3
    testException occur some error,Error type is :java.lang.IllegalAccessException
    Error message is :You have some problem.
    

    Constructor 的操控

    在平常开发的时候,构造器也称构造方法,但是在反射机制中却把它与 Method 分离开来,单独用 Constructor 这个类表示。

    Constructor 同 Method 差不多,但是它特别的地方在于,它能够创建一个对象。

    在 Java 反射机制中有两种方法可以用来创建类的对象实例:Class.newInstance() 和 Constructor.newInstance()。官方文档建议开发者使用后面这种方法,下面是原因。

    • Class.newInstance() 只能调用无参的构造方法,而 Constructor.newInstance() 则可以调用任意的构造方法。

    • Class.newInstance() 通过构造方法直接抛出异常,而 Constructor.newInstance() 会把抛出来的异常包装到 InvocationTargetException 里面去,这个和 Method 行为一致。

    • Class.newInstance() 要求构造方法能够被访问,而 Constructor.newInstance() 却能够访问 private 修饰的构造器。

    还是通过代码来验证

    public class TestConstructor {
        
        private String self;
    
        public TestConstructor() {
            self = " wwj";
        }
    
        public TestConstructor(String self) {
            this.self = self;
        }
    
        @Override
        public String toString() {
            return "TestConstructor [self=" + self + "]";
        }
    }
    

    上面的类中有 2 个构造方法,一个无参,一个有参数。编写测试代码:

    public class NewInstanceTest {
    
        public static void main(String[] args) {
            Class clz = TestConstructor.class;
            try {
                TestConstructor test1 = (TestConstructor) clz.newInstance();
                
                System.out.println(test1.toString());
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            try {
                Constructor con = clz.getConstructor(String.class);
                
                TestConstructor test2 = (TestConstructor) con.newInstance("Zhao");
                
                System.out.println(test2.toString());
                
            } catch (NoSuchMethodException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
         }
    }
    

    分别用 Class.newInstance() 和 Constructor.newInstance() 方法来创建类的实例,打印结果如下:

    TestConstructor [self= wwj]
    TestConstructor [self=Zhao]
    

    可以看到通过 Class.newInstance() 方法调用的构造方法确实是无参的那个。

    现在,我们学习了 Class 对象的获取,也能够获取它内部成员 Filed、Method 和 Constructor 并且能够操作它们。在这个基础上,我们已经能够应付普通的反射开发了。

    但是,Java 反射机制还另外细分了两个概念:数组和枚举。

    反射中的数组

    数组本质上是一个 Class,而在 Class 中存在一个方法用来识别它是否为一个数组。
    Class.java

    public native boolean isArray();
    

    为了便于测试,我们创建一个新的类

    public class Shuzu {
        
        private int[] array;
        
        private Car[] cars;
    }
    

    其中有一个 int 型数组属性,它的名字叫做 array。还有一个 cars 数组,它的类型是 Car,是之前定义好的类。 当然,array 和 cars 是 Shuzu 这个类的 Field,对于 Field 的角度来说,它是数组类型,我们可以这样理解数组可以同 int、char 这些基本类型一样成为一个 Field 的类别。

    我们可能通过一系列的 API 来获取它的具体信息,刚刚有提到它本质上还是一个 Class 而已。

    getName();
    
    getComponentType();
    

    第二个方法是获取数组的里面的元素的类型,比如 int[] 数组的 componentType 自然就是 int。

    按照惯例,写代码验证。

    public class ArraysTest {
    
        public static void main(String[] args) {
            Class clz = Shuzu.class;
            
            Field[] fields = clz.getDeclaredFields();
            
            for ( Field f : fields ) {
                // 获取 Field 的类型
                Class c = f.getType();
                // 判断这个类型是不是数组类型
                if ( c.isArray()) {
                    System.out.println("Type is "+c.getName());
                    System.out.println("ComponentType type is :"+c.getComponentType());
                }
            }
        }
    
    }
    

    打印结果如下:

    Type is [I
    ComponentType type is :int
    Type is [Lcom.wwj.test.Car;
    ComponentType type is :class com.wwj.test.Car
    

    反射中动态创建数组

    反射创建数组是通过 Array.newInstance() 这个方法。
    Array.java

    public static Object newInstance(Class<?> componentType, int... dimensions)
            throws IllegalArgumentException, NegativeArraySizeException {}
    

    第一个参数指定的是数组内的元素类型,后面的是可变参数,表示的是相应维度的数组长度限制。

    比如,我要创建一个 int[2][3] 的数组。

    Array.newInstance(int.class,2,3);
    

    Array 的读取与赋值

    首先,对于 Array 整体的读取与赋值,把它作为一个普通的 Field,根据 Class 中相应获取和设置就好了。调用的是 Field 中对应的方法。

    public void set(Object obj,
                    Object value)
             throws IllegalArgumentException,
                    IllegalAccessException;
    
    
    public Object get(Object obj)
               throws IllegalArgumentException,
                      IllegalAccessException;
    

    还需要处理的情况是对于数组中指定位置的元素进行读取与赋值,这要涉及到 Array 提供的一系列 setXXX() 和 getXXX() 方法。因为和之前 Field 相应的 set 、get 方法类似,所以我在下面只摘抄典型的几种,大家很容易知晓其它类型的怎么操作。

    public static void set(Object array,
                           int index,
                           Object value)
                    throws IllegalArgumentException,
                           ArrayIndexOutOfBoundsException;
    
    
    public static void setBoolean(Object array,
                                  int index,
                                  boolean z)
                           throws IllegalArgumentException,
                                  ArrayIndexOutOfBoundsException;
    
    
    
    public static Object get(Object array,
                             int index)
                      throws IllegalArgumentException,
                             ArrayIndexOutOfBoundsException;
    
    
    public static short getShort(Object array,
                                 int index)
                          throws IllegalArgumentException,
                                 ArrayIndexOutOfBoundsException;
    

    进行代码测试:

    public class ArraysTest {
    
        public static void main(String[] args) {
            Class clz = Shuzu.class;
            
            try {
                Shuzu shu = (Shuzu) clz.newInstance();
                
                Field arrayF = clz.getDeclaredField("array");
                arrayF.setAccessible(true);
                
                Object o = Array.newInstance(int.class, 3);
                Array.set(o, 0, 1);
                Array.set(o, 1, 3);
                Array.set(o, 2, 3);
                
                arrayF.set(shu, o);
                
                int[] array = shu.getArray();
                
                for ( int i = 0;i < array.length;i++) {
                    System.out.println("array index "+i+" value:"+array[i]);
                }
                
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        
        }
    }
    

    打印结果如下:

    array index 0 value:1
    array index 1 value:3
    array index 2 value:3
    

    反射中的枚举 Enum

    同数组一样,枚举本质上也是一个 Class 而已,但反射中还是把它单独提出来了。

    我们来看一般程序开发中枚举的表现形式。

    public enum State {
        IDLE,
        DRIVING,
        STOPPING,
        
        test();
        
        int test1() {
            return 0;
        }
    }
    
    

    枚举真的跟类很相似,有修饰符、有方法、有属性字段甚至可以有构造方法。

    在 Java 反射中,可以把枚举看成一般的 Class,但是反射机制也提供了 3 个特别的的 API 用于操控枚举。

    // 用来判定 Class 对象是不是枚举类型
    Class.isEnum()
    
    // 获取所有的枚举常量
    Class.getEnumConstants()
    
    
    // 判断一个 Field 是不是枚举常量
    java.lang.reflect.Field.isEnumConstant()
    

    枚举的获取与设定

    因为等同于 Class,所以枚举的获取与设定就可以通过 Field 中的 get() 和 set() 方法。

    需要注意的是,如果要获取枚举里面的 Field、Method、Constructor 可以调用 Class 的通用 API。

    用例子来加深理解吧。

    public enum State {
        IDLE,
        DRIVING,
        STOPPING,
        
        test();
        
        int test1() {
            return 0;
        }
    
    }
    
    public class Meiju {
        
        private State state = State.DRIVING;
    
        public State getState() {
            return state;
        }
    
        public void setState(State state) {
            this.state = state;
        }
    }
    
    public static void main(String[] args) {
            
            Class clz = State.class;
            
            if ( clz.isEnum()){
                System.out.println(clz.getName()+" is Enum");
                
                System.out.println(Arrays.asList(clz.getEnumConstants()));
                // 获取枚举中所有的 Field
                Field[] fs = clz.getDeclaredFields();
                
                for ( Field f : fs ) {
                    if ( f.isEnumConstant()){
                        System.out.println(f.getName()+" is EnumConstant");
                    }else {
                        System.out.println(f.getName()+" is not EnumConstant");
                    }
                }
                
                Class cMeiju = Meiju.class;
                Meiju meiju = new Meiju();
                
                try {
                    Field f = cMeiju.getDeclaredField("state");
                    f.setAccessible(true);
                    
                    
                    try {
                        State state = (State) f.get(meiju);
                        
                        System.out.println("State current is "+state);
                        
                        f.set(meiju, State.STOPPING);
                        
                        
                        System.out.println("State current is "+meiju.getState());
                        
                    } catch (IllegalArgumentException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                } catch (NoSuchFieldException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (SecurityException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            
        }
    
    }
    
    
    

    打印结果如下:

    com.wwj.test.State is Enum
    [IDLE, DRIVING, STOPPING, test]
    IDLE is EnumConstant
    DRIVING is EnumConstant
    STOPPING is EnumConstant
    test is EnumConstant
    ENUM$VALUES is not EnumConstant
    State current is DRIVING
    State current is STOPPING
    
    
    

    反射与自动驾驶

    文章开头,我用自动驾驶的技术来比喻反射,实际上的目的是为了给初学者一个大体的印象和一个模糊的轮廓,实际上反射不是自动驾驶,它是什么取决于你自己对它的理解。

    下段代码的目标是为了对比,先定义一个类 AutoDrive,这个类有一系列的属性,然后有一系列的方法,先用普通编码的方式来创建这个类的对象,调用它的方法。然后用反射的机制模拟自动驾驶。

    汽车开动的步骤,以手动档为例。

    1. 空档发动。
    2. 打左转向灯。
    3. 踩离合挂一档。
    4. 起步松手铩。

    现在代码模拟

    public class AutoDrive {
        
        public enum Color {
            WHITE,
            REN,
            BLUE
        }
        private String vendor;
        
        private Color color;
    
        public AutoDrive(String vendor, Color color) {
            super();
            this.vendor = vendor;
            this.color = color;
        }
        
        public AutoDrive() {
            vendor = "Nissan";
            color = Color.WHITE;
        }
    
        public void drive(){
            
            boot();
            
            turnOnLeftLight();
            
            cailiheguayidang();
            
            songshousha();
            
            
            tips();
            
        }
    
        private void tips() {
            System.out.println("您正在驾驶 "+color+" "+vendor+" 汽车,小心行驶。");
        }
    
        private void songshousha() {
            // TODO Auto-generated method stub
            System.out.println("起步松手铩。");
        }
    
        private void cailiheguayidang() {
            // TODO Auto-generated method stub
            System.out.println("踩离合器,挂一档");
        }
    
        private void turnOnLeftLight() {
            // TODO Auto-generated method stub
            System.out.println("打左向灯");
        }
    
        private void boot() {
            // TODO Auto-generated method stub
            System.out.println("空档发动汽车");
            
        }
    
    }
    

    我们只要创建一个 AutoDrive 的对象,调用它的 drive() 方法就好了。

    public class DriveTest {
    
    
        public static void main(String[] args) {
            AutoDrive car = new AutoDrive();
            
            car.drive();
        }
    
    }
    

    结果如下:

    空档发动汽车
    打左向灯
    踩离合器,挂一档
    起步松手铩。
    您正在驾驶 WHITE Nissan 汽车,小心行驶。
    

    我们现在要使用自动驾驶技术,具体到代码就是反射,因为非常规嘛。

    public class DriveTest {
    
    
        public static void main(String[] args) {
            AutoDrive car = new AutoDrive();
            
            car.drive();
            
            Class cls = AutoDrive.class;
            try {
                Constructor cons = cls.getConstructor(String.class,AutoDrive.Color.class);
                
                // 利用反射技术创建 AutoDrive 对象
                AutoDrive autoDrive = (AutoDrive) cons.newInstance("Tesla",AutoDrive.Color.RED);
                
                // 获取能够驱动汽车的 drive 方法
                Method method = cls.getMethod("drive");
                
                System.out.println("=====================\n自动驾驶马上开始\n================");
                // 通过反射调用 Method 方法,最终车子跑去起来
                method.invoke(autoDrive, null);
    
    
            } catch (NoSuchMethodException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    }
    

    最后,打印结果:

    空档发动汽车
    打左向灯
    踩离合器,挂一档
    起步松手铩。
    您正在驾驶 WHITE Nissan 汽车,小心行驶。
    =====================
    自动驾驶马上开始
    ================
    空档发动汽车
    打左向灯
    踩离合器,挂一档
    起步松手铩。
    您正在驾驶 RED Tesla 汽车,小心行驶。
    

    总结

    • Java 中的反射是非常规编码方式。
    • Java 反射机制的操作入口是获取 Class 文件。 有 Class.forName()、 .class 和 Object.getClass() 3 种。
    • 获取 Class 对象后还不够,需要获取它的 Members,包含 Field、Method、Constructor。
    • Field 操作主要涉及到类别的获取,及数值的读取与赋值。
    • Method 算是反射机制最核心的内容,通常的反射都是为了调用某个 Method 的 invoke() 方法。
    • 通过 Class.newInstance() 和 Constructor.newInstance() 都可以创建类的对象实例,但推荐后者。因为它适应于任何构造方法,而前者只会调用可见的无参数的构造方法。
    • 数组和枚举可以被看成普通的 Class 对待。

    只是,在日常开发中,利用反射飙车的时候,记得提醒自己一句:老哥,稳住。

    关于反射更多细节,可以阅读这篇博文《反射进阶,编写反射代码值得注意的诸多细节》

    相关文章

      网友评论

          本文标题:细说Java反射1

          本文链接:https://www.haomeiwen.com/subject/axvidhtx.html