最近,我与一位同事进行了一次有趣的对话,内容涉及在Java / Kotlin代码中使用注释。 他曾在不鼓励使用某些语言功能(在这种情况下为通过注释进行元编程)的组织中工作,因为这些功能“太神奇了”和“难以理解”。 虽然选择避免使用框架或语言的某些功能是合理的,因为它们通常被认为是有害的,但一个开发人员的“魔力”是另一位开发人员的日常生产力工具。
我将以另一种方式重申对“魔术”的反对:
我不了解该语言的此功能,因此我选择不使用它,而无需考虑其预期的用法或可能带来的生产率提高。
作为开发人员,我们的工作是了解何时以及如何使用我们现有的工具来解决我们使用软件解决的问题。 因此,保持开放态度来利用我们使用的语言和框架的功能可以极大地提高生产力。 本文介绍了Kotlin和Java中注释的一些应用程序,并提出了一些对所看到的注释进行分类的方法,以便您可以推断出它们的预期用途。
What are Annotations For?
在较高的层次上,注释为编译器提供了有关正在编译的代码的信息,以便程序员可以更改其行为(这就是为什么我们使用术语“元编程”的原因)或在运行时提供有关正在运行的代码的其他信息。 与注释处理器(在编译时运行)结合使用时,某些注释可用于生成编译错误或警告,甚至生成更多代码供编译器进行编译。
因此,注释的属性只能是其他注释或值(基元,字符串,基元/字符串数组,类)。
What Generic Benefit do Annotations provide?
使用批注,您可以编写更简洁明了的代码,并且不太容易出错。 它们还可以通过向开发人员指示应如何使用代码来增强代码的可读性。
How are Annotations used?
据我所知,它们的用法可以分为以下三类(我使用自己的术语):
- 标记(BINARY / CLASS)浓缩机(运行时)发电机(来源)
Markers
标记不是很性感,但是它们仍然很重要。 它们既可以向读者(代码)通知已注释的语言元素的性质(如类,类型,字段/属性等),也可以向静态分析工具或编译器告知被注释的元素。 它们始终包含在类文件中(这就是批注保留二进制/保留政策枚举),以便将其用法与通过代码创建的任何jar文件一起发布。
@已弃用Java中的标记是用来告诉开发人员该Element即将消失的标记,并且编写任何消耗该Element的新代码将来可能会为您增加更多工作,因为您将被迫迁移(通常在 主要版本更新)。 某些IDE使用此批注向用户表明已弃用该元素。@已弃用Kotlin中的功能已得到增强,以允许开发人员提供@用。。。来代替成员,该成员将允许某些IDE(IntelliJ / Android Studio)用不推荐使用的代码智能地替换不推荐使用的代码。 但大多数情况下,这是供代码阅读者使用的工具。
Another useful marker annotation in Java (even though it has RetentionPolicy.RUNTIME) is @Nonnull
(which is part of the outcome of JSR-305). If you use google's implementation, this annotation happens to be useful in Java/Kotlin interop because your Kotlin code will be able to tell that @Nonnull String foo
is String
in Kotlin and not String?
. This also aids in static analysis tooling (such as findbugs) so that null-checks may be skipped. In case you were wondering, this annotation has a companion in @Nullable
.
Enrichers
Enrichers add information to a language Element that is readable during runtime (thus, these annotations have AnnotationRetention.RUNTIMË
/RetentionPolicy.RUNTIMË
). Say, for example, that you were developing a custom XML serialization/deserialization library with the intention of avoiding your consumers needing to write custom serializer/deserializer classes. Then you could use a small set of annotations combined with a single serializer/deserializer that reads the information contained within the annotations on the Element(s) of the classes its intended to serialize to/from. There is a library that does this: http://simple.sourceforge.net/.
My personal favorite library that leverages runtime annotations is (Retrofit2)[https://square.github.io/retrofit/]. Retrofit2 uses reflection to generate Java Proxy objects capable of performing a network call based solely on your method declaration. For example,
public interface UserService {
@POST("users/new")
Call<User> createUser(@Body User user);
}
的@POST和@身体 annotations are read at runtime when the call is made,和when this call is executed, retrofit will assemble a URL that corresponds to the endpoint in the @POST annotation和create an OkHttp request that has some serialized form of the 用户请求主体中的对象。 (如果您不知道如何配置改造来执行此操作,建议阅读文档)。
That's pretty great, right? In particular on Android, AnnotationRetention.RUNŤIME
/RetentionPolicy.RUNŤIME
annotations have a cost: https://blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html. So before you go wild using them for everything--at least on an Android project, be aware of the costs involved.
Generators
Kotlin和Java都具有注释处理功能。 注释处理器在项目编译时运行(因此,它们本身就可以通过注释保留源/保留政策来源)。 他们可以做的事情很简单,例如添加注释以将输出记录到控制台,或者复杂的事情,例如生成要编译的代码。 生成器特别适用于传统的重用模式崩溃的情况,并且对于消除样板代码很有用。
例如,如果您曾经实现过构建器模式,以使具有许多字段的类的实例更易于理解和更多地进行自我说明,则您可能熟悉与之关联的样板。 您会发现自己经常经历复制-粘贴-更改流程。 重复执行10或20次此流程后,您可能在某个地方犯了一个错误。 然后,为了写合理等于,hashCode,and toString methods,you'll have to repeat that flow three more times. You'll also have to write a constructor that understands how to apply the builder to construct your object without accidentally assigning the wrong builder field to the wrong class field.
Enter Google's awesome AutoValue annotation processor: https://github.com/google/auto/blob/master/value/userguide/builders.md. This tool enables you to declare an abstract version of your class and an abstract version of the builder for the class, delegating the implementation details to the annotation processor. Additionally, the equals
, hashCode
, and toString
, methods and the constructor for the class are also generated.
假设您现在必须向该类添加一个字段。 不用担心保留构造函数的实现,等于,hashCode和直到最新的toString`方法,您只需要为该类及其构建器声明一个抽象方法。 下次编译代码时,实际的实现将被更新。
但这还不是全部。 AutoValue被设计为可扩展扩展,并且引入了许多AutoValue扩展以提高您的生产率。
Serialization is a natural extension of AutoValue, and quite a few extensions generate serializers/deserializers the objects of classes that are generated by AutoValue. I'll highlight auto-value-moshi in this article as a representative library because there is an additional Kotlin Annotation Processor called moshi-codegen that will operate on and generate Kotlin classes to serialize/deserialize your Kotlin objects to/from JSON.
Conclusion
了解如何应用可供使用的工具以解决软件开发人员所面临的问题非常重要。 与其避免避免使用某些“魔术”工具,还应学习一些知识以使它们神秘化。
注释是一个有用的工具,可以提高您在Kotlin / Java中的生产率,并在适当使用时提高代码质量。 因此,下次您看到一些Java / Kotlin具有@,我希望你会更能理解