zoukankan      html  css  js  c++  java
  • 注解(一)- 基础知识与运行时注解

    在java和android 中,注解的运用非常广泛,很多的类库,第三方框架中都用到了注解。所以我们有必要来熟悉注解的相关知识。

    Annotation,注解(也称为元数据),可以为我们在代码中添加额外的信息,我们也可以很方便的使用这些数据

    当然,在代码中添加额外信息我们最经常使用的是注释(comment),好的注释对于理解代码或逻辑是非常重要的,comment通俗易懂,并且使用一些工具,注释也可以生成专门的文档,但是注解相比注释,拥有更加强大的,不可替代的功能。它可以提供编译期的一些操作,比如类型检查,生成新的文件(包括java文件等)。

    annotation是java5才引入的新特性,它通过将信息和源代码结合在一起,可以提供一些java语言本身无法的表达的额外信息。它的相关内容可以由编译器来测试或者验证,当然,运行时期也可以使用注解来提供额外信息。

    注解的语法其实比较简单,除了多个@符号,其他和java本身语法一样。java 5 在java.lang中内置了三种标准注解。(android中内置的注解更多)。

    • @Override: 表示当前方法其实是覆盖父类的方法,如果有拼写错误等,编译器(和IDE)就可以发出错误提示。
    • @Deprecated:表示该类或方法不建议使用了,未来有可能被废弃或者被移除,如果程序员使用了该类或方法,那么编译器(和IDE)将会发出警告信息。 这里也就就是warning而已,你也可以继续使用,不过建议还是不要使用被Deprecated的API,说不定未来哪个版本就被移除了。
    • @SuppressWarnings:给编译器一条指令,告诉它对范围内的某些类型的警告保持静默。这样编译时就不再输出该警告了。

    看一个简单的demo:

    从截图看到看到,eclipse中对不同类型的annotation,都做了相应的提示。

    并且也可以看到同一个元素上也可以使用多个不同的注解。而SuppressWarnings注解接收的其实是一个数组,例如demo当中的 unchecked,表示是未检查的转换时的警告,而fallthrough则表示在switch块中,某个case没有使用break,而直接流向了下一条case时的警告。从编码习惯上来讲,你屏蔽了fallthrough的警告,也可以告别其他人,case没有使用break,这是因为代码的逻辑,而不是你忘记写了。

    自定义注解

    从以上demo就可以看出,注解很有用,也方便,但是内置的注解不可能满足我们五花八门的需求,所以此时就需要我们来自定义注解了。
    java中内置了四种元注解(meta-annotation),元注解就是负责注解其他注解(这句话好绕啊),来帮助我们自定义注解的。

    • @Target : 表示该注解可以用在什么地方(使用范围),可能的ElementType参数包括:
    • CONSTRUCTOR:构造器的声明。
    • FIELD:域声明(包含enum的实例)。
    • LOCAL_VARIABLE : 局部变量声明。
    • METHOD : 方法声明。
    • PACKAGE: 包声明。
    • PARAMETER: 参数声明。
    • TYPE: 类,接口(包括注解类型)或 enum类型。
    • @Retention: 表示需要在什么级别保存该注解信息(生命周期),可选的RetentionPolicy参数包括:
    • SOURCE:源码级别,注解将被编译器丢弃。
    • CLASS: 在编译器生成的class文件中可用,但是会被VM丢弃。(默认的就是该级别)。
    • RUNTIME: 运行期保留该注解,所以此时可以通过反射机制来读取注解的信息。
    • @Documented:将注解包含在javadoc中。
    • @Inherited:允许子类继承父类的注解。

    具体的关于如何定义自己的注解,几行简单的代码胜过千言万语。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UseCase {
    	public  int id();
    	public String description() default "no description";
    }
    
    public class PasswordUtils {
    	@UseCase(id = 47, description = "password must contain one number")
    	public boolean validatePassword(String password){
    		return true;
    	}
    	@UseCase(id = 48)
    	public String encryptPassword(String password){
    		return "encryptPassword()";
    	}
    	@UseCase(id = 49, description = "new password can't equal previously used ones")
    	public boolean checkouForNewPassword(){
    		return false;
    	}
    }
    

    通过代码可以看出,定义一个注解和定义一个interface非常相似,只不过多了一个@符号罢了。在定义注解时,使用了一些元注解,比如@Target或者 @Retention,它们分别用来表明注解的应用范围,生命周期情况。

    而从语法的角度来看,注解的使用方法,也和public,static或void等修饰符一样,没什么大的差别。

    而定义注解时,可以看到定义体里面包含了一些比较特殊的方法。我们也称之为配置参数,这些方法只能是public或者default访问权限,方法的返回值就是配置参数的类型,并且我们可以为其指定默认值,我们在分析处理注解时,程序或工具就可以利用这些值。

    @UseCase由UseCase.java定义,并且其中包含了两个配置参数id和description,并且它们都是有类型的,配置参数可以使用的类型如下:

    • 所有的基本类型。(int,float,boolean等)。
    • String
    • Class
    • enum
    • Annotation
    • 以上类型的数组

    关于注解元素的问题,我们还需要注意如下几个方面。

    1. 配置参数本身还可以是一个注解,这就说明注解可以嵌套。
    2. 配置参数不能有不确定的值,也就是说,配置参数要么(在定义时)有默认值,要么(在使用时)提供相应的值。并且这个值还不能是null。所以这个是比较尴尬的地方,因此比如String类型的元素,我们不能赋值为null,那么习惯用法就是使用 "";
    3. 我们在使用注解时,采用的是 键值对这种语法,(id = 48),有一个快捷方式,就是注解中如果定义了名为value的配置参数,那么在使用时,如果该参数是唯一需要赋值的一个参数,那么可以不使用键值对,直接在括号内给出value的值就可以了。(value可以是任何合法类型的参数)。
    4. 注解可以嵌套,但是不可以继承。而实际上,当你使用@来定义注解时,默认继承的是java.lang.annotation.Annotation。

    而对于上面我们那个 UseCase的demo,我们写一个程序来进行处理。

    public class UseCaseTracker {
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		List<Integer> useCases = new ArrayList<Integer>();
    		Collections.addAll(useCases, 47, 48, 49, 50);
    		trackUseCases(useCases, PasswordUtils.class);
    	}
    	public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
    		for (Method m : cl.getDeclaredMethods()){
    			// getDeclaredAnnotation  返回指定类型的 注解对象 
    			UseCase uc = m.getDeclaredAnnotation(UseCase.class);
    			if (uc != null){
    				System.out.println("Found UseCase :" + uc.id() + "	" + uc.description());
    				useCases.remove(new Integer(uc.id()));
    			}
    		}
    		
    		System.out.println("--------------------------------------");
    		for (int i : useCases){
    			System.out.println("Warning : Miss use case --" + i);
    		}
    	} 
    
    }
    

    输出结果为:

    Found UseCase :47	password must contain one number
    Found UseCase :48	no description
    Found UseCase :49	new password can't equal previously used ones
    --------------------------------------
    Warning : Miss use case --50
    

    通过上面代码我们可以看出,利用反射,我们可以很好的处理注解,这其实就像处理普通类那样。

    注解的基本知识本身不复杂,那么下面我们看一个复杂点的例子。在android常用的xutils3框架中,包含了一个数据库模块,这个数据库在使用时,就是通过注解来创造表名或列名的,那么下面,我们也自定义一个注解,通过注解来生成javaBean对象的创建数据库表的语句。

    //告诉注解处理器,需要生成一个数据库表
    //这个注解只能用于类,接口,enum
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DBTable {
    	public String name() default "";
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Constraints {
    	//这些元素都有默认值,这样我们就不必强迫程序员必须赋值了
    	boolean primaryKey() default false;
    	boolean allowNull() default true;
    	boolean unique() default false;
    }
    
    /**
     * @author www.yaoxiaowen.com
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SQLString {
    	//定义了元素名为value,在符合条件时,我们使用时,可以直接在括号内输入value的值就ok了。
    	int value() default 0;
    	String name() default "";
    	Constraints constraints() default @Constraints;
    }
    
    /**
     *  SQLInteger 和 SQLString一样,都是要求在javabean上,根据不同的数据类型使用不同的注解
     *  @author www.yaoxiaowen.com
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SQLInteger {
    	String name() default "";
    	//注解嵌套
    	Constraints constraints() default @Constraints;
    }
    
    /**
     * 我们的目的就是为该javabean生成一个创建表的语句
     * @author www.yaoxiaowem.com
     */
    @DBTable(name = "MEMBER")
    public class Member {
    	@SQLString(30)
    	String firstName;
    	
    	@SQLString(50)
    	String lastName;
    	
    	@SQLInteger
    	Integer age;
    	
    	@SQLString(value = 30,
    			constraints = @Constraints(primaryKey = true))
    	String handle;
    	
    	static int memberCount;
    	
    }
    
    public class TableCreator {
    	public static void main(String[] args) throws Exception{
    		
    			StringBuilder createCommand = new StringBuilder();
    			
    			String className = "test.annotation.database.Member";
    			Class<?> cl = Class.forName(className);
    			
    			DBTable dbTable = cl.getAnnotation(DBTable.class);
    			
    			String tableName = dbTable.name();
    			//如果名字为空,就使用类名
    			if (tableName.length() < 1){
    				tableName = cl.getName().toUpperCase();
    			}
    			
    			createCommand.append(
    					"CREATE TABLE " + tableName + "(");
    			
    			List<String> columnDefs = new ArrayList<String>();
    			for (Field field : cl.getDeclaredFields()){
    				String columnName = null;
    				Annotation[] anns = field.getDeclaredAnnotations();
    				if (anns.length < 1){
    					continue;
    				}
    				//这里的写法之所以简单,因为我们每个Field上面最多只有一个 注解
    				if (anns[0] instanceof SQLInteger){
    					SQLInteger sInt = (SQLInteger)anns[0];
    					//没有名字的话,我们就使用Field的名字来做为 列名
    					if (sInt.name().length() < 1){
    						columnName = field.getName();
    					}else {
    						columnName = sInt.name();
    					}
    					
    					columnDefs.add(columnName + " INT " + getConstraints(sInt.constraints()));
    				}
    				
    				if (anns[0] instanceof SQLString){
    					SQLString sString = (SQLString)anns[0];
    					if (sString.name().length() < 1){
    						columnName = field.getName();
    					}else {
    						columnName = sString.name();
    					}
    					
    					columnDefs.add(columnName + " VARCHAR(" + sString.value() 
    								+ ")" + getConstraints(sString.constraints()));
    				}
    				
    				
    				
    			}
    			for (String columnDef : columnDefs){
    				createCommand.append("
    	" + columnDef + ",");
    			}
    			
    			//移除最后的一个逗号
    			String tableCreate = createCommand.substring(0, createCommand.length()-1) + ");";
    			
    			System.out.println("TABLE Creation SQL for " + className + "  is: 
    " + tableCreate);
    				
    	}
    
    	//解析出 Constraints 注解的内容
    	private static String getConstraints(Constraints con){
    		String constraints = "";
    		if (!con.allowNull()){
    			constraints += " Not Null";
    		}
    		if (con.primaryKey()){
    			constraints += " PRIMARY KEY ";
    		}
    		
    		if (con.unique()){
    			constraints += " NNIQUE ";
    		}
    		return constraints;
    	}
    }
    

    输出结果为:

    TABLE Creation SQL for test.annotation.database.Member  is: 
    CREATE TABLE MEMBER(
    	firstName VARCHAR(30),
    	lastName VARCHAR(50),
    	age INT ,
    	handle VARCHAR(30) PRIMARY KEY );
    

    这个demo虽然没有实际的意义,但是仔细分析该demo对于我们理解注解还是比较有帮助的。

    在java 5当中引入 annotation时,java也引入了注解处理工具Annotation Processing Tool (apt),apt是一个可以在编译时使用的命令行工具,但是它是Oracle提供的私有实现,所以该工具在java 8中被移除了,而在java6中,通过 JSR 269 annotation processing facility来规范了自定义注解处理器的这一功能。也有了新的API(javax.annotation.processing),而关于这些内容,我们下一个篇文章再进行介绍。


    作者: www.yaoxiaowen.com

    github: https://github.com/yaowen369

    欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件(yaowen369@gmail.com)联系

    <p >
    		 欢迎转载,转载请注明出处.谢谢
    </p>
    
    
    <script type="text/javascript">
     function    Curgo()   
     {   
         window.open(window.location.href);
     }   
    </script>
    
  • 相关阅读:
    【问题】解决python3不支持mysqldb
    《Craking the Coding interview》python实现---02
    《Craking the Coding interview》python实现---01
    python标准库--functools.partial
    Multimodal Machine LearningA Survey and Taxonomy
    概率热图的绘制--gradcam
    Pytorch 技巧总结(持续更新)
    linux教训
    Destruction and Construction Learning for Fine-grained Image Recognition----论文
    Ubuntu16.04+3090+cuda11.0+cudnnV8+pytorch-nightly
  • 原文地址:https://www.cnblogs.com/yaoxiaowen/p/6750192.html
Copyright © 2011-2022 走看看