反射与注解的基本使用

一、反射

对于反射的理解

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

获取Class实例的三种方式

调用Class静态方法:forName(String classPath)

Class<?> clazz = Class.forName("java.lang.Object");
System.out.println(clazz);

在这里插入图片描述

通过运行时类的对象调用getClass()

String name = "qi";
Class<? extends String> clazz = name.getClass();
System.out.println(clazz);

在这里插入图片描述

调用运行时类的属性 .class

lass clazz = String.class;
System.out.println(clazz);

在这里插入图片描述

反射之Constructor

定义一个Student类

package reflect;

public class Student {
    private String name;
    private int age;
    public String gender;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void say() {
        System.out.println("hello");
    }
}

获取构造器

获取Student类的Class实例,获取构造器,并输出构造器中的参数

@Test
    public void test01() throws ClassNotFoundException {
        // 获取Class实例
        Class<?> clazz = Class.forName("reflect.Student");
        // 获取构造器(多个)
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            // 输出构造器的名称
            System.out.println(constructor.getName());
            // 获取构造器中的参数
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.print(parameterType + "--");
            }
            System.out.println("\n===================");
        }
    }

在这里插入图片描述
Student类中定义了两个构造器,getParameterTypes()都获取到了;也可以通过getConstructor(String.class, int.class);获取单个构造器

Class<?> clazz = Class.forName("reflect.Student");
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
System.out.println(constructor);

在这里插入图片描述

创建运行时类对象

通过构造器获取类对象 constructor.newInstance()

Class<?> clazz = Class.forName("reflect.Student");
// 获取构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 获取运行时类对象
Object stu = constructor.newInstance("xiaoming", 21);
Student s = (Student) stu;
System.out.println(s.getName());

在这里插入图片描述
通过Class实例对象获取对象

Class<?> clazz = Class.forName("reflect.Student");
// 默认是通过无参构造器获取
Student student = (Student)clazz.newInstance();
System.out.println(student.getName());

在这里插入图片描述

反射之Field

  • getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
// 获取Class实例
Class<?> clazz = Class.forName("reflect.Student");
// 获取当前运行类中声明的所有属性(不包含父类中的属性)
Field[] declaredFields = clazz.getDeclaredFields();
// 输出属性名和属性类型
for (Field declaredField : declaredFields) {
    System.out.println(declaredField.getName() + ":" + declaredField.getType());
}

在这里插入图片描述

  • getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for (Field field : fields) {
    System.out.println(field.getName() + ":" + field.getType());
}

在这里插入图片描述
只有gender属性声明为public,其他属性都是private

  • getDeclaredField(String name),可以传入属性名获取单个属性
Field name = clazz.getDeclaredField("name");
System.out.println(name);

在这里插入图片描述

反射之Method

获取运行时类的方法

  • getDeclaredMethods():获取当前运行时类中声明的所方法。(不包含父类中声明的方法)
  • getMethods():获取当前运行时类及其所父类中声明为public权限的方法
  • getDeclaredMethod(String name, Class type):根据输入的方法名和方法的参数类型获取单个方法
Class<?> clazz = Class.forName("reflect.Student");
// 获取运行时类的所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    System.out.println(method.getName());
}

在这里插入图片描述
另外两种方法就不一一演示了

唤醒方法:invoke()

Class<?> clazz = Class.forName("reflect.Student");
// 创建运行时类Student的对象
Object o = clazz.newInstance();
// 根据方法名和方法的参数类型获取 setName方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
// 唤醒方法:传入对象以及方法的参数,等价于一个对象调用方法
setName.invoke(o, "qi");
// 获取 getName 方法,该方法没有参数
Method getName = clazz.getDeclaredMethod("getName");
// 唤醒 getName方法,该方法没有参数,所以没有指定参数类型
System.out.println(getName.invoke(o));

在这里插入图片描述

反射之类型擦除

有以下场景,声明一个类型为String的List,如果想要插入Integer类型的数字怎么办?

List<String> list = new ArrayList<>();
list.add("abc");

很显然通过add方法只能插入String类型的字符串,因为我们指明了泛型
那么通过反射可以做到,思路如下:
首先获取 list 对象的运行时类的Class实例,再通过Class实例获取声明参数类型的add方法,然后在invoke唤醒该方法即可。
查看add方法的源码:在这里插入图片描述
代码如下:

List<String> list = new ArrayList<>();
list.add("abc"); // 添加成功
// list.add(123); 肯定添加失败

// 获取Class实例
Class<?> clazz = list.getClass();
// 获取泛型为Object类型的add方法
Method addObject = clazz.getDeclaredMethod("add", Object.class);
// 对 list 对象唤醒该方法 ==> list.add(123)
addObject.invoke(list, 123); // 插入成功
// 输出list对象
System.out.println(list);

在这里插入图片描述

私有变量/私有方法能否被外界访问?

如果不用反射的话,私有方法和私有变量是不能被外界访问到的
Student有如下私有属性和私有方法,现在通过反射从外部访问他们

private String name;
private int age;
private void say() {
	System.out.println("hello");
}
Class<?> clazz = Class.forName("reflect.Student");
// 获取stu对象
Student stu = (Student)clazz.newInstance();
// 获取私有属性 name
Field name = clazz.getDeclaredField("name");
// 设置私有属性能被访问到!!!
name.setAccessible(true);
// 从外部访问name属性,并设置为 "qi"
name.set(stu, "qi");
// 查看一下名字是否被设置为 "qi"
System.out.println(stu.getName());

Method say = clazz.getDeclaredMethod("say");
// 设置私有方法能被访问
say.setAccessible(true);
// 唤醒方法
say.invoke(stu);

在这里插入图片描述

二、注解

对于注解的理解

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。

定义注解

首先定义两个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface QiBean {
    String value() default "nihao"; 
}


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface QiField {
    String value();
}
  • @Target注解:ElementType可以取TYPE:可以用在类、接口上;FIELD:可以用在类的属性上;METHOD:可以用在类的方法上等等
  • @Retention:表示注解的生命周期SOURCE/CLASS/RUNTIME(默认行为\RUNTIME,只有声明为RUNTIME生命周期的注解,才能通过反射获取
  • @Inherited:可以被继承(后面会例子)

获取注解

@QiBean(table = "qi_animal")
public class Animal {
    private Integer id;
    private String name;
    @QiField("gender")
    private String sex;

    public Animal() {
    }

    public Animal(Integer id, String name, String sex) {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }
}

public class Pig extends Animal {}

通过getAnnotation方法可以获取注解

Class<?> clazz = Class.forName("annotation.Animal");
// 是否存在注解
System.out.println(clazz.isAnnotationPresent(QiBean.class));
// 获取到注解
QiBean qiBean = clazz.getAnnotation(QiBean.class);
if (null != qiBean) {
    System.out.println(qiBean.table() + ":"
            + qiBean.annotationType()
    );
}
// 获取到属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
	// 获取到属性对应的注解
    QiField qiField = field.getAnnotation(QiField.class);
    if (null != qiField) {
    	// 输出属性名称和对应的注解中的名称
        System.out.println(field.getName() + "=>" + qiField.value());
    }
}

在这里插入图片描述
从输入结果可以看到,获取到了Animal类注解中的值:qi_animal,也获取到了sex属性上的注解值:gender

现在我们看一些Pig类有没有注解(Pig继承Aanimal,但是自己没有声明注解)

Class<?> clazz = Class.forName("annotation.Pig");
System.out.println(clazz.isAnnotationPresent(QiBean.class));

在这里插入图片描述
可以发现Pig类上是有注解的,这是因为定义注解的时候声明了@Inherited

总结

本文记录了的反射的一些基本用法:获取运行时类对象、通过反射获取方法、属性,以及通过反射进行类型擦除、访问私有属性和方法;同时也简单的介绍如何获取类、属性中的注解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值