首页 > 技术文章 > Java反射机制

lgh544 2020-05-13 16:42 原文

一、反射概念

  Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法加载完类之后在堆内存的方法区中就产生了一个Class类型的对象一个类只有一个Class对象这个对象就包含了完整的类的结构信息我们以通过这个对象看到类的结构这个对象就像一面镜子透过这个镜子看到类的结构所以我们形象的称之为:反射

  通过下面的代码初步理解反射机制,通过这个大概理解反射机制,不需要每行代码都理解,下文会细讲。创建了一个Person类。

 //反射之前,对于Person的操作
    @Test
    public void test1() {

        //1.创建Person类的对象
        Person p1 = new Person("Tom", 12);

        //2.通过对象,调用其内部的属性、方法
        p1.age = 10;
        System.out.println(p1.toString());

        p1.show();

        //在Person类外部,不可以通过Person类的对象调用其内部私有结构。
        //比如:name、showNation()以及私有的构造器
    }

    //反射之后,对于Person的操作
    @Test
    public void test2() throws Exception{
        Class clazz = Person.class;
        //1.通过反射,创建Person类的对象
        Constructor cons = clazz.getConstructor(String.class,int.class);
        Object obj = cons.newInstance("Tom", 12);
        Person p = (Person) obj;
        System.out.println(p.toString());
        //2.通过反射,调用对象指定的属性、方法
        //调用属性
        Field age = clazz.getDeclaredField("age");
        age.set(p,10);
        System.out.println(p.toString());

        //调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(p);

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

        //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
        //调用私有的构造器
        Constructor cons1 = clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true);
        Person p1 = (Person) cons1.newInstance("Jerry");
        System.out.println(p1);

        //调用私有的属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1,"HanMeimei");
        System.out.println(p1);

        //调用私有的方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        String nation = (String) showNation.invoke(p1,"中国");//相当于String nation = p1.showNation("中国")
        System.out.println(nation);

    }

 

Person 类

package com.atguigu.java1;

/**
 * @author shkstart
 * @create 2019 下午 3:12
 */
@MyAnnotation(value="hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{

    private String name;
    int age;
    public int id;

    public Person(){}

    @MyAnnotation(value="abc")
    private Person(String name){
        this.name = name;
    }

     Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    @MyAnnotation
    private String show(String nation){
        System.out.println("我的国籍是:" + nation);
        return nation;
    }

    public String display(String interests,int age) throws NullPointerException,ClassCastException{
        return interests + age;
    }


    @Override
    public void info() {
        System.out.println("我是一个人");
    }

    @Override
    public int compareTo(String o) {
        return 0;
    }

    private static void showDesc(){
        System.out.println("我是一个可爱的人");
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}

通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
  建议:直接new的方式。什么时候会使用反射的方式呢?由于反射的特征是动态性,需要用到动态性的时候可以用反射。

从上述代码可以大概了解到Java反射机制提供的功能有

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判断任意一个类所具有的成员变量和方法
  4. 在运行时获取泛型信息
  5. 在运行时调用任意一个对象的成员变量和方法
  6. 运行时处理注解
  7. 生成动态代理

反射相关的主要API

  1. java.lang.Class:代表一
  2. java.lang.reflect.Method:代表类方法
  3. java.lang.reflect.Field:代表类的成员变量
  4. java.lang.reflect.Constructor:代表类构造

二、 Class类的理解
1.类的加载过程:程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

Class类常用的方法

 三、获取Class实例的几种方式:(前三种方式需要掌握)

//方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

        //方式三:调用Class的静态方法:forName(String classPath)
        Class clazz3 = Class.forName("com.atguigu.java.Person");
//        clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);

        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);

        //方式四:使用类的加载器:ClassLoader  (了解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
        System.out.println(clazz4);

        System.out.println(clazz1 == clazz4);

有Class对象的

  1class部类成员(成员内部类静态内部类)局部内部类匿名内部类

  2interface接口

  (3[]:数组

  (4enum:枚举

  (5annotation:注解@interface

  (6primitivetype:基本数据类型

  7void

 

 四创建运行时类的对象

  创建类的对象:调用Class对象的newInstance()方法

求:

  1)类必须有一个无参数的构造器。

  2)类的构造器的访问权限需要足够。

 

代码举例
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
System.out.println(obj);

 难道没有无参的构造器就不能创建对象了吗?不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。

步骤如下:

  1)通过Class类的getDeclaredConstructor(Class ... parameterTypes)取得本类的指定形参类型的构造器

  2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。

  3)通过Constructor实例化对象。

//1.获取对应的Class对象
Class clazz= Class.forName(Person);

//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getDeclaredConstructor(String.class,Integer.class);

//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2)

五、获取运行时类的完整结构

  我们可以通过反射,获取对应的运行时类中所有的属性(Field)、方法(Method)、构造器(Constructor)、父类(Superclass)、接口(Interface)、父类的泛型、包(package)、注解(Annotation)、异常等。。。。

  获取全部的属性:

  Field:public Field[] getFields()  public Field[] getDeclaredFields()

public void test1(){

    Class clazz = Person.class;

    //获取属性结构
    //getFields():获取当前运行时类及其父类中声明public访问权限的属性
    Field[] fields = clazz.getFields();
for(Field f : fields){
        System.out.println(f);
    }
    System.out.println();

    //getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
    Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
        System.out.println(f);
    }
}

Field方法中:

  public int getModifiers() 以整数形式返回此Field的修饰符

  public Class<?> getType() 得到Field的属性类型

  public String getName() 返回Field的名称。

 

  获取全部的方法(Method)

  public Method[] getDeclaredMethods()   public Method[] getMethods()

@Test
public void test1(){

    Class clazz = Person.class;

    //getMethods():获取当前运行时类及其所父类中声明为public权限的方法
    Method[] methods = clazz.getMethods();
for(Method m : methods){
        System.out.println(m);
    }
    System.out.println();
    //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法
    Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
        System.out.println(m);
    }
}

Method类中:

  public Class<?> getReturnType()取得全部的返回值

  public Class<?>[] getParameterTypes()取得全部的参数

  public intgetModifiers()取得修饰符

  public Class<?>[] getExceptionTypes()取得异常信息

 

  获取全部的构造器(Constructor)

  public Constructor<T>[] getConstructors()  public Constructor<T>[] getDeclaredConstructors()

@Test
    public void test1(){

        Class clazz = Person.class;
        //getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
for(Constructor c : constructors){
            System.out.println(c);
        }

        System.out.println();
        //getDeclaredConstructors():获取当前运行时类中声明的所的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors){ System.out.println(c); } }

Constructor类中:

  取得修饰符:public intgetModifiers();

  取得方法名称: public String getName();

  取得参数的类型:public Class<?>[] getParameterTypes();

  全部的父类(Superclass)public Class<? Super T> getSuperclass()

 @Test
    public void test2(){
        Class clazz = Person.class;

        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
    }

 
  获取全部的接口(Interface)

  public Class<?>[] getInterfaces()

 @Test
    public void test5(){
        Class clazz = Person.class;
        Class[] interfaces = clazz.getInterfaces();
        for(Class c : interfaces){
            System.out.println(c);
        }

        System.out.println();
        //获取运行时类的父类实现的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for(Class c : interfaces1){
            System.out.println(c);
        }

    }

 

  获取泛型

  获取父类泛型类型:Type getGenericSuperclass()

  泛型类型:ParameterizedType

  获取实际的泛型类型参数数组:getActualTypeArguments()

/*
    获取运行时类的带泛型的父类

     */
    @Test
    public void test3(){
        Class clazz = Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }

    /*
    获取运行时类的带泛型的父类的泛型

    代码:逻辑性代码  vs 功能性代码
     */
    @Test
    public void test4(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;

//获取泛型类型 Type[] actualTypeArguments = paramType.getActualTypeArguments(); // System.out.println(actualTypeArguments[0].getTypeName()); System.out.println(((Class)actualTypeArguments[0]).getName()); }

 

  获取包和注解

/*
        获取运行时类所在的包

     */
    @Test
    public void test6(){
        Class clazz = Person.class;
        Package pack = clazz.getPackage();
        System.out.println(pack);
    }

    /*
        获取运行时类声明的注解

     */
    @Test
    public void test7(){
        Class clazz = Person.class;

        Annotation[] annotations = clazz.getAnnotations();for(Annotation annos : annotations){
            System.out.println(annos);
        }
    }

六、调用运行类的指定结构

  调用指定方法

通过反射,调用类中的方法,通过Method类完成。

  步骤:1.通过Class类的getMethod(String name,Class...parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。

       2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。

  说明:1.Object 对应原方法的返回值,若原方法无返回值,此时返回null

     2.若原方法若为静态方法,此时形参Object obj可为null

     3.若原方法形参列表为空,则Object[] argsnull

            4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

@Test
    public void testMethod() throws Exception {
        Class clazz = Person.class;
        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();
        /*
        1.获取指定的某个方法
        getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
         */   Method show = clazz.getDeclaredMethod("show", String.class);//2.保证当前方法是可访问的
        show.setAccessible(true);
        /*
        3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
        invoke()的返回值即为对应类中调用的方法的返回值。
         */  Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
        System.out.println(returnValue);

        System.out.println("*************如何调用静态方法*****************");

        // private static void showDesc() 无参
  Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
//如果调用的运行时类中的方法没返回值,则此invoke()返回null
//        Object returnVal = showDesc.invoke(null);
        Object returnVal = showDesc.invoke(Person.class);
        System.out.println(returnVal);//null

    }

 

  调用指定属性

  在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()get()方法就可以完成设置和取得属性内容的操作。

  public Field getField(String name) 返回此Class对象表示的类或接口的指定的publicField

  public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field

Field中:

  public Object get(Object obj) 取得指定对象obj上此Field的属性内容

  public void set(Object obj,Objectvalue) 设置指定对象obj上此Field的属性内容

@Test
public void testField1() throws Exception {
    Class clazz = Person.class;
    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();
    //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
    Field name = clazz.getDeclaredField("name");

    //2.保证当前属性是可访问的
    name.setAccessible(true);
    //3.获取、设置指定对象的此属性值
    name.set(p,"Tom");

    System.out.println(name.get(p));
}

 

关于setAccessible方法的使用

  1. MethodFieldConstructor对象都有setAccessible()方法
  2. setAccessible启动和禁用访问安全检查的开关
  3. 参数true则指示反射的对象在使用时应该取消Java语言访问检查
  4. 提高反射的效率如果代码中必须用反射而该句代码需要频繁的被调用那么请设置为true
  5. 使得原本无法访问的私有成员也可以访问参数false则指示反射的对象应该实施Java语言访问检查

七、动态性

  下面代码中,在0、1、2三个数中随机生成一个数,不同的数代表不同的类,由于是随机的,事先不知道需要创建的对象是谁,此时就需要使用到反射机制,在运行时根据产生的随机数再去创建相应的对象,体现出了反射机制的特征:动态性。

//体会反射的动态性
@Test
public void test2(){

    for(int i = 0;i < 100;i++){
        int num = new Random().nextInt(3);//0,1,2
        String classPath = "";
        switch(num){
            case 0:
                classPath = "java.util.Date";
                break;
            case 1:
                classPath = "java.lang.Object";
                break;
            case 2:
                classPath = "com.atguigu.java.Person";
                break;
        }

        try {
            Object obj = getInstance(classPath);
            System.out.println(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

/*
创建一个指定类的对象。
classPath:指定类的全类名
 */
public Object getInstance(String classPath) throws Exception {
   Class clazz =  Class.forName(classPath);
   return clazz.newInstance();
}

 

注:尚硅谷学习笔记

 

 

 

 

 

 

 

 

 

 
反射之后,对于Person的操作
[Fǎnshè zhīhòu, duìyú Person de cāozuò]
After reflection, the operation for Person

 

//体会反射的动态性 @Test public void test2(){     for(int i = 0;i < 100;i++){         int num = new Random().nextInt(3);//0,1,2         String classPath = "";         switch(num){             case 0:                 classPath = "java.util.Date";                 break;             case 1:                 classPath = "java.lang.Object";                 break;             case 2:                 classPath = "com.atguigu.java.Person";                 break;         }         try {             Object obj = getInstance(classPath);             System.out.println(obj);         } catch (Exception e) {             e.printStackTrace();         }     } } /* 创建一个指定类的对象。 classPath:指定类的全类名  */ public Object getInstance(String classPath) throws Exception {    Class clazz = Class.forName(classPath);    return clazz.newInstance(); }
[//Tǐhuì fǎnshè de dòngtài xìng @Test public void test2(){ for(int i = 0;i < 100;i++){ int num = new Random().NextInt(3);//0,1,2 String classPath =""; switch(num){ case 0: ClassPath ="java.Util.Date"; break; case 1: ClassPath ="java.Lang.Object"; break; case 2: ClassPath ="com.Atguigu.Java.Person"; break; } try { Object obj = getInstance(classPath); System.Out.Println(obj); } catch (Exception e) { e.PrintStackTrace(); } } } /* chuàngjiàn yīgè zhǐdìng lèi de duìxiàng. ClassPath: Zhǐdìng lèi de quán lèi míng */ public Object getInstance(String classPath) throws Exception { Class clazz = Class.ForName(classPath); return clazz.NewInstance(); }]
Experience dynamic reflected //
@Test
public void test2 () {

    for (int i = 0; i <100; i ++) {
        int num = new Random () nextInt (3);. // 0,1,2
        String classPath = "";
        switch (num) {
            case 0:
                classPath = "java.util.Date";
                break;
            case 1:
                classPath = "java.lang.Object";
                break;
            case 2:
                classPath = "com.atguigu.java.Person";
                break;
        }

        try {
            Object obj = getInstance (classPath);
            System.out.println (obj);
        } Catch (Exception e) {
            e.printStackTrace ();
        }
    }

}

/ *
Create an object of a specified class.
classPath: Full class name of the specified class
 * /
public Object getInstance (String classPath) throws Exception {
   Class clazz = Class.forName (classPath);
   return clazz.newInstance ();
}

推荐阅读