java中的反射及Class对象常用方法介绍

本文详细介绍了Java中的反射机制,特别是Class对象的常用方法,如isAssignableFrom()、asSubclass()、cast()等,以及Method对象的getDeclaredAnnotations()、getDefaultValue()、invoke()等。反射允许程序在运行时检查类的属性、方法和构造器,实现动态调用和类型转换。此外,还提到了Constructor和Field的相关方法,强调反射在理解和操作Java对象中的重要作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍

有一个有意思的小实验,有一盆水,在盆中放上镜子,放到阳光下,镜子反射到墙面的光变成了彩虹。通过反射,可以知道光是由七种颜色组成的。
java中的反射又叫内省或者自省,简单来说就是知道自己身上有什么东西。我们知道一个类有自己的继承体系,由构造器、方法和字段组成,jdk1.5之后加入了注解。所有这些东西都能通过反射拿到。
Java中跟反射有关的类有:Class、Constructor、Method、Filed。下面介绍这四个类中经常使用的方法。

一、Class对象

Class对象是由java虚拟机创建的,当我们的应用程序需要某个类的时候 ,java虚拟机会加载该类,并创建对用的Class对象。Java中的对象、接口、枚举、注解、基本类型及其数组类型,都会有对应的Class对象。
创建Class对象的方式:

  1. 类名.class方式,例如:Object.class;
  2. Class.forName()方法,例如:Class.forName(“com.xxx.User”);
  3. 对象通过继承自Object的getClass()方法,例如:user.getClass(),每个实例都保存了指向当前类的Class实例的指针

Class常用的方法

1、java.lang.Class#isAssignableFrom(Class)

方法名称直译:是可赋值的从(某个Class对象),表示的是当前Class对象所代表的的类或接口要么是指定的参数的Class对象所代表的类或者接口的子类,要么是相同的类型。作用是通过此方法判断后,可以进行安全的强制类型转换。

//老虎类
public class Tiger {
}

//用法,此方法可以接受所有的Class对象
@SuppressWarnings("unchecked")
public void process(Class<?> clazz) {
	//进行类型判断
    if (clazz.isAssignableFrom(Tiger.class)) {
        processTigerClass((Class<Tiger>) clazz);
    }
    //......
}
//此方法处理Tiger及其子类的Class对象
public void processTigerClass(Class<? extends Tiger> clazz) {
    //.......
}
2、java.lang.Class#asSubclass(Class)

方法名称直译:作为子类,意思把当前的Class实例,转成指定的参数Class实例的子类。主要的作用就是窄化类型,传递给方法参数。
例如:

//老虎类
public class Tiger {
}

//用法,此方法可以接受所有的Class对象
//@SuppressWarnings("unchecked")
public void process(Class<?> clazz) {
    if (clazz.isAssignableFrom(Tiger.class)) {
        processTigerClass(clazz.asSubclass(Tiger.class));//进行类型转换
        //当然我们也可以不使用上述方式,参见下方,只不过会产生编译警告,未受检的类型转换
        //在此方法上添加@SuppressWarnings("unchecked")即可
        //processTigerClass((Class<Tiger>) clazz);
    }
    //......
}
//此方法处理Tiger及其子类的Class对象
public void processTigerClass(Class<? extends Tiger> clazz) {
    //.......
}

3、java.lang.Class#cast(Object)

方法名直译:转换,把指定的对象转成当前Class对象所代表的类或着接口
例如:


//老虎类
public class Tiger {
}
//处理所有的对象
public void processObject(Object object) {
	//获取老虎的Class对象
    Class<Tiger> clazz = Tiger.class;
    //进行类型判断
    if (clazz.isAssignableFrom(object.getClass())) {
        processTiger(clazz.cast(object));
    }
    //平时开发中用的是另一种形式,更简单,参见下方
    /*
    if (object instanceof Tiger){
        processTiger((Tiger) object);
    }
    */
    //......
}
//只处理老虎对象
public void processTiger(Tiger tiger) {
    //......
4、java.lang.Class#forName(String)

方法名直译:对于(什么)的名字,加载由字符串所指定全限定名类的Class对象。还有一个重载的方法,java.lang.Class#forName(String, boolean,ClassLoader)
解释下重载方法的参数:

  1. 类的全限定名
  2. 是否初始化,是否执行static代码块以及静态变量的赋值
  3. 类加载器,指定当前对象由那个类加载器加载
//user对象
public class User {
	//由public static final 修饰的字段称为编译时期常量,在使用时不会触发类的加载
    public static final String name= "zs";
    
    static {
        System.out.println("user");
    }
    
    private static  int a = getA();

    private static int getA() {
        System.out.println("getA");
        return 0;
    }
}
//测试
public void testUser() throws Exception {
	//加载User类时,不会触发静态代码块执行以及静态字段的赋值
    Class<?> clazz = Class.forName("com.xxx.User", false, Thread.currentThread().getContextClassLoader());
    //另外由public static final 修饰的字段称为编译时期常量,在使用时不会触发类的加载
    System.out.println(User.name);
}
5、java.lang.Class#getAnnotation(Class)

方法名直译:获取(指定类型)注解

@MyAnnotation
public class Order{}
//假设获取到了Order的Class对象
public void process(Class<?> clazz) {
	//获取类上的指定注解
    MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
    System.out.println(annotation.name());
}

还有几个跟注解有关的方法

  1. java.lang.Class#getAnnotations(),获取当前Class对象上全部的注解,当然只能获取到生命周期是RUNTIME的注解
  2. java.lang.Class#getAnnotationsByType(Class),获取指定注解类型的全部注解,指定的注解类型是可重复的
  3. java.lang.Class#isAnnotationPresent(Class),判断Class对象上是否出现指定的注解
6、java.lang.Class#getCanonicalName()

方法名直译:获取标准名称
用法参见代码:

//定义一个Category类以及内部类Helper
public class Category {
    public static class Helper{}
}

public void testCanonicalName(){
    //获取Category的标准名称
    System.out.println(Category.class.getCanonicalName());//com.xxx.pojo.Category
    //获取Category数组的标准名称
    System.out.println(Category[].class.getCanonicalName());//com.xxx.pojo.Category[]
    //获取Category内部类的Helper标准名称
    System.out.println(Category.Helper.class.getCanonicalName());//com.xxx.pojo.Category.Helper
    //获取Category内部类的Helper数组标准名称
    System.out.println(Category.Helper[].class.getCanonicalName());//com.xxx.pojo.Category.Helper[]
    //定义一个方法的内部类
    class A{}
    //获取A的标准名称
    System.out.println(A.class.getCanonicalName());//null
    //获取A的匿名类的标准名称
    System.out.println(new A(){}.getClass().getCanonicalName());//null
}
7、java.lang.Class#getClasses()

方法名直译:获取类,获取当前Class对象所代表的类或接口的内部public的类或者接口
java.lang.Class#getDeclaredClasses()方法获取全部的内部类或者接口

//Category
public class Category {
    public static class Helper{}
    public class Other{}
    public interface Another{}
}
//测试
public void testInnerClass(){
    for (Class<?> clazz : Category.class.getClasses()) {
        System.out.println("clazz = " + clazz);
    }
}
//输出结果
/*
clazz = interface com.xxx.pojo.Category$Another
clazz = class com.xxx.pojo.Category$Other
clazz = class com.xxx.pojo.Category$Helper
*/
8、java.lang.Class#getComponentType()

方法名直译:获取组件的类型,获取数组组件类型,如果不是数组返回null

public void testComponentType() {
    Class<?> clazz = Category[].class;
    System.out.println(clazz.getComponentType());//class com.xxx.pojo.Category
}
9、获取构造器

第一、获取public修饰的构造器

  1. java.lang.Class#getConstructor(Class<?> …) 获取指定参数类型的public修饰的构造器
  2. java.lang.Class#getConstructors() 获取全部的public修饰的构造器
    第二、获取任意修饰符的构造器
  3. java.lang.Class#getDeclaredConstructor(Class<?> …) 获取指定参数类型的构造器
  4. java.lang.Class#getDeclaredConstructors() 获取全部的构造器
//Category
public class Category {
    private String name;
    private int age;
    public Category(){}
    public Category(String name){
        this.name = name;
    }
    private Category(int age){
        this.age = age;
    }
}

public void testConstructor() throws Exception {
    Class<?> clazz = Category.class;
    //public修饰的无参构造器
    Constructor<?> nonArgPublic = clazz.getConstructor();
    //public修饰的带有String参数的构造器
    Constructor<?> argPublic = clazz.getConstructor(String.class);
    //public修饰的全部构造器
    Constructor<?>[] allPublic = clazz.getConstructors();
    //private修饰的带有int参数的构造器
    Constructor<?> argPrivate = clazz.getDeclaredConstructor(int.class);
    //获取全部构造器
    Constructor<?>[] all = clazz.getDeclaredConstructors();
}
10、获取方法或者字段

参见第九条,只是在传递参数时需要多指定一个方法名或字段名参数

11、java.lang.Class#getDeclaringClass()

方法名直译:获取声明的类,内部类的Class对象获取外部类的Class对象

//Category
public class Category {
    public static class Helper{}
    public class Other{}
    public interface Another{}
}

public void testDeclaringClass(){
    Class<?> clazz = Category.Helper.class;
    System.out.println(clazz.getDeclaringClass());//class com.xxx.pojo.Category
}
12、java.lang.Class#getEnumConstants()

方法名直译:获取枚举常量,如果不是枚举类则返回null
与枚举相关的还有一个方法: java.lang.Class#isEnum()判断当前类是否是枚举类

public enum Color {
    RED,
    ORANGE,
    YELLOW,
    GREEN,
    CYAN,
    BLUE,
    PURPLE
}
public void testEnum(){
    Class<Color> clazz = Color.class;
    for (Color enumConstant : clazz.getEnumConstants()) {
        System.out.println("enumConstant = " + enumConstant);
    }
}
//输出
/*
enumConstant = RED
enumConstant = ORANGE
enumConstant = YELLOW
enumConstant = GREEN
enumConstant = CYAN
enumConstant = BLUE
enumConstant = PURPLE
*/
13、java.lang.Class#getGenericInterfaces()

方法名直译:获取泛型接口,可以用于泛型解析
还有一个方法与之类似:java.lang.Class#getGenericSuperclass()用于获取泛型超类

//泛型接口
public interface Creature<T> {
}
//实现类
public class Tiger<T> implements Creature<T> {
}

//测试
public void testGenericInterfaces() {
    Class<?> clazz = Tiger.class;
    for (Type genericInterface : clazz.getGenericInterfaces()) {
        //class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
        System.out.println(genericInterface.getClass());
        //genericInterface = com.xxx.interfaces.Creature<T>
        System.out.println("genericInterface = " + genericInterface);
    }
}
14、java.lang.Class#getInterfaces()

获取所有接口
与之类似的还有java.lang.Class#getSuperclass()获取超类

15、java.lang.Class#getModifiers()

有一个辅助类,可以判断修饰符类型:java.lang.reflect.Modifier类,有很多以静态方法用于判断修饰符类型,具体参见Modifier类,比较简单就不一一列举了。

16、java.lang.Class#getSimpleName()

方法名直译:获取简单的名称

public void testSimpleName(){
    Class<?> clazz = User.class;
    System.out.println(clazz.getSimpleName());//User
}
17、几个用于判断的方法

1、java.lang.Class#isArray() 判断此Class对象是否是数组类型

public void testIsArray(){
    Class<?> userClass = User.class;
    Class<?> userArrayClass = User[].class;
    System.out.println(userClass.isArray());//false
    System.out.println(userArrayClass.isArray());//true
}

2、java.lang.Class#isInstance(Object)
方法名直译:(指定对象)是(当前类)实例

public void testIsInstance(Class<?> clazz) {
    User user = new User();
    if (clazz.isInstance(user)) {
        System.out.println(true);
    }
}

3、java.lang.Class#isInterface() 判断当前Class对象是否代表接口
4、java.lang.Class#isMemberClass() 判断当前Class对象所代表的类或者接口是否是另外一个接口或类的成员(即内部类或接口)
5、java.lang.Class#isPrimitive()判断是否是原生类型,int.class等等

18、java.lang.Class#newInstance()

使用默认构造器创建对象。

//Class对象需要有无参构造器
//不建议直接使用,除非有约定,提供无参构造器
public void testNewInstance(Class<?> clazz) throws Exception {
    Object object = clazz.newInstance();
}

Class对象的方法就介绍这么多,应该是包括了基本常用的方法
Constructor、Method、Field
这三个类是很类似的,其中的方法就不一一介绍,能知道Class中常用的方法,自然就可以理解这三个类中很多的方法,下面主要介绍些Class对象中没有的方法

二、Method

先介绍Method,因为构造器是特殊的方法,与Method有很多共同之处,带有generic关键字的都是跟泛型有关就不解释了,
可参考:https://blog.youkuaiyun.com/weixin_45341408/article/details/109804201

1、java.lang.reflect.Method#getDeclaredAnnotations()获取声明在方法上的全部注解
2、java.lang.reflect.Method#getDefaultValue()返回注解中的默认值,不是注解类型则返回null
3、java.lang.reflect.Method#getExceptionTypes()获取方法声明时抛出的异常类型(跟在throws后面的)
4、java.lang.reflect.Method#getParameterAnnotations()获取方法参数上的注解类型

返回值是一个二维数组,第一维对应的方法参数的位置,第二维就是方法参数所对应的全部注解。

5、java.lang.reflect.Method#getParameterCount()获取方法参数个数
6、java.lang.reflect.Method#getParameterTypes()获取方法参数类型
7、java.lang.reflect.Method#getReturnType()获取方法的返回值类型
8、java.lang.reflect.Method#getTypeParameters()获取方法上声明的泛型

例如:public <T,E> void say(){} 则返回值是T和E的TypeVariable对象

9、java.lang.reflect.Method#isDefault()是否是默认方法,jdk1.8可以在接口中声明默认方法
public interface Animal {
    default void say(){}
}
10、java.lang.reflect.Method#isVarArgs()方法参数是否是可变参数
11、java.lang.reflect.Method#invoke(Object,Object…)执行方法

第一个参数是目标对象(是静态方法时可以为null),第二个参数是方法参数

12、java.lang.reflect.AccessibleObject#setAccessible(boolean)这个是父类中的方法

当Method是不是public是,调用此方法指定参数为true,既可以进行方法的反射调用

13、java.lang.reflect.Method#isBridge()是否是桥接方法

桥接方法需要解释下:
如何才会出现桥接方法?

重写(实现)父类(父接口)的方法时,返回值是父类或父接口返回值的子类型,也就是协变返回类型

有以下两种情况:
1、泛型,泛型会被擦除为Object类型,所以重写泛型方法,当我们指定泛型类型是Object子类时,就会出现桥接方法
2、方法的重写,指定返回值类型为父类或父接口返回值的子类型

//带有泛型的接口
public interface Creature<T> {
	//第一种情况
    public T get();
	//第二种情况
    public Object getObject();
}
//实现类
public class Sparrow implements Creature<String> {
	//第一种情况
    @Override
    public String get() {
        return "OK";
    }
    //第二种情况
     @Override
    public User getObject() {
        return new User();
    }
}
//下面是通过javap反编译的Sparrow的字节码文件
public class Sparrow implements Creature<java.lang.String> {
  public Sparrow();
  public java.lang.String get();
  public User getObject();
  //桥接方法
  public java.lang.Object getObject();
  //桥接方法
  public java.lang.Object get();
}

//测试
public void testBridge(){
    for (Method method : Sparrow.class.getDeclaredMethods()) {
        System.out.print("method name : " +method.getName());
		System.out.println(" isBridge : "+method.isBridge());
    }
}
//输出是
/*
method name : get 		isBridge : true
method name : get 		isBridge : false
method name : getObject isBridge : true
method name : getObject isBridge : false
*/

看到反编译的代码很奇怪,在java规范中是不允许出现这种情况的,因为方法的重载是不考虑返回值的。因为桥接方法是由编译器生成的,是安全的,而且桥接方法会调用的都是对应的方法,例如:public java.lang.Object getObject()桥接方法会调用public User getObject()方法来执行方法体。

桥接方法之所以出现,是为了能够有协变返回类型,用来支持泛型的出现。

三、Constructor

介绍下Method类中没有的方法,这就是构造器和普通方法的区别,能够创建对象:

java.lang.reflect.Constructor#newInstance(Object …),根据传入的参数创建对应的实例

与获取Constructor是指定的参数类型要一致。

四、Filed

Filed中有很多getXXX(Object)和setXXX(Object,XXX)这个非常简单不介绍了,还有一些和Constructor和Method对象一样的方法也不介绍了。

java.lang.reflect.Field#isEnumConstant() 判断字段是否是枚举常量

总结

只是介绍了Java中反射用到的对象及常用法法,非常的基础,没有涉及业务场景,反射实在是非常强大,对于反射只有一句话,学好它,java的各种框架就自动学会了一半。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值