Java注解与反射

Java注解与反射

一、注解(Annotation)

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

自定义注解

格式:public @interface 注解名 { 定义体 }

元注解

我们在自定义注解时,需要使用java提供的元注解,就是负责注解的其他注解。java定义了四个标准的meta-annotation类型,他们被用来提供对其他注解类型声明。

@Target
这个注解的作用主要是用来描述注解的使用范围

@Retention
这个注解的作用就是我们需要告诉编译器我们需要在什么级别保存该注释信息,用于描述注解的生命周期。

@Document

是否在javadoc中生成

@Inherited

  • 标记这个注解是继承于哪个注解类

二、反射(Reflect)

Java反射:入门,使用,原理。

反射指的是我们可以在运行期间加载、探知、使用编译期间完全未知的类。是一个动态的机制,允许我们通过字符串来指挥程序实例化,操作属性、调用方法。使得代码提高了灵活性,但是同时也带来了更多的资源开销。

class类
我们在使用反射时,需要先获得我们需要的类,而java.lang.Class这个类必不可少,他十分特殊,用来表示java中类型 (class/interface/enum/annotation/primitive type/void)本身。

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

上面这样子进行类对象的初始化,我们可以理解为「正」。

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

这时候,我们使用 JDK 提供的反射 API 进行反射调用:

Class clz = Class.forName("com.xxx.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.xxx.reflect.Apple)。

一般情况下我们使用反射获取一个对象的步骤:

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射调用
        Class clz = Class.forName("com.chenshuyi.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

一、获取类的 Class 对象实例

//第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("com.xxx.reflect.Apple")
//第二种,使用 .class 方法。这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
//第三种,使用类对象的 getClass() 方法。
String str = new String("Hello");
Class clz = str.getClass();

二、根据 Class 对象实例获取 Constructor 对象

Constructor appleConstructor = clz.getConstructor();

三、使用 Constructor 对象的 newInstance 方法获取反射类对象

Object appleObj = appleConstructor.newInstance();

四、获取方法的 Method 对象

Method setPriceMethod = clz.getMethod("setPrice", int.class);

五、利用 invoke 方法调用方法

setPriceMethod.invoke(appleObj, 14);

获取类的字段、某些变量

Class aClass = Class.forName("sml.reflect.User");

//获取该类的所有public字段,包括父类的
Field[] fields = aClass.getFields();
//根据字段名获取该类的public字段
Field field = aClass.getField("age");

//获取该类的所有字段,不包括父类(仅自定义)
Field[] fields1 = aClass.getDeclaredFields();
//根据字段名获取该类的字段
Field field1 = aClass.getDeclaredField("name");

获取类的方法

Class aClass = Class.forName("sml.reflect.User");

//获取该类的所有public方法,包括父类的
Method[] methods = aClass.getMethods();
//根据方法名获取该类的public方法
Method method = aClass.getMethod("getName");
//如果该类为重写方法,可以在第二个参数加上重写方法的参数类型,不写为无参数的方法
Method paramMethod = aClass.getMethod("getName",String.class)

//获取该类的所有方法,不包括父类(仅自定义)
Method[] declaredMethods = aClass.getDeclaredMethods();
//根据方法名获取该类的方法
Method declaredMethod = aClass.getDeclaredMethod("getName");

获取类的构造器

 Class aClass = Class.forName("sml.reflect.User");

//获取该类的所有构造器,包括父类
Constructor[] constructors = aClass.getConstructors();
//根据构造器的参数类型来获取指定构造器,不写为无参构造器
Constructor constructor = aClass.getConstructor();
Constructor constructor1 = aClass.getConstructor(String.class,int.class);

//获取该类的所有构造器,不包括父类
Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
//根据构造器的参数类型来获取指定的自定义构造器,不写为无参构造器
Constructor declaredConstructor = aClass.getDeclaredConstructor();
Constructor declaredConstructor1 = aClass.getDeclaredConstructor(String.class, int.class);

类的实例化

Class aClass = Class.forName("sml.reflect.User");

//通过class类直接实例化,使用的是User类的无参构造器
User user = (User) aClass.newInstance();
            
//获取构造器来进行实例化,这里获取有参构造器
Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class);
//根据构造器进行实例化
User user1 = (User) declaredConstructor.newInstance("sml",18);
/*
我们在使用类对象直接实例化时,一定要确保需实例化的类中存在无参构造器,否则会报错。默认获取的是Object类型,因此最后需要进行下类型转化。
*/

如果我们调用的方法为私有方法,虽然编译器通过,在运行时会报错的(java.lang.IllegalAccessException),这是因为java的安全检查。我们可以使用setAccessible(true)这个方法来跳过安全检查。

Class aClass = Class.forName("sml.reflect.User");

User user = (User) aClass.newInstance();
Method setName = aClass.getDeclaredMethod("setName", String.class);
//若setName为私有方法,跳过安全检查
setName.setAccessible(true);
setName.invoke(user,"sml");

泛型的操作(Generic)

java采用泛型擦除的机制来引入泛型。也就是说java的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是一旦编译完成,所有和泛型有关的数据全部擦除。
为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType, GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class 类中的类型但是又和原始类型齐名的类型,这四种类型实现了Type接口。

读取方法的参数类型

public class TestReflect {

    //测试方法,返回类型与参数都为泛型
    public static Map<String,User> GenericityTest(List<User> list,User user){
        return null;
    }

    public static void main(String[] args) {
        try {
            //先获取到该类
            Class aClass = Class.forName("sml.reflect.TestReflect");
            //获取到测试方法
            Method genericityTest = aClass.getDeclaredMethod("GenericityTest", List.class,User.class);
            //获取到类型参数数组,就是获取方法所有的参数类型
            Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
            for (Type genericParameterType : genericParameterTypes) {
                //输出一下类型参数
                System.out.println(genericParameterType);
                //我们在循环时判断该参数类型,若该参数属于参数化类型
                if(genericParameterType instanceof ParameterizedType){
                    //若属于参数化类型,则获取类型对象的数组,表示此类型的实际类型参数
                    Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                    for (Type actualTypeArgument : actualTypeArguments) {
                        //打印下实际类型参数
                        System.out.println(actualTypeArgument);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

获取返回值的类型

//获取方法所有的参数类型
Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
//获取返回值的参数类型,返回值只有一个,所有不是数组
Type genericReturnType = genericityTest.getGenericReturnType();

在反射中使用 Annotation

在反射的 Class, Method, Field 等函数中,有许多于 Annotation 相关的接口。

这也意味着,我们可以在反射中解析并使用 Annotation。

import java.lang.annotation.Annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
import java.lang.reflect.Method;

/**
 * Annotation在反射函数中的使用示例
 */

//@Retention(RetentionPolicy.RUNTIME)定义MyAnnotation的作用域RUNTIME
@Retention(RetentionPolicy.RUNTIME)
/*
@interface意味着 MyAnnotationMyAnnotation 实现了 java.lang.annotation.Annotation 接口;即MyAnnotationMyAnnotation是一个注释
*/
@interface MyAnnotation {
    String[] value() default "unknown";
}

/**
 * Person类。它会使用MyAnnotation注解。
 */
class Person {
   
    /**
     * empty()方法同时被 "@Deprecated" 和 "@MyAnnotation(value={"a","b"})"所标注
     * (01) @Deprecated,意味着empty()方法,不再被建议使用
     * (02) @MyAnnotation, 意味着empty() 方法对应的MyAnnotation的value值是默认值"unknown"
     */
    @MyAnnotation
    @Deprecated
    public void empty(){
        System.out.println("\nempty");
    }
   
    /**
     * sombody() 被 @MyAnnotation(value={"girl","boy"}) 所标注,
     * @MyAnnotation(value={"girl","boy"}), 意味着MyAnnotation的value值是{"girl","boy"}
     */
    @MyAnnotation(value={"girl","boy"})
    public void somebody(String name, int age){
        System.out.println("\nsomebody: "+name+", "+age);
    }
}

public class AnnotationTest {

    public static void main(String[] args) throws Exception {
       
        // 新建Person
        Person person = new Person();
        // 获取Person的Class实例
        Class<Person> c = Person.class;
        // 获取 somebody() 方法的Method实例
        Method mSomebody = c.getMethod("somebody", new Class[]{String.class, int.class});
        // 执行该方法
        mSomebody.invoke(person, new Object[]{"lily", 18});
        iteratorAnnotations(mSomebody);
       

        // 获取 somebody() 方法的Method实例
        Method mEmpty = c.getMethod("empty", new Class[]{});
        // 执行该方法
        mEmpty.invoke(person, new Object[]{});        
        iteratorAnnotations(mEmpty);
    }
   
    public static void iteratorAnnotations(Method method) {

        // 判断 somebody() 方法是否包含MyAnnotation注解
        if(method.isAnnotationPresent(MyAnnotation.class)){
            // 获取该方法的MyAnnotation注解实例
            MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
            // 获取 myAnnotation的值,并打印出来
            String[] values = myAnnotation.value();
            for (String str:values)
                System.out.printf(str+", ");
            System.out.println();
        }
       
        // 获取方法上的所有注解,并打印出来
        Annotation[] annotations = method.getAnnotations();
        for(Annotation annotation : annotations){
            System.out.println(annotation);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值