【年薪百万之IT界大神成长之路】注解都玩不溜,还怎么称霸IT界(入门篇)!!!

本文详细介绍了Java注解的使用,包括元注解、内置注解和自定义注解的创建与应用。通过示例展示了注解如何在运行时通过反射获取信息,以及在代码中如何使用自定义注解进行注解处理。同时,文章还探讨了Java和javax包的区别与联系。
摘要由CSDN通过智能技术生成

愿你如阳光,明媚不忧伤。

 


1. 什么是注解

Annotation(注解)也叫元数据(描述数据属性的信息)用@interface表示。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。简单来说注解其实就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理,以便于其他工具补充信息或者进行部署。但注解不会改变代码逻辑

 


2. Java 提供的元注解(第四节自定义注解中有用到)

meta-annotation(元注解)的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation 类型作说明,这些类型和它们所支持的类在java.lang.annotation包中可以找到。

注解(annotations)
说明(explain)
Target对象范围: packages,types,类型成员,方法参数,本地变量
Retention保留期:源码保留,class保留(默认),运行时保留
Documented文档: 被修饰的注解类将被javadoc工具提取成文档,可以定制Javadoc不支持的文档属性
Inherited继承:父类注解会被子类继承得到,如果某个父类使用了@Xxx,其子类将自动被@Xxx修饰(只有@Target被定义为ElementType.TYPE时有效)
Repeatable(1.8)可重复:表示在同一个位置重复相同的注解,相当于一个容器注解(数组)
Native(1.8)本地:可以从本地代码引用定义常量值的字段(字段是一个常量)
  • 示例
******************* @Target - ElementType ******************
/* 取值(ElementType)有:
    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述属性
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
    8.ANNOTATION_TYPE:用于给一个注解进行注解
    9.TYPE_PARAMETER(1.8)用于类型变量的声明语句中
    10.TYPE_USE(1.8)用于标注类型的任意语句中(不包括class)
    11.MODULE(9)用于模块声明 */
******************* @Retention - RetenionPolicy *******************
/* 取值(RetenionPolicy)有:
    1.CLASS 编译器把该注解记录在class文件中,可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到。这是默认值!
    2.RUNTIME 编译器把该注解记录在class文件中。当运行java程序时,JVM可获取注解信息,程序可以通过反射获取该注解信息,实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME
    3.SOURCE 该注解只保存在源代码中,编译器直接丢弃该注解,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到 */
package com.example.annotation.controller;

import java.lang.annotation.*;

public class MetaAnnotation {
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    @Documented
    @Inherited
    public @interface Demo {
        // 可以为成员变量指定初始值
        public String value() default "";
    }

    @Target({ElementType.PACKAGE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DemoTable {
        public String tableName() default "className";
    }

    @interface Persons {
        Person[] value();
    }

    @Repeatable(Persons.class)
    @interface Person {
        String role() default "";
    }

    @Person(role = "IT")
    @Person(role = "God")
    @Person(role = "Road")
    public class SuperMan {

    }

}

 


3. Java 提供的基本内置注解

注解(annotations)
说明(explain)
Override限定父类重写方法:当子类重写父类方法时,子类可以加上这个注解,确保子类确实重写了父类的方法,避免出现低级错误
Deprecated标示已过时:表示某个程序元素类,方法等已过时,当其他程序使用已过时的类,方法时编译器会给出警告(删除线)
SuppressWarnings抑制编译器警告:被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告
SafeVarargs抑制堆污染警告1: 告诉编译器可变长参数中的泛型是类型安全的
FunctionalInterface函数式接口2:用来指定该接口是函数式接口,保证这个接口只有一个抽象方法,方便转换为 Lambda 表达式
  • 示例
******************* @SuppressWarnings - Arguments ******************
/*	1.deprecation:使用了不赞成使用的类或方法时的警告;
	2.unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 
	3.fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
	4.path:在类路径、源文件路径等中有不存在的路径时的警告; 
	5.serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告; 
	6.finally:任何 finally 子句不能正常完成时的警告; 
	7.all:关于以上所有情况的警告。*/
package annotation;

import java.util.Arrays;
import java.util.List;

public class InternalAnnotation {
	// 方法名会有删除线表示
    @Deprecated
    public void say() {
        System.out.println("Noting has to say!");
    }

    public void speak() {
        System.out.println("I have a dream!");
    }

    @SuppressWarnings(value = { "deprecation" })
    public void test() {
        say();
        speak();
    }

    @SafeVarargs
    static void unsafe(List<String>... stringLists) {
        Object[] array = stringLists;
        List<Integer> tmpList = Arrays.asList(66);
        // 语义无效,但编译时没有警告
        array[0] = tmpList;
        // ClassCastException在运行时出现!
        String s = stringLists[0].get(0);
    }

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }

}

 


4. 自定义注解

4.1 基本语法

注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。

  • 注解声明
public @interface CustomAnnotation {
}

4.2 注解类型元素的实现

根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。

  • 注解类型元素
    1.访问修饰符必须为public,不写默认为public;
    2.该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
    3.该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
    4.() 不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
    5.default代表默认值,值必须和第2点定义的类型一致;
    6.如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
public @interface CustomAnnotation {
	public String name();
	int age() default 18;
	int[] array();
}

4.3 注解自定义注解

一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!

// 指定Target @CustomAnnotation 被限定只能使用在类、接口或方法上面
@Target(value = { ElementType.TYPE, ElementType.METHOD })
// 指定Retention @CustomAnnotation 在运行时有效可以被读取到
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomAnnotation {
    public String name();
    int age() default 18;
    int[] array();
}

4.4 自定义注解的配置使用

首先,需要定义一个注解、和一个供注解修饰的简单Java类。

  • 上文中定义好的注解CustomAnnotation的@Target定义为ElementType.TYPE, ElementType.METHOD,那么它书写的位置应该在方法定义的上方或者是类的上方;
  • 在CustomAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以元素名=元素值的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用,号分割;
  • 特殊语法
    1.如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!
    2.如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)
    3.如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!
    4.如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。
public class AnnotationTest {
    String name;
    
    public static void main(String[] args) {
        AnnotationTest a = new AnnotationTest();
        a.person();
    }
    
    @CustonAnnotation(name = "Ouseki", array = { 1, 2, 3 })
    public void person() {
        System.out.println(name);
    }

}
--------------------------------------------------------------------
【console】
	null

运行上面的程序会发现,输出的结果为null,虽然配置了自定义注解也赋值了,但是却没有获取到值。因此我们需要进一步操作。

4.5 反射操作获取注解

只有当注解的保留期处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。

  • 反射实例
    1.如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!
    2.isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是专门判断该元素上是否配置有某个指定的注解;
    3.getAnnotation(Class<A> annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
    4.反射对象上还有一个方法getAnnotations(),该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。

    了解更多 → 【Reflection】阅读过本文之后,反手在简历上写下:熟悉Java的反射机制,不服来问!
public class AnnotationTest {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @CustonAnnotation(name = "Ouseki", array = { 1, 2, 3 })
    public void person() {
        System.out.println(name);
    }

    public static void main(String[] args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, InstantiationException {
        try {
            // 获取AnnotationTest的Class对象
            Class<?> annotationTestClass = Class.forName("com.ITGodRoad.controller.AnnotationTest");
            // 获取AnnotationTest的Method对象
            Method annotationTestMethod = annotationTestClass.getMethod("person");

            if (annotationTestMethod.isAnnotationPresent(CustonAnnotation.class)) {
                System.out.println("AnnotationTest类上配置了CustonAnnotation注解!");
                // 获取该元素上指定类型的注解
                CustonAnnotation custonAnnotation = annotationTestMethod.getAnnotation(CustonAnnotation.class);
                System.out.println("name: " + custonAnnotation.name() + ", age: " + custonAnnotation.age() + ", number: " + custonAnnotation.array()[0]);
                // 实例化对象:newInstance()从jdk1.9已废弃,因为绕过了编译时异常;推荐使用下面的方法
                Object obj = annotationTestClass.getDeclaredConstructor().newInstance();
                Method setMethod = annotationTestClass.getMethod("setName", String.class);
                setMethod.invoke(obj, custonAnnotation.name());
                // 执行person()方法,查看注解的值是否已经成功注入到类中
                ((AnnotationTest) obj).person();
            } else {
                System.out.println("AnnotationTest类上没有配置CustonAnnotation注解!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

}
--------------------------------------------------------------------
【console】
	AnnotationTest类上配置了CustonAnnotation注解!
	name: Ouseki, age: 18, number: 1
	Ouseki

 


【每日一面】

java 和 javax 的区别与联系

java 是Java的API(Application Programming Interface)包,java是核心包。java类库是java发布之初就确定了的基础库。
javax 也是Java的API(Application Programming Interface)包,javax的x是extension的意思,也就是扩展包。javax类库是在java基础库上面增加的一层东西,就是为了保持版本兼容要保存原来的,但有些东西有了更好的解决方案,所以,就加上些,典型的就是awt(Abstract Windowing ToolKit)和swing。
将扩展从javax包移动到java包太麻烦了,最终会破坏一堆现有的代码。所以实际上java和javax没有区别,都是java标准API的一部分


  1. 其实很好理解,就是把不带泛型的对象赋给一个带泛型的对象,为什么不行?很简单,因为不带泛型的话,默认会给泛型设定为object,意思就是什么类型都可以往里面塞,那你一个不带泛型的怎么可能给一个带泛型塞呢。
    注意:可变参数更容易引发堆污染异常,因为java不允许创建泛型数组,可变参数恰恰是数组。
    ↩︎

  2. 什么是函数式?如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法)接口体内只能声明常量字段和抽象方法,并且被隐式声明为public,static,final。接口里面不能有私有的方法或变量。 ↩︎

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值