温文尔雅的烤面包 · Weblogic反序列化(CVE-2023- ...· 1 月前 · |
成熟的小熊猫 · CVE-2023-21839 ...· 1 月前 · |
叛逆的泡面 · jackson ...· 4 周前 · |
内向的花卷 · jackson json序列化 首字母大写 ...· 4 周前 · |
满身肌肉的梨子 · Jackson序列化、反序列化首字母大写的j ...· 4 周前 · |
阳刚的皮带 · spark中使用udf执行filter_笛在 ...· 1 年前 · |
闯红灯的八宝粥 · 字符编码(一)|字符编码终极指南 - 知乎· 1 年前 · |
文质彬彬的椰子 · (爬虫)使用ChromeDriver时报错u ...· 1 年前 · |
满身肌肉的剪刀 · ResultSetMetaData中getC ...· 1 年前 · |
有人能解释一下Kotlin数据类的
copy
方法到底是如何工作的吗?对于一些成员来说,实际上并没有创建一个(深)副本,而且引用仍然是原始的。
fun test() {
val bar = Bar(0)
val foo = Foo(5, bar, mutableListOf(1, 2, 3))
println("foo : $foo")
val barCopy = bar.copy()
val fooCopy = foo.copy()
foo.a = 10
bar.x = 2
foo.list.add(4)
println("foo : $foo")
println("fooCopy: $fooCopy")
println("barCopy: $barCopy")
data class Foo(var a: Int,
val bar: Bar,
val list: MutableList<Int> = mutableListOf())
data class Bar(var x: Int = 0)
输出: foo : Foo(a=5,bar=Bar(x=0),list=1,2,3) foo : Foo(a=10,bar=Bar(x=2),list=1,2,3,4) fooCopy: Foo(a=5,bar=Bar(x=2),list=1,2,3,4) barCopy: Bar(x=0)
为什么是
barCopy.x=0
(预期),但是
fooCopy.bar.x=2
(我认为应该是0)。因为
Bar
也是一个数据类,所以在执行
foo.copy()
时,
foo.bar
也是一个副本。
为了深入复制所有成员,我可以这样做:
val fooCopy = foo.copy(bar = foo.bar.copy(), list = foo.list.toMutableList())
fooCopy: Foo(a=5,bar=Bar(x=0),list=1,2,3)
但是,我是否遗漏了一些东西,或者是否有更好的方法来做到这一点,而无需具体说明这些成员需要强制提交一份深刻的副本?
Kotlin的
copy
方法根本不应该是一个深拷贝。正如参考文档(
https://kotlinlang.org/docs/reference/data-classes.html
)中所解释的那样,对于一个类,如:
data class User(val name: String = "", val age: Int = 0)
copy
的实现将是:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
所以正如你所看到的,这是一个浅薄的复制品。在特定情况下,
copy
的实现应该是:
fun copy(a: Int = this.a, bar: Bar = this.bar, list: MutableList<Int> = this.list) = Foo(a, bar, list)
fun copy(x: Int = this.x) = Bar(x)
正如@Ekeko所说,为数据类实现的默认
copy()
函数是一个浅表副本,如下所示:
fun copy(a: Int = this.a, bar: Bar = this.bar, list: MutableList<Int> = this.list)
要执行深度复制,必须重写
copy()
函数。
fun copy(a: Int = this.a, bar: Bar = this.bar.copy(), list: MutableList<Int> = this.list.toList()) = Foo(a, bar, list)
有一种方法可以在Kotlin (和Java)中对对象进行深度复制: 将其序列化为内存,然后反序列化它回 到一个新对象。这只有在对象中包含的所有数据都是原语或实现可序列化接口时才能工作。
下面是使用示例Kotlin代码 https://rosettacode.org/wiki/Deepcopy#Kotlin 的说明
import java.io.Serializable
import java.io.ByteArrayOutputStream
import java.io.ByteArrayInputStream
import java.io.ObjectOutputStream
import java.io.ObjectInputStream
fun <T : Serializable> deepCopy(obj: T?): T? {
if (obj == null) return null
val baos = ByteArrayOutputStream()
val oos = ObjectOutputStream(baos)
oos.writeObject(obj)
oos.close()
val bais = ByteArrayInputStream(baos.toByteArray())
val ois = ObjectInputStream(bais)
@Suppress("unchecked_cast")
return ois.readObject() as T
}
注意:这个解决方案也应该适用于Android,使用Parcelable界面而不是Serializable。Parcelable更有效。
在前面的答案的基础上,使用
kotlinx.serialization
工具是一种简单而又不优雅的解决方案。按照文档将插件添加到
build.gradle
中,然后对对象进行深度复制,用
@Serializable
对其进行注释,并添加一个复制方法,该方法将对象转换为序列化的二进制形式,然后再返回。新对象将不会引用原始对象中的任何对象。
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.Cbor
@Serializable
data class DataClass(val yourData: Whatever, val yourList: List<Stuff>) {
var moreStuff: Map<String, String> = mapOf()
fun copy(): DataClass {
return Cbor.load(serializer(), Cbor.dump(serializer(), this))
}
这不会像手写复制函数那样快,但是如果对象被更改,它不需要更新,所以更健壮。
注意那些只是将列表引用从旧对象复制到新对象的答案。深度复制的一种快速方法(虽然不是很有效)是序列化/反序列化对象,即将对象转换为JSON,然后将它们转换回POJO。如果您正在使用GSON,下面是一段快速代码:
class Foo {
fun deepCopy() : Foo {
return Gson().fromJson(Gson().toJson(this), this.javaClass)
}
也许您可以在这里以某种方式使用 科特林反射 ,这个示例不是递归的,但是应该给出如下的想法:
fun DataType.deepCopy() : DataType {
val copy = DataType()
for (m in this::class.members) {
if (m is KProperty && m is KMutableProperty) {
m.setter.call(copy, if (m.returnType::class.isData) {
(m.getter.call(this) to m.returnType).copy()
} else m.setter.call(copy, m.getter.call(this)))
return copy
}
我也面临着同样的问题。因为在kotlin中,
ArrayList.map {it.copy}
不复制对象的所有项,特别是如果一个成员是其中另一个对象的
列表。
要深入复制我在web上找到的对象的所有项,唯一的解决方案是在发送或分配对象给新变量时,序列化和反序列化对象。代码如下所示。
@Parcelize
data class Flights(
// data with different types including the list
) : Parcelable
在收到航班列表之前,我们可以使用JSON反序列化对象并同时序列化对象!
首先,我们创建两个扩展函数。
// deserialize method
fun flightListToString(list: ArrayList<Flights>): String {
val type = object : TypeToken<ArrayList<Flights>>() {}.type
return Gson().toJson(list, type)
// serialize method
fun toFlightList(string: String): List<Flights>? {
val itemType = object : TypeToken<ArrayList<Flights>>() {}.type
return Gson().fromJson<ArrayList<Flights>>(string, itemType)
}
我们可以像下面这样使用它。
// here I assign list from Navigation args
private lateinit var originalFlightList: List<Flights>
val temporaryList = ArrayList(makeProposalFragmentArgs.selectedFlightList.asList())
originalFlightList = toFlightList(flightListToString(temporaryList))!!
--稍后的--我将此列表发送给回收适配器&在那里,将修改 would 对象的内容。
bindingView.imageViewReset.setOnClickListener {
val temporaryList = ArrayList(makeProposalFragmentArgs.selectedFlightList.asList())
val flightList = toFlightList(flightListToString(temporaryList))!!
**adapter**.resetListToOriginal(flightList)
}
如果您使用Jackson而不关心性能,那么这个简单的扩展函数将为您提供此功能。
private val objectMapper = ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
fun <T> Any.copyDeep(): T {
return objectMapper.readValue(objectMapper.writeValueAsString(this), this.javaClass) as T
闯红灯的八宝粥 · 字符编码(一)|字符编码终极指南 - 知乎 1 年前 |