夯实 Java 基础 - 注解

不知道大家有没有一种感觉,当你想要了解某个知识点的时候,就会发现好多技术类 APP 或者公众号在推一些关于这个知识点的文章。也许这就是大数据的作用,这也说明总有人比你抢先一步。学习不能停滞,要不你就会被别人越落越远。

本文接着来回顾和总结 Java 基础中注解的知识点和简单的使用,同样本文将从以下几个方面来回顾注解知识:

  1. 注解的定义
  2. 注解的语法
  3. 源码级别的注解的使用
  4. 运行时注解的使用
  5. 编译时注解的使用
  6. Android 预置的注解

注解的定义

注解(Annotation),也叫元数据。一种代码级别的说明。它是 JDK 1.5 以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等元素上。它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响

注解有许多用处,主要如下:

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

如我们所熟知的依赖注入框架 ButterKnife 就是在编译阶段来生成 findViewById 的代码(文件)的,而我们所见过的 @Deprecated 就是提供信息给编辑器的RetentionPolicy.SOURCE类型注解,说明这个属性已经过时的,对于运行时的注解在反射的文章的最后我们也举了个小例子,说明了它的作用。

在自定义了一个编译或者运行阶段的注解后,需要一个开发者编写相应的代码来解释这些注解,从而来发挥注解的作用。这些用来解释注解的代码被统称为是 APT(Annotation Processing Tool)。换句话说注解其实是给 APT 或者编辑器来使用的,而对于非框架开发人员的我们我们只需要关注注解的使用,并遵守规则即可,从而我们节省了很多代码提高了效率。

但是凡事如果只满足于用上,就不算是一个合 (tong) 格 (guo) 程 (mian)序 (shi) 员 (de)! 但是不要慌,当你打开这篇文章的时候你已经离 offer 又进了一步。

注解的语法

注解的声明

注解的声明和声明一个接口十分类似,没错只是名字很类似~ 我们使用@interface 来声明一个注解,如我们最常见的Override 注解的声明

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
  • 1
  • 2
  • 3
  • 4

注解声明的修饰符,可以是 private,public, protected 或者默认 default 这一点跟定义一个类或者接口相同。

在声明一个注解的时候我们常常需要一些其他注解来修饰和限制该定义注解的使用和运行方式。上述的 @Target@Retention 就是如此,我们称之为元注解,详细的元注解在下边说明。

注解成员

注解跟一个类相似,它们并不是都是像上面的 @Override一样只有声明。一个类大概可以包含构造函数,成员变量,成员函数等,而一个注解只能包含注解成员,注解成员的声明格式为:

类型 参数名() default 默认值;

注解成员可以是:

  1. 基本类型 byte,short,int,long,float,double,boolean 八种基本类型及这些类型的数组, 注意这里没对应基本数据类型的包装类。
  2. String,Enum,Class,annotations 及这些类型的数组

  3. 注解的成员修饰符只能是 public 或默认(default)

  4. 注解元素必须有确定的值,可以在注解中定义默认值,也可以使用注解时指定。即我们在定义注解的时候声明的成员,可以不赋值,但是就跟抽象函数一样,在使用的时候就必须指定。

如:

public @interface TestAnnotation {String value() default "";String[] values();int id() default -1;int[] ids();// 错误的不能使用包装类 以及自定义类型// Integer idInt();// Apple apple();enum Color {BULE, RED, GREEN}Color testEnum() default Color.BULE;Color[] testEnums();//注解类型成员 注解元素必须有确定的值,可以在注解中定义默认值,也可以使用注解时指定FruitName fruitName() default @FruitName("apple");
}@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
protected @interface FruitName {String value();String alias() default "no alias";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

元注解

我们在 Override 注解的声明中可以看到还有注解修饰着如@Target(ElementType.METHOD),我们讲元注解理解为修饰注解定义的注解。换句话说元注解为 JDK 提供给我们的一些基本注解,我们使用元注解来定义一个注解是如何工作的。

JDK 1.8 中存在的元注解有以下 5 种:

@Target, @Retention、@Documented、@Inherited、@Repeatable

下面我们依次来说明这几种类型的注解是如何使用的。

@Target 元注解

@Target 指定了被修饰的注解运用的地方,这些 “地方” 定义在 ElementType 类中,包括:

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

其中 METHODPARAMETERFIELD 最为常见,如 Override 注解被 @Target(ElementType.METHOD) 修饰,如果我们想要标记一个参数不能为空则可以使用 @NonNull 去修饰一个 param, FIELD 用来指定注解只能用来修饰成员变量如我们经常使用的 @BindView

值得注意的是 @Target 元注解定义如下,

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {ElementType[] value();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

它内部的成员为ElementType[] 数组也就是说,我们可以同时指定一个注解可以用于很多地方。如 @ColorRes 的注解的元注解为@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})

@Retention 元注解

Retention 翻译过来是保留期的意思。当@Retention 用于修饰一个注解上的时候,它规定了了被修饰的注解应用的时期,或者存活的时期

它可以有如下 3 种取值:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃。
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时通过反射获取到它们,并解释他们。
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。

源码级别注解 RetentionPolicy.SOURCE

对于第一种 RetentionPolicy.SOURCE 注解只在源码阶段保留,更多的效果时做一些编译检查,在 Android 中有个为 @IntDef 的注解,他可以和常量组合一起 代替枚举 enum 做参数限制作用,来优化内存使用。

这里说只是替代了参数限制作用,而 JDK 1.5 为我们带来的 enum 的作用不只是简单的参数限制作用作用,对于 Enum 更多优雅使用可以参考 《Effective Java》。

@IntDef 的注解定义如下:

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {long[] value() default {};boolean flag() default false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如我们常用的设置一个 View 的可见属性就使用了 @IntDef 注解来保证使用者传入的参数是对的,如下:

@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {}@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {setFlags(visibility, VISIBILITY_MASK);
}//设置一个 View 的属性:
...
toolbar.setVisibility(View.VISIBLE);// it is Ok//toolbar.setVisibility(1000);// 如果我们随便写一个数值 那么编辑器将会报错 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

运行期时的注解 RetentionPolicy.RUNTIME

源码级别的注解对我们的编码约束,运行期注解与之不同的是,如果要是让该注解生效,我们必须要编写一定的代码去将定义好的注解,在运行中”注入”应用中,看到运行时注入就可以应该能想得起反射,是的注入这个操作就是需要开发人员自己编写的。

另外,我们也都了解,在运行反射的时候效率是无法保证的。因为反射将遍历对应类的 Class 文件来获取相应的信息。所以运行时注解,并不是那么广泛被运用,而稍后我们要说明的编译期注解则不会对程序的运行造成效率的影响,因此应用更广泛一些。

我们来试着写一个 Dota 英雄名称的运行期注解来了解下他的运作方式:

/**
* 定义一个注解表示英雄的名字
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
private @interface HeroName {String value();String alias();
}/**
* 定义一个类包含英雄名称的属性
*/
public class Hero {// 定义注解的时候没有 deflaut 属性名称所以在使用的时候必须赋值@HeroName(value = "Spirit Walker", alias = "SB")private String heroName;public void setHeroName(String heroName) {this.heroName = heroName;}public String getHeroName() {return heroName;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

ok 声明就是这么简单,那么如何让一个属性生效呢,这时候我们就需要一个注解处理方法。为了方便观察运行注解的结果,所以我们这个处理方法选择传递一个 Hero 对象,不过你为了更通用也可以不用这么做。

/** 运行时注解处理方法*/
public static void getHeroNameInfo(Hero hero) {try {Class<? extends Hero> clazz = hero.getClass();Field field = clazz.getDeclaredField("heroName");// Field isAnnotationPresent 判断一个属性是否被对应的注解修饰if (field.isAnnotationPresent(HeroName.class)) {//field.getAnnotation 获取属性的注解HeroName fruitNameAnno = field.getAnnotation(HeroName.class);hero.setHeroName("name = " +fruitNameAnno.value() +" alias = " + fruitNameAnno.alias());}} catch (NoSuchFieldException e) {e.printStackTrace();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

下面我们来运行下程序测试下:

public static void main(String[] args) {Hero hero = new Hero();getHeroNameInfo(hero);System.out.println("hero = " + hero);
}
  • 1
  • 2
  • 3
  • 4
  • 5

运行结果:

hero = Hero{heroName='name = Spirit Walker alias = SB'}
  • 1

通过上述的例子,可以了解运行时注解就是这样声明和运用的。相信 SB 这个别名更容易让大家记得这个例子(白牛这个英雄其实很好玩,只是别名…)。

编译时期的注解 RetentionPolicy.CLASS

经过运行时注解的了解,相比对于注解应该都有一个大概的了解了。接下来到了编译时注解,这个注解类型,便是众多工具库中应用的注解类型,它不会影响运行时的效率问题,而是在编译期,或者打包过程中就生成了对应的代码,在运行时将会生效。如我们常见的 ButterKnifeEventBus

编译时注解与运行时注解不同,编译时注解主要是帮助我们在编译器编译期使用注解处理器生成相应的代码,帮我们解放劳动力。

我们知道运行时注解是通过反射来解释对应注解并使注解生效的,那么编译时如何解释对应的注解呢?这里就需要用到注解处理器的知识了。

注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册相应的注解处理器(自定义的注解处理器需继承自AbstractProcessor)。

Java 中提供给我们了注解处理器实现方法,主要是通过实现一个名为 AbstractProcessor 的注解处理器基类。该抽象类要求我们必须实现 process 方法来定义处理逻辑。下边我们来看下注解处理器中的几个方法的作用:

public class NameProcessor extends AbstractProcessor {//会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类如Elements, Types和Filer等@Overridepublic synchronized void init(ProcessingEnvironment env){ }//返回最高所支持的java版本, 如返回 SourceVersion.latestSupported();@Overridepublic SourceVersion getSupportedSourceVersion() { }//一个注解处理器可能会处理多个注解逻辑,这个方法将返回待处理的注解类型集合,返回值作为参数传递给 process 方法。@Overridepublic Set<String> getSupportedAnnotationTypes() { }//process 函数就是我们处理待处理注解的地方了,我们需要在这里编写生成 java 文件的具体逻辑。 方法返回布尔值类型,表示注解是否已经处理完成。一般情况下我们返回 true 即可。@Overridepublic boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

注解处理器的处理步骤主要有以下:

  1. 编译器开始执行注解处理器
  2. process 方法中循环处理注解元素(Element),找到被该注解所修饰的类,方法,或者属性
  3. 拿上一步得到的备注注解修饰的类或者属性,方法,生成对应的辅助类,并写入 java 文件
  4. 生成 java 文件后就可以在运行时,在程序中获取并调用对应的辅助方法,如 ButterKnife.bind(this); 方法就是获取对应 Activity 的注解处理器生成的java 文件,并执行了构造函数。

自定义一个编译时注解

自定义编译时注解要比运行时注解要繁琐一些。下面我们来举一个简单的例子,意在说明编译时注解是如何工作的。

在 Android 中为了实现一个编译时注解我们一般需要借助两个三方库:

  1. com.google.auto.service:auto-service:1.0-rc2 这是谷歌官方提供的一个注解处理注册插件可以帮助我们更方便的注册注解处理器,只需要在自定义的 Processor 类上方添加@AutoService(Processor.class)即可,不用自己动手执行注解处理器的注册工作(即编写 resource/META-INF/services/javax.annotation.processing.Processor文件)。

  2. 为了更方便的在 process 文件中生成 Java 类,需要依赖一个 Square 公司开源的 javapoet 库,com.squareup:javapoet:1.9.0 这个库中包装提供了一些好用的 API 帮助我们更快更准确的构建 .java 文件。当然你也可以自己手写拼接字符串然后写入文件(如果你能保证正确)。

仿照 ButterKnife 的实现,我们建立一个新的 Android project ,然后创建两个 Java Moudle,其中 processor 用来存放注解处理器,processor-lib 用来存放对应的注解,如下图所示:

在注解处理器存在的lib的 build.gradle 中添加依赖关系:

dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])compile project(':processor-lib')compile 'com.squareup:javapoet:1.9.0'compile 'com.google.auto.service:auto-service:1.0-rc3'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

主 moudle 中也需要添加对 processor 和 processor -lib 的依赖:

dependencies {....implementation project(':processor-lib')// 注意这里的注解处理器的依赖方式annotationProcessor project(':processor')
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

好了经过上述的准备我们终于能够编写我们的编译时注解了:

  1. 在 processor-lib 定义一个 Name 注解如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Name {String name();String alias();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 编写两个类使用我们定义的注解:
public class SBHero {@Name(name = "Spirit Walker", alias = "SB")private String heroName;
}public class PAHero {@Name(name = "Phantom Assassin", alias = "PA")private String heroName;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  1. 在 processor 注解处理lib 下定义一个 NamePorcessor
// @AutoService(Processor.class) 帮助我们生成对应的注解处理器配置
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.wangshijia.www.processor.Name")
public class NamePorcessor extends AbstractProcessor {//文件写入工具类private Filer filer;//可以帮助我们在 gradle 控制台打印信息的类private Messager messager;// 元素操作的辅助类private Elements elementUtils;//自定义文件名的后缀private static final String SUFFIX = "AutoGenerate";@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);filer = processingEnv.getFiler();messager = processingEnv.getMessager();elementUtils = processingEnv.getElementUtils();}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}/*** @return 你所需要处理的所有注解,该方法的返回值会被 process()方法所接收, 这里其实只有Name 注解,*/@Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> set = new HashSet<>();set.add(Name.class.getCanonicalName());return set;}...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  1. 最后我们要编辑我们的 process 方法了,process 方法中一共进行了下面这几件事:

  • 遍历程序中所有被该注解修饰器处理注解修饰的元素 存放进创建的Map集合
  • 依次取出map 中的元素构建对应的类和方法
  • 构建对应的方法内容
  • 生成.java 文件 位置在 ~/app/build/generated/source/apt 目录下
 @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {String packageName= "";// 获得被该注解声明的元素Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Name.class);// 声明一个存放成员变量的列表List<VariableElement> fields;//key 对应包含注解修饰元素的类的全类名 vaule 代表所有被注解修饰的变量Map<String, List<VariableElement>> maps = new HashMap<>();// 遍历程序中所有被该注解修饰器处理注解修饰的元素for (Element ele : elememts) {//  ele.getKind() 获取注解修饰的成员的类型,判断该元素是否为成员变量if (ele.getKind() == ElementKind.FIELD) {VariableElement varELe = (VariableElement) ele;// 获取该元素封装类型TypeElement enclosingElement = (TypeElement) varELe.getEnclosingElement();// 拿到包含 enclosingElement 元素的类的名称 样式如 com.wangshijia.www.annotationapplication.HeroString key = enclosingElement.getQualifiedName().toString();messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + key);packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();fields = maps.get(key);if (fields == null) {maps.put(key, fields = new ArrayList<>());}fields.add(varELe);}}/** maps 包含有所有被 @Name 修饰的类*/for (String key : maps.keySet()) {List<VariableElement> elementFileds = maps.get(key);messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + key);messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + elementFileds);String className = key.substring(key.lastIndexOf(".") + 1);className += SUFFIX;// 创建 className 类TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC, Modifier.FINAL);// 创建方法MethodSpec.Builder methodBuild = MethodSpec.methodBuilder("printNameAnnotation").addModifiers(Modifier.PUBLIC, Modifier.STATIC).returns(void.class);//创建方法中的打印语句for (VariableElement e : elementFileds) {Name annotation = e.getAnnotation(Name.class);// 创建 printNameAnnotation 方法methodBuild.addStatement("$T.out.println($S)", System.class, e.getSimpleName() + " = " + annotation.name()).addStatement("$T.out.println($S)", System.class, e.getSimpleName() + " = " + annotation.alias());}//将方法中添加到类中MethodSpec printNameMethodSpec = methodBuild.build();TypeSpec classTypeSpec = classBuilder.addMethod(printNameMethodSpec).build();try {//构造的 java 文件 参数一 包名,参数二 上述构建的类描述 TypeSpecJavaFile javaFile = JavaFile.builder(packageName, classTypeSpec).addFileComment(" This codes are generated automatically. Do not modify!").build();javaFile.writeTo(filer);} catch (IOException exception) {exception.printStackTrace();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

上述注释写的很详细了,这里希望不熟悉的朋友,自己动手实现下,才能更好的理解是如何构建对应的文件的。生成的文件位于指定目录下:

  1. 使用我们定义好的注解生成文件

使用注解生成器生成的 java 文件和普通的类没什么区别,通过编译后就放在上述文件夹中,我们可以正常调用我们构造类的方法,ButterKnife.bind(this) 实际上就是调用生成类的方法的过程。我们是一个简单的 demo 就不这么复杂的调用了。直接在 App 目录下的任意文件调用,如在一个 Activity 中:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);PAHeroAutoGenerate.printNameAnnotation();SBHeroAutoGenerate.printNameAnnotation();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

对于 JavaPoet 生成 java 文件的过程如果想深入了解的话可以查看该博客:JavaPoet - 优雅地生成代码

Android 预置的注解

日常开发中,注解能够帮助我们写出更好更优秀的代码,为了更好地支持 Android 开发,在已有的 android.annotation 基础上,Google 开发了 android.support.annotation 扩展包,共计50个注解,帮助开发者们写出更优秀的程序,这五十多种注解得以应用场景各不相同,常见的如 @IntDef @ColorInt @Nullable。

对于这些注解的用途这里不再详细说明,感兴趣的可以去查看下一个朋友写的关于 Android 中注解的作用的文章: Android 注解指南

总结

这篇文章写的时候遇到很多的困难,因为本人对于注解之前了解情况和大多数人一样,只停留在很少的使用阶段,在文章的构成方面也是一改再改。但是功夫不负有心人,在查阅了大量的资料后,学习到了很多注解的使用和原理的知识。也发现自己的知识掌握程度已经落下不少,比如鸿洋大神写的 Android 打造编译时注解解析框架 这只是一个开始 这篇文章在15年的时候就有了,想想当时刚毕业,与大神的距离整整拉开了进3年,让我去哭一会。但是个人认为这是件好事。总比一直停留在用上好一些,每次深一步了解,就感觉我跟大神之间的差距少了一些。

参考

  • Thinking In java
  • Android 打造编译时注解解析框架 这只是一个开始
  • Android 注解指南
  • JavaPoet - 优雅地生成代码

夯实 Java 基础 - 注解相关推荐

  1. 夯实Java基础系列15:Java注解简介和最佳实践

    Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...

  2. 夯实Java基础系列9:深入理解Class类和Object类

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  3. string substring的用法_夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战...

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  4. Java基础-注解和反射

    Java基础-注解和反射 前言 对于注解,我主要还是在自定义APT还有运行时反射获取类来让自己能够构建出复用性更高的代码逻辑. 知识点1-注解: 注解的应用场景由元注解@Retention来进行指定, ...

  5. java中this_夯实Java基础系列7:一文读懂Java 代码块和执行顺序

    目录 #java中的构造方法 #构造方法简介 #构造方法实例 #例-1 #例-2 #java中的几种构造方法详解 #普通构造方法 #默认构造方法 #重载构造方法 #java子类构造方法调用父类构造方法 ...

  6. [转载] 夯实Java基础系列8:深入理解Java内部类及其实现原理

    参考链接: Java内部类 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tut ...

  7. 夯实Java基础系列22:一文读懂Java序列化和反序列化

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  8. 夯实JAVA基础(个人笔记)

    夯实JAVA基础(个人笔记) 一.Java 异常处理 二.泛型 三.集合 四.算法 五.输入与输出(字节流和字符流) 六.并发和多线程 七.网络连接 八.lambda表达式 九.反射 十.Java S ...

  9. 夯实Java基础系列11:深入理解Java中的回调机制

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

最新文章

  1. 漫画:如何辨别二逼互联网公司!?
  2. [书目20130216]深入浅出WPF
  3. Matplotlib 日期格式转换
  4. C++语言(11)——C++类成员函数调用分析
  5. Eclipse窗口显示:独立、嵌入式
  6. 第一批做移动开发的程序员,现在怎么样了?
  7. mysql 创建表字段Double类型长度
  8. 联想g400从u盘启动计算机,联想G40怎么从U盘启动|联想G40设置U盘启动方法
  9. 工具系列 | FPM进程管理器详解
  10. 信号与系统作业之我的朋友把我的大作业分享了好朋友
  11. vsftp中anon_world_readable_only参数具体使用
  12. 微信公众平台-小程序开发工具源码
  13. it工种分类_什么是运维?运维工种有哪些
  14. unicloud操作数据库(一)——clientDb
  15. plc实验报告流程图_plc实训总结范文
  16. type object ‘TestGetToken‘ has no attribute ‘test_right_gett_token‘
  17. web分享QQ空间api接口
  18. js对象是什么?如何理解js对象
  19. Linux驱动开发(十五)---如何使用内核现有驱动(显示屏)
  20. 音视频数据采集及单向传输的实现(海思3516EV200平台)

热门文章

  1. 挖掘QQ超级技术-----你用过吗?
  2. Unity Shader 实现鬼魂效果
  3. Mac 数字键盘中数字键(0-9)不能用,解决方法:
  4. win8 thinkpad 忘记开机密码,怎么破解
  5. 智能家居平台乱战,京东欲借“超级APP”争夺控制中心
  6. 「雷锋前线」攻破一把智能门锁的N种方法
  7. bzoj-1502 月下柠檬树
  8. 不定高度实现slidedown和slideUp动画
  9. 为社交零售赋能,打造直播新零售模式
  10. Windows Server 2003 防木马、权限设置、IIS服务器安全配置整理