动态和对象在Dart中有什么区别?

84

它们似乎都可以在相同的情况下使用。类型检查方面是否有不同的表示或不同的细微差别?

8个回答

111

已编辑以更新空安全性(使用Object?代替Object)。

dynamic的另一个视角是,它实际上不是一种类型 - 它是一种关闭类型检查并告诉静态类型系统“相信我,我知道我在做什么”的方法。编写dynamic o;声明一个未经过类型检查的变量,而不是具有明确类型的变量。

当你写Object? o = something;时,你告诉系统除了它是一个Objectnull之外,不能假设任何关于o的事情。你可以调用toStringhashCode,因为这些方法在ObjectNull上定义,但如果你尝试执行o.foo(),你会收到一个警告 - 它无法查看你是否能够执行该操作,因此它警告你代码可能是错误的。

如果你写dynamic o = something,那么你告诉系统不要假设任何东西,也不要检查任何东西。如果你写o.foo(),那么它就不会警告你。你告诉它:“与o相关的任何东西都可以!相信我,我知道我在做什么”,所以它认为o.foo()是可以的。

伴随着巨大的力量而来的是巨大的责任 - 如果你禁用变量的类型检查,那么就要由你来确保你不会做错任何事情。


71

第三版的Dart编程语言规范中的 Type dynamic 部分声明:

类型dynamic具有每个可能标识符和元数的方法,以及每个可能命名参数组合。这些方法的返回类型均为dynamic,它们的形式参数的类型也均为dynamic。类型dynamic还具有每个可能标识符的属性,这些属性的类型均为dynamic。

这意味着调用 dynamic 类型变量上的任何方法都不会产生警告。而对于一个类型为Object的变量,则不是这种情况。例如:

dynamic a;
Object b;

main() {
  a = "";
  b = "";
  printLengths();
}

printLengths() {
  // no warning
  print(a.length);

  // warning:
  // The getter 'length' is not defined for the class 'Object'
  print(b.length);
}

在运行时,我认为,您不应该看到任何区别。


15

dynamic不是一种类型,它只是禁用类型检查。 Object是所有非空类型的“联合”,类型检查规则仍然适用。

比较这两种情况:

情况1(dynamic)

// a 'dynamic' variable can be assigned value of any type
dynamic a = 2;

// assign 'dynamic' value to any variable and code checker will not complain
int b = a;
// even when there is a bug
String c = a;

案例2(对象)

// It is OK to assign a 'int' value to an 'Object' variable, because 'int' is a subtype of 'Object'
Object a = 2;

// will get type error: "A value of type 'Object' can't be assigned to a variable of type 'int'"
int b = a;

// typecast is required when assign a 'Object' value to a variale of one of its subtypes.
int c = a as int;

12

除了Alexandre的回答中提到的实际差异,还存在语义差异。使用正确的方式将有助于更好地向其他程序员传达您的意图。

当您使用Object时,您表明您知道正在使用的类型是Object。例如:

int getHashCode(Object obj) {
  return obj.hashCode;
}

由于hashCodeObject上的属性,因此我们使用Object作为参数类型来指定该函数可以接受任何类型的Object

另一方面,使用dynamic意味着Dart系统无法正确表达您想要使用的类型:

void setEmail(dynamic email) {
  if (email is Email) {
    _email = email;
  } else if (email is String) {
    _email = new Email.fromString(email);
  }
}

由于Dart目前不支持联合类型,因此无法表达类型Email | String,所以我们被迫使用dynamic来接受所有类型,然后仅处理我们感兴趣的类型。


1
在你提供的例子中,使用Objectdynamic实际上没有区别。 - lrn
5
没有实际的区别,你可以交换这两个并且程序将运行相同。当我提到“语义差异”时,我是指代码将如何被其他Dart程序员理解。我稍微修改了我的答案以更好地反映这一点。 - Pixel Elephant

6

对象无法访问类的属性和方法,但是当使用动态语言时,您可以访问它们。

请查看以下示例:

class MyClass{
    myFunction() => print("This works");        
}

void main(){
dynamic a = new MyClass();
Object b = new MyClass();
a.myFunction(); // prints without error
b.myFunction(); // error : myFunction isn't defined for b
}

3
没问题,使用 Object 时你需要使用对象引用来访问 MyClass 实例,但这个引用本身并不知道实例具有哪些方法。而使用 dynamic,则可以直接访问 MyClass 实例,希望能够调用已经存在的方法。 - acmoune

3
除了由lrn给出的答案外,值得注意的是,对于启用了Null安全的Dart版本2.12及更高版本,声明为dynamic的变量可以为null,而Object类型的变量则不可以为null

1
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

2
此外,我注意到扩展方法与动态类型不兼容,但与Object类型兼容。

// I used to have the extension on dynamic and had 
// problems that didn't occur when using the same extension on Object

extension UtilExtensions on Object {   

  bool get isStringNotEmpty => this is String && (this as String).isNotEmpty;
  String get asStringNotEmpty => isStringNotEmpty ? this as String : null;

  bool get isIntNotZero => this is int && (this as int) != 0;
  int get asIntNotZero => isIntNotZero ? this as int : null;

  Map<String, Object> get asPair {
    if (this != null && this is Map) {
      return (this as Map).cast<String, Object>();
    }

    return null;
  }

  Map<String, Object> get asFullPair {
    if (this != null && this is Map) {
      var ret = (this as Map).cast<String, Object>();

      for (var key in ret.keys) {
        var val = ret[key];

        if (val is Map) {
          ret[key] = val.asFullPair;
        }
      }

      return ret;
    }

    return null;
  }
}

1

dynamic是一个关键字,用于在变量中分配值,不进行类型检查。 Object是Dart中所有类的父类,它可以作为数据类型进行类型检查


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接