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

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

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

感谢您的澄清!

【问题讨论】:

    标签: object copy clone 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() 也可以使用stackoverflow.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 是我最近发现的一个问题。
                • 这是错误的答案。仅仅因为一种语言具有传递引用调用,并不意味着不支持深度复制。
                猜你喜欢
                • 2011-09-05
                • 1970-01-01
                • 1970-01-01
                • 2011-03-24
                • 1970-01-01
                • 2010-11-30
                • 2011-09-16
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多