Dart语言中的元数据注解

举报
jcLee95 发表于 2023/07/14 22:31:53 2023/07/14
【摘要】 Dart笔记:Dart语言中的注解(元数据)
Dart、Flutter
Dart语言中的注解

- 文章信息 -
Author: Jack Lee (jcLee95)
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函数来获取MyClassClassMirror。然后,我们遍历ClassMirrormetadata属性,检查每个元数据项是否为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注解。然而,这个注解不会影响程序的执行,也不会自动触发任何操作。要利用这个注解,我们需要使用相应的工具(例如反射或代码生成)来读取和处理它。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。