java基础_反射

什么是反射

反射是一个很牛的功能,能够在程序运行时修改程序的行为。但是反射是非常规手段,反射有风险,应用需谨慎。

把程序代码比作一辆车,因为Java是面向对象的语言,车子有自己的型号、车牌号、发动机,有倒车、泊车等功能。

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

反射是非常规手段,正常行驶的时候,车子需要司机的驾驶。但是反射不需要,因为它就是车子的自动驾驶。(非常规嘛)

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

Class

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

  • 其中小写的class是定义类的关键字。

  • Class本质是一个类。

    public final class Class<T> implements java.io.Serializable,
                                  GenericDeclaration,
                                  Type,
                                  AnnotatedElement,
                                  TypeDescriptor.OfField<Class<?>>,
                                  Constable {}
    
    
  • Class就是一个对象,运行在Java虚拟机中的类和接口。

Class的获取

反射的入口是Class,但是反射中Class是没有公开的构造函数,不能通过关键字new来获取Class的对象,所以就有如下3种获取Class的方式

  • Object.getClass()

    • 对于一个对象而言,如果能访问,那么调用getClass()方法就可以获取到它对应的Class对象。
    • 如下这种方法不适合基本类型 :int float
    public class Car {
    }
    
    public class CarTest {
        public static void main(String[] args) {
            Car car = new Car();
            Class aClass = car.getClass();
    
        }
    }
    
  • 通过 .class标识

    • 对于上述方法,Car是一个类,car是一个对象,通过car.getClass()就获取到Car这个类的Class对象。
    • 如果不想通过类的对象的getClass()方式去获取,就可以通过 ‘.class的标识’来获取。
    public class CarTest {
        public static void main(String[] args) {
           
            Class<Car> carClass = Car.class;
            Class<Integer> integerClass = int.class;
            Class<String> stringClass = String.class;
    
        }
    }
    
  • 通过 Class.forName()方法

    • 在Android中,Google的Android工程师把一些类加上了@hide注解,这些被@hide注解修饰的类就没有出现在SDK中。
    • 我们要获取到这个在当前开发环境中不存在的类的Class对象,就可以通过此方法来获取到。
    • 如下代码中,"com.dashingqi.reflect.Car"就是Car类的全限定类名称,包括 包名+类名。
    • 如果找不到该类的时候,会抛出ClassNotFoundException,因为如果这个没有加载到JVM中的时候,需要告诉我们。
    public class CarTest {
        public static void main(String[] args) {
           
            try {
                Class.forName("com.dashingqi.reflect.Car");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    

Class的内容清单

Class的名字获取

Class对象也有名字,涉及到API如下

  • Class.getName()
  • Class.getSimpleName()
  • Class.getCanonicalName()

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

getName

当Class代表一个引用时

  • getName放回的是一个二进制形式的字符串 “com.dashingqi.reflect.Car”

当Class代表是一个基本数据类型的时候

  • getName返回的就是它们的关键字 比如 int.class的名字就是int

当Class代表是基本数据类型的数组时(int [] [] [] 这样的3维数组)

  • getName返回的是[[[I 这样的字符串

  • Java在这个地方有相应的规则,在元素类型的前面添加相应数量的[符号,用[符号的数量来表示数组的维度,对于基本数据类型都有相应的编码,都是大写的字母;规则如下

元素类型 编码
boolean Z
byte B
char C
Double D
Float F
int I
Long L
short S
类或者接口 L类名;(是有;的哟)
  • 代码操作一下

    public class CarTest {
        public static void main(String[] args) {
    
            try {
    
                Class<?> aClass = Class.forName("getclassinstance.Car");
                Class<Integer> integerClass = int.class;
                Class<Float> floatClass = float.class;
                Class<? extends int[]> aClass1 = new int[]{}.getClass();
                Class<? extends Car[]> aClass2 = new Car[]{}.getClass();
    
                  //代表一个引用
                System.out.println("aClass = " + aClass.getName());
                //基本数据类型
                System.out.println("integerClass = " + integerClass.getName());
                System.out.println("floatClass=" + floatClass.getName());
                  //基本数据类型的数组
                System.out.println("aClass1=" + aClass1.getName());
               //类或者接的数组
                System.out.println("aClass2=" + aClass2.getName());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    //运行结果如下
    
    aClass = getclassinstance.Car
    integerClass = int
    floatClass=float
    aClass1=[I
    aClass2=[Lgetclassinstance.Car;
    
    
getSimpleName()
  • 先看一个嵌套类

    public class ClassA {
    
        public static class ClassB{
    
        }
    }
    
    public class ClassTest {
    
        public static void main(String[] args) {
            ClassA.ClassB classB = new ClassA.ClassB();
            String name = classB.getClass().getName();
            String simpleName = classB.getClass().getSimpleName();
            System.out.println("name = " + name);
            System.out.println("simpleName = " + simpleName);
        }
    }
    
    //运行结果
    name = getSimpleName.ClassA$ClassB
    simpleName = ClassB
    
    • 因为是内部类,通过getName得到是的二进制形式的全限定类名,并且类名前面还有一个$符号。
    • getSimpleName()则返回一个ClassB,去掉了包名限定。
  • 当数组的Class获取simpleName的时候,不同于getName,simpleName不在前面加[,而是在后面加[]

    public class ClassTest {
    
        public static void main(String[] args) {
    
            Class<? extends ClassA[]> aClass = new ClassA[]{}.getClass();
            String name1 = aClass.getName();
            System.out.println("name1 = " + name1);
            String simpleName1 = aClass.getSimpleName();
            System.out.println("simpleName1=" + simpleName1);
        }
    }
    
    //运行结果如下
    name1 = [LgetSimpleName.ClassA;
    simpleName1=ClassA[]
    
  • 对于匿名内部类来说,通过getSimpleName()获取的是一个空的字符串

    public class ClassTest {
    
        public static void main(String[] args) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
    
                }
            };
    
            System.out.println("name = " + runnable.getClass().getName());
            System.out.println("simple name = " + runnable.getClass().getSimpleName());
        }
    }
    
    // 运行结果如下
    name = getSimpleName.ClassTest$1
    simple name = 
    
getCanonicalName

Canonial 是官方、标准的意思,那么该方法就是获取Class对象的官方名字。

这个CanonicalName是官方规定的,如果Class对象没有canonicalName的话就返回一个null

getCanonicalName()是getName()与getSimpleName()的结合

  • getCanonicalName()返回的也是全限定类名,但是对于内部类来说,不用$开头,而是用.。

  • getCanonicalName()对于数组的Class,同getSimpleName()也是在后面加[]。

  • getCanonicalName()不同于getSimpleName()的是,如果不存在canonicalName就返回null而不是空字符串。

  • 局部类和匿名内部类不存在canonicalName。

  • 代码

    public class ClassTest {
    
        public static void main(String[] args) {
            ClassA.ClassB classB = new ClassA.ClassB();
    
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
    
                }
            };
            String canonicalName = classB.getClass().getCanonicalName();
            String canonicalName1 = new ClassA.ClassB[][][]{}.getClass().getCanonicalName();
    
            System.out.println("canonicalName = " + canonicalName);
            System.out.println("canonicalName1 = " + canonicalName1);
            System.out.println("run name = " + runnable.getClass().getCanonicalName());
    
    
            /**
             * local是局部类
             */
            class Local {
            }
            ;
            String name = Local.class.getName();
            String simpleName = Local.class.getSimpleName();
            String canonicalName2 = Local.class.getCanonicalName();
    
            System.out.println("local name = " + name);
            System.out.println("local simple name = " + simpleName);
            System.out.println("local canonical name = " + canonicalName2);
    
        }
    }
    
    //运行结果
    
    canonicalName = getSimpleName.ClassA.ClassB
    canonicalName1 = getSimpleName.ClassA.ClassB[][][]
    run name = null
    local name = getSimpleName.ClassTest$1Local
    local simple name = Local
    local canonical name = null
    

Class获取修饰符

Java开发中定义一个类,往往要通过很多修饰符来配合使用的。大致分为如下四类

  • 用来限制作用域:publish、protected、private
  • 用来标记为静态:static
  • 注解
  • 用来提示子类来复写:abstract

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

  • 定义一个被 abstract 和 publish 修饰的类

    public  abstract class DemoA {
    }
    
  • 我们现在要提取这些修饰符,只需要调用 Class.getModifiers()

    public class Demo {
        public static void main(String[] args) {
              /**
              * public native int getModifiers();
              * 是一个native层的方法
              */
            int modifiers = DemoA.class.getModifiers();
            System.out.println("modifiers = " + modifiers);
            System.out.println("modifiers_str = "+ Modifier.toString(modifiers));
        }
    }
    
    
  • 运行结果如下

    modifiers = 1025
    modifiers_str = public abstract
    
    
  • Java工程师考虑到了位运算,用一个int数值来记录所有的修饰符,然后不同的位对应不同的修饰符,这些修饰符都定义在Modifier类中。

    public static String toString(int mod) {
            StringJoiner sj = new StringJoiner(" ");
    
            if ((mod & PUBLIC) != 0)        sj.add("public");
            if ((mod & PROTECTED) != 0)     sj.add("protected");
            if ((mod & PRIVATE) != 0)       sj.add("private");
    
            /* Canonical order */
            if ((mod & ABSTRACT) != 0)      sj.add("abstract");
            if ((mod & STATIC) != 0)        sj.add("static");
            if ((mod & FINAL) != 0)         sj.add("final");
            if ((mod & TRANSIENT) != 0)     sj.add("transient");
            if ((mod & VOLATILE) != 0)      sj.add("volatile");
            if ((mod & SYNCHRONIZED) != 0)  sj.add("synchronized");
            if ((mod & NATIVE) != 0)        sj.add("native");
            if ((mod & STRICT) != 0)        sj.add("strictfp");
            if ((mod & INTERFACE) != 0)     sj.add("interface");
    
            return sj.toString();
        }
    
        /*
         * Access modifier flag constants from tables 4.1, 4.4, 4.5, and 4.7 of
         * <cite>The Java&trade; Virtual Machine Specification</cite>
         */
    
        /**
         * The {@code int} value representing the {@code public}
         * modifier.
         */
        public static final int PUBLIC           = 0x00000001;
    
        /**
         * The {@code int} value representing the {@code private}
         * modifier.
         */
        public static final int PRIVATE          = 0x00000002;
    
        /**
         * The {@code int} value representing the {@code protected}
         * modifier.
         */
        public static final int PROTECTED        = 0x00000004;
    
        /**
         * The {@code int} value representing the {@code static}
         * modifier.
         */
        public static final int STATIC           = 0x00000008;
    
        /**
         * The {@code int} value representing the {@code final}
         * modifier.
         */
        public static final int FINAL            = 0x00000010;
    
        /**
         * The {@code int} value representing the {@code synchronized}
         * modifier.
         */
        public static final int SYNCHRONIZED     = 0x00000020;
    
        /**
         * The {@code int} value representing the {@code volatile}
         * modifier.
         */
        public static final int VOLATILE         = 0x00000040;
    
        /**
         * The {@code int} value representing the {@code transient}
         * modifier.
         */
        public static final int TRANSIENT        = 0x00000080;
    
        /**
         * The {@code int} value representing the {@code native}
         * modifier.
         */
        public static final int NATIVE           = 0x00000100;
    
        /**
         * The {@code int} value representing the {@code interface}
         * modifier.
         */
        public static final int INTERFACE        = 0x00000200;
    
        /**
         * The {@code int} value representing the {@code abstract}
         * modifier.
         */
        public static final int ABSTRACT         = 0x00000400;
    
        /**
         * The {@code int} value representing the {@code strictfp}
         * modifier.
         */
        public static final int STRICT           = 0x00000800;
    
        // Bits not (yet) exposed in the public API either because they
        // have different meanings for fields and methods and there is no
        // way to distinguish between the two in this class, or because
        // they are not Java programming language keywords
        static final int BRIDGE    = 0x00000040;
        static final int VARARGS   = 0x00000080;
        static final int SYNTHETIC = 0x00001000;
        static final int ANNOTATION  = 0x00002000;
        static final int ENUM      = 0x00004000;
        static final int MANDATED  = 0x00008000;
    

获取Class的成员

获取Filed

获取到指定名字的属性API

  • getDeclaredField()方法获取的是Class中任何修饰符修饰的属性名字,name

    public Field getDeclaredField(String name)
            throws NoSuchFieldException, SecurityException {
        }
    
  • getField(String name):当前 name是被publish修饰 就能获取到,否则抛出异常

    @CallerSensitive
        public Field getField(String name)
           
        }
    

获取到所有属性

  • getDeclaredFieleds()获取到所有属性,但不包括从父类继承下来的属性

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

    public Field[] getFields() throws SecurityException {
            
        }
    
  • 代码实操

    public class MainTest {
    
        public static void main(String[] args) {
            Class<Son> sonClass = Son.class;
    
            try {
                //获取到属性名字,
                Field a = sonClass.getDeclaredField("d");
                System.out.println("getDeclaredField name = " + a.getName());
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                System.out.println("getDeclaredField = " + e.getMessage());
            }
    
            try {
                //获取 publish修饰的属性名字,如果自己找不到,会去查找父类中的
                Field c = sonClass.getField("c");
                System.out.println("getField name = " + c.getName());
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
    
            //获取到所有属性的名字,不包括父类的
            Field[] declaredFields = sonClass.getDeclaredFields();
            for (Field f : declaredFields){
                System.out.println("getDeclaredFields name =  "+f.getName());
            }
    
            //获取到自身所有的publish修饰的属性名字,包括从父类继承下来的
            Field[] fields = sonClass.getFields();
            for (Field field : fields){
                System.out.println("getFields name = "+field.getName());
            }
        }
    }
    
    //运行结果如下 
    
    getDeclaredField name = d
    java.lang.NoSuchFieldException: c
      at java.base/java.lang.Class.getField(Class.java:2004)
      at get_class_filed.MainTest.main(MainTest.java:21)
    getDeclaredFields name =  c
    getDeclaredFields name =  d
    getDeclaredFields name =  f
    getFields name = b
    
获取Method

类获取接口中的方法对应到Class就是Method

  • getDeclaredMethod():获取到任何修饰符修饰的方法,不包括从父类继承到的

     public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
           
        }
    
  • getMethod():获取到任何publish修饰符修饰的方法,包括从父类中继承得到的

    public Method getMethod(String name, Class<?>... parameterTypes)
           
        }
    
  • getDeclaredMethods():获取所有方法的名字,不包括从父类继承得到的

    public Method[] getDeclaredMethods() throws SecurityException {
           
        }
    
  • getMethods():获取所有publish修饰方法的名字,包括从父类继承的来的(包括Object)

    public Method[] getMethods() throws SecurityException {
        }
    
  • 代码演示

    public class MainTest {
    
        public static void main(String[] args) {
            Class<Son> sonClass = Son.class;
    
            try {
                Method methodC = sonClass.getDeclaredMethod("methodF", null);
                System.out.println("methodC = " + methodC.getName());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
    
            try {
                Method methodA = sonClass.getMethod("methodF", null);
                System.out.println("methodA name = " + methodA.getName());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
    
            Method[] declaredMethods = sonClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println("getDeclaredMethods name =  " + method.getName());
            }
    
            Method[] methods = sonClass.getMethods();
            for (Method method : methods) {
                System.out.println(" getMethods name = " + method.getName());
            }
        }
    }
    
    //运行结果如下 
    
    methodC = methodF
    java.lang.NoSuchMethodException: get_class_filed.Son.methodF()
      at java.base/java.lang.Class.getMethod(Class.java:2113)
      at get_class_filed.MainTest.main(MainTest.java:20)
    get_class_filed.Son.methodF()
    getDeclaredMethods name =  methodC
    getDeclaredMethods name =  methodF
    getDeclaredMethods name =  methodD
    getDeclaredMethods name =  methodG
     getMethods name = methodC
     getMethods name = methodD
     getMethods name = methodA
     getMethods name = wait
     getMethods name = wait
     getMethods name = wait
     getMethods name = equals
     getMethods name = toString
     getMethods name = hashCode
     getMethods name = getClass
     getMethods name = notify
     getMethods name = notifyAll
     
    
Constructor

Java把构造器从Method中拎出来,用Constructor来表示

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

  • getDeclaredConstructor():获取任何修饰符修饰的构造方法名字

    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException
        }
    
  • getConstructor():获取当前Class对象中 publish修饰的构造方法的名字

    public Constructor<T> getConstructor(Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException{
        }
    
  • getDeclaredConstructors():获取所有构造方法名字

    public Constructor<?>[] getDeclaredConstructors() throws SecurityException {
    }
    
  • getConstructors():获取所有publish修饰的构造方法名字

    public Constructor<?>[] getConstructors() throws SecurityException {
          
    }
    

Field的操作

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

其中 c、d、f、g、demo这些变量都是属性,在反射机制中映射到Class对象中都是Field

它们要么是8中基本数据类型,或者是引用,所有的引用都是Object的子类。

public class Son extends Father {
    int c;

    private String d;

    protected float f;

    public int g;

    Demo demo;
}
Field类型的获取
  • 获取Field类型有2种方法(首先获取到Class对象种的Field,通过Field的一下两种方法获取Field类型)

    public Type getGenericType() {
        }
    public Class<?> getType() {
        }
    //两种方法返回参数不一样,getGenericType()可以获取到泛型类型
    
  • 代码实操

    public class Son extends Father {
        int c;
    
        private String d;
    
        protected float f;
    
        public int g;
    
        Demo demo;
    
       public List<Demo> mDemoList;
    
        public Map<Integer, Integer> maps;
    }
    
    public static void main(String[] args) {
            Class<Son> sonClass = Son.class;
    
            Field[] fields = sonClass.getFields();
            for (Field field : fields) {
                System.out.println("getName == " + field.getName());
                System.out.println("getType = " + field.getType());
                System.out.println("getGenericType = " + field.getGenericType());
                System.out.println("======================");
            }
    }
    //运行结果如下
    getName == g
    getType = int
    getGenericType = int
    ======================
    getName == mDemoList
    getType = interface java.util.List
    getGenericType = java.util.List<get_class_filed.Demo>
    ======================
    getName == maps
    getType = interface java.util.Map
    getGenericType = java.util.Map<java.lang.Integer, java.lang.Integer>
    ======================
    getName == b
    getType = int
    getGenericType = int
    ======================
      
    // 我们看到 把自己Class对象种的publish属性和父类中的publish修饰的属性都给找到了
    // getGenericType()把泛型的类型都给打印出来了  ,相比较与getType 更详细
    
Field修饰符的获取
  • 通过getModifiers()方法就可以获取

    public int getModifiers() {
            return modifiers;
        }
    
  • 代码实操

    public static void main(String[] args) {
            Class<Son> sonClass = Son.class;
    
            Field[] fields = sonClass.getFields();
            for (Field field : fields) {
                System.out.println("getModifiers = "+ Modifier.toString(field.getModifiers()));
                System.out.println("======================");
            }
      //运行结果如下
    getModifiers = public
    ======================
    getModifiers = public
    ======================
    getModifiers = public
    ======================
    getModifiers = public
    ======================
    
Field内容的读取与赋值

这也是反射机制中对Filed主要的目的

Filed定义了一些列set和get方法来设置和获取不同类型的值

  • get

    public Object get(Object obj);
    
    public int getInt(Object obj);
    
    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
    
  • set

    public void set(Object obj, Object value);
    
    public void getInt(Object obj,int value);
    
    public void getLong(Object obj,long value)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void getFloat(Object obj,float value)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void getShort(Object obj,short value)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void getDouble(Object obj,double value)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void getChar(Object obj,char value)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void getByte(Object obj,byte b)
            throws IllegalArgumentException, IllegalAccessException;
    
    public void getBoolean(Object obj,boolean b)
            throws IllegalArgumentException, IllegalAccessException
    
  • 其中Object就是类的实例引用,Class本身不对成员进行存储,它只提供检索,所以需要Field、Method、Constructor对象来承载这些成员,所以针对成员的操作时,一般需要为成员指定类的实例引用,表示这个成员属于哪个Object的,是为了精准定位。

  • 代码实操

    public class MainTest {
    
        public static void main(String[] args) {
    
            Son son = new Son();
            son.g = 10;
    
            System.out.println("son.g = " + son.g);
            //Class本身不对成员做存储
            Class<Son> sonClass = Son.class;
    
            try {
                Field g = sonClass.getField("g");
                int anInt = g.getInt(son);
                System.out.println(" reflection son.g = " + anInt);
                g.setInt(son, 16);
                System.out.println("son.g = " + son.g);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
      
      //运行结果如下
    son.g = 10
     reflection son.g = 10
    son.g = 16
    
  • 上述代码是获取publish修饰的属性,这回找个private修饰的属性 h

    Son son = new Son();
            son.setH(10);
    
            System.out.println("son.g = " + son.getH());
            //Class本身不对成员做存储
            Class<Son> sonClass = Son.class;
    
            try {
                Field h = sonClass.getDeclaredField("h");
                int anInt = h.getInt(son);
                System.out.println(" reflection son.g = " + anInt);
                h.setInt(son, 16);
                System.out.println("son.g = " + son.getH());
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
    //运行结果如下
    son.g = 10
    java.lang.IllegalAccessException: class get_class_filed.MainTest cannot access a member of class get_class_filed.Son with modifiers "private"
      at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:376)
      at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:639)
      at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
      at java.base/java.lang.reflect.Field.getInt(Field.java:592)
      at get_class_filed.MainTest.main(MainTest.java:21)
     //报错了
    
  • 这是因为反射过程操作了private属性,如果要消除异常加如下代码

    h.setAccessible(true)
    
    • 运行结果如下

      son.g = 10
       reflection son.g = 10
      son.g = 16
      

Method操控

Method对应普通类的方法;

一般普通类的构成

public int add(int a, int b){

​ return a+b;

}

主要包括如下几个方面

  1. 方法名
  2. 方法的返回值类型
  3. 方法的参数
  4. 方法的修饰符
  5. 方法抛出的异常

Java的反射中针对Method 有相关操作API的来提取相关元素

获取Method方法名
  • 获取方法名字的通过getName()方法

  • 代码实操

    public class MainClass {
    
        public static void main(String[] args) {
    
            Class<Car> carClass = Car.class;
            Method[] declaredMethods = carClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println("method name = " + method.getName());
            }
        }
    }
    //运行结果如下
    method name = getCarName
    method name = setCarName
    method name = getCarId
    method name = setCarId
    
Method获取方法参数
  • 通过 getParameters()来获取

    public Parameter[] getParameters() {}
    
  • 返回是一个Parameter的数组,在反射中Parameter对象就是用来映射方法中的参数,经常使用的方法有如下

    // 获取参数名字
    public String getName() {}
    
    // 获取参数类型
    public Class<?> getType() {}
    
    // 获取参数的修饰符
    public int getModifiers() {}
    
  • 当然有时我们不需要参数的名字,只需要参数的类型就可以,通过Method下面的方法获取

    //获取所有参数的类型
    public Class<?>[] getParameterTypes() {
        }
    //获取所有参数的类型,包括泛型    
    public Type[] getGenericParameterTypes() {
            return super.getGenericParameterTypes();
        }
    
  • 代码实操

    public class MainClass {
    
        public static void main(String[] args) {
    
            Class<Car> carClass = Car.class;
            Method[] declaredMethods = carClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println("method name = " + method.getName());
    
                //获取到方法中的参数
                Parameter[] parameters = method.getParameters();
    
                for (Parameter parameter : parameters) {
                    //获取到参数的名字和参数和参数的修饰符
                    System.out.println("parameter = " + parameter.getName() + " " + parameter.getType().getName() + " Modifiers name = " + Modifier.toString(parameter.getModifiers()));
                }
    
                //获取到所有参数的类型
                Class<?>[] pTypes = method.getParameterTypes();
                System.out.println("method para types");
                for (Class type : pTypes) {
                    System.out.println(" type name = " + type.getName());
                }
    
                //获取到所有参数的类型,包括泛型
                Type[] genericParameterTypes = method.getGenericParameterTypes();
                for (Type type : genericParameterTypes) {
                    System.out.println(" generic type name = " + type.getTypeName());
                }
    
            }
        }
    }
    
    //运行结果如下
    method name = toString
    method para types
    method name = test
    parameter = arg0 [Ljava.lang.String; Modifiers name = 
    parameter = arg1 java.util.List Modifiers name = 
    parameter = arg2 java.util.HashMap Modifiers name = 
    method para types
     type name = [Ljava.lang.String;
     type name = java.util.List
     type name = java.util.HashMap
     generic type name = java.lang.String[]
     generic type name = java.util.List<java.lang.String>
     generic type name = java.util.HashMap<java.lang.String, java.lang.Integer>
    method name = drive
    method para types
    
获取Method的返回值类型
  • 与如下两种方法

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

    • getReturnType()

      public class MainClass {
      
          public static void main(String[] args) {
      
              Class<Car> carClass = Car.class;
              Method[] declaredMethods = carClass.getDeclaredMethods();
              for (Method method : declaredMethods) {
                  System.out.println("method name = " + method.getName());
      
                  Class<?> returnTypes = method.getReturnType();
                  String name = returnTypes.getName();
                  System.out.println("return type name = " + name);
      
              }
          }
      }
      
      //运行结果如下
      method name = toString
      return type name = java.lang.String
      method name = test
      return type name = void
      method name = drive
      return type name = java.lang.String
      method name = getNames
      return type name = java.util.List
      
    • getGenericReturnType();

      public class MainClass {
      
          public static void main(String[] args) {
      
              Class<Car> carClass = Car.class;
              Method[] declaredMethods = carClass.getDeclaredMethods();
              for (Method method : declaredMethods) {
                  System.out.println("method name = " + method.getName());
      
                  Type genericReturnType = method.getGenericReturnType();
                  System.out.println("genericReturnType name = " + genericReturnType.getTypeName());
      
              }
          }
      }
      //运行结果如下
      method name = toString
      genericReturnType name = java.lang.String
      method name = test
      genericReturnType name = void
      method name = drive
      genericReturnType name = java.lang.String
      method name = getNames
      genericReturnType name = java.util.List<java.lang.String>
      
Method获取修饰符
  • 通过如下方法获取

    public int getModifiers() {}
    
  • 代码实操

    public class MainClass {
    
        public static void main(String[] args) {
    
            Class<Car> carClass = Car.class;
            Method[] declaredMethods = carClass.getDeclaredMethods();
            for (Method method : declaredMethods) {
                System.out.println("method name = " + method.getName());
    
                int modifiers = method.getModifiers();
                System.out.println("modifiers name = " + Modifier.toString(modifiers));
            }
        }
    }
    //运行结果如下
    method name = toString
    modifiers name = public
    method name = test
    modifiers name = public
    method name = drive
    modifiers name = public
    method name = getNames
    modifiers name = private
    
Method获取异常类型
  • 通过如下方法获取

    public Class<?>[] getExceptionTypes() {}
    
    public Type[] getGenericExceptionTypes() {}
    
Method方法的执行

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

  • Method类型,是如下这个方法

    public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
               InvocationTargetException
        {}
    
  • Method在调用invoke()方法的时候,存在许多细节

    • invoke()方法中的第一个参数obj是Method依附的Class对应的类的实例,如果这个方法是一个静态方法,那么obj为null,第二个参数args对应的是方法的参数
    • invoke()方法返回的是Object,所以实际上执行的时候要进行强制转换。
    • 在对Method的invoke()方法调用的时候,如果方法本身会抛出异常,那么这个异常就会进过包装,由Method统一抛出InvocationTargetException。而通过InvocationTargetException.getCause()可以获取真正的异常。
  • 代码实操

    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 MainTest {
        public static void main(String[] args) {
            Class<TestMethod> testMethodClass = TestMethod.class;
    
            try {
                Method testStatic = testMethodClass.getMethod("testStatic", null);
                //测试静态方法
                testStatic.invoke(null, null);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
            TestMethod testMethod = new TestMethod();
    
            try {
                Method add = testMethodClass.getDeclaredMethod("add", int.class, int.class);
                //通过这行代码才能访问private修饰的method
                add.setAccessible(true);
                int addValue = (int) add.invoke(testMethod, 3, 4);
                System.out.println("addValue = " + addValue);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
            try {
                Method testException = testMethodClass.getMethod("testException", null);
                testException.invoke(testMethod, null);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
                System.out.println("testException occur some error,Error type is :" + e.getCause().getClass().getName());
                System.out.println("Error message is : " + e.getCause().getMessage());
            }
        }
    }
    
    // 运行结果如下
    test static
    addValue = 7
    java.lang.reflect.InvocationTargetException
      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.base/java.lang.reflect.Method.invoke(Method.java:567)
      at method_invoke.MainTest.main(MainTest.java:40)
    Caused by: java.lang.IllegalAccessException: You have some problem
      at method_invoke.TestMethod.testException(TestMethod.java:14)
      ... 5 more
    
Constructor的操控

构造器也叫做构造方法,在Java的反射机制中把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 TestClass {
    
        private String name;
    
        public TestClass(String name) {
            this.name = name;
        }
    
        public TestClass() {
            name = "zhangqi";
        }
    
        @Override
        public String toString() {
            return "TestClass{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    public class TestMain {
    
        public static void main(String[] args) {
            Class<TestClass> testClassClass = TestClass.class;
    
            //Class.newInstance() 测试
            try {
                //在Java 9 中已经标注为过时的API
                TestClass testClass = testClassClass.newInstance();
                System.out.println(testClass.toString());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
            // Constructor.newInstance() 测试
    
            try {
                Constructor<TestClass> constructor = testClassClass.getConstructor(String.class);
                TestClass dashingqi = constructor.newInstance("dashingqi");
                System.out.println("name = "+dashingqi);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    //运行结果如下
    TestClass{name='zhangqi'}
    name = TestClass{name='dashingqi'}
    

反射中的数组

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

  • public native boolean isArray();
    
  • 代码实操

    public class Arrays {
    
        private int[] array;
        private Car[] cars;
    }
    //我们定义了一个类 Arrays 内部有两个数组 array和cars 。当然array和cars是类Arrays的Field,从Field角度来说,它们是数组类型。
    //我们可以通过一些列的API来获取它们的具体信息
    
    //获取数组的元素对应的编码 同Field中的
    getName()
     //该方法是获取数组里面元素的类型
    getComponentType() array 就是 int cars就是 class operate_array.Car
    
public class MainTest {

    public static void main(String[] args) {
        Class<Arrays> arraysClass = Arrays.class;

        Field[] declaredFields = arraysClass.getDeclaredFields();
        for (Field field : declaredFields) {
            Class<?> type = field.getType();
            if (type.isArray()) {
                System.out.println("Type is " + type.getName());
                System.out.println("ComponentType type is " + type.getComponentType());
            }
        }
    }
}
//运行结果如下
Type is [I
ComponentType type is int
Type is [Loperate_array.Car;
ComponentType type is class operate_array.Car
反射中动态创建数组
  • 是通过Array.newInstance()方法创建的。

    // Array.java
    public static Object newInstance(Class<?> componentType, int... dimensions)
            throws IllegalArgumentException, NegativeArraySizeException {
            return multiNewArray(componentType, dimensions);
        }
    
    • 第一个参数表示数组内元素类型,第二个参数表示相应维度数组中的长度限制

      //比如我们呢要创建 int [][] ints = new int[2][3];
      Array.newInstance(int.class,2,3);
      
Array的读取与赋值

对于Array整体的读取赋值,把它作为一个普通的Field,调用Field中对应的方法即可。

也就是 Field中提供的 set() 和get

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


public Object get(Object obj)
           throws IllegalArgumentException,
                  IllegalAccessException;

当数组中指定位置的元素进行读取与赋值,这要涉及到Array提供的一些列 setXXX()和getXXX()方法

public static native boolean getBoolean(Object array, int index)
        throws IllegalArgumentException, ArrayIndexOutOfBoundsException;

    public static native byte getByte(Object array, int index)
        throws IllegalArgumentException, ArrayIndexOutOfBoundsException;
  • 代码操作

    public class ArrayTest {
    
        public static void main(String[] args) {
            Class<Arrays> arraysClass = Arrays.class;
    
            try {
                //Constructor 能够创建一个对象
                //类的实例对象的创建
                Arrays arrays = arraysClass.newInstance();
    
                Field fieldArray = arraysClass.getDeclaredField("array");
                fieldArray.setAccessible(true);
    
                //创建一个数组
                Object o = Array.newInstance(int.class, 3);
                Array.set(o, 0, 1);
                Array.set(o, 1, 2);
                Array.set(o, 2, 3);
                //操作Field中的set
                fieldArray.set(arrays, o);
    
                int[] array = arrays.getArray();
                for (int i = 0; i < array.length; i++) {
                    System.out.println("array index "+i+"value "+array[i]);
                }
    
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    }
    
    //运行结果如下
    array index 0value 1
    array index 1value 2
    array index 2value 3
    

反射中的枚举类Enum

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

  • 定义一个枚举类

    public enum EnumTestState {
        IDLE,
        DRIVING,
        STOPPING,
    
        test();
    
        int test1() {
            return 1;
        }
    }
    
  • 枚举跟类很相似,有自己的修饰符,方法,属性字段,还可以有构造方法。

  • 在Java的反射机制中,提供了3个特别的API用于操控枚举。

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

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

如果枚举要获取里面的Field、Method、Constructor可以调用Class通用的API

  • 代码实操

    public enum EnumTestState {
        IDLE,
        DRIVING,
        STOPPING,
    
        test();
    
        int test1() {
            return 1;
        }
    }
    
    
    public class EnumTestClass {
    
        private EnumTestState state = EnumTestState.DRIVING;
    
        public EnumTestState getState() {
            return state;
        }
    
        public void setState(EnumTestState state) {
            this.state = state;
        }
    }
    
    public class EnumTestMain {
    
        public static void main(String[] args) {
            //获取到枚举对应的Class对象
            Class<EnumTestState> enumTestStateClass = EnumTestState.class;
            //Class中有判断当前获取的Class对象是否是枚举
            if (enumTestStateClass.isEnum()) {
    
                System.out.println(Arrays.asList(enumTestStateClass.getEnumConstants()));
    
                //活到枚举中所有的Field
                Field[] declaredFields = enumTestStateClass.getDeclaredFields();
                for (Field field : declaredFields) {
                    //判断一个Field是不是一个枚举常量
                    if (field.isEnumConstant()) {
                        System.out.println("is Enum");
                    } else {
                        System.out.println("is not Enum");
                    }
                }
    
                Class<EnumTestClass> enumTestClassClass = EnumTestClass.class;
                EnumTestClass enumTestClass = new EnumTestClass();
                try {
                    //获取到名字叫做"state"的Field
                    Field state = enumTestClassClass.getDeclaredField("state");
                    state.setAccessible(true);
    
                    EnumTestState enumTestState = (EnumTestState) state.get(enumTestClass);
                    System.out.println("enumTestState = " + enumTestState);
                    state.set(enumTestClass, EnumTestState.STOPPING);
    
                    System.out.println("State current is " + enumTestClass.getState());
    
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
    
    
        }
    }
    
    //运行结果如下
    [IDLE, DRIVING, STOPPING, test]
    is Enum
    is Enum
    is Enum
    is Enum
    is not Enum
    enumTestState = DRIVING
    State current is STOPPING
    

总结

  • Java中的反射是非常规编码方式。
  • Java中的反射入口是Class文件,可通过 getClass()、.class、Class.forName()三种方式获取它。
  • 获取到Class对象之后,按照需要还能获取到Field、Constructor、Method。
  • Field的操作主要涉及到类别的获取以及数值的读取与赋值。
  • Constractor:可通过Class.newInstance()和Constructor.newInstance()创建类的对象实例,推荐后者。
  • Method算是反射的最核心的,发射都是为了调用某个Method的invoke方法。
  • 数组和枚举可以看成Class对待。

反射是非常规手段,它会抛弃Java虚拟机的很多优化,所以同样功能代码,反射要比正常方式要慢,所以考虑到采用反射时,要考虑它的时间成本。
参考文章:细说反射,Java 和 Android 开发者必须跨越的坎

知识点总结
反射机制.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342