Java高级特性之注解与反射

一、注解

注解在开发中的作用不言而喻,目前比较流行的开源框架基本都用到了注解,本文结合Retrofit框架的注解来学习和总结,手写注解绑定框架

annotation注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据,不会影响代码的实际逻辑,仅仅起到辅助性的作用

内置注解

比较常见的内置注解:

@Override 此注解只适用于修饰方法,主要用于在子类中覆盖父类中的方法

@Deprecated 此注解可以修饰方法、属性、类,用来标志被弃用的代码

@SuppressWarnings 用于忽略编译器的警告信息

元注解

在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 metaannotation(元注解)。

一般的,我们在定义自定义注解时,需要用到@Retention、@Target、@Inherited、 @Documented这几个元注解。

@Retention

注解指定标记注解的存储方式,有三个取值

RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略,编译时被丢弃,可以用作语法检查

RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 JVM会忽略,可以配合APT用来生成代码,比如ARouter、Butterknifer等框架。

RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它,可以结合反射获取注解中的所有信息 ,比如Retrofit框架。

@Target

注解标记另一个注解,定义注解可以用于什么对象取值范围是ElementType类型,定义如下

ElementType.ANNOTATION_TYPE - 可以应用于注解类型,注解的注解

ElementType.CONSTRUCTOR - 可以应用于构造函数

ElementType.FIELD - 可以应用于字段或属性

ElementType.LOCAL_VARIABLE - 可以应用于局部变量

ElementType.METHOD - 可以应用于方法级注解

ElementType.PACKAGE - 可以应用于包声明

ElementType.PARAMETER - 可以应用于方法的参数

ElementType.TYPE - 可以应用于类的任何元素

@Inherited

表示允许子类继承父类中定义的注解。 如果一个子类想获取到父类上的注解信息,那么必须在父类使用的注解上面加上@Inherited

@Documented

表明这个注解应该被 javadoc工具记录,默认情况下javadoc是不包括注解的,但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理,所以注解类型信息也会被包括在生成的文档中,是一个标记注解,没有成员。

@Documented //表明这个注解应该被javadoc工具记录
@Target(METHOD) //允许在方法上使用该注解
@Retention(RUNTIME) //注解保留在运行时
public @interface GET {
  //default声明参数默认值,一般参数名为value,在使用的时候可以把value省略掉
  String value() default "";
}

二、什么是反射?

假如你写了一段代码:Object o=new Object();

这个时候,我们明确知道Object是什么类,并且能够获得此类的引用,于是通过new直接对这个类进行实例化,之后使用这个类对象进行操作。

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用JDK 提供的反射API进行反射调用。

反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性。

Java反射机制主要在运行时提供了以下功能:

- 实例化任意一个类的对象

- 调用任意一个对象的方法(属性)

- 获取任意对象的属性,并且能改变对象的属性

- 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
Java 的动态就体现在这,通过反射可以实现动态装配、动态代理等,但是反射的过度使用会严重消耗系统资源。
 

Class

反射始于ClassClass是一个类,封装了当前对象所对应的类的信息。一个类中有属性,方法,构造器等,比如说 有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是 Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。

Class 类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。 对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class 实例

获取反射中的Class对象

在 Java API 中,获取 Class 类对象有三种方法:

第一种:使用 Class.forName 静态方法当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象

Class clz = Class.forName("java.lang.String");
第二种: 通过类名获取,这种方法只适合在编译前就知道操作的Class
Class clz = String.class;

第三种:调用对象的getClass方法

String str = new String("Hello World");
Class clz = str.getClass();

通过反射创建类对象

通过反射来创建对象主要有两种方式。

第一种:通过 Class 对象的 newInstance() 方法,这种方法只能使用默认的无参构造方法

Class<?> c = String.class;
Object str = c.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法,这种方法可以选择特定构造方法

先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。

//获取Apple所对应的Class对象 
Class clz = Apple.class;
//获取Apple类带两个参数的构造器
Constructor constructor = clz.getConstructor(String.class, int.class);
//根据构造器创建实例 
Apple apple = (Apple)constructor.newInstance("红富士", 10);

通过反射获取类属性、方法、构造器

通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性,而getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性

Class clz = Apple.class;
//获得命名的公共字段
Field[] fields = clz.getField(String name);
//获得Apple类的所有公共字段
Field[] fields = clz.getFields();
//获得Apple类声明的命名的字段
Field[] fields = clz.getDeclaredField(String name);
//获得Apple类声明的所有字段
Field[] fields = clz.getDeclaredFields();

获取方法信息的方法

//使用特定的参数类型,获得命名的公共方法
Method getMethod(String name, Class[] params)  
//获得类的所有公共方法
Method[] getMethods() 
//使用特写的参数类型,获得类声明的命名的方法 
Method getDeclaredMethod(String name, Class[] params)
//获得类声明的所有方法
Method[] getDeclaredMethods()

获取构造器的方法

//获得使用特殊的参数类型的public构造函数(包括父类)
Constructor getConstructor(Class[] params) 
//获得类的所有公共构造函数 
Constructor[] getConstructors() 
//获得使用特定参数类型的构造函数(包括私有) 
Constructor getDeclaredConstructor(Class[] params) 
//获得类的所有构造函数(与接入级别无关)
Constructor[] getDeclaredConstructors() 

获取类属性类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

反射获取泛型真实类型

当我们对一个泛型类进行反射时,需要得到泛型中的真实数据类型,来完成如json反序列化的操作,此时需要通过 Type 体系来完成,Type 接口包含了一个实现类(Class)和四个实现接口,他们分别是:

GenericArrayType:该接口表示一种数组类型,比如List[]、Map[],此接口会作为Type的实现;

public class TestType<T> {
  List<String>[] lists;

  public static void main(String[] args) throws Exception{
     Field f = TestType .class.getDeclaredField("lists");
     GenericArrayType genericType =(GenericArrayType)f.getGenericType(); 
     System.out.println(genericType.getGenericComponentType());
 }
}

ParameterizedType:该接口表示参数化类型,具体的泛型类型,如 Collection<String>;

public class TestType {
   Map<String, String> map;

   public static void main(String[] args) throws Exception {
       Field f = TestType.class.getDeclaredField("map");
       System.out.println(f.getGenericType());//java.util.Map<java.lang.String, java.lang.String> 
       ParameterizedType pType = (ParameterizedType) f.getGenericType(); 
       System.out.println(pType.getRawType()); // interface java.util.Map 
       for (Type type : pType.getActualTypeArguments()) { 
           System.out.println(type); // 打印两遍: class java.lang.String 
       } 
   }         
}

TypeVariable<D>:该接口是各种类型变量的公共高级接口,可以表示泛型声明的参数类型(不存在的类型),如 class ClassName<T>和Collection<T>,这里的T就是一个类型变量;

    public class TestType<K extends Comparable & Serializable, V> {
        K key;
        V value;

        public static void main(String[] args) throws Exception { 
            // 获取字段的类型
            Field fk = TestType.class.getDeclaredField("key");
            Field fv = TestType.class.getDeclaredField("value");
            TypeVariable keyType = (TypeVariable)fk.getGenericType();
            TypeVariable valueType = (TypeVariable)fv.getGenericType();
            // getName 方法
            System.out.println(keyType.getName());   // K
            System.out.println(valueType.getName()); // V 
            //getGenericDeclaration 方法
            System.out.println(keyType.getGenericDeclaration());   // class com.test.TestType
            System.out.println(valueType.getGenericDeclaration()); // class com.test.TestType
            // getBounds 方法
            System.out.println("K 的上界:"); // 有两个
            for (Type type : keyType.getBounds()) {  //interface java.lang.Comparable
                System.out.println(type);  //interface java.io.Serializable
            }
            System.out.println("V 的上界:"); //没明确声明上界的, 默认上界是 Object
            for (Type type : valueType.getBounds()) { // class java.lang.Object
                System.out.println(type);
            }
        }
    }

WildcardType:表示一个通配符类型表达式,如 ?、? extends Number 或 ? super Integer。 

public class TestType {
    private List<? extends Number> a; // 上限
    private List<? super String> b; //下限

    public static void main(String[] args) throws Exception {
        Field fieldA = TestType.class.getDeclaredField("a");
        Field fieldB = TestType.class.getDeclaredField("b"); // 先拿到范型类型
        ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();
        ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();
        // 再从范型里拿到通配符类型
        WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];
        WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];
        // 方法测试
        System.out.println(wTypeA.getUpperBounds()[0]); // class java.lang.Number
        System.out.println(wTypeB.getLowerBounds()[0]); // class java.lang.String
        // 看看通配符类型到底是什么, 打印结果为: ? extends java.lang.Number
    }
 }

反射的原理

java类的执行需要经历以下过程:
编译:java文件编译后生成.class字节码文件
加载:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例
验证:格式(class文件规范) 语义(final类是否有子类) 操作
准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值,此处不是用户指定的初值。
解析:符号引用转化为直接引用,分配地址
初始化:有父类先初始化父类,然后初始化自己;将static修饰代码执行一遍,如果是静态变量,则用户指定值覆盖原有初值;如果是代码块,则执行一遍操作。

Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的,Java在编译之后会生成一个 class文件,反射通过 字节码文件找到其类中的方法和属性等。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永琪-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值