JAVA面向对象-----注解

Annotation注解

什么是注解?

注解:Annotation是从JDK5.0开始引入的新技术。可以将它理解成“元数据”,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:

@Override
public String toString() {
    return "This is String Representation of current object.";
}

上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。

总结什么是注解:

1):不是程序本身,可以对程序作出解释。(这一点,跟注释没什么区别)

2): 可以被其他程序(比如:编译器等)读取。(注解信息处理流程,是注解和注释的重大区别 。如果没有注解信息处理流程,则注解毫无意义)

预置注解

  1. @Override
    :空注解,用于标记那些覆盖父类方法的方法,如果父类没有这个方法,或者复写的方法访问权限比父类的权限小,编译器就会报错;
  2. @Deprecated : 空注解,用于标记那些不应该被使用的代码,如果使用了过时的代码,编译器会发出警告;
  3. @SafeVarargs : 空注解,(varargs可变参数)用于标记构造函数或者方法,通知编译器,这里的可变参数相关的操作保证安全;
  4. @FunctionInterface : Java SE 8 出现的,用于通知编译器,这个类型是 function 接口;
  5. @SuppressWarning:抑制错误,可以用于标记整个类、某个方法、某个属性或者某个参数,用于告诉编译器这个代码是安全的,不必警告(强烈建议最小范围使用这个注解,一旦你在一个比较大的范围抑制错误,可能会把真正的问题掩盖了)。

元注解

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

– @Target
– @Retention
– @Documented
– @Inherited

  1. @Target的作用:用于描述注解的使用范围,可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。

ElementType.ANNOTATION_TYPE :可以给一个注解进行注解;
ElementType.CONSTRUCTOR :可以给构造方法进行注解;
ElementType.FIELD :可以给属性进行注解;
ElementType.LOCAL_VARIABLE :可以给局部变量进行注解;
ElementType.METHOD :可以给方法进行注解;
ElementType.PACKAGE :可以给一个包进行注解;
ElementType.PARAMETER :可以给一个方法内的参数进行注解;
ElementType.TYPE :可以给一个类型进行注解,比如类、接口、枚举。

  1. @Inherited的作用:当前注解是否可以继承。但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。

  1. @Documented的作用:它的作用是能够将注解中的元素包含到 Javadoc 中去。

  2. @Retention的作用: 指出注解如何存储,支持以下三种参数:

RetentionPolicy.SOURCE : 注解只保留在源码中,编译时会忽略 ;
RetentionPolicy.CLASS : 更高一级,编译时被编译器保留,但是运行时会被 JVM 忽略 ; RetentionPolicy.RUNTIME :最高级,运行时会被保留,可以被运行时访问。

注解的用处:

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

  1. 提供信息给编译器:编译器可以利用注解来探测错误和警告信息;
  2. 编译阶段时的处理:软件工具可以利用注解信息来生成代码 html 文档或者其他相应的处理;
  3. 运行时的处理:某些注解可以在程序运行的时候接受代码的提取。

注解的好处:

  1. 提升开发效率;
  2. 编译期间就可以验证正确性,差错很容易;
  3. 保存在 class 文件中,降低维护成本;

注解的坏处:

  1. 若要对配置项进行修改,需要修改 Java 文件,还需要重新打包编译;
  2. 配置项编码在 Java 文件中,可扩展性差。

自定义注解

第一步:定义注解

创建注解时,需要声明的类型为 @interface,看起来和接口有一些相似哈,其中的 @ 标明这是一个注解类型( “@ ~ annotation type”):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
    //属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
    int value();
}

注解除了名字和接口有些相似,内容也很相似,都是声明一个方法,规定返回值,不同的是这里的方法其实是个属性,返回值规定了属性的类型(至于为什么要声明成方法而不是属性,可能是为了后续直接使用这个方法获取值比较直观吧)。

注意:如果你的注解中创建了多个属性,但是使用时只需要使用某几个,这时编译器会提示你有没有指明的属性。可以使用 default为注解的某个属性指定默认值,这样即使不指定某个属性,编译器也不会报错。这通常可以节约很多时间,比如这样:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Author {
    String name() default "ABC";
    String date();
}

当我们使用 @Author 时没有指定 name = XXX,则会默认为 “ABC”。

定义注解类型元素时需要注意如下几点:

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

可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在使用时可以感受到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法

第二步:配置注解

到目前为止我们只是完成了第一步,接下来我们就来学习第二步,配置注解,如何在另一个类当中配置它。首先,定义一个注解、和一个供注解修饰的简单Java类。

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CherryAnnotation {
    String name();
    int age() default 18;
    int[] score();
}
public class Student{
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}

分析:

  1. CherryAnnotation的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方,即:public void study(int times)之上;
  2. 由于我们在CherryAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;

所以最终形式如下:

public class Student {
    @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77})
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}

需要注意以下几个特殊语法:

  1. 如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
	//省略实现部分
}
  1. 如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface SecondAnnotation {
	String value();
}
//等效于@ SecondAnnotation(value = "this is second annotation")
@SecondAnnotation("this is annotation")
public class JavaBean{
	//省略实现部分
}
  1. 如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
	String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
	//省略实现部分
}
  1. 如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。

第三步:解析注解

注解保持力的三个阶段:Java源文件阶段;编译到class文件阶段;运行期阶段。

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

因此,明确我们的目标:在运行期探究和使用编译期的内容(编译期配置的注解),要用到Java中的灵魂技术——反射机制!

我们先自定义一个 ContentView 注解,表示当前布局对应的 layout 文件:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
    //属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
    int value() ;
}

然后用它修饰一个 Activity:

@ContentView(R.layout.activity_annotation)
public class AnnotationTestActivity extends BaseActivity {
        @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);  //调用父类的 onCreate
        setContentView(R.layout.activity_annotation);
    }
}

在 BaseActivity 中反射获取当前类使用的注解,拿到注解的值,就可以直接设置布局了:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        annotationProcess();
    }

    //读取注解,进行处理
    private void annotationProcess() {
        Class c = this.getClass();
        //遍历所有子类
        for (; c != Context.class; c = c.getSuperclass()) {
            //找到使用 ContentView 注解的类
            ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
            if (annotation != null) {
                try {   //有可能出错的地方都要 try-catch
                    //获取 注解中的属性值,为 Activity 设置布局
                    this.setContentView(annotation.value());
                } catch (RuntimeException e) {
                    e.printStackTrace();
                }
                return;
            }

        }
    }

这样就简单实现了运行时根据注解动态设置布局的功能。

瑞思拜!!!

参考1

参考2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值