Java反射基础入门,一篇就够啦

Java基础之反射

1 前言

你是否还在为不懂Java反射而苦恼?
是否因为面试,不能熟练地回答反射相关的知识而感到无奈?
是否因为不懂反射,看起框架源码,不能够得心应手?

这里,将带你入门,Java反射的基础知识。

Let’s go!

2 反射的基本概念

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。 百度百科

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力, 并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。Java中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接。反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需注意的是:如果使用不当,反射的成本很高

直白一点来说,JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够使用对象中任意一个方法和属性,这种动态获取信息以及动态调用对象的方法的功能,就可以称之为Java语言的反射机制

3 reflection的工作机制

程序运行时,java系统会一直对所有对象进行所谓的运行时类型识别,这项信息记录了每个对象所属的类。通过专门的类可以访问这些信息。用来保存这些信息的类是class类,class类为编写可动态操纵的java代码程序提供了强大功能。

4 构造Class对象方式

构造Class对象有以下三种方式:

  • Class.forName
  • 通过Class类的静态方法,类名.class
  • Object.getClass()

代码举例:

扫描二维码关注公众号,回复: 11232959 查看本文章
public static void getMyClass() {
        try {
            // 构造Class对象的三种方式:
            // 1、Class.forName(注意:必须是完整的类名(也就是包名+类名格式)(类的全限定名称))
            Class clazzOne = Class.forName("cn.smilehappiness.reflect.Student");
            // 创建类对象第一种方式
            // 获取class类型之后,可以通过newInstance()创建该类型的对象(实际上就是调用的类中的默认的无参数的构造方法)
            Object obj = clazzOne.newInstance();
            System.out.println("clazzOne:" + clazzOne);//clazzOne:class cn.smilehappiness.reflect.Student
            System.out.println("clazzOne:" + obj.toString());//clazzOne:cn.smilehappiness.reflect.Student@445b84c0

            // 2、通过Class类的静态方法,类名.class
            // 获取类对象的第二种方式
            Class clazzTwo = Student.class;
            System.out.println("clazzTwo:" + clazzTwo);// clazzTwo:class cn.smilehappiness.reflect.Student

            // 3、Object.getClass()
            // 获取类对象的第三种方式
            Student student = new Student();
            Class clazzThree = student.getClass();
            System.out.println("clazzThree:" + clazzThree);// clazzThree:class cn.smilehappiness.reflect.Student
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }

5 Java反射机制操作的基础套路

Java反射中的主要类和方法:
Class, Method, Field, Constructor

主要的包:java.lang.reflect

以下反射机制的测试示例,都是基于该Student基础测试类

public class Student {
    private int id;
    private String name;
    private Date birthday;
    private String birthdayStr;

    private Student(String name) {
        this.name = name;
    }

    private Student(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    private String getBirthdayDateStr(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String strDate = sdf.format(date);
        return strDate;
    }

    private Date getBirthdayDate(String date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date dateFormat = sdf.parse(date);
        return dateFormat;
    }

    public int getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public Date getBirthday() {
        return this.birthday;
    }

    public String getBirthdayStr() {
        return this.birthdayStr;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public void setBirthdayStr(String birthdayStr) {
        this.birthdayStr = birthdayStr;
    }

    public Student(int id, String name, Date birthday, String birthdayStr) {
        this.id = id;
        this.name = name;
        this.birthday = birthday;
        this.birthdayStr = birthdayStr;
    }

    public Student() {
    }

    public String toString() {
        return "Student(id=" + this.getId() + ", name=" + this.getName() + ", birthday=" + this.getBirthday() + ", birthdayStr=" + this.getBirthdayStr() + ")";
    }
}

5.1 Java中的类反射

Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”或“自省”,并能直接操作程序的内部属性。Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。

Java类反射的基础应用:

  • 通过指定类名获取类的Class对象
  • 通过Class获取类的相关信息(包名修饰符类名基类实现的接口

代码举例如下:

public static void getClassBaseInfo() {
    try {
         // 通过反射机制,获取类的基本信息,类基本信息包括:包名,修饰符,类名,基类,实现的接口
         // 1、获取指定类名类的Class
         // (1)通过String指定要获取哪个类的Class 注意:必须是完整的类名(也就是包名+类名格式)
         String className = "cn.smilehappiness.reflect.Student";
         // (2)获取指定类名的Class,也就是获取到了cn.smilehappiness.reflect.Student的Class
         Class clazz = Class.forName(className);

         // 2、通过Class获取类的相关信息
         // 获取包名
         System.out.println("包名信息:" + clazz.getPackage().getName());// cn.smilehappiness.reflect

         // 3、获取修饰符信息
         int modifiers = clazz.getModifiers();
         System.out.println("修饰符:" + Modifier.toString(modifiers));// public
         // final

         // 4、获取类名信息
         System.out.println("获取完整类名:" + clazz.getName());// cn.smilehappiness.reflect.Student
         System.out.println("获取简单类名:" + clazz.getSimpleName());// Student

         // 5、获取基类信息
         System.out.println("获取基类信息:" + clazz.getSuperclass().getName());// java.lang.Object

         // 6、获取实现的接口信息(一个类可以实现多个接口)
         Class[] interfaces = clazz.getInterfaces();
         if (interfaces.length > 0) {
             System.out.print("实现的接口信息:");// java.io.Serializable,java.lang.Comparable,java.lang.CharSequence,
             for (Class interfaceClass : interfaces) {
                 System.out.print(interfaceClass.getName() + ",");
             }
         }

     } catch (Exception e) {
         // TODO: handle exception
         e.printStackTrace();
     }
 }

执行结果如下:

包名信息:cn.smilehappiness.reflect
修饰符:public
获取完整类名:cn.smilehappiness.reflect.Student
获取简单类名:Student
获取基类信息:java.lang.Object

5.2 Java反射机制获取类中构造方法的信息

在java中把构造方法封装成Constructor类,通过反射机制获取类中构造方法的信息。

Java类反射的基础应用:

  • 通过指定类名获取类的Class对象
  • 通过Class获取构造方法的相关信息(修饰符方法名称参数列表抛出的异常

代码举例如下:

public static void getConstructorInfo() {
     try {
         // 1、获取指定类名类的Class
         // (1)通过String指定要获取哪个类的Class 注意:必须是完整的类名(也就是包名+类名格式)
         String className = "cn.smilehappiness.reflect.Student";
         // (2)获取指定类名的Class,也就是获取到了cn.smilehappiness.reflect.Student的Class
         Class clazz = Class.forName(className);

         // 2、通过Class获取类的相关的信息
         // 一个类中可以有多个构造方法
         // a、获取类对象的公共构造方法(claz.getConstructors()获取的是类中的所有的公开的构造方法)
         //Constructor[] clazzConstructors = clazz.getConstructors();
         // b、获取类对象的所有构造方法(claz.getDeclaredConstructors()获取类中声明的构造方法的数组,包括公开的,保护的,缺省的,私有的构造方法)
         Constructor[] clazzConstructors = clazz.getDeclaredConstructors();

         for (Constructor constructor : clazzConstructors) {
             // 获取构造方法的修饰符信息
             System.out.print(Modifier.toString(constructor.getModifiers()) + " ");//public

             // 获取构造方法的名称信息
             System.out.print(constructor.getName() + " ");//cn.smilehappiness.reflect.Student

             // 获取构造方法的参数列表信息
             StringBuilder paramStr = new StringBuilder("(");
             Class[] classParameterTypes = constructor.getParameterTypes();
             for (int i = 0; i < classParameterTypes.length; i++) {
                 Class classParameter = classParameterTypes[i];
                 if (i == classParameterTypes.length - 1) {
                     paramStr.append(classParameter.getName());
                     continue;
                 }

                 paramStr.append(StringUtils.join(classParameter.getName(), ", "));
             }

             paramStr.append(")");
             System.out.print(paramStr.toString());//(int,java.lang.String,java.util.Date)

             // 获取构造方法抛出的异常信息,一个构造方法可以抛出多个异常
             Class[] classExceptionTypes = constructor.getExceptionTypes();
             if (classExceptionTypes.length > 0) {
                 System.out.print(" throws ");
                 for (Class classException : classExceptionTypes) {
                     //这种,打印的信息最后面会多个逗号,这里不做处理,重点不在这里
                     System.out.print(StringUtils.join(classException.getName(), ","));
                 }
             }

             // 换行
             System.out.println();
         }

     } catch (Exception e) {
         // TODO: handle exception
         e.printStackTrace();
     }
     
 }

执行结果如下:

public cn.smilehappiness.reflect.Student (int, java.lang.String, java.util.Date, java.lang.String)
public cn.smilehappiness.reflect.Student ()
private cn.smilehappiness.reflect.Student (java.lang.String, java.util.Date)
private cn.smilehappiness.reflect.Student (java.lang.String)

5.3 获取类中方法的信息

在java中,把方法封装成Method类,通过反射机制获取类中方法的信息。

Java类反射的基础应用:

  • 通过指定类名获取类的Class对象
  • 通过Class获取方法的相关信息(修饰符返回类型方法名称参数列表抛出的异常

代码举例如下:

public static void getMethodInfo() {
    try {
         // 1、获取指定类名类的Class
         // (1)通过String指定要获取哪个类的Class 注意:必须是完整的类名(也就是包名+类名格式)
         String className = "cn.smilehappiness.reflect.Student";
         // (2)获取指定类名的Class,也就是获取到了cn.smilehappiness.reflect.Student的Class
         Class clazz = Class.forName(className);

         // 2、通过Class获取类的相关的信息
         // 一个类中可以有多个方法
         // clazz.getMethods()获取的是类中的所有的公开的方法,包括从基类继承下来的公开的方法
         // Method[] methods = clazz.getMethods();

         // clazz.getDeclaredMethods() 获取类中声明的方法的相关信息:包括类中声明的公开的,保护的,缺省的,私有的方法。但是,不包含继承的方法
         Method[] methods = clazz.getDeclaredMethods();

         for (Method method : methods) {
             // 获取方法的修饰符信息
             System.out.print(Modifier.toString(method.getModifiers()) + " ");

             // 获取方法的返回类型信息
             System.out.print(method.getReturnType().getName() + " ");

             // 获取方法的名称信息
             System.out.print(StringUtils.join(method.getName(), " "));

             // 获取方法的参数列表信息
             StringBuilder sb = new StringBuilder();
             sb.append("(");
             Class[] classMethodParameterTypes = method.getParameterTypes();
             for (int i = 0; i < classMethodParameterTypes.length; i++) {
                 Class classParameter = classMethodParameterTypes[i];
                 if (i == classMethodParameterTypes.length - 1) {
                     sb.append(classParameter.getName());
                     continue;
                 }

                 sb.append(StringUtils.join(classParameter.getName(), ", "));
             }

             sb.append(")");
             System.out.print(sb.toString());//(int,java.lang.String,java.util.Date)

             // 获取方法抛出的异常信息,一个方法可以抛出多个异常
             Class[] classExceptionTypes = method.getExceptionTypes();
             if (classExceptionTypes.length > 0) {
                 System.out.print(" throws ");
                 for (Class classException : classExceptionTypes) {
                     //这种,打印的信息最后面会多个逗号,这里不做处理,重点不在这里
                     System.out.print(StringUtils.join(classException.getName(), ","));
                 }
             }

             // 换行
             System.out.println();
         }

     } catch (Exception e) {
         // TODO: handle exception
         e.printStackTrace();
     }

 }

执行结果如下:

public java.lang.String toString ()
public java.lang.String getName ()
public int getId ()
public void setName (java.lang.String)
private java.lang.String getBirthdayDateStr (java.util.Date)
public void setBirthday (java.util.Date)
public void setBirthdayStr (java.lang.String)
public java.lang.String getBirthdayStr ()
private java.util.Date getBirthdayDate (java.lang.String) throws java.text.ParseException,
public java.util.Date getBirthday ()
public void setId (int)

6 Java反射的简单应用

6.1 通过反射机制创建对象

场景:假如一个类对象是单例的,构造方法进行了私有化,这时候普通的方式,就不可能创建这个对象了,这时候,反射就可以派上用场了,实际中基本上不会这么使用。

大致思路:

  • 获取指定类名类的Class
  • 通过Class获取类的指定的私有的构造方法
  • 通过指定的私有的构造方法来创建对象

代码示例:

public static Object createObject() {

        Object obj = null;
        try {
            // 1、获取指定类名类的Class
            // (1)通过String指定要获取哪个类的Class 注意:必须是完整的类名(也就是包名+类名格式)
            String className = "cn.smilehappiness.reflect.Student";
            // (2)获取指定类名的Class,也就是获取到了cn.smilehappiness.reflect.Student的Class
            Class clazz = Class.forName(className);

            // ////////////////////////////////////////////////////////////////////////
            // 2、通过指定的私有的构造方法来创建对象
            // 通过这个参数数组来指明构造方法的参数列表,为了说明要获取哪个构造方法
            Class[] classParams = {String.class};
            //获取指定参数类型的构造方法
            Constructor constructor = clazz.getDeclaredConstructor(classParams);
            // 设置构造方法可以访问
            constructor.setAccessible(true);

            // 3.通过指定的私有的构造方法来创建对象
            // constructor.newInstance()方法public T newInstance(Object ... initargs)的参数是一个可变参数,即是一个Object[],也就是调用方法时的实参列表
            Object[] paramObjs = {"张3"};
            //通过构造方法对象,创建实际的对象
            //Object obj = constructor.newInstance("zhangsan"); // 等价于new Student("zhangsan");
            obj = constructor.newInstance(paramObjs);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return obj;
    }

    public static void main(String[] args) {
        Object obj = createObject();
        System.out.println(obj);
    }

执行结果如下:

Student(id=0, name=张3, birthday=null)

这个结果说明,使用Student对象的私有构造方法,成功的创建了实例对象。

6.2 通过反射机制调用方法

大致思路:

  • 获取指定类名类的Class
  • 通过Class获取类的指定的私有的方法
  • 通过指定的私有的方法来调用类中方法

代码示例:

public static Object createObject() {
  Object obj = null;
   try {
       // 1、获取指定类名类的Class
       // (1)通过String指定要获取哪个类的Class 注意:必须是完整的类名(也就是包名+类名格式)
       String className = "cn.smilehappiness.reflect.Student";
       // (2)获取指定类名的Class,也就是获取到了cn.smilehappiness.reflect.Student的Class
       Class clazz = Class.forName(className);

       // ////////////////////////////////////////////////////////////////////////
       // 2、通过指定的私有的构造方法来创建对象
       // 通过这个参数数组来指明构造方法的参数列表,为了说明要获取哪个构造方法
       Class[] classParams = {String.class, Date.class};
       //获取指定参数类型的构造方法
       Constructor constructor = clazz.getDeclaredConstructor(classParams);
       // 设置构造方法可以访问
       constructor.setAccessible(true);

       // 3.通过指定的私有的构造方法来创建对象
       Object[] paramObjs = {"李四", new Date()};
       //通过构造方法对象,创建实际的对象
       obj = constructor.newInstance(paramObjs);

   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   } catch (NoSuchMethodException e) {
       e.printStackTrace();
   } catch (InstantiationException e) {
       e.printStackTrace();
   } catch (IllegalAccessException e) {
       e.printStackTrace();
   } catch (InvocationTargetException e) {
       e.printStackTrace();
   }

   return obj;
}

public static void invokeMethod() {
    try {
        //通过反射,创建一个Student对象(Student(id=0, name=李四, birthday=Mon May 04 15:12:17 CST 2020, birthdayStr=null)),这里只是为了准备一些测试数据
        Student student = (Student) createObject();

        // 1、获取指定类名类的Class
        // (1)通过String指定要获取哪个类的Class 注意:必须是完整的类名(也就是包名+类名格式)
        String className = "cn.smilehappiness.reflect.Student";
        // (2)获取指定类名的Class,也就是获取到了cn.smilehappiness.reflect.Student的Class
        Class clazz = Class.forName(className);

        // 2、通过Class获取类的指定的私有的方法
        // 指明获取的是类中Date类型参数的getBirthdayDateStr()
        Method method = clazz.getDeclaredMethod("getBirthdayDateStr", Date.class);

        //设置方法可以访问
        method.setAccessible(true);

        // 3.通过指定的私有的方法来调用类中方法(如果不使用反射,对于私有构造器的对象,拿不到对象,就不可能调用私有方法)
        // method.invoke()方法的第一个参数是一个Object,也就是要提前准备好一个Student的实例
        // method.invoke()方法的第二个参数是Object[],getBirthdayDateStr()方法的实参列表
        // getBirthdayDateStr()方法是有返回值的,所以method.invoke()也是有返回结果的,返回的是一个Object
        Object res = method.invoke(student, student.getBirthday());
        System.out.println(res);//拿到了返回的结果:2020-05-04

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

}

执行结果如下:

2020-05-04

6.3 通过反射机制获取并改变类中的属性

大致思路:

  • 获取指定类名类的Class
  • 通过Class获取类中相关的Field
  • 通过Field改变类中的属性

代码示例:

 public static void changeField() {
   try {
        //通过反射,创建一个Student对象(Student(id=0, name=李四, birthday=Mon May 04 15:12:17 CST 2020, birthdayStr=null)),这里只是为了准备一些测试数据
        Object obj = createObject();
        Class clazz = obj.getClass();

        // 通过Class获取类中相关的Field
        Field nameField = clazz.getDeclaredField("name");
        Field birthdayField = clazz.getDeclaredField("birthday");

        // 设置私有的属性是可以访问的
        nameField.setAccessible(true);
        birthdayField.setAccessible(true);

        // 通过Field改变对象上面的属性
        nameField.set(obj, "赵六");
        birthdayField.set(obj, DateUtils.addDays(new Date(), 1));

        // 通过Field获取对象上面的属性
        String name = (String) nameField.get(obj);
        Date birthday = (Date) birthdayField.get(obj);

        System.out.println(name + "," + birthday);//赵六,Tue May 05 15:39:49 CST 2020
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

}

执行结果如下:

赵六,Tue May 05 15:41:19 CST 2020

7 反射和安全性

在处理反射时安全性是一个较复杂的问题。反射经常在框架中大量使用,由于这一点,我们可能希望框架能够全面接入代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,例如当代码在不值得信任的共享的环境中运行时。

8 反射的缺点

反射是一种强大的工具,但也存在一些不足。

  • 性能问题。

    使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。

  • 使用反射会模糊程序内部实际要发生的事情。

    程序人员希望在源代码中看到程序的逻辑,反射技术绕过了源代码会带来维护问题。反射代码比相应的直接代码更复杂,更难看懂逻辑解决这些问题的最佳方案是保守地使用反射,仅在它可以真正增加灵活性的地方,记录其在目标类中的使用

创作不易,该博客耗时大半天才整理好,如果这篇文章对你有所帮助,欢迎老铁们给个赞,您的支持,将是博主创作的最大动力!

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!

猜你喜欢

转载自blog.csdn.net/smilehappiness/article/details/105913353