您的位置:首页 > 移动开发 > Android开发

android&java注解详解

2016-12-03 18:26 489 查看
通过拜读《java编程思想》中的注解篇,谈谈自己对注解中的理解!

学习android经常会用到注解,但是对于注解也是半懵逼状态,至于它是怎么来的,怎么起作用是什么都不知道;比如熟悉的@Override,@Deprecated……等等只知道是注解,以及其起到的作用,至于它是或不是java中的语法,怎么由来怎么读取的其实是没探究的。本文起到一个抛砖引玉的作用,探索注解的由来以及如何自定义自己的注解

一、注解是什么

       《java编程思想》的定义:注解(也被成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

        看完这个定义如果是没接触注解的我相信很多开发者都处于懵懂状态,也不知道怎么用。其实具体点的就是给类、方法、属性附加一些说明信息,等到想用的时候在读取。而且这个信息(元数据)是同源代码一起结合在一起的,所以可通过java代码获取到注解信息。

        如系统的@Override:说明信息就是:指名这个方法是重载的方法;这个例子没有值不够形象,往下读,等会会有例子,相信很快你就知道了

 

二、定义

      注解的定义很想java定义接口:如   

public @interface CupAnnotation {}


     这样就定义好了一个注解类,是不是特别像接口,但是与接口不一样的是,它里边定义的方法可以有默认值 如:

@Target({ElementType.METHOD, ElementType.FIELD}) //方法、属性都可以和使用注解
@Retention(RetentionPolicy.RUNTIME)  //vm运行期间保留注解 通过反射机制获取注解信息
public @interface CupAnnotation {
int id() default -1;

String color() default "white";

String shaper() default "点";

String methodName() default "defualt";
}


接口中是没有@符号的,而且声明的方法是不会有default ;

而注解是通过@interface来指明这是一个注解类,default “呵呵”是方法的默认值;

默认值的作用:如果有这个没有这个默认值,则在使用注解的使用必须给其赋值,如果有默认值可以不给其赋值,会                          默认使用指定的默认值。如熟悉的@Override 这就是典型的不需要指定值,使用的都是默认值

三、使用

     注解的使用,相信学习android或者java的很熟悉系统中自带的@Override,@Deprecated等等了,但是如何使用自定义注解,怎么给赋值,如何读取这些值很可能还只是一个谜团。
    注解可以与java中的任何修饰符共同作用于方法,如:public、static或void,注解的使用方式几乎与修饰符一模一样。
   这里使用我通过一个demo来讲解;但是在讲解之前先介绍一些元注解。java中系统通过只内置了7种标准的元注解:可以用它们来先定我们自定义的作用
  @Override @Deprecated @SuppressWarning 以及@Target @Retention @Document @Inherited。’  
  这又是什么鬼?细心的你,在自定义自己的注解时是不是有用到@Target和@Retention?
  《java编程思想》的图示:
   


   由于前边三个自定义注解时不怎么用上,我们就不说了,这里比较常用的可能就是@Target和@Retention了。@Target是指明这个注解只能作用于那个属性、类、方法等等上边列出支持的,如果没指明则默认都支持;而@Retention只的是什么级别保存信息,即在运行时期或编译期保留或丢弃;比如RUNTIME则在运行期时可以利用反射机制获取注解信息,如果是其它两个就没法使用反射获取元数据,因为注解已经被编译器在编译阶段丢掉,运行时就没有注解的信息,所以是无法获取到的。
要注意,如果是注解使用多个支持,则是数组要用{}包裹,注解的属性通过“,”分割开:如果不是数组则直接使用“,”分割开就行:如:数组@Test({"str",“23”})   非数组 @Test(test1 = "helloworld",test2=23)
接下来看看demo:

一、声明一个cup的注解

package com.example.zwr.annotationdemo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* author : zhongwr on 2016/12/1
* 用以杯子的注解
*/
@Target({ElementType.METHOD, ElementType.FIELD}) //方法、属性都可以使用注解
@Retention(RetentionPolicy.RUNTIME)  //vm运行期间保留注解 通过反射机制获取注解信息
public @interface CupAnnotation {
int id() default -1;

String color() default "white";

String shaper() default "点";

String methodName() default "defualt";
}
upAnnotation(methodName = "DefualtCup of getColor()") public String getColor() { return "color = defualt"; } public String getId() { return "id = -1"; } public String getShape() { return "shaper = defualt"; }}
定义这个注解值得注意的是下边这两行:

@Target({ElementType.METHOD, ElementType.FIELD}) //方法、属性都可以使用注解
@Retention(RetentionPolicy.RUNTIME)  //vm运行期间保留注解 通过反射机制获取注解信息

说明它的作用,否则在运行时是无法生效的,可以查看java中用到注解的源码,跟进去它们的定义也会有这两个

定义好后,我们声明几个实体类,通过注解来为其说明这个实体类的信息:说明杯子的形状/id/color

cup:为绿色的圆形cup,id值为1 color

/**
* author : zhongwr on 2016/12/1
*/
public class CircleGreenCup {
@CupAnnotation(id = 1)
private int ids;
@CupAnnotation(color = "green")
private String color;
@CupAnnotation(shaper = "圆形")
private String shape;

public String getColor() {
return "color = red";
}
@CupAnnotation(methodName = "circleGreenCup of getId")
public String getId() {
return "id = 1";
}
public String getShape() {
return "shaper = 圆的";
}
}

将cup声明为:红色的正方形cup

/**
* author : zhongwr on 2016/12/1
* 使用声明注解类的默认值
*/
public class SquareRedCup {
@CupAnnotation(id = 2)
private int ids;
@CupAnnotation(color = "Square")
private String color;
@CupAnnotation(shaper = "正方形")
private String shape;

public String getColor() {
return "color = red";
}

public String getId() {
return "id = 2";
}
@CupAnnotation(methodName = "SquareRedCup of getShape()")
public String getShape() {
return "shaper = 正方形";
}
}


默认值:即使用注解的时候不给赋值,前边两个都会相应修改器默认值

package com.example.zwr.annotationdemo.model;

import com.example.zwr.annotationdemo.CupAnnotation;

/**
* author : zhongwr on 2016/12/1
* 使用声明注解类的默认值
*/
public class DefualtCup {
@CupAnnotation
private int ids;
@CupAnnotation
private String color;
@CupAnnotation
private String shape;

@CupAnnotation(methodName = "DefualtCup of getColor()")
public String getColor() {
return "color = defualt";
}

public String getId() {
return "id = -1";
}
public String getShape() {
return "shaper = defualt";
}
}


可以看到:注解修改默认值时可以只指定要修改的默认值,而不需要都修改,如:只修改形状而不修改id和颜色

@CupAnnotation(shaper = "圆形")


此外:注意到我们使用注解赋值方式:直接用的就是注解声明的方法名来赋值的,如注解类Test的方法是 int id();那么在使用赋值的时候就用 如@Test(id=23)来为其赋值

解析注解:即获取注解的信息如下:

package com.example.zwr.annotationdemo;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* author : zhongwr on 2016/12/1
* 获取杯子的注解
*/
public class CupAnnotationUtil {
public static String getAnnotationInfo(Class<?> targetClass) {
StringBuilder annotationInfo = new StringBuilder();
//判断targetClass类声明是否用了注解:如在类声明前用了注解则可通过这个获取类注解的value  举例:@CupAnnotation public class Cupdemo{}
CupAnnotation cupAnnotation = targetClass.getAnnotation(CupAnnotation.class);
annotationInfo.append("类声明用的注解:");
annotationInfo.append("\n");
annotationInfo.append("类 :" + targetClass.getName());
if (null == cupAnnotation) {
annotationInfo.append("类声明没用到注解 :");
annotationInfo.append("\n");
} else {
annotationInfo.append("杯子标签:" + cupAnnotation.id() + " 杯子颜色:" + cupAnnotation.color() + " 杯子形状:" + cupAnnotation.shaper());
}

annotationInfo.append("\n");
annotationInfo.append("方法声明用的注解:");
annotationInfo.append("\n");
//判断targetClass所有方法是否有用到注解
Method methods[] = targetClass.getDeclaredMethods();
if (null != methods) {
int length = methods.length;
for (int i = 0; i < length; i++) {
CupAnnotation cupMethodAnno = methods[i].getAnnotation(CupAnnotation.class);
if (null != cupMethodAnno) {//方法中使用了注解则不会为空
annotationInfo.append("注解方法名:" + cupMethodAnno.methodName());
} else {
annotationInfo.append("没使用注解的方法名:" + methods[i].getName());
}
annotationInfo.append("\n");
}
}

annotationInfo.append("\n");
annotationInfo.append("属性声明用的注解:");
annotationInfo.append("\n");
//判断targetClass所有属性是否有用到注解
Field[] fields = targetClass.getDeclaredFields();
if (null != fields) {
int length = fields.length;
for (int i = 0; i < length; i++) {
CupAnnotation cupFieldAnno = fields[i].getAnnotation(CupAnnotation.class);
if (null != cupFieldAnno) {//方法中使用了注解则不会为空
annotationInfo.append("属性注解信息:");
annotationInfo.append("\n");
annotationInfo.append("属性名:" + fields[i].getName() + " 杯子标签:" + cupFieldAnno.id() + " 杯子颜色:" + cupFieldAnno.color() + " 杯子形状:" + cupFieldAnno.shaper());
} else {
annotationInfo.append("没使用注解的属性名:" + fields[i].getName());
}
annotationInfo.append("\n");
}
}
return annotationInfo.toString();
}
}


一:类声明的注解:通过如下方式获取一个注解类:如果不为null这表示在类声明时用了注解,如果为null则类声明没用注解

CupAnnotation cupAnnotation = targetClass.getAnnotation(CupAnnotation.class)


二:方法声明的注解:通过如下方式获取一个注解类:如果不为null这表示在方法声明时用了注解,如果为null则方法声明没用注解

CupAnnotation cupMethodAnno = methods[i].getAnnotation(CupAnnotation.class);


二:属性声明的注解:通过如下方式获取一个注解类:如果不为null这表示在属性声明时用了注解,如果为null则属性声明没用注解

CupAnnotation cupFieldAnno = fields[i].getAnnotation(CupAnnotation.class);


获取信息方式都是通过这个注解类对象直接调用其方法来获取其值,具体看上边例子的获取。
由于作者是做android 的所以通过android的一个demo来测试:

package com.example.zwr.annotationdemo;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.example.zwr.annotationdemo.model.CircleGreenCup;
import com.example.zwr.annotationdemo.model.DefualtCup;
import com.example.zwr.annotationdemo.model.SquareRedCup;
import com.example.zwr.annotationdemo.model.UnuseCupAnnotation;

public class MainActivity extends Activity implements View.OnClickListener {

private static final String TAG = "MainActivity";
private TextView tvGetAnnotationInfo;
private TextView tvShowAnnotationInfo;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}

private void initViews() {
tvGetAnnotationInfo = (TextView) findViewById(R.id.tv_get_annotation_info);
tvShowAnnotationInfo = (TextView) findViewById(R.id.tv_show_annotation_info);
tvGetAnnotationInfo.setOnClickListener(this);
}

@Override
public void onClick(View v) {
if (v == tvGetAnnotationInfo) {
String annotationContent = CupAnnotationUtil.getAnnotationInfo(DefualtCup.class) + "\n" +
CupAnnotationUtil.getAnnotationInfo(CircleGreenCup.class) + "\n" +
CupAnnotationUtil.getAnnotationInfo(SquareRedCup.class) + "\n" +
CupAnnotationUtil.getAnnotationInfo(UnuseCupAnnotation.class);

Log.d(TAG, annotationContent);
tvShowAnnotationInfo.setText(annotationContent);
}
}
}


当点击按钮后,将解析的注解信息显示在TextView上:信息如下:



懒得录制,就放这么一小段信息,你也可以下载demo自己跑一下就知道了;

这里注意一点:注解类没有继承,但是可以使用组合,组合的方式声明:demo没有说明,感兴趣的可以试试;举个栗子:

注解类1 Test1:
public @interface Test1{
    int id() defualt -1;
    String getTestInfo() defualt "Test";
}

注解类2 Test2:
public @interface Test2{
    int id() defualt -1;
    String getTestInfo() defualt "Test";
    //这里使用组合,使用Test1的注解
    Test1 testInstance() defualt @Test1(id = 1);
}

调用的使用就跟调用方法一样就可以返回一个Test1实例了,如 Test1 test1 = test2.testInstance();

既然是学android的,那么我们学了有什么用处呢?
其实android的用处大了去了,比如我们常见的插件butterKnife,数据库框架OrmLite等等都是通过注解去完成实现的。

扩展:这里只是提到,感兴趣的自行了解:
    java中可通过apt工具去解析注解,以及通过ProcessFiles查找遍历所有文件;可通过字节码获取精确读取class文件以及通过ClassPool修改java文件的内容,这是多么强大的功能,as插件也可以做到修改java文件内容,而不需要读写文件;以上内容可在《java编程思想》一书中的第20章注解的单元测试小节中找到大概是630页左右
as的插件开发请见另一篇博文:
http://blog.csdn.net/zhongwn/article/details/53257263

到这里基本就结束了: 有什么问题大家可以一起探讨,若有理解不对的地方请不吝指正!

demo:http://download.csdn.net/detail/zhongwn/9700895

注解类1 Test1:
public @interface Test1{
    int id() defualt -1;
    String getTestInfo() defualt "Test";
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: