Java-基础-反射

1. 简介

JAVA反射机制是在运行状态中

对于任意一个类,都能够知道这个类的所有属性和方法

对于任意一个对象,都能够调用它的任意一个方法和属性

这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

2. Class

Class类其实也是一个Java类,存在于JDK的java.lang包中。

java在运行时的类信息就是通过Class对象表示的。它包含了类的所有信息。

所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。

所以,当我们写的.java文件被加载到JVM的后,会在方法区生成该类的Class实例。此实例包含了该类的所有信息。

2.1 如何获取Class实例

  1. 类型.class
  2. 类实例.getClass()
  3. Class.forName(类的权限定类名)
public class Test {
   public static void main(String[] args) throws ClassNotFoundException {
      Class<Test> testClass = Test.class;
      Test test = new Test();
      Class<? extends Test> testClass1 = test.getClass();
      Class<Test> testClass2 = (Class<Test>) Class.forName("com.ldx.test.Test");
   }
}

2.2 使用Class实例

创建一个User

package com.ldx.test;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class User {
   private String name;

   private Integer age;

   public boolean isAdmin(String name) {
      if("admin".equals(name)) {
         return true;
      }
      return false;
   }
}

创建一个测试类Test

package com.ldx.test;

import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<User> userClass = User.class;
      Field[] declaredFields = userClass.getDeclaredFields();
      log.info("User类的名称:{}", userClass.getSimpleName());
      log.info("User类的权限定类名:{}", userClass.getName());
      log.info("User类的字段有:{}", Arrays.toString(declaredFields));
      log.info("User类的方法有:{}", Arrays.toString(userClass.getDeclaredMethods()));
      Method isAdmin = userClass.getMethod("isAdmin", String.class);
      log.info("User::isAdmin方法返回值为:" + isAdmin.invoke(userClass.newInstance(), "admin"));
   }
}

输出内容如下:

com.ldx.test.Test - User类的名称:User
com.ldx.test.Test - User类的权限定类名:com.ldx.test.User
com.ldx.test.Test - User类的字段有:[private java.lang.String com.ldx.test.User.name, private java.lang.Integer com.ldx.test.User.age]
com.ldx.test.Test - User类的方法有:[public boolean com.ldx.test.User.isAdmin(java.lang.String), public java.lang.Integer com.ldx.test.User.getAge(), public void com.ldx.test.User.setAge(java.lang.Integer), public java.lang.String com.ldx.test.User.toString(), public java.lang.String com.ldx.test.User.getName(), public void com.ldx.test.User.setName(java.lang.String)]
com.ldx.test.Test - User::isAdmin方法返回值为:true

2.3 常用方法摘要

返回值 方法说明
<U> Class<? extends U> asSubclass(Class<U> clazz)
强制转换该 Class 对象,以表示指定的 class 对象所表示的类的一个子类。
static Class<?> forName(String className)
返回与带有给定字符串名的类或接口相关联的 Class 对象。
<A extends Annotation>A getAnnotation(Class<A> annotationClass)
如果存在该元素的指定类型的注解,则返回这些注解,否则返回 null。
Annotation[] getAnnotations()
返回此元素上存在的所有注解。
ClassLoader getClassLoader()
返回该类的类加载器。
Class<?> getComponentType()
返回表示数组组件类型的 Class
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。
Class<?> getDeclaringClass()
如果此 Class 对象所表示的类或接口是另一个类的成员,则返回的 Class 对象表示该对象的声明类。
T[] getEnumConstants()
如果此 Class 对象不表示枚举类型,则返回枚举类的元素或 null。
Type[] getGenericInterfaces()
返回表示某些接口的 Type,这些接口由此对象所表示的类或接口直接实现。
Type getGenericSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type
Class<?>[] getInterfaces()
确定此对象所表示的类或接口实现的接口。
int getModifiers()
返回此类或接口以整数编码的 Java 语言修饰符。
String getName()
String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
String getCanonicalName()
返回 Java Language Specification 中所定义的底层类的规范化名称。主要用于输出(toString)或log打印,大多数情况下和getName()一样,但是在内部类、数组等类型的表示形式就不同了。不能用getCanonicalName()去加载类对象,必须用getName()
Package getPackage()
获取此类的包。
InputStream getResourceAsStream(String name)
查找具有给定名称的资源。
String getSimpleName()
返回源代码中给出的底层类的简称。
Class<? super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class
boolean isAnnotation()
如果此 Class 对象表示一个注解类型则返回 true。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
boolean isAnonymousClass()
当且仅当底层类是匿名类时返回 true
boolean isArray()
判定此 Class 对象是否表示一个数组类。
boolean isAssignableFrom(Class<?> cls)
判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。
boolean isEnum()
当且仅当该类声明为源代码中的枚举时返回 true。
boolean isInstance(Object obj)
如果obj是这个类的一个实例此方法返回true。
boolean isInterface()
判定指定的 Class 对象是否表示一个接口类型。
boolean isLocalClass()
当且仅当这个类是局部类此方法返回true。
boolean isMemberClass()
当且仅当底层类是成员类时返回 true
boolean isPrimitive()
判定指定的 Class 对象是否表示一个基本类型。
T newInstance()
创建此 Class 对象所表示的类的一个新实例。

3. new Instance

想通过反射创建对象大概有以下几种方式:

  1. 通过Class.newInstance()直接创建对象。
  2. 通过Class实例获取到Constructor(构造器),通过构造器创建对象。

获取构造方法的途径有以下几种:

返回值 方法说明
Constructor getConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的public构造方法。
Constructor<?>[] getConstructors()
返回所有 Constructor 对象,它反映此 Class 对象所表示的类的public构造方法。
Constructor getDeclaredConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类的public/private构造方法。
Constructor<?>[] getDeclaredConstructor()
返回所有 Constructor 对象,该对象反映此 Class 对象所表示的类的public/private构造方法。

示例代码如下:

@Getter
@Setter
@ToString
public class User {
   private String name;
   private Integer age;
   public User() {
      this.name = "张三";
      this.age = 24;
   }
   public boolean isAdmin(String name) {
      if("admin".equals(name)) {
         return true;
      }
      return false;
   }
}
@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<User> userClass = User.class;
      User user = userClass.newInstance();
      Constructor<User> constructor = userClass.getConstructor();
      User user1 = constructor.newInstance();
      Constructor<User> declaredConstructor = userClass.getDeclaredConstructor();
      // 如果构造器为private,则可以设置访问权限为true,即可newInstance
      declaredConstructor.setAccessible(true);
      User user2 = declaredConstructor.newInstance();

      log.info(user.toString());
      log.info(user1.toString());
      log.info(user2.toString());
   }
}

输出内容如下:

15:59:01.653 [main] INFO com.ldx.test.Test - User(name=张三, age=24)
15:59:01.663 [main] INFO com.ldx.test.Test - User(name=张三, age=24)
15:59:01.663 [main] INFO com.ldx.test.Test - User(name=张三, age=24)

4. Method

返回值 方法说明
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类的public方法。(可以获取父类的方法)
Method[] getMethods()
返回所有 Method 对象,它反映此 Class 对象所表示的类的public方法。(可以获取父类的方法)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类的public/private方法。(只获取当前类的方法)
Method[] getDeclaredMethods()
返回所有 Method 对象,它反映此 Class 对象所表示的类的public/private方法。(只获取当前类的方法)

示例代码如下:

@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<Man> manClass = Man.class;
      Man man = manClass.newInstance();
      Method getHairColor = manClass.getMethod("getHairColor");
      Method getHairColor1 = manClass.getDeclaredMethod("getHairColor");
      // 如果方法为private,则可以设置访问权限为true,即可newInstance
      getHairColor1.setAccessible(true);
      log.info(getHairColor.invoke(man).toString());
      log.info(getHairColor1.invoke(man).toString());
      // 可以调用到父类方法
      Method isAdmin = manClass.getMethod("isAdmin", String.class);
      log.info(isAdmin.invoke(man, "admin").toString());
      // 获取不到父类方法
      Method isAdmin1 = manClass.getDeclaredMethod("isAdmin", String.class);
      log.info(isAdmin1.invoke(man, "admin").toString());

   }
}

class Man extends User {
   public String getHairColor() {
      return "一头黑色秀发";
   }
}

输出内容如下:

16:30:49.809 [main] INFO com.ldx.test.Test - 一头黑色秀发
16:30:49.818 [main] INFO com.ldx.test.Test - 一头黑色秀发
16:30:49.818 [main] INFO com.ldx.test.Test - true
Exception in thread "main" java.lang.NoSuchMethodException: com.ldx.test.Man.isAdmin(java.lang.String)
	at java.lang.Class.getDeclaredMethod(Class.java:2130)
	at com.ldx.test.Test.main(Test.java:31)

Method类除了有上面的invoke方法,常用方法还有如下:

返回值 方法说明
<T extends Annotation>T getAnnotation(Class<T> annotationClass)
如果存在该元素的指定类型的注解,则返回这些注释,否则返回 null。
Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注解。
Class<?> getDeclaringClass()
返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象。
Type[] getGenericParameterTypes()
按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。
Type getGenericReturnType()
返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象。
int getModifiers()
以整数形式返回此 Method 对象所表示方法的 Java 语言修饰符。
String getName()
String 形式返回此 Method 对象表示的方法名称。
Annotation[][] getParameterAnnotations()
返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注解的那个数组的数组。
Class<?>[] getParameterTypes()
按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。
Class<?> getReturnType()
返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。
Object invoke(Object obj, Object... args)
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
boolean isVarArgs()
如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false
String toGenericString()
返回描述此 Method 的字符串,包括类型参数。
void setAccessible(boolean flag)
将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性。

其中:getReturnType/getGenericReturnType都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type,Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息。getParameterTypes/getGenericParameterTypes亦是如此。

其返回结果和Class.getTypeName()放回结果相同,就是输出返回值的类的name(带包名)。

public interface Type {
    default String getTypeName() {
        return toString();
    }
}

5. Field

返回值 方法说明
Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类的public属性。(可以获取父类的方法)
Field[] getFields()
返回所有Field属性,它反映此 Class 对象所表示的类的public属性。(可以获取父类的方法)
Field getDeclaredField(String name)
返回一个 Field 对象,该对象反映此 Class 对象所表示的类的public/private属性。(只获取当前类的方法)
Field[] getDeclaredFields()
返回所有Field属性,该对象反映此 Class 对象所表示的类的public/private属性。(只获取当前类的方法)

示例代码如下:

@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<Man> manClass = Man.class;
      Man man = manClass.newInstance();
      Field hair = manClass.getDeclaredField("hair");
      hair.setAccessible(true);
      hair.set(man, "浓密的黑发");
      log.info(hair.get(man).toString());
      log.info(man.getHair());
      Class<?> type = hair.getType();
      Class<?> declaringClass = hair.getDeclaringClass();
      log.info(type.getCanonicalName());
      log.info(declaringClass.getCanonicalName());
   }
}

@Getter
class Man extends User {
   private String hair;
}

输出内容如下:

17:13:17.903 [main] INFO com.ldx.test.Test - 浓密的黑发
17:13:17.909 [main] INFO com.ldx.test.Test - 浓密的黑发
17:13:17.909 [main] INFO com.ldx.test.Test - java.lang.String
17:13:17.909 [main] INFO com.ldx.test.Test - com.ldx.test.Man

Field类除了有上面写到的方法,常用方法还有如下:

返回值 方法说明
Object get(Object obj)
返回指定对象上此 Field 表示的字段的值。
<T extends Annotation>T getAnnotation(Class<T> annotationClass)
如果存在该元素的指定类型的注解,则返回这些注释,否则返回 null。
boolean getBoolean(Object obj)
获取一个静态或实例 boolean 字段的值。
byte getByte(Object obj)
获取一个静态或实例 byte 字段的值。
char getChar(Object obj)
获取 char 类型或另一个通过扩展转换可以转换为 char 类型的基本类型的静态或实例字段的值。
Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注解。
Class<?> getDeclaringClass()
返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段。
double getDouble(Object obj)
获取 double 类型或另一个通过扩展转换可以转换为 double 类型的基本类型的静态或实例字段的值。
float getFloat(Object obj)
获取 float 类型或另一个通过扩展转换可以转换为 float 类型的基本类型的静态或实例字段的值。
Type getGenericType()
返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型。
int getInt(Object obj)
获取 int 类型或另一个通过扩展转换可以转换为 int 类型的基本类型的静态或实例字段的值。
long getLong(Object obj)
获取 long 类型或另一个通过扩展转换可以转换为 long 类型的基本类型的静态或实例字段的值。
int getModifiers()
以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符。
String getName()
返回此 Field 对象表示的字段的名称。
short getShort(Object obj)获取 short 类型或另一个通过扩展转换可以转换为 short 类型的基本类型的静态或实例字段的值。
Class<?> getType()
返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
boolean isEnumConstant()
如果此字段表示枚举类型的元素,则返回 true;否则返回 false
void set(Object obj, Object value)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
void setBoolean(Object obj, boolean z)
将字段的值设置为指定对象上的一个 boolean 值。
void setByte(Object obj, byte b)
将字段的值设置为指定对象上的一个 byte 值。
void setChar(Object obj, char c)
将字段的值设置为指定对象上的一个 char 值。
void setDouble(Object obj, double d)
将字段的值设置为指定对象上的一个 double 值。
void setFloat(Object obj, float f)
将字段的值设置为指定对象上的一个 float 值。
void setInt(Object obj, int i)
将字段的值设置为指定对象上的一个 int 值。
void setLong(Object obj, long l)
将字段的值设置为指定对象上的一个 long 值。
void setShort(Object obj, short s)
将字段的值设置为指定对象上的一个 short 值。
String toGenericString()
返回一个描述此 Field(包括其一般类型)的字符串。
void setAccessible(boolean flag)
将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性。

6. 反射机制的执行流程

posted @ 2021-09-25 17:54  张铁牛  阅读(322)  评论(0编辑  收藏  举报