Dart语言中的元数据注解
Visit me at: https://jclee95.blog.csdn.net
Email: 291148484@163.com.
Shenzhen China
Address of this article:https://blog.csdn.net/qq_28550263/article/details/131384014
【介绍】:本文讲解 Dart语言中的注解。
注解也称元数据(metadata),用于在代码中添加的用于描述代码的额外的信息。注解可以应用于类、方法、属性、构造函数、函数参数等。
注解的作用是提供一种用于在 编译期 或 运行时 获取有关程序结构的附加信息的方法,从而可以实现更高级的编程技巧,例如自动序列化和反序列化、依赖注入等。
注解可以用于为代码添加额外的信息,它本身并不会影响程序的执行,但它们可以被编译器、静态分析工具或者运行时反射API所读取。这些工具可以根据注解提供的信息来执行特定的操作,比如:
- 生成额外的代码
- 检查代码的正确性
- 修改程序的行为
注解的语法形式为:@Annotation
,可以直接放在需要添加注解的代码前面。一个代码元素可以有多个注解,每个注解之间用逗号隔开。例如:
class Person {
void speak() {
print('你好!');
}
}
class Man extends Person {
@override
void speak() {
print('你好,世界!');
}
}
这个例子中,@override
注解是一个内置注解 表示 Man 类的 speak
方法覆盖了 Person 类的 speak
方法。可以帮助编译器检查是否正确地覆盖了父类的方法。关于该内置注解将在下一节中进一步详细介绍。
如上一节中使用的 @override
注解,Dart 提供了一些内置的注解,可以直接在代码中使用。目前Dart语言中内置注解一共三个—— @Deprecated, @deprecated, 以及 @override
内置注解 | 描述 |
---|---|
@Deprecated |
用于创建一个具有自定义消息和过时时间的过时注解。 |
@deprecated |
表示某个API已经过时,不建议使用。在使用过时的API时,静态分析工具会给出警告。 |
@override |
表示子类的方法覆盖了父类的方法。如果没有正确覆盖,静态分析工具会给出警告。 |
接下来将逐个解析更多的常见的内置注解。
@override
注解用于表示子类的方法覆盖了父类的方法。这有助于在重构代码时确保正确地覆盖了父类方法。如果没有正确覆盖,Dart分析器会发出警告。
这个注解已经给出过例子,这里不重复给出。
@deprecated
注解用于表示一个类、方法或属性已被弃用,不建议使用。这有助于在升级库或框架时向开发者传达哪些功能已被弃用。使用已弃用的功能时,Dart分析器会发出警告。
例如:
class MyClass {
@deprecated
void oldMethod() {
print('This method is deprecated');
}
void newMethod() {
print('Use this method instead');
}
}
在这个示例中,我们将oldMethod
方法标记为已弃用,并推荐使用newMethod
方法。当开发者尝试使用oldMethod
时,Dart分析器会发出警告。
@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
注解。
@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
注解。
@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 分析器会给出警告,提示我们不应在子类之外使用受保护的成员。
@immutable
用于标记一个 类 为不可变(Immutable)。不可变类是指 其实例在创建后不能被修改的类,所有字段都是final
的,且 没有公共的可变方法。这样的类可以提供更强的安全性和线程安全性。
使用 @immutable
注解可以让编译器检查类的不可变性,并防止对其进行修改。如果在不可变类中尝试修改字段的值或添加可变方法,编译器将会产生警告或错误。
@immutable
class Person {
final String name;
final int age;
Person(this.name, this.age);
}
在上面的示例中,Person 类被标记为不可变,name
和 age
字段都是 final
的,且没有公共的可变方法。一旦创建了 Person 类的实例,其字段的值将不可更改。使用不可变类的好处如:
- 线程安全:不可变对象不需要担心并发修改的问题,可以在多线程环境中安全地共享。
- 可预测:由于不可变对象的值不会改变,可以在任何时候保证对象的状态一致性。
- 代码简化:不可变对象可以减少出错的机会,因为没有修改状态的可能性。
需要注意的是,@immutable注解只是一种元数据,它本身并不会强制类的不可变性。它主要用于提供给静态分析工具和代码生成工具使用,以检查和生成相关的代码。
@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
注解可以帮助静态分析器更准确地分析代码,从而提高代码的可读性和可维护性。
我们除了可以使用内置的注解外,其实我们还可以自定义注解。自定义注解需要创建一个类,并继承自Object
。类名通常以Annotation
为后缀。自定义注解类可以包含任意数量的参数,这些参数可以在使用注解时传入。要定义自定义注解,需要创建一个带有const
构造函数的类。这个类可以有任意数量的属性,但它们必须是编译时常量。
例如:
class MyAnnotation {
final String description;
const MyAnnotation(this.description);
}
在这个示例中,我们定义了一个名为MyAnnotation
的自定义注解。这个注解有一个属性description
,用于存储与注解关联的文本描述。我们还为这个类提供了一个const
构造函数,以便在使用注解时可以创建编译时常量。
要使用自定义注解,只需在要注解的代码元素之前添加一个@
符号,后跟注解的名称和const
构造函数的调用。例如,可以将自定义注解应用于类、函数、变量等:
// 使用自定义注解注解类
@MyAnnotation('这是一个注解类的元数据')
class MyClass {
// ...
}
// 使用自定义注解注解函数
@MyAnnotation('这是一个注解函数的元数据')
void myFunction() {
// ...
}
// 使用自定义元素据注解变量
@MyAnnotation('这是一个注解变量的元数据')
int myVariable;
在这个示例中,我们使用MyAnnotation
注解分别注解了一个类、一个函数和一个变量。注意,注解的值必须是编译时常量,因此我们使用const
构造函数来创建注解实例。
要读取自定义注解,可以使用反射(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
库)来读取和处理自定义注解。
注解的值必须是一个编译时常量表达式。这意味着注解不能包含运行时计算的值,例如变量、非常量函数调用等。常量表达式的要求确保了注解在编译时可以被解析,从而可以用于编译器优化、静态分析等。
// 示例:定义一个简单的自定义注解
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
),则会导致编译错误。
注解本身不能直接访问或修改被注解对象的属性或方法。要实现这一点,我们需要借助反射(dart:mirrors
库)或代码生成工具(如source_gen
库)。
class MyAnnotation {
final String description;
const MyAnnotation(this.description);
// 错误示例:试图直接访问被注解对象的属性或方法
void printDescription(Object annotatedObject) {
print(annotatedObject.description); // 错误:不能直接访问被注解对象的属性
}
}
在上面的示例中,我们尝试在MyAnnotation
类中定义一个printDescription
方法,该方法试图直接访问被注解对象的description
属性。这是不允许的,因为注解不能直接访问被注解对象的属性或方法。
注解本身不会影响程序的执行。它们仅作为元数据提供给编译器、静态分析工具或运行时反射API。要利用注解实现某些功能,我们需要使用这些工具来读取和处理注解。
@MyAnnotation('This is a description')
class MyClass {
// ...
}
void main() {
// 注解不会影响程序的执行
MyClass myObject = MyClass();
// ...
}
在上面的示例中,我们给MyClass
类添加了一个MyAnnotation
注解。然而,这个注解不会影响程序的执行,也不会自动触发任何操作。要利用这个注解,我们需要使用相应的工具(例如反射或代码生成)来读取和处理它。
- 点赞
- 收藏
- 关注作者
评论(0)