1. 注解的作用
注解也称元数据(metadata),用于在代码中添加的用于描述代码的额外的信息。注解可以应用于类、方法、属性、构造函数、函数参数等。
注解的作用是提供一种用于在 编译期 或 运行时 获取有关程序结构的附加信息的方法,从而可以实现更高级的编程技巧,例如自动序列化和反序列化、依赖注入等。
注解可以用于为代码添加额外的信息,它本身并不会影响程序的执行,但它们可以被编译器、静态分析工具或者运行时反射API所读取。这些工具可以根据注解提供的信息来执行特定的操作,比如:
生成额外的代码
检查代码的正确性
修改程序的行为
2. 注解的用法
注解的语法形式为:@Annotation,可以直接放在需要添加注解的代码前面。一个代码元素可以有多个注解,每个注解之间用逗号隔开。例如:
class Person {
void speak() {
print('你好!');
}
}
class Man extends Person {
@override
void speak() {
print('你好,世界!');
}
}
这个例子中,@override注解是一个内置注解 表示 Man 类的 speak 方法覆盖了 Person 类的 speak 方法。可以帮助编译器检查是否正确地覆盖了父类的方法。关于该内置注解将在下一节中进一步详细介绍。
2. 常见内置注解
如上一节中使用的 @override注解,Dart 提供了一些内置的注解,可以直接在代码中使用。目前Dart语言中内置注解一共三个—— @Deprecated, @deprecated, 以及 @override
@Deprecated:用于创建一个具有自定义消息和过时时间的过时注解。
@deprecated:表示某个API已经过时,不建议使用。在使用过时的API时,静态分析工具会给出警告。
@override:表示子类的方法覆盖了父类的方法。如果没有正确覆盖,静态分析工具会给出警告。
接下来将逐个解析更多的常见的内置注解。
2.1 @override
@override注解用于表示子类的方法覆盖了父类的方法。这有助于在重构代码时确保正确地覆盖了父类方法。如果没有正确覆盖,Dart分析器会发出警告。
这个注解已经给出过例子,这里不重复给出。
2.2 @Deprecated
@Deprecated注解用于表示一个类、方法或属性已被弃用,不建议使用。这有助于在升级库或框架时向开发者传达哪些功能已被弃用。使用已弃用的功能时,Dart分析器会发出警告。
例如:
class MyClass {
@deprecated
void oldMethod() {
print('This method is deprecated');
}
void newMethod() {
print('Use this method instead');
}
}
3. meta库中提供的的常用注解
3.1 @required
@required注解用于表示构造函数或方法的命名参数是必需的。这有助于确保在调用构造函数或方法时提供必需的参数。如果缺少必需的参数,Dart分析器会发出警告。
例如:
import 'package:meta/meta.dart';
class Person {
final String name;
MyClass({@required this.name}) {
if (name == null) {
throw ArgumentError('name is required');
}
}
}
void main() {
MyClass obj = Person(name: 'Jack'); // 正确使用
MyClass obj2 = Person(); // 分析器会发出警告,缺少必需的参数
}
在这个示例中,我们创建了一个MyClass类,它有一个必需的命名参数name。我们在构造函数参数上使用@required注解来表示这一点。请注意,我们需要从package:meta/meta.dart库中导入@required注解。
3.2 @sealed
@sealed注解用于表示一个类是密封的,即它只能在定义它的库中扩展。这有助于限制类的继承,确保库的内部实现不会被外部代码破坏。
例如:
// 在my_library.dart文件中
import 'package:meta/meta.dart';
@sealed
class MyBaseClass {
void myMethod() {
print('This is a sealed class');
}
}
class MyDerivedClass extends MyBaseClass {
// 这是允许的,因为MyDerivedClass和MyBaseClass在同一个库中
}
// 在main.dart文件中
import 'my_library.dart';
class AnotherDerivedClass extends MyBaseClass {
// 这是不允许的,因为AnotherDerivedClass和MyBaseClass在不同的库中
}
在这个示例中,我们创建了一个名为MyBaseClass的密封类,并在同一个库中创建了一个名为MyDerivedClass的子类。在另一个库中,我们尝试创建一个名为AnotherDerivedClass的子类,但这是不允许的,因为MyBaseClass是密封的。请注意,我们需要从package:meta/meta.dart库中导入@sealed注解。
3.3 @protected
@protected 注解用于表示一个类成员(方法、属性等)只能被其子类访问。这有助于实现封装,确保类的内部实现不会被其他类误用。
例如:
import 'package:flutter/foundation.dart';
class MyBaseClass {
@protected
void protectedMethod() {
print('This is a protected method.');
}
}
class MyDerivedClass extends MyBaseClass {
void callProtectedMethod() {
// 可以在子类中访问受保护的方法
protectedMethod();
}
}
void main() {
MyDerivedClass derivedClass = MyDerivedClass();
derivedClass.callProtectedMethod(); // 输出 "This is a protected method."
}
在这个例子中,我们定义了一个基类 MyBaseClass,它有一个受保护的方法 protectedMethod()。我们使用 @protected 注解来表示这个方法只能在 MyBaseClass 及其子类中使用。然后,我们创建了一个继承自 MyBaseClass 的子类 MyDerivedClass,并在其中调用了受保护的方法。
请注意,如果我们尝试在 MyDerivedClass 之外的类中调用 protectedMethod(),Dart 分析器会给出警告,提示我们不应在子类之外使用受保护的成员。
3.4 @immutable
@immutable 用于标记一个 类 为不可变(Immutable)。不可变类是指 其实例在创建后不能被修改的类,所有字段都是final 的,且 没有公共的可变方法。这样的类可以提供更强的安全性和线程安全性。
使用 @immutable 注解可以让编译器检查类的不可变性,并防止对其进行修改。如果在不可变类中尝试修改字段的值或添加可变方法,编译器将会产生警告或错误。
@immutable
class Person {
final String name;
final int age;
Person(this.name, this.age);
}
在上面的示例中,Person 类被标记为不可变,name 和 age 字段都是 final 的,且没有公共的可变方法。一旦创建了 Person 类的实例,其字段的值将不可更改。使用不可变类的好处如:
线程安全:不可变对象不需要担心并发修改的问题,可以在多线程环境中安全地共享。
可预测:由于不可变对象的值不会改变,可以在任何时候保证对象的状态一致性。
代码简化:不可变对象可以减少出错的机会,因为没有修改状态的可能性。
需要注意的是,@immutable注解只是一种元数据,它本身并不会强制类的不可变性。它主要用于提供给静态分析工具和代码生成工具使用,以检查和生成相关的代码。
3.5 @alwaysThrows
@alwaysThrows 注解表示一个函数总是抛出异常。这有助于静态分析工具了解代码的行为,以便更好地捕获潜在的错误。
例如:
import 'package:meta/meta.dart';
@alwaysThrows
void throwError(String message) {
throw Exception(message);
}
int foo(bool condition) {
if (condition) {
return 1;
} else {
throwError("Error occurred");
}
}
void main() {
try {
print(foo(false));
} catch (e) {
print(e); // 输出 "Exception: Error occurred"
}
}
在这个例子中,我们定义了一个名为 throwError 的函数,它接受一个字符串参数并抛出一个异常。我们使用 @alwaysThrows 注解表示这个函数总是抛出异常。然后,我们在 foo 函数中使用 throwError 函数。由于我们使用了 @alwaysThrows 注解,Dart 静态分析器可以正确地识别在 foo 函数的 else 分支中,throwError 函数不会返回值。
使用 @alwaysThrows 注解可以帮助静态分析器更准确地分析代码,从而提高代码的可读性和可维护性。
4. 自定义注解
4.1 定义自定义注解语法(如何定义)
我们除了可以使用内置的注解外,其实我们还可以自定义注解。自定义注解需要创建一个类,并继承自Object。类名通常以Annotation为后缀。自定义注解类可以包含任意数量的参数,这些参数可以在使用注解时传入。要定义自定义注解,需要创建一个带有const构造函数的类。这个类可以有任意数量的属性,但它们必须是编译时常量。
例如:
class MyAnnotation {
final String description;
const MyAnnotation(this.description);
}
在这个示例中,我们定义了一个名为MyAnnotation的自定义注解。这个注解有一个属性description,用于存储与注解关联的文本描述。我们还为这个类提供了一个const构造函数,以便在使用注解时可以创建编译时常量。
4.2 自定义注解用法(如何使用)
要使用自定义注解,只需在要注解的代码元素之前添加一个@符号,后跟注解的名称和const构造函数的调用。例如,可以将自定义注解应用于类、函数、变量等:
// 使用自定义注解注解类
@MyAnnotation('这是一个注解类的元数据')
class MyClass {
// ...
}
// 使用自定义注解注解函数
@MyAnnotation('这是一个注解函数的元数据')
void myFunction() {
// ...
}
// 使用自定义元素据注解变量
@MyAnnotation('这是一个注解变量的元数据')
int myVariable;
在这个示例中,我们使用MyAnnotation注解分别注解了一个类、一个函数和一个变量。注意,注解的值必须是编译时常量,因此我们使用const构造函数来创建注解实例。
4.3 读取自定义注解
要读取自定义注解,可以使用反射(dart:mirrors库)或代码生成工具(如source_gen库)。以下是一个使用反射来读取自定义注解的示例:
import 'dart:mirrors';
void main() {
// 获取MyClass的ClassMirror
ClassMirror classMirror = reflectClass(MyClass);
// 遍历MyClass的元数据
for (var metadata in classMirror.metadata) {
// 检查元数据是否为MyAnnotation类型
if (metadata.reflectee is MyAnnotation) {
// 获取MyAnnotation实例
MyAnnotation annotation = metadata.reflectee;
// 输出注解的描述
print('MyClass annotation: ${annotation.description}');
}
}
}
在这个示例中,我们使用dart:mirrors库中的reflectClass函数来获取MyClass的ClassMirror。然后,我们遍历ClassMirror的metadata属性,检查每个元数据项是否为MyAnnotation类型。如果找到一个匹配的注解,我们可以获取其description属性并输出它。
请注意,由于dart:mirrors库在Flutter中不受支持,因此在Flutter应用程序中需要使用代码生成工具(如source_gen库)来读取和处理自定义注解。
5 使用注解的一些注意点
5.1 注解必须是常量表达式
注解的值必须是一个编译时常量表达式。这意味着注解不能包含运行时计算的值,例如变量、非常量函数调用等。常量表达式的要求确保了注解在编译时可以被解析,从而可以用于编译器优化、静态分析等。
// 示例:定义一个简单的自定义注解
class MyAnnotation {
final String description;
const MyAnnotation(this.description);
}
// 使用自定义注解(正确示例)
@MyAnnotation('This is a constant description')
class MyClass {
// ...
}
// 使用自定义注解(错误示例)
String runtimeDescription = 'This is a runtime description';
@MyAnnotation(runtimeDescription) // 错误:注解必须是常量表达式
class MyClass2 {
// ...
}
在上面的示例中,我们定义了一个简单的自定义注解MyAnnotation,它接受一个字符串参数。在使用这个注解时,我们需要提供一个常量表达式作为参数。如果我们尝试使用一个运行时计算的值(例如变量runtimeDescription),则会导致编译错误。
5.2 注解不能直接访问被注解对象的属性或方法
注解本身不能直接访问或修改被注解对象的属性或方法。要实现这一点,我们需要借助反射(dart:mirrors库)或代码生成工具(如source_gen库)。
class MyAnnotation {
final String description;
const MyAnnotation(this.description);
// 错误示例:试图直接访问被注解对象的属性或方法
void printDescription(Object annotatedObject) {
print(annotatedObject.description); // 错误:不能直接访问被注解对象的属性
}
}
在上面的示例中,我们尝试在MyAnnotation类中定义一个printDescription方法,该方法试图直接访问被注解对象的description属性。这是不允许的,因为注解不能直接访问被注解对象的属性或方法。
5.3 注解不会影响程序的执行
注解本身不会影响程序的执行。它们仅作为元数据提供给编译器、静态分析工具或运行时反射API。要利用注解实现某些功能,我们需要使用这些工具来读取和处理注解。
@MyAnnotation('This is a description')
class MyClass {
// ...
}
void main() {
// 注解不会影响程序的执行
MyClass myObject = MyClass();
// ...
}