ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

彻底搞定 Java 注解

2021-09-22 22:02:22  阅读:125  来源: 互联网

标签:搞定 Java String value public interface 注解 class


本文内容

1、什么是注解
2、JDK内置注解
3、自定义注解
4、案例实战:模拟ORM,使用反射读取注解

1、什么是注解

Annotation是从JDK5.0开始引入的新技术.

作用

1 Annotation不是程序本身,它是对程序作出解释;

2 可以被其他程序(比如:编译器等)读取 ,根据不同的注解做出不同的处理,比如编译器会检查@Override标记的方法是否在存在于父类、接口中;

格式

@注解名 形式在代码中存在,比如@Override

class Obj extends Object{
    private int id;
    @Override
    public String toString() {
        return "Obj{" +
                "id=" + id +
                '}';
    }
}

在哪里使用?

可以附加在package , class , method , field等上面﹐相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

2、JDK内置注解

@Override

注解 @Override 限定重写父类方法或实现接口方法, 该注解只能加在方法上。

@Deprecated

注解@Deprecated 表示所修饰的元素已过时,通常是因为使用该元素很危险(比如后续版本不支持了)或存在更好的选择。

当在非弃用代码中使用或重写@Deprecated程序元素时,编译器会发出警告。

例:

public class AnnotationTest {
    public static void main(String[] args) {
    	// Date构造方法加了@Deprecated注解
        Date date = new Date(2021, 10, 11);
        new Student().deprecatedMethod();
    }
}

class Student{
    @Deprecated
    void deprecatedMethod(){
        System.out.println("deprecatedMethod is invoked.");
    }
}
D:\>javac AnnotationTest.java
注: AnnotationTest.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。

D:\>javac AnnotationTest.java -Xlint:deprecation
AnnotationTest.java:12: 警告: [deprecation] Date中的Date(int,int,int)已过时
        Date date = new Date(2021, 10, 11);
                    ^
AnnotationTest.java:13: 警告: [deprecation] Student中的deprecatedMethod()已过时
        new Student().deprecatedMethod();
                     ^
2 个警告

@SuppressWarnings

@SuppressWarnings 用于抑制编译器警告

例:


public class AnnotationTest {
	public static void main(String[] args) {
		List list = new ArrayList();
	}
}

程序有警告,如下图
在这里插入图片描述
加了@SuppressWarnings(“rawtypes”)后仍然后警告
在这里插入图片描述
加了"unused" 后警告消失

public class AnnotationTest {
	@SuppressWarnings({ "rawtypes", "unused" })
	public static void main(String[] args) {
		List list = new ArrayList();
	}
}

自定义Annotation

1、使用 @interface 关键字来定义新的 Annotation 类型;
例:

public @interface NormalAnnotation {
   String value();
}

2、自定义注解自动继承了java.lang.annotation.Annotation接口;

Annotation源码如下:

package java.lang.annotation;
public interface Annotation {
   /**
    * 如果指定的对象表示的注释在逻辑上与此注释等价,则为True,否则为false
    */
   boolean equals(Object obj);

   int hashCode();
   /**
    * 返回此注释的字符串表示形式。表示的细节是依赖于实现的
    */
   String toString();
   /**
    * @return 返回此注释的注释类型
    */
   Class<? extends java.lang.annotation.Annotation> annotationType();
}

3、 注解的成员在注解的定义中以无参数方法的形式来声明

方法名定义了成员的名字
返回值定义了类型

public @interface NormalAnnotation {
   /**
    * 成员名为 value
    * 类型为 String
    */
   String value();
}

类型只能是:
八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。

4、可以在定义 Annotation 的成员变量时为其指定初始值, 可以使用default关键字指定成员变量的默认值

public @interface NormalAnnotation {
    // 成员value的默认值为空字符串
    String value() default "";
}

5、 如果只有一个成员,建议使用的成员名为 value;

6、如果定义的注解含有成员,使用时必须指定成员的值,格式是 “成员名 = 成员值”

@NormalAnnotation(value="abc")
public class AnnotationTest {
	...
}

如果在定义时使用了default 设置了某个成员的默认值,则可以不指定该成员的值 ,例:

public @interface NormalAnnotation {
    String value() default "";
}
@NormalAnnotation
public class AnnotationTest {
	...
}

如果只有一个参数成员且成员名称为value,可以省略“value=”,这就是上面第5条建议的价值。例:

@NormalAnnotation("abc")
public class AnnotationTest {
	...
}

7、没有成员定义的注解称为标记;

比如@Override 就是一个标记,源码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

8、包含成员变量的 Annotation 称为元数据注解

注意:我们定义的注解,你的程序要去使用这个注解,同时你其他的程序要去获取这个注解做一些逻辑或业务上的处理,这样才能体现这个注解的价值。

反射

class NewClass {
	public void m1() {
	}
	private void pm1() {
	}
	
	public static void main(String[] args) {
		Class<NewClass> cls = NewClass.class;
		Method[] methods = cls.getMethods();
		methods = cls.getDeclaredMethods();		
		for (Method method : methods) {
		    System.out.println(method.getName());
		}
	}
	private static void printMethods(Method[] methods) {
		for (Method method : methods) {
		    System.out.println(method.getName());
		}
    }
}

// 返回本类中显式定义的所有方法(包括private方法,不包括未覆盖的父类的方法)
Class#getDeclaredMethods 
Class#getMethods 

练习

JDK中的元注解(meta-annotation)

JDK 的元注解是用于修饰其他注解的注解.

可以类比数据库中元数据的概念:

数据库表中有一列一列的数据,而每一列都有列名, 这个列名用来指明这一列是什么数据,这个列名就可以认为是元数据

JDK5.0提供了4个标准的元注解类型,分别是:

@Retention
@Target
@Documented
@Inherited

@Retention

Retention /rɪˈtenʃn/ n. 保持,保留

@Retention只能用于修饰注解, 用来指定该注解的生命周期

@Rentention 包含一个 RetentionPolicy 类型的成员, 使用@Rentention 时必须为该 value 成员指定值:

1、RetentionPolicy.SOURCE 表示在源文件中有效,编译器是使用,编译完直接丢弃这个注释(反编译就看不到了)。

比如 @Override的@Retention就是RetentionPolicy.SOURCE

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

2、RetentionPolicy.CLASS(默认值) 表示该注解在编译时会编译到class文件中, 当运行 Java 程序时, JVM 不会保留该注解。

3、RetentionPolicy.RUNTIME 表示在运行时也有效,即当运行 Java 程序时, JVM 也会保留该注解。程序可以通过反射获取该注解。

@Target

@Target 用来指定注解可以标注在哪里。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * 指定注解可应用在哪几个位置
     */
    ElementType[] value();
}

枚举类ElementType枚举了10个位置:

public enum ElementType {
    /** 类,接口,注解,枚举定义的位置*/
    TYPE,
    /** 实例变量的位置(包括声明枚举常量的位置) */
    FIELD,
    /** 方法定义的位置 */
    METHOD,
    /** 参数位置 */
    PARAMETER,
    /** 构造方法位置 */
    CONSTRUCTOR,
    /** 局部变量位置 */
    LOCAL_VARIABLE,
    /** 声明注解的位置声明*/
    ANNOTATION_TYPE,
    /** 包定义的位置 */
    PACKAGE,
    /**
     * Type parameter declaration
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * Use of a type
     */
    TYPE_USE
}

@Documented

@Documented 标注的注解,在使用javadoc生成标注了该注解的元素的doc文档时,该注解会显示在文档中。

比如@Documented标注了 java.lang.Deprecated注解,而java.util.Date的一个构造方法上加了@Deprecated注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD
				, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

public class Date
    implements java.io.Serializable,Cloneable, Comparable<Date>
{    
	 /**
     * Allocates a <code>Date</code> object and initializes it so that
     * it represents the date and time indicated by the string
     * <code>s</code>, which is interpreted as if by the
     * {@link Date#parse} method.
     *
     * @param   s   a string representation of the date.
     * @see     java.text.DateFormat
     * @see     java.util.Date#parse(java.lang.String)
     * @deprecated As of JDK version 1.1,
     * replaced by <code>DateFormat.parse(String s)</code>.
     */
	@Deprecated
    public Date(String s) {
        this(parse(s));
    }
}

点击查看 java8的doc文档
在这里插入图片描述
@Deprecated 显示在doc文档中

@Inherited

被@Inherited 修饰的注解将具有继承性。

如果某个类使用了被@Inherited 修饰的注解, 则其子类将自动具有该注解。

例:

@MarkablelAnnotation
@NormalAnnotation("abc")
public class AnnotationTest {
    public static void main(String[] args) {
        Annotation[] annotations = AnnotationTest.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        annotations = SubAnnotationTest.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

输出

@com.annotation.NormalAnnotation(value=abc)
@com.annotation.NormalAnnotation(value=abc)

⚠️⚠️注意:想要有输出,必须使用@Retention(RetentionPolicy.RUNTIME)

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface NormalAnnotation {
    String value();
}

public @interface MarkablelAnnotation {
}

@MarkablelAnnotation 没有使用@Retention(RetentionPolicy.RUNTIME) , 所以没有输出。

jdk 8 中注解的新特性

可重复注解、类型注解

(1)可重复注解 @Repeatable

@Repeatable用于指示它注解的注解是可重复的。@Repeatable的值表示包含的注解类型。

@Repeatable源码如下:

/**
 * @Repeatable用于指示它注解的注解是可重复的。@Repeatable的值表示包含的注解类型。
 * @since 1.8
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * 指示重复注解包含的注解类型
     */
    Class<? extends Annotation> value();
}

例:
在一个注解NormalAnnotation上加@Repeatable,@Repeatable的成员-value值为注解A的数组

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NormalAnnotations.class)
public @interface NormalAnnotation {
    String value();
}
public @interface NormalAnnotations {
    NormalAnnotation[] value();
}

⚠️⚠️: @NormalAnnotation的@Target的ElementType值必须包含@NormalAnnotations的@Target的ElementType值

(2)@Target新增2个ElementType类型成员

(2.1)ElementType.TYPE_PARANETER

ElementType.TYPE_PARANETER 表示该注解能写在类型的声明语句处(如:泛型声明)。

@Target({ElementType.TYPE_PARAMETER})
public @interface NormalAnnotation {
    String value();
}
class UserService<@NormalAnnotation T> {
    private T t;
    public <@NormalAnnotation T> void setT(T t) {
        List<T> list = new ArrayList<>();
    }
}

具体用在上面地方?不知道! 我搜索了SpringFramework、SpringBoot2、MyBatis的源码都没有搜到使用TYPE_PARAMETER的地方。

(2.2)ELementType. TYPE_USE

ELementType. TYPE_USE表示该注解能写在使用类型的任何语句中。

class UserService<@NormalAnnotation T>{
    @NormalAnnotation
    private T t ;
    public <@NormalAnnotation T> void setT(@NormalAnnotation T t){
        List<@NormalAnnotation T> list = new ArrayList<>();
    }
}

在这里插入图片描述
在@Target的成员中添加一个ElementType.TYPE_USE, 编译错误消失

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE,ElementType.TYPE_PARAMETER})
public @interface NormalAnnotation {
    String value() default "";
}

案例实战

现在我们想着想提供基本的对象/关系映射功能,能够自动生成数据库表。

一种方案:使用 XML 文件的方式提供对象/关系映射信息。

更好的方案:使用注解提供对象/关系映射信息,注解看上去更直接和方便,我们可以把映射信息(注解)都保存在 JavaBean 源文件中。

为此我们需要定义一些注解:
1、用于“映射JavaBean到数据表的注解”;
2、用于“映射JavaBean的属性到列的注解”;

1、定义“映射JavaBean到数据表的注解”

/**
 * 类的注解,将类映射到表
 */
@Target(ElementType.TYPE) // Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
  // 类对应的表名称
  public String name() default "";
} 

2、定义 “映射JavaBean的实例变量到列的注解”

/**
 * 列的限制属性
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
  // 实例变量对应的列是否为主键
  boolean primaryKey() default false;
  // 实例变量对应的列是否允许为空
  boolean allowNull() default true;
  // 实例变量对应的列的值是否唯一
  boolean unique() default false;
} 


/**
 * 注解int/Integer类型的变量
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
  // 实例变量对应的列名称
  String name() default "";
  // 实例变量对应的列的限制属性
  Constraints constraints() default @Constraints;
}


/**
 * 注解 String 类型的实例变量
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
  // 实例变量对应字段的长度
  int value() default 0;
  // 实例变量对应字段的名称
  String name() default "";
  Constraints constraints() default @Constraints;
}

定义一个Member类,使用我们自定义的注解


@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;
    // get/set 方法略...
}

解析注解的成员,拼接成SQL的DDL语句

public class TableCreator {

    /**
     * @param args 类全限定名
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        checkParam(args);
        for (String className : args) {
            Class<?> cl = Class.forName(className);
            // 1 根据class类上的注解,获取表名
            String tableName = getTableName(cl);
            if (tableName == null) continue;

            // 2 根据class中各字段上的注解,生成定义列的语句
            List<String> columnDefs = createColumnDefs(cl);

            System.out.println("Table Creation SQL for " + className + " is :");
            printCreateTableDDL(tableName, columnDefs);
        }
    }

    private static void printCreateTableDDL(String tableName, List<String> columnDefs) {
        StringBuilder sqlCommandBuilder = new StringBuilder("CREATE TABLE " + tableName + "(");
        for (String columnDef : columnDefs) {
            sqlCommandBuilder.append("\n    " + columnDef + ",");
        }
        // Remove trailing comma
        String tableCreate = sqlCommandBuilder.substring(0, sqlCommandBuilder.length() - 1) + "\n);";
        System.out.println(tableCreate);
    }

    private static List<String> createColumnDefs(Class<?> cl) {
        List<String> columnDefs = new ArrayList<String>();
        for (Field field : cl.getDeclaredFields()) {
            Annotation[] fieldAnns = field.getDeclaredAnnotations();
            if (fieldAnns.length < 1) {
                continue;
            }
            Annotation firstFieldAnn = fieldAnns[0];
            String columnName = null;
            if (firstFieldAnn instanceof SQLInteger) {
                SQLInteger sInt = (SQLInteger) firstFieldAnn;
                // Use field name if name not specified
                if (sInt.name().length() < 1)
                    columnName = field.getName().toUpperCase();
                else
                    columnName = sInt.name();
                columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
            }
            if (firstFieldAnn instanceof SQLString) {
                SQLString sqlStr = (SQLString) firstFieldAnn;
                // Use field name if name not specified.
                if (sqlStr.name().length() < 1)
                    columnName = field.getName().toUpperCase();
                else
                    columnName = sqlStr.name();
                columnDefs.add(columnName + " VARCHAR(" + sqlStr.value() + ")" + getConstraints(sqlStr.constraints()));
            }
        }
        return columnDefs;
    }

    /**
     * @param cl
     * @return
     */
    private static String getTableName(Class<?> cl) {
        DBTable dbTable = cl.getAnnotation(DBTable.class);
        if (dbTable == null) {
            System.out.println("No DBTable annotations in class " + cl.getName());
            return null;
        }
        String tableName = dbTable.name();
        // If the name is empty, use the Class name:
        if (tableName.length() < 1) {
            tableName = cl.getName().toUpperCase();
        }
        return tableName;
    }

    private static void checkParam(String[] args) {
        if (args.length < 1) {
            System.out.println("arguments: annotated classes");
            System.exit(0);
        }
    }

    private static String getConstraints(Constraints con) {
        String constraints = "";
        if (!con.allowNull())
            constraints += " NOT NULL";
        if (con.primaryKey())
            constraints += " PRIMARY KEY";
        if (con.unique())
            constraints += " UNIQUE";
        return constraints;
    }
}

标签:搞定,Java,String,value,public,interface,注解,class
来源: https://blog.csdn.net/dingshuo168/article/details/120395397

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有