Java基础之反射Reflection总结

Java反射

1. 反射基础
1.1 什么是反射?

Java反射是可以让我们在运行时获取类的方法、属性、父类、接口等 Class 内部信息的机制。通过反射还可以让我们在运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值,即使方法或属性是私有的的也可以通过反射的形式调用,这种“看透 class”的能力被称为内省,这种能力在框架开发中尤为重要。 有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如 JDBC),这是反射用得比较多的场景。
还有一个比较常见的场景就是编译时我们对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。比如 ORM 框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。这也是反射比较经典应用场景之一

1.2 Class类

反射是操作Class信息的

当我们编写完一个 Java 项目之后,所有的 Java 文件都会被编译成一个.class 文件,这些 Class 对象承载了这个类型的父类、接口、构造方法、成员方法、成员字段等原始信息,这些 class 文件在程序运行时会被 ClassLoader 加载到虚拟机中。当一个类被加载以后,Java 虚拟机就会在内存中自动产生一个 Class 对象。我们通过 new 的形式创建对象实际上就是通过这些 Class 来创建,只是这个过程对于我们是不透明的而已。

反射举例说明的相关类

// 父类
public class Animal {
    private String name;

    public Animal() {}

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    private void animalOwnMethod() {
        System.out.println("animal own method");
    }

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                '}';
    }
}

// 接口
public interface Sleep {}

// 子类
public class Cat extends Animal implements Sleep {
    private String color;
    public int age;

    public Cat() {
        super();
    }

    public Cat(String name) {
        super(name);
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    private void catOwnMethod() {
        System.out.println("cat own method");
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + getName() + '\'' +
                ", color='" + color + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}
2. 反射Class以及构造对象
2.1 获取Class对象
  • 通过类名来获取Class对象:类名.class
Class<?> cls = Cat.class;
  • 通过对象名来获取Class对象:对象名.getClass()
Cat cat = new Cat();
Class<?> cls = cat.getClass()
  • 通过Class.forName()来获取Class对象
Class<?> cls = Class.forName("com.reflection.Cat");
  • 在使用 Class.forName()方法时,你必须提供一个类的全名(包含包名)
  • 如果在调用 Class.forName()方法时,没有在编译路径下(classpath)找到对应的类,那么将会抛出 ClassNotFoundException。
2.2 通过 Class 对象构造目标类型的对象

我们首先要获取类的 Constructor(构造器)对象,然后通过 Constructor 来创建目标类的对象。

try {
    Class<?> cls = Class.forName("com.reflection.Cat");

    // 无参构造方法
    // Constructor<?> constructor = cls.getConstructor();
    // Object obj = constructor.newInstance();

    // 有参构造方法
    Constructor<?> constructor = cls.getConstructor(String.class);
    Object obj = constructor.newInstance("cat");
    System.out.println(obj.toString());
} catch (Exception e) {
    e.printStackTrace();
}

方法说明

// 获取Constructor对象,参数为可变参数
// 如果构造函数有参数,那么需要将参数的类型(Class)传递进去
public Constructor<T> getConstructor(Class<?>... parameterTypes)

// 创建一个实例对象,参数为可变参数
// 这里的可变参数必须要与上面方法(getConstructor)的参数一一对应(包括参数数量和类型)。
// 否则会抛出异常:java.lang.IllegalArgumentException: argument type mismatch
public T newInstance(Object ... initargs)
3. 反射获取类中的方法
3.1 获取当前类中定义的方法
try {
    Class<?> cls = Class.forName("com.reflection.Cat");

    // 获取当前类定义的所有方法:包括public/default/protected/private
    Method[] methods = cls.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method.getName());
    }

    Constructor<?> constructor = cls.getConstructor(String.class);
    Object obj = constructor.newInstance("cat");
    // 获取某个指定方法的Method对象
    Method method = cls.getDeclaredMethod("catOwnMethod");
    // 取消对private的访问限制
    method.setAccessible(true);
    // 方法调用
    method.invoke(obj);
} catch (Exception e) {
    e.printStackTrace();
}

注意:当通过反射获取由private修饰的Constructor、Method、Field时,必须在反射调用之前将此对象的 accessible 标志设置为 true,以此来取消访问限制。
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

3.2 获取当前类、父类中定义的公有方法
try {
    Class<?> cls = Class.forName("com.reflection.Cat");

    // 获取当前类、父类中定义的所有公有方法(public)
    Method[] methods = cls.getMethods();
    for (Method method : methods) {
        System.out.println(method.getName());
    }

    Constructor<?> constructor = cls.getConstructor();
    Object obj = constructor.newInstance();
    // 获取当前类、父类中定义的指定公有方法
    Method setScoreMethod = cls.getMethod("setColor", String.class);
    setScoreMethod.invoke(obj, "white");
    
    System.out.println(obj.toString());
} catch (Exception e) {
    e.printStackTrace();
}

方法说明

// 获取当前 Class 对象中的所有方法,不包括父类的方法
public Method[] getDeclaredMethods()

// 获取 Class 对象中指定函数名和参数的函数,
// 参数1为函数名,参数2为参数类型列表
public Method getDeclaredMethod(String name, Class...<?> parameterTypes)

// 获取该 Class 对象中的所有公有方法,包括父类的公有方法
public Method[] getMethods()

// 获取指定的 Class 对象中的公有方法,
// 参数1为函数名,参数2为参数类型列表
public Method getMethod (String name, Class...<?> parameterTypes)

// 方法调用 Method#invoke
// 参数1为该Class的实例对象,参数2为要方法传递的参数列表,必须要与getMethod()或getDeclaredMethod()方法的参数列表一一对应
public native Object invoke(Object obj, Object... args)
4. 反射获取类中的字段

其实与反射获取类中的方法类似,只是调用方法不同

4.1 获取当前类中定义的字段
try {
    Class<?> cls = Class.forName("com.reflection.Cat");

    // 获取当前类中定义的所有字段
    Field[] fields = cls.getDeclaredFields();
    for (Field field : fields) {
        System.out.println(field.getName());
    }

    Constructor<?> constructor = cls.getConstructor();
    Object obj = constructor.newInstance();
    // 获取当前类中定义的指定字段
    Field scoreField = cls.getDeclaredField("color");
    scoreField.setAccessible(true);
    scoreField.set(obj, "white");

    System.out.println(obj.toString());
} catch (Exception e) {
    e.printStackTrace();
}
4.2 获取当前类、父类中定义的公有字段
try {
    Class<?> cls = Class.forName("com.reflection.Cat");

    // 获取当前类、父类中定义的所有公有字段
    Field[] fields = cls.getFields();
    for (Field field : fields) {
        System.out.println(field.getName());
    }

    Constructor<?> constructor = cls.getConstructor();
    Object obj = constructor.newInstance();
    Field scoreField = cls.getField("age");
    scoreField.set(obj, 3);

    System.out.println(obj.toString());
} catch (Exception e) {
    e.printStackTrace();
}
5. 反射获取父类与接口
5.1 获取父类
try {
    Class<?> cls = Class.forName("com.reflection.Cat");

    Class<?> superCls = cls.getSuperclass();
    while (superCls != null) {
        System.out.println(superCls.getName());
        superCls = superCls.getSuperclass();
    }
} catch (Exception e) {
    e.printStackTrace();
}
5.2 获取接口
try {
    Class<?> cls = Class.forName("com.reflection.Cat");

    Class<?>[] clsArr = cls.getInterfaces();
    for (Class<?> clazz : clsArr) {
        System.out.println(clazz.getName());
    }
} catch (Exception e) {
    e.printStackTrace();
}
6. 获取注解信息

注解与反射的组合是在框架开发中,是最为常见形式的。如常见的Retrofit、ORM框架等都使用了注解。但是这些框架处理注解形式有些差异:有些使用反射来处理注解,有些使用APT来处理注解。我们这里只讨论反射。
Java注解详情请参考:Java基础之注解Annotation总结

定义一个注解

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoTest {
    String tag();
}

使用注解

@AnnoTest(tag = "cat class")
public class Cat extends Animal implements Sleep {
    @AnnoTest(tag = "color field")
    private String color;
    
    ...
}

获取注解信息

Class<?> cls = Class.forName("com.reflection.Cat");

// 获取类上的注解信息
AnnoTest classAnno = cls.getAnnotation(AnnoTest.class);
System.out.println(classAnno.tag());

Field field = cls.getDeclaredField("color");
// 获取字段上的注解信息
AnnoTest fieldAnno = field.getAnnotation(AnnoTest.class);
System.out.println(fieldAnno.tag());

注:反射要获取注解信息,注解必须要声明为运行期注解,即RetentionPolicy.RUNTIME

7. 参考

公共技术点之 Java 反射 Reflection

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值