如何在 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<String, dynamic> 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 中克隆对象(深拷贝)?的主要内容,如果未能解决你的问题,请参考以下文章