Dart笔记:Dart语言中的注解(元数据)

简介: Dart笔记:Dart语言中的注解(元数据)

Dart、FlutterDart语言中的注解



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');
  }
}

在这个示例中,我们将oldMethod方法标记为已弃用,并推荐使用newMethod方法。当开发者尝试使用oldMethod时,Dart分析器会发出警告。

【注】:在旧的Dart版本中,使用的是 @deprecated,现在已经发被 @Deprecated 替代

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.2 @immutable

@immutable 用于标记一个 为不可变(Immutable)。不可变类是指 其实例在创建后不能被修改的类,所有字段都是final 的,且 没有公共的可变方法。这样的类可以提供更强的安全性和线程安全性。

使用 @immutable 注解可以让编译器检查类的不可变性,并防止对其进行修改。如果在不可变类中尝试修改字段的值或添加可变方法,编译器将会产生警告或错误。

@immutable
class Person {
  final String name;
  final int age;
  Person(this.name, this.age);
}

在上面的示例中,Person 类被标记为不可变,nameage 字段都是 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函数来获取MyClassClassMirror。然后,我们遍历ClassMirrormetadata属性,检查每个元数据项是否为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();
  // ...
}

在上面的示例中,我们给MyClass类添加了一个MyAnnotation注解。然而,这个注解不会影响程序的执行,也不会自动触发任何操作。要利用这个注解,我们需要使用相应的工具(例如反射或代码生成)来读取和处理它。

目录
相关文章
N..
|
6月前
|
Dart
Dart语言中类的定义和使用
Dart语言中类的定义和使用
N..
63 0
|
6月前
|
Dart 安全 前端开发
【教程】混淆Dart 代码
代码混淆是一种将应用程序二进制文件转换为功能上等价,但人类难于阅读和理解的行为。在编译 Dart 代码时,混淆会隐藏函数和类的名称,并用其他符号替代每个符号,从而使攻击者难以进行逆向工程。
|
6月前
|
Dart 数据安全/隐私保护
Dart笔记:Dart 语言中的存取器及其用法解析
Dart笔记:Dart 语言中的存取器及其用法解析
78 0
|
存储 Dart 索引
带你读《深入浅出Dart》六、Dart中的集合类型(1)
带你读《深入浅出Dart》六、Dart中的集合类型(1)
带你读《深入浅出Dart》十二、Dart库的使用和创建(1)
带你读《深入浅出Dart》十二、Dart库的使用和创建(1)
带你读《深入浅出Dart》六、Dart中的集合类型(2)
带你读《深入浅出Dart》六、Dart中的集合类型(2)
|
存储 Dart JavaScript
《深入浅出Dart》Dart库的使用和创建
Dart库的使用和创建 引言 在Dart中,代码重用和模块化可以通过库(libraries)和包(packages)实现。一个库就是一组代码,被一起打包为了实现一种或多种特定功能。一个包则是一种发布和分享Dart库的方式。在这一章,我们将详细介绍如何使用和创建Dart库和包,以及如何实现一个具有大数相加功能的库。 Dart库的使用
142 0
|
存储 Dart 安全
带你读《深入浅出Dart》十九、Dart中泛型
带你读《深入浅出Dart》十九、Dart中泛型
|
存储 Dart
带你读《深入浅出Dart》十二、Dart库的使用和创建(2)
带你读《深入浅出Dart》十二、Dart库的使用和创建(2)
|
JSON Dart 数据格式
带你读《深入浅出Dart》十四、Dart中使用JSON(2)
带你读《深入浅出Dart》十四、Dart中使用JSON(2)
122 0
下一篇
无影云桌面