先啰嗦几句
android的开发中数据库操作肯定是一个绕不过去的坎,当然如果你非得用什么骚操作做数据存储这件事,我只能说少年,请继续你的表演。但是如果使用数据库操作数据存储,我们的选择也就那么两种,自己撸一个原生的和使用第三方框架,自己撸一个原生的有点low了,而且解耦和后期维护方面都存在一些不便,那我们的选择可能就是借用第三方的开源框架了。那么问题来了,第三方框架里面到底是个什么实现思路,它怎么做到解耦和易扩展的?装逼装到这里,接下来我们来看看正主——注解。
注解是什么?
先看下百度百科上的定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
大白话的说法是什么呢?
说白了就是一种代码层级的标注,只是它是一种可以通过某些手段进行解析的标记。如果不对它进行解析,那它在我们使用时给注释没有任何区别。当然,如果是元注解,编译器会根据元注解做相应处理。
注解的分类
jdk注解
image.png@SuppressWarnings 再程序前面加上可以在javac编译中去除警告--阶段是SOURCE
@Deprecated 带有标记的包,方法,字段说明其过时----阶段是SOURCE
@Override 打上这个标记说明该方法是将父类的方法重写--阶段是SOURCE
接下来 image.png
@Override
限定重写父类方法。对于子类中被@Override 修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在,则报错。@Override 只能作用于方法,不能作用于其他程序元素。这种操作在继承关系的父类和子类里面很常见。
@Deprecated
用于表示某个程序元素(类、方法等)已过时。如果使用被@Deprecated修饰的类或方法等,编译器会发出警告。这个元素在实际编程中还是有那么一丢丢作用的,比如:有一个方法我们做了重构,弃用它了,但是我们不确定将来是不是还要换回这个方法,所以删除这个方法不是很合适,这个时候我们就可以使用这个注解去标记了。
@SuppressWarning
抑制编译器警告。指示被@SuppressWarning修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法.....)取消显示指定的编译器警告。例如,常见的@SuppressWarning(value="unchecked")。像这类操作一般都是为那些风骚加警告消除强迫症的少年准备的,我特么都告诉你这个方法被弃用了,你还非得调用,那好吧,消除编译器警告你就用这玩意吧。
@SafeVarargs
@SafeVarargs是JDK 7 专门为抑制“堆污染”警告提供的。好吧,我表示这玩意我也没用过,喜欢自己去了解去吧。
在上文我们其实留了一个小悬念,SOURCE这个到底是什么鬼?
别急,容小生细细到来。
自定义注解
jdk提供的这些注解,在java代码里面我们很常见,但是如果只是这样的话好像并没有什么太大的卵用 image.png 你们是不是想嘲讽我了,放心我不会给你们这个机会的。 image.png装逼完了,上正菜。
元注解
在定义注解(Annotation)时,也可以使用JDK提供的元注解来修饰Annotation定义。JDK提供了如下4个元注解(注解的注解,不是上述的”元数据Annotation“):
@Retention用于指定注解的保留时间,包含一个名为“value”的成员变量,这个成员变量的取值是RetentionPolicy枚举:
- RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器编译时,直接丢弃这种Annotation。
- RetentionPolicy.CLASS:编译器把Annotation记录在class文件中。当运行Java程序时,JVM中不再保留该Annotation。
- RetentionPolicy.RUNTIME:编译器把Annotation记录在class文件中。当运行Java程序时,JVM会保留该Annotation,程序可以通过反射获取该Annotation的信息。
@Target用于指定注解可以修饰哪些程序元素,同样包含一个名为“value”的成员变量,取值为ElementType的枚举:
- ElementType.TYPE:能修饰类、接口或枚举类型
- ElementType.FIELD:能修饰成员变量
- ElementType.METHOD:能修饰方法
- ElementType.PARAMETER:能修饰参数
- ElementType.CONSTRUCTOR:能修饰构造器
- ElementType.LOCAL_VARIABLE:能修饰局部变量
- ElementType.ANNOTATION_TYPE:能修饰注解
- ElementType.PACKAGE:能修饰包
@Documented如果使用该注解,那么在使用javadoc生成API文档后所有使用该注解的类都会有文档说明
@Inherited用于指定注解的继承性,使用该注解修饰的注解可以被继承
package com.demo2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyTag{
}
package com.demo2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyTag{
}
package com.demo2
@MyTag
public class Base {
}
package com.demo2;
//SubClass只是继承了Base类
//并未直接使用@MyTag注解修饰
public class SubClass extends Base {
public static void main(String[] args) {
System.out.println(SubClass.class.isAnnotationPresent(MyTag.class));
}
}
有上面的例子,注解的基本使用我就不凑字数了。注解的基本使用我们到这里就算说完了,但是只是这样的定义一下好像不太爽,下面来duang的爽一下
image.png
注解解析
注解的解析其实在“@Retention”这个属性的声明中已经为我们的解析埋下铺垫了,根据RetentionPolicy.CLASS和RetentionPolicy.RUNTIME两种类型可分别使用反射解析和自定义注解解析器进行解析。
反射解析
反射解析很简单,我们直接上代码。
@Documented
@Inherited
//该注解可以作用于方法,类与接口
@Target({ElementType.METHOD,ElementType.TYPE})
//JVM会读取注解,所以利用反射可以获得注解
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
//定义成员变量
//成员变量可以通过default指定默认值
//如果成员变量不指定默认值的情况下
//我们在引用接口时则必须给没有默认值的成员变量赋值
String name() ;
int age() default 18 ;
}
@TestAnnotation(name="I'm class annotation")
public class Test {
@TestAnnotation(name="I'm method annotation")
public static void showAnnotation(){
}
}
public static void main(String[] args) {
//解析注解
//获得我们需要解析注解的类
Class<Test> clz = Test.class;
//解析Class
//由于我们的注解是可以给类使用的,所以首先判断类上面有没有我们的注解
//判断类上面是否有注解
boolean clzHasAnnotation = clz.isAnnotationPresent(TestAnnotation.class);
if(clzHasAnnotation){
//类存在我们定义的注解
//获得注解
TestAnnotation clzAnnotation = clz.getAnnotation(TestAnnotation.class);
//输出注解在类上的属性
System.out.println("name="+clzAnnotation.name()+"\tage="+clzAnnotation.age());
}
//解析Method
//两种解析方法上的注解方式
//获得类中所有方法
Method[] methods = clz.getMethods();
//第一种解析方法
for(Method m : methods){
//获得方法中是否含有我们的注解
boolean methodHasAnnotation = m.isAnnotationPresent(TestAnnotation.class);
if(methodHasAnnotation){
//注解存在
//获得注解
TestAnnotation methodAnnotation = m.getAnnotation(TestAnnotation.class);
System.out.println("name="+methodAnnotation.name()+"\tage="+methodAnnotation.age());
}
}
//第二种解析方式
for(Method m : methods){
//获得方法上所有注解
Annotation[] annotations = m.getAnnotations();
//循环注解
for(Annotation a : annotations){
//如果是我们自定义的注解
if(a instanceof TestAnnotation){
//输出属性,需要强制装换类型
System.out.println("name="+((TestAnnotation)a).name()+"\tage="+((TestAnnotation)a).age());
}
}
}
}
通过这种方式我们可以解析得到被注解类的名称,属性名等相关信息,试想一下,如果我们把这种技术手段运用到我们数据库存储的过程中去,是不是省去了很多麻烦事,比如建表不需要传一些复杂的列信息和默认值等,我们只需要传对应的class类就ok,其他的由注解和反射去实现。
image.png
你以为我装逼到这里就为止了,我只能告诉你……
image.png
我还得再装一波。这种反射解析如果业务量不重或者性能要求不那么高的时候,这个解决方案已经算是相当炫酷了。但是我们做java系列的都知道,反射对性能的影响还是比较大的,你要问我具体数据?好吧,其实我也不知道。但是反射比方法调用确实更影响性能这是不争的事实。那么我们如果业务量大并且性能要求相对较高的情况下是不是就不能使用注解这种骚操作呢?肯定不是,如果是的话,我还怎么往下装逼呢。我的人生格言可是妹子可以不撩,但装逼可是生生不息的。闲话少说,开始另一波装逼的旅程……
网友评论