如何在 Dart 中克​​隆对象(深拷贝)?

Posted

技术标签:

【中文标题】如何在 Dart 中克​​隆对象(深拷贝)?【英文标题】:How can I clone an Object (deep copy) in Dart? 【发布时间】:2012-10-17 22:58:48 【问题描述】:

是否有语言支持的方式在 Dart 中制作对象的完整(深度)副本?

仅限中学;有多种方法可以做到这一点,有什么区别?

感谢您的澄清!

【问题讨论】:

【参考方案1】:

这个问题有一个更简单的方法 只需使用... 运算符 例如,克隆地图

Map p = 'name' : 'parsa','age' : 27;
Map n = ...p;

此外,您可以对类属性执行此操作。 就我而言,我需要克隆一个类的列出属性。 所以:

class P1 
List<String> names = [some data];


/// codes
P1 p = P1();
List<String> clonedList = [...p.names]
// now clonedList is an unreferenced type

【讨论】:

【参考方案2】:

很遗憾,没有语言支持。我所做的是创建一个名为 Copyable 的抽象类,我可以在我希望能够复制的类中实现它:

abstract class Copyable<T> 
  T copy();
  T copyWith();

然后我可以按如下方式使用它,例如对于位置对象:

class Location implements Copyable<Location> 
  Location(
    required this.longitude,
    required this.latitude,
    required this.timestamp,
  );

  final double longitude;
  final double latitude;
  final DateTime timestamp;

  @override
  Location copy() => Location(
        longitude: longitude,
        latitude: latitude,
        timestamp: timestamp,
      );

  @override
  Location copyWith(
    double? longitude,
    double? latitude,
    DateTime? timestamp,
  ) =>
      Location(
        longitude: longitude ?? this.longitude,
        latitude: latitude ?? this.latitude,
        timestamp: timestamp ?? this.timestamp,
      );

【讨论】:

【参考方案3】:

假设你想深拷贝一个对象Person,它的属性是其他对象Skills的列表。按照惯例,我们使用带有可选参数的copyWith 方法进行深拷贝,但您可以随意命名。

你可以这样做

class Skills 
  final String name;

  Skills(required this.name);

  Skills copyWith(
    String? name,
  ) 
    return Skills(
      name: name ?? this.name,
    );
  


class Person 
  final List<Skills> skills;

  const Person(required this.skills);

  Person copyWith(
    List<Skills>? skills,
  ) =>
      Person(skills: skills ?? this.skills.map((e) => e.copyWith()).toList());

请记住,仅使用 this.skills 只会复制列表的引用。所以原始对象和复制的对象将指向相同的技能列表。

  Person copyWith(
    List<Skills>? skills,
  ) =>
      Person(skills: skills ?? this.skills);

如果您的列表是原始类型,您可以这样做。原始类型会自动复制,因此您可以使用这种较短的语法。

class Person 
  final List<int> names;

  const Person(required this.names);

  Person copyWith(
    List<int>? names,
  ) =>
      Person(names: names ?? []...addAll(names));

【讨论】:

【参考方案4】:

参考@Phill Wiggins 的回答,这里有一个带有 .from 构造函数和命名参数的示例:

class SomeObject
  String parameter1;
  String parameter2;

  // Normal Constructor
  SomeObject(
    this.parameter1,
    this.parameter2,
  );

  // .from Constructor for copying
  factory SomeObject.from(SomeObject objectA)
    return SomeObject(
      parameter1: objectA.parameter1,
      parameter2: objectA.parameter2,
    );
  


然后,在您要复制的地方执行此操作:

SomeObject a = SomeObject(parameter1: "param1", parameter2: "param2");
SomeObject copyOfA = SomeObject.from(a);

【讨论】:

这是最简单直接的方法。我尝试了几种措施,它们都太复杂了,不值得。【参考方案5】:

很多答案说您可以定义一个新的构造函数来启用深度复制,但CONSIDER copying nullable field to enable type promotion 下的Effective Dart Usage Guide 提供了一个更简单的解决方案。

文档建议使用 局部变量 来制作可用的副本。从标题可以推断,如果可以为空的字段有值,该技术也可以将它们转换为不可为空的字段。

示例代码:

class UploadException 
  final Response? response;

  UploadException([this.response]);

  @override
  String toString() 
    var response = this.response; // This makes a new copy.
    if (response != null) 
      return "Could not complete upload to $response.url "
          "(error code $response.errorCode): $response.reason.";
    

    return "Could not upload (no response).";
  

注意:

如果在整个代码库中广泛使用,那么围绕这个局部变量的便利构造函数包装器会更合适。我认为这种技术更简洁,因为它减少了编写相同最终结果的代码量。

注意事项:

使用本地副本时要小心。如果您需要写回该字段,请确保您这样做,而不仅仅是写入局部变量。此外,如果在本地仍在范围内时字段可能会更改,则本地可能具有陈旧的值。有时最好在现场直接使用!

【讨论】:

【参考方案6】:

创建一个辅助类:

class DeepCopy 
  static clone(obj) 
    var tempObj = ;
    for (var key in obj.keys) 
      tempObj[key] = obj[key];
    
    return tempObj;
  

然后复制你想要的:

 List cloneList = [];
 if (existList.length > 0) 
    for (var element in existList) 
        cloneList.add(DeepCopy.clone(element));
    
 

【讨论】:

它不起作用。这是我的自定义模型的例外。 'Scenario' 类没有实例 getter 'keys'。接收方:“场景”实例尝试调用:键【参考方案7】:

没有内置的深度克隆对象的方法 - 您必须自己提供方法。

我经常需要从 JSON 编码/解码我的类,所以我通常提供 MyClass fromMap(Map)Map&lt;String, dynamic&gt; toJson() 方法。这些可用于创建深度克隆,方法是先将对象编码为 JSON,然后再将其解码。

但是,出于性能原因,我通常会实现一个单独的clone 方法。这是几分钟的工作,但我发现它通常是值得的。

在下面的示例中,cloneSlow 使用 JSON 技术,cloneFast 使用显式实现的克隆方法。打印输出证明该克隆确实是深度克隆,而不仅仅是对 a 的引用的副本。

import 'dart:convert';

class A
  String a;
  A(this.a);
  
  factory A.fromMap(Map map)
    return A(
        map['a']
   );
  
  
  Map<String, dynamic> toJson()
    return 
      'a': a
    ;
  
  
  
  A cloneSlow()
    return A.fromMap(jsonDecode(jsonEncode(this)));
  

  A cloneFast()
    return A(
      a
    );
  
  
  
  @override
  String toString() => 'A(a: $a)';


void main() 
  A a = A('a');
  A b = a.cloneFast();
  b.a = 'b';
  
  print('a: $a   b: $b');





【讨论】:

【参考方案8】:

//希望这个工作

 void main() 
  List newList = ["top": 179.399, "left": 384.5, "bottom": 362.6, "right": 1534.5, "top": 384.4, "left": 656.5, "bottom": 574.6, "right": 1264.5];
  List tempList = cloneMyList(newList);
  tempList[0]["top"] = 100;
  newList[1]["left"] = 300;
  print(newList);
  print(tempList);


List cloneMyList(List originalList) 
 List clonedList = new List();
  for(Map data in originalList) 
    clonedList.add(Map.from(data));
  
  return clonedList;

【讨论】:

谢谢。由于浅克隆,我整天都在为一个错误而苦苦挣扎,因为我一直认为 Map.from 总是深拷贝并且我一直在使用它。【参考方案9】:

dart 中的 Deep copy 示例。

void main() 
  Person person1 = Person(
      id: 1001,
      firstName: 'John',
      lastName: 'Doe',
      email: 'john.doe@email.com',
      alive: true);

  Person person2 = Person(
      id: person1.id,
      firstName: person1.firstName,
      lastName: person1.lastName,
      email: person1.email,
      alive: person1.alive);

  print('Object: person1');
  print('id     : $person1.id');
  print('fName  : $person1.firstName');
  print('lName  : $person1.lastName');
  print('email  : $person1.email');
  print('alive  : $person1.alive');
  print('=hashCode=: $person1.hashCode');

  print('Object: person2');
  print('id     : $person2.id');
  print('fName  : $person2.firstName');
  print('lName  : $person2.lastName');
  print('email  : $person2.email');
  print('alive  : $person2.alive');
  print('=hashCode=: $person2.hashCode');


class Person 
  int id;
  String firstName;
  String lastName;
  String email;
  bool alive;
  Person(this.id, this.firstName, this.lastName, this.email, this.alive);

还有下面的输出。

id     : 1001
fName  : John
lName  : Doe
email  : john.doe@email.com
alive  : true
=hashCode=: 515186678

Object: person2
id     : 1001
fName  : John
lName  : Doe
email  : john.doe@email.com
alive  : true
=hashCode=: 686393765

【讨论】:

【参考方案10】:

尝试使用 Dart 提供的Copyable 接口。

【讨论】:

看起来不错,但所有者似乎没有维护回购。我不建议使用这样的随机 3rd 方包。 我个人更喜欢copy_with_extension_gen 1.4.0 包,它基本上做同样的事情,但给你更多的控制权并且正在维护【参考方案11】:

它仅适用于可以用 JSON 表示的对象类型。

ClassName newObj = ClassName.fromMap(obj.toMap());

ClassName newObj = ClassName.fromJson(obj.toJson());

【讨论】:

在这种情况下,您还需要为所有子类编写 fromJson() 以及需要完成的大量工作。 是的,如果你不想实现 fromJson() 也可以使用***.com/a/26616081/1556386 解决方案【参考方案12】:

要复制没有引用的对象,我找到的解决方案与此处发布的类似,但是如果对象包含 MAP 或 LIST,则必须这样做:

class Item 
  int id;
  String nome;
  String email;
  bool logado;
  Map mapa;
  List lista;
  Item(this.id, this.nome, this.email, this.logado, this.mapa, this.lista);

  Item copyWith( int id, String nome, String email, bool logado, Map mapa, List lista ) 
    return Item(
      id: id ?? this.id,
      nome: nome ?? this.nome,
      email: email ?? this.email,
      logado: logado ?? this.logado,
      mapa: mapa ?? Map.from(this.mapa ?? ),
      lista: lista ?? List.from(this.lista ?? []),
    );
  

Item item1 = Item(
    id: 1,
    nome: 'João Silva',
    email: 'joaosilva@gmail.com',
    logado: true,
    mapa: 
      'chave1': 'valor1',
      'chave2': 'valor2',
    ,
    lista: ['1', '2'],
  );

// -----------------
// copy and change data
Item item2 = item1.copyWith(
    id: 2,
    nome: 'Pedro de Nobrega',
    lista: ['4', '5', '6', '7', '8']
  );

// -----------------
// copy and not change data
Item item3 = item1.copyWith();

// -----------------
// copy and change a specific key of Map or List
Item item4 = item1.copyWith();
item4.mapa['chave2'] = 'valor2New';

在 dartpad 上查看示例

https://dartpad.dev/f114ef18700a41a3aa04a4837c13c70e

【讨论】:

这个解决方案的问题是当你想给变量设置一个空值时。 真的,我现在能想到的一个建议是复制后设置为null ...: // ------------------ --------------------- 项目 item2 = item1.copyWith( id: 2, nome: 'Pedro de Nobrega', lista: ['4', '5' , '6', '7', '8'] ); item2.email = null; // ---------------------------------------- 是的,但这不适用于不可变对象,其中一切都是最终的。我有一个建议: Item copyWith( int Function() id, String Function() name .. ) return Item( id: id?.call() ?? this.id, nome: nome?.call() ?? this.name, ... );现在你可以调用: item.copyWith(name: () => null);【参考方案13】:

假设你有一个班级

Class DailyInfo

   
     String xxx;
  

通过

创建类对象dailyInfo的新克隆
 DailyInfo newDailyInfo =  new DailyInfo.fromJson(dailyInfo.toJson());

为此,您的班级必须已实施

 factory DailyInfo.fromJson(Map<String, dynamic> json) => _$DailyInfoFromJson(json);


Map<String, dynamic> toJson() => _$DailyInfoToJson(this);

这可以通过使用

使类可序列化来完成
@JsonSerializable(fieldRename: FieldRename.snake, includeIfNull: false)
Class DailyInfo 
 String xxx;

【讨论】:

使用 JSON 克隆不是一个好主意。它限制了您可以克隆的对象并降低了性能。 如果对象有一个包含其他可序列化对象的列表,这也不能很好地工作。例如 Order 有一个产品列表...即使产品是可序列化的,当订单中有产品时,您也会看到问题。【参考方案14】:

聚会迟到了,但我最近遇到了这个问题,不得不按照以下方式做一些事情:-

class RandomObject 

RandomObject(this.x, this.y);

RandomObject.clone(RandomObject randomObject): this(randomObject.x, randomObject.y);

int x;
int y;

然后,你可以用原件调用副本,如下所示:-

final RandomObject original = RandomObject(1, 2);
final RandomObject copy = RandomObject.clone(original);

【讨论】:

嗨@Phill 我复制/粘贴了您的代码,但它不起作用。你能改进它吗? 嘿@pedromassango。很难改进该代码。把你的代码或实现发给我,我可以给你看。类似于:- ```var newObject = Random object.clone(oldObject) 将返回一个复制的实例。 它给了我一个错误,我只是用这个替换了 super 不知道为什么这也被否决了。这是在不使用反射的情况下克隆对象的最接近和最干净的方法。【参考方案15】:

Darts 内置集合使用名为“from”的命名构造函数来完成此操作。看到这个帖子:Clone a List, Map or Set in Dart

Map mapA = 
    'foo': 'bar'
;
Map mapB = new Map.from(mapA);

【讨论】:

这可能适用于简单的 地图,但不适用于更复杂的地图,例如 。我和你有同样的想法,但是没有成功。 from 是一个构造函数。使用new Map.from(mapA); @MosheShaham 我不明白 @jerinho 你说得对,Moshe Shaham 的解决方案不适用于嵌套地图。【参考方案16】:

我猜对于不太复杂的对象,您可以使用转换库:

import 'dart:convert';

然后使用 JSON 编码/解码功能

Map clonedObject = JSON.decode(JSON.encode(object));

如果您使用自定义类作为要克隆的对象中的值,则该类需要实现 toJson() 方法,或者您必须为 JSON.encode 方法提供 toEncodable 函数,并为解码调用。

【讨论】:

它会丢失类型信息,并且它只适用于可以用 JSON 表示的对象类型。最重要的是,它比应有的速度方式慢。 当我将其自定义为 ClassName.fromJson(jsonDecode(jsonEncode(object))); 时,它适用于我。谢谢。 很好的解决方案,因为我们已经为服务器通信添加了 JSON 支持【参考方案17】:

就未解决的问题似乎表明没有:

http://code.google.com/p/dart/issues/detail?id=3367

特别是:

.. Objects have identity, and you can only pass around references to them. There is no implicit copying.

【讨论】:

谢谢,我刚刚为需要复制的几个类(不仅仅是参考)自己编写了一个克隆方法。我被 Ruby 宠坏了——认为这是标准的语言特性。 这应该是标准语言功能 =_=" 6年后怎么样?这个答案仍然是事实吗? 这令人失望。公平地说,许多语言都存在这个问题,甚至 Go 也无法像在 C++ 或 Rust 中那样复制对象。无论如何here 是我最近发现的一个问题。 这是错误的答案。仅仅因为一种语言具有传递引用调用,并不意味着不支持深度复制。

以上是关于如何在 Dart 中克​​隆对象(深拷贝)?的主要内容,如果未能解决你的问题,请参考以下文章

Java如何对一个对象进行深拷贝?

如何实现数组深拷贝和浅拷贝?

Spring Boot 深拷贝对象

深拷贝和浅拷贝怎样理解(通俗具体点儿)

在 Dart 中克​​隆列表、地图或集合

2.深拷贝与浅拷贝的区别