【问题标题】:What is concise way of deep-copying a 2D MutableList?深度复制 2D MutableList 的简洁方法是什么?
【发布时间】:2017-12-25 15:37:17
【问题描述】:

元素已经实现了深度复制。

fun <T : DeepCopiable> f(a: MutableList<MutableList<T>>) {
    val copied = a.map { it.map { it.deepCopy() }.toMutableList() }.toMutableList()
    ...
}

我正在使用这种代码,但是看起来很冗长。

【问题讨论】:

  • 为你的复制操作写f(a)似乎不是很冗长。
  • @IngoKegel 不同意,如果接口方法名称稍有不同,代码的意图(深拷贝)将不会立即清晰。

标签: list collections kotlin deep-copy


【解决方案1】:

由于类型系统的限制,这个问题不能在不绕过类型安全的情况下推广到单个函数(并且由于 JVM 类型擦除,您绝对不想在 那个兔子洞涉及泛型¹)。

但是,您可以编写一个 扩展函数链来实现深拷贝模式,在类型安全的情况下,每次增加维度时都委派给前一个函数:

private typealias I<E> = Iterable<E>
private typealias Copy<E> = (E) -> E
private inline fun <T, R> I<T>.mapToMutable(transform: (T) -> R): I<R> = mapTo(mutableListOf(), transform)
fun <E> I<E>.deepCopy1(c: Copy<E>) = mapToMutable { c(it) }
fun <E> I<I<E>>.deepCopy2(c: Copy<E>) = mapToMutable { it.deepCopy1(c) }
fun <E> I<I<I<E>>>.deepCopy3(c: Copy<E>) = mapToMutable { it.deepCopy2(c) }
fun <E> I<I<I<I<E>>>>.deepCopy4(c: Copy<E>) = mapToMutable { it.deepCopy3(c) }
fun <E> I<I<I<I<I<E>>>>>.deepCopy5(c: Copy<E>) = mapToMutable { it.deepCopy4(c) }

由于 JVM 类型擦除,函数需要不同的名称(@JVMName 由于类型干扰模糊而无济于事²)。类型别名用于防止水平空间爆炸³,函数集通过通用复制函数参数与深度可复制接口解耦。

示例用法:

fun main(args: Array<String>) {
    data class IntHolder(var value: Int)
    val original = List(3) { a ->
        List(3) { b ->
            IntHolder(a + b)
        }
    }

    val copied = original.deepCopy2 { it.copy() }
    original[0][0].value = 18258125
    println("original=$original")
    println("copied  =$copied")
}

->

original=[[IntHolder(value=18258125), IntHolder(value=1), IntHolder(value=2)], [IntHolder(value=1), IntHolder(value=2), IntHolder(value=3)], [IntHolder(value=2), IntHolder(value=3), IntHolder(value=4)]]
copied  =[[IntHolder(value=0), IntHolder(value=1), IntHolder(value=2)], [IntHolder(value=1), IntHolder(value=2), IntHolder(value=3)], [IntHolder(value=2), IntHolder(value=3), IntHolder(value=4)]]

[1]:因为泛型类型转换由编译器在运行时执行,所以从List&lt;Foo&gt;List&lt;Baz&gt; 的转换在运行时总是会成功,但在访问转换后的列表时会失败。实现上述魔术“单一功能”是可能的,但最轻微的错误将导致返回的数据结构在访问时看似“随机”失败,并出现类转换异常。

[2]:Iterable&lt;Iterable&lt;Foo&gt;&gt; 类型的值满足两者

fun &lt;T&gt; Iterable&lt;T&gt;.baz() (T = Iterable&lt;Foo&gt;) 和
fun &lt;T&gt; Iterable&lt;Iterable&lt;T&gt;.baz() (T = Foo)

因此,如果链中的所有方法具有相同的函数名称,但 JVM 名称不同,编译器将无法确定要使用的正确方法。

[3]:

Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<Iterable<ithinkyougetthepoint>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

【讨论】:

  • 你为什么要重复自己?序列化和反序列化非常适合 java 中的深层复制。
  • 鉴于 OP 最初的尝试,我认为序列化超出了范围。
【解决方案2】:

这是一个使用java.lang.reflect.Array & java.lang.Cloneable 的简单深拷贝示例。

Noetclone 方法执行此对象的 复制,而不是 复制操作,但您可以覆盖clone方法提供复制操作,例如:

val list = mutableListOf(mutableListOf(arrayOf(1)))
val copied = list.deepCopy()

println(copied !== list) //true: not the same
println(copied.map{it.map{it.toList()}} == list.map{it.map{it.toList()}}) 
//                                      ^---true: content equals

//                v--- Array is cloned, since it has implemented Cloneable
println(copied[0][0] !== array[0][0]) // true

typealias NativeArray = java.lang.reflect.Array
@Suppress("UNCHECKED_CAST")
fun <T> T.deepCopy(): T {
    return when (this) {
        is Array<*> -> {
            val type = this.javaClass.componentType
            NativeArray.newInstance(type, size).also {
                this.forEachIndexed { i, item ->
                    NativeArray.set(it, i, item.deepCopy())
                }
            } as T
        }
        is MutableList<*> -> this.mapTo(mutableListOf()) { it.deepCopy() } as T
        is List<*> -> this.map { it.deepCopy() } as T
        is Cloneable -> this.javaClass.getDeclaredMethod("clone").let {
            it.isAccessible = true;
            it.invoke(this) as T
        }
        else -> this
    }
}

【讨论】:

  • 我不确定这是否在问题的范围内,因为 OP 要求复制 2D list 而不是数组。如果您要引入克隆和java.lang.reflect.Array,还可以通过 ObjectOutputStream->ByteArrayOutputStream->ByteArrayInputStream->ObjectInputStream 序列化对象,但这会强制所有对象可序列化..
  • @mEQ5aNLrK3lqs3kfSa5HbvsTWe0nIu 现在怎么样?
猜你喜欢
  • 2018-02-13
  • 2010-11-05
  • 2017-02-09
  • 2010-11-19
  • 2015-08-29
  • 1970-01-01
  • 2022-01-12
  • 2011-04-23
相关资源
最近更新 更多