注解和反射

注解和反射

注解(Annotation)

什么是注解

注解是放在Java源码的类、方法、参数前的一种特殊的“注释”。

所有的注解都继承自java.lang.annotation.Annotation

Java注解分为三类:

1.编译器使用的注解,SOURCE类型,在编译期就被丢掉了,如:

  • @Override:让编译器检查该方法是否正确地实现了覆写
  • @SuppressWarnings:告诉编译器忽略此处代码产生的警告

2.工具处理.class文件使用的注解,CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM

3.程序运行期间能够读取的注解,RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取

注解定义:

使用@interface定义一个注解:

public @interface Annotation1{
}

定义注解时还可以定义配置参数。配置参数可以包括:

  • 所有基本类型;
  • String;
  • 枚举类型;
  • 基本类型、String、Class以及枚举的数组
public @interface TestAnnotation{
    int id() default 0; // 可设置默认值
    String name();
    String value(); //最常用的参数应当命名为value
}
元注解

修饰其他注解的注解称为元注解。Java标准库定义了一些常用的元注解:

@Target

使用@Target定义注解可以应用在什么位置(类,字段,方法构造方法,方法参数)

@Target({ElementType.METHOD, ElementType.TYPE})  //定义了该注解可以放在类(TYPE)和方法(METHOD)上
@interface TestAnnotation{
    int id() default 0;
    String name();
}

ElementType是一个枚举类型,里面的值包括:

  • 类或接口:ElementType.TYPE
  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER
@Retention

@Retention定义了注解的生命周期

@Retention(RetentionPolicy.RUNTIME)  // 运行期,最常使用
@Target({ElementType.METHOD, ElementType.TYPE})
@interface TestAnnotation{
    int id() default 0;
    String name();
}

RetentionPolicy是一个枚举类型,里面的值包括:

  • 仅编译期:RetentionPolicy.SOURCE
  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME
@Inherited

@Inherited定义子类是否继承父类定义的注解,@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效

@Inherited  // 子类可继承父类的注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@interface TestAnnotation{
    int id() default 0;
    String name();
}
注解的使用

如何使用注解完全由工具决定。SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有RUNTIME类型的注解不但要使用,还经常需要编写。

此处注解的使用针对于RUNTIME类型的注解,一般结合反射机制使用注解。

import java.lang.annotation.*;
import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws NoSuchFieldException {
        Student student = new Student();
        Class cls = student.getClass();
        ClsAnnotation clsAnnotation = (ClsAnnotation)cls.getAnnotation(ClsAnnotation.class); // 获取类注解
        System.out.println(clsAnnotation.value());  // 输出"类注解"

        Field name = cls.getDeclaredField("name");
        FieldAnnotation fieldAnnotation = name.getAnnotation(FieldAnnotation.class); // 获取字段注解
        System.out.println(fieldAnnotation.columnName()); // 输出"db_name"
        System.out.println(fieldAnnotation.type()); // 输出"varchar"
        System.out.println(fieldAnnotation.length()); // 输出"50"
    }
}

@ClsAnnotation("类注解")
class Student{
    @FieldAnnotation(columnName = "db_id", type = "int", length = 10)
    private int id;
    @FieldAnnotation(columnName = "db_name", type = "varchar", length = 50)
    private String name;
    @FieldAnnotation(columnName = "db_age", type = "int", length = 3)
    private int age;
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClsAnnotation{
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation{
    String columnName();
    String type();
    int length();
}

反射(Reflection)

反射机制允许程序在执行期借助于Refelection API获取一个类的所有信息,并能够直接操作任意对象的内部属性及方法。

Class类

JVM每加载一种class(或interface),如果是第一次加载,就为其创建一个Class实例,这个实例对象包含了类class完整的结构信息。这个对象就如同一面镜子,透过这个对象可以看到完整的类的结构信息。

通过一个class获取对应的Class实例,有三种方法:

// 1.通过类名获得
Class cls1 = String.class;  

// 2.通过实例对象获得
String s = "Hello";
Class cls2 = s.getClass();

// 3.通过完整的包名
Class cls3 = Class.forName("java.lang.String");

Class实例是唯一的,一个类只有一个Class实例。所以上面三种方法获得的实例实际上是同一个。

反射的优点和缺点:

优点:实现动态创建对象和编译,体现出很大的灵活性

缺点:对性能有影响,性能比较——不使用反射 > 检测关闭的反射 > 正常的反射

类的加载过程

1.类的加载(Load):类加载器将类的class文件读入内存,并将这些静态数据转换成方法区运行数据结构,然后为之创建一个java.lang.Class对象;

2.类的链接(Link):将类的二进制数据合并到JVM运行状态之中的过程;

3.类的初始化(Initialize):JVM负责对类进行初始化。

Java类加载是动态加载的,只有该类被用到时才加载

访问字段

Class类提供了以下几个方法来获取Field:

  • Field getField(name):根据字段名获取某个publicfield(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有publicfield(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

一个Field对象包含一个字段的所有信息:

  • getName():返回字段名称,例如,"name"
  • getType():返回字段类型,也是一个Class实例,例如,String.class
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

获取/设置字段值:

1.首先获取一个类的Class实例

2.通过这个Class实例去获取对应的字段,private字段要使用getDeclaredField(String fieldname)方法

3.通过字段对象的get(Object obj)方法获取对应的字段值,private字段要setAccessible(true)才能访问

4.通过字段对象的set(Object obj, Object value)方法设置字段值

import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Object person = new Person("sen");
        Class cls = person.getClass();
        Field field = cls.getDeclaredField("name");
        field.setAccessible(true); // 由于name字段是private的,必须将setAccessible设置true才能访问
        Object value = field.get(person);
        System.out.println(value); // 输出"sen"
        field.set(person, "chen");
        value = field.get(person);
        System.out.println(value); // 输出"chen"
    }
}

class Person{
    private String name;
    public Person(String name){
        this.name = name;
    }
}
调用方法

Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

获取特定方法时,除了要传入方法名外,还要传入对应参数的Class实例,因为Java存在方法重载,可能会存在同名的方法,必须通过参数签名进行区分。如果为无参方法,第二个参数传入null

一个Method对象包含一个方法的所有信息

  • getName():返回方法名称,例如:"getName"
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

调用方法:

1.首先获取一个类的Class实例

2.通过这个Class实例去获取对应的方法对象,private方法要使用getDeclaredMethod(name, Class...)方法

3.通过方法对象invoke(Object obj, Class...)方法调用对象的方法,private方法要setAccessible(true)才能访问

4.如果获得的Method是一个静态方法,调用invoke方法时传入的第一个参数永远为null

5.如果是无参方法,第二个参数为null

6.调用方法时遵循多态,根据传入的对象调用其对应的方法

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Student student = new Student();
        student.setName("sen");
        Class cls = student.getClass();
        Method getName =  cls.getMethod("getName");  // 获得父类public方法
        Method setScore = cls.getDeclaredMethod("setScore", int.class);  // 获得子类private且带参的方法
        Method getScore = cls.getMethod("getScore");  // 获得子类public方法
        setScore.setAccessible(true); // 调用私有方法必须设置setAccessible(true)
        // 调用方法
        System.out.println(getName.invoke(student));
        setScore.invoke(student, 99);
        int score = (int)getScore.invoke(student);
        System.out.println(score);
    }
}

class Student extends Person{
    private int score;

    private void setScore(int score) {
        this.score = score;
    }

    public int getScore() {
        return score;
    }
}

class Person{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
调用构造方法

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class...):获取某个publicConstructor
  • getDeclaredConstructor(Class...):获取某个Constructor
  • getConstructors():获取所有publicConstructor
  • getDeclaredConstructors():获取所有Constructor

调用构造方法:

首先获取一个类的Class实例

2.通过这个Class实例去获取对应的构造方法对象Constructorprivate构造方法要使用getDeclaredMethod(name, Class...)方法

3.通过构造方法对象newInstance(Class...)方法调用构造方法创建对象,private构造方法要setAccessible(true)才能调用

4.默认newInstance()是调用无参构造器

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
       Constructor constructor = Student.class.getConstructor(String.class, int.class);
       Student student = (Student) constructor.newInstance("chen", 24);
        System.out.println(student);
    }
}

class Student extends Person{
    private int age;
    public Student(String name, int age){
        super(name);
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }
}

class Person{
    private String name;

    public Person(String name){
        this.name = name;
    }
}
获取注解

读取Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

参数中的Class为注解的Class实例

判断某个注解是否存在于ClassFieldMethodConstructor

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值