定义
data是kotlin中一种很重要的数据类型,用来代替java中的bean使用,反编译后多了一些方法,方便使用
与普通class不同之处
为了举例说明以下不同,定义data类 D1,D2,D3,普通class D4
data class D1<T>(val id: Int,val name: String,val inner : D3,val list: List<T>?,)data class D2(val price: Double,val desc: String,val list: List<D3>?)data class D3(val boss: String,)class D4(val boss: String,)
toString()方法
private fun m2(){val d3 = D3("食尸鬼") // data类型val d4 = D4("东京食尸鬼") // 普通classprintln(d3.toString())println(d4.toString())}
D3(boss=食尸鬼) com.xxd.kt.data.base.D4@7a79be86
data自动生成的toString方法更加友好,可以完美打印数据类,而普通class不能自动生成这种打印。
更加高级的地方:
- 可以打印内部的data类型
可以打印List
// 测试打印,内部包含listprivate fun m1() {val d3List1 = listOf(D3("深渊领主"), D3("六翼天使"), D3("八岐大蛇"))val d3List2 = listOf(D3("放射物"), D3("天地大冲撞"), D3("热力学第二定律"))val d21 = D2(123356.33, "微不足道的boss", d3List1)val d22 = D2(9999999999.99, "终极boss", d3List2)val innerD3 = D3("内部boss,哈哈")val d1 = D1(233, "毁灭的",innerD3, listOf(d21, d22))println(d1.toString())}
D1(id=233, name=毁灭的, inner=D3(boss=内部boss,哈哈), list=[D2(price=123356.33, desc=微不足道的boss, list=[D3(boss=深渊领主), D3(boss=六翼天使), D3(boss=八岐大蛇)]), D2(price=9.99999999999E9, desc=终极boss, list=[D3(boss=放射物), D3(boss=天地大冲撞), D3(boss=热力学第二定律)])])
内部的data类型,list都会将数据打印出来,除了打印log直观,还可以用做content内容是否改变的判断条件,在DiffUtil中很有用
翻译版后的toString
@NotNullpublic String toString() {return "D1(id=" + this.id + ", name=" + this.name + ", list=" + this.list + ")";}
都是自动生成的方法,这里有个特殊之处,this.list打印的并非地址,这是list特性,与数组打印不同
copy() 方法
先测试普通copy方法
// 测试data的copy方法private fun m3() {val d3List1 = listOf(D3("深渊领主"), D3("六翼天使"), D3("八岐大蛇"))val d3List2 = listOf(D3("放射物"), D3("天地大冲撞"), D3("热力学第二定律"))val d21 = D2(123356.33, "微不足道的boss", d3List1)val d22 = D2(9999999999.99, "终极boss", d3List2)val innerD3 = D3("内部boss,哈哈")val d1 = D1(233, "毁灭的",innerD3, listOf(d21, d22))val copy = d1.copy(id = 2) // 改变id的copyprintln(d1)println(copy)println("${d1 === copy}") // 本身是否同一个对象,结论:不是println("${d1.inner === copy.inner}") // copy后的内部obj是否同一个对象,结论:是println("${d1.list === copy.list}") // copy后的内部list是否同一个对象,结论:是}
D1(id=233, name=毁灭的, inner=D3(boss=内部boss,哈哈), list=[D2(price=123356.33, desc=微不足道的boss, list=[D3(boss=深渊领主), D3(boss=六翼天使), D3(boss=八岐大蛇)]), D2(price=9.99999999999E9, desc=终极boss, list=[D3(boss=放射物), D3(boss=天地大冲撞), D3(boss=热力学第二定律)])]) D1(id=2, name=毁灭的, inner=D3(boss=内部boss,哈哈), list=[D2(price=123356.33, desc=微不足道的boss, list=[D3(boss=深渊领主), D3(boss=六翼天使), D3(boss=八岐大蛇)]), D2(price=9.99999999999E9, desc=终极boss, list=[D3(boss=放射物), D3(boss=天地大冲撞), D3(boss=热力学第二定律)])]) false true true
结论:data类型的copy是浅拷贝,无法拷贝内部的数据,引用的还是同一个对象
手动来一层深拷贝
private fun m4() {val d3List1 = listOf(D3("深渊领主"), D3("六翼天使"), D3("八岐大蛇"))val d3List2 = listOf(D3("放射物"), D3("天地大冲撞"), D3("热力学第二定律"))val d21 = D2(123356.33, "微不足道的boss", d3List1)val d22 = D2(9999999999.99, "终极boss", d3List2)val innerD3 = D3("内部boss,哈哈")val d1 = D1(233, "毁灭的", innerD3, listOf(d21, d22))val copyList = mutableListOf<D2>()d1.list?.forEach {copyList.add(it.copy())}val copy = d1.copy(inner = d1.inner.copy(), list = copyList) // 改变id的copyprintln("${d1 === copy}") // 本身是否同一个对象,结论:不是println("${d1.inner === copy.inner}")println("${d1.list === copy.list}")println("${d1.list!![0].list === copy.list!![0].list}")}
false false false true
结论:手动实现的深拷贝已经不是同一个对象,但是因为只手动拷贝了一层,第二层中还是同一个对象,所以最后一个判断结果为true
深拷贝
在使用data数据类型时,很多时候都需要进行深拷贝。
如:Android中的RecyclerView使用DiffUtil进行比较,如果不深拷贝data数据,永远不会产生payload局部刷新。
深拷贝方法很多,这里最终选择的序列化+反序列化的方式进行深拷贝,因为这种方法简单,不需要考虑反射等问题,而且兼容list,map,array等的深拷贝。
github地址:https://github.com/sergey-volkov-lm/kotlin-deep-copy-helper
需要引入jackson的包
// jackson序列化工具,做kotlin的deepCopy使用api 'com.fasterxml.jackson.core:jackson-core:2.9.0'api 'com.fasterxml.jackson.core:jackson-databind:2.9.0'
修改了一部分内容
package com.xxd.common.extendimport com.fasterxml.jackson.core.JsonGeneratorimport com.fasterxml.jackson.core.type.TypeReferenceimport com.fasterxml.jackson.databind.DeserializationFeatureimport com.fasterxml.jackson.databind.JsonNodeimport com.fasterxml.jackson.databind.ObjectMapperimport com.fasterxml.jackson.databind.SerializationFeatureimport com.fasterxml.jackson.databind.exc.InvalidFormatExceptionimport com.fasterxml.jackson.databind.exc.UnrecognizedPropertyExceptionimport com.fasterxml.jackson.databind.node.ArrayNodeimport com.fasterxml.jackson.databind.node.JsonNodeTypeimport com.fasterxml.jackson.databind.node.ObjectNodeimport com.xxd.common.extend.ArrayModificationMode.*/** Exposed so you can configure it */val mapper: ObjectMapper = ObjectMapper().findAndRegisterModules().configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false).configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false).configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, true).configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)/*** Enables easy copying object tree with deeply nested properties.* You can use this method on data classes as well as collections.* If you mess up with [propertyPath]/[newValue], there are a couple of exceptions to show what's wrong.** @param propertyPath property pointers separated by '/'. Use number as pointer to array element. Examples:* order/creator/username; lines/0/lineId; 5/products* @param newValue object of the same type as what is currently sitting at propertyPath* @param arrayModificationMode if your last property pointer is array index, you can replace, add/insert or remove element at this index** @throws IllegalArgumentException* @throws IllegalStateException* @throws InvalidFormatException* @throws UnrecognizedPropertyException* @throws IndexOutOfBoundsException** @author Sergey Volkov** 这里使用的jackson的解析,需要引入2个jackson的包* 内部使用是序列化+反序列化的操作来实现深拷贝,而且不需要实现Serializable,Parcelable接口,非常方便* 解析加入了缓存配置,初次反序列化时间比较长,需要300-500mm,之后再次反序列化都是100mm以内,10000个数据的集合反序列化页只花了44mm,100个数据的集合反序列化1mm* 原来内实现了拷贝时替换某些字段,而且必须替换,这里增加了原始拷贝,不做替换操作* github地址:https://github.com/sergey-volkov-lm/kotlin-deep-copy-helper* 需要引入的2个jackson包:* implementation 'com.fasterxml.jackson.core:jackson-core:2.9.0'* implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.0'*/inline fun <reified T : Any> T.deepCopy(propertyPath: String? = null,newValue: Any? = null,arrayModificationMode: ArrayModificationMode = REPLACE): T = deepCopy(propertyPath, newValue, arrayModificationMode, object : TypeReference<T>() {})/*** You are not supposed to use this directly, but it reduces amount of inlined code*/fun <T : Any> T.deepCopy(propertyPath: String?,newValue: Any?,arrayModificationMode: ArrayModificationMode,type: TypeReference<T>): T {// 这里为修改原来的内容,不需要替换下,不需要做匹配替换操作,直接序列化,反序列化即可if (propertyPath == null || propertyPath.isEmpty()) {val sourceJsonNode = mapper.valueToTree<JsonNode>(this)val resultJson = mapper.writeValueAsString(sourceJsonNode)return mapper.readValue(resultJson, type)}val pathTokensRaw: List<String> = propertyPath.split('/')require(!pathTokensRaw.contains("")) {"propertyPath must not contain empty parts"}val onlyDigitsRegex = """\d+""".toRegex()val wordRegex = """\w+""".toRegex()val pathTokens: List<PathToken> = pathTokensRaw.map {PathToken(stringValue = it,type = when {it.matches(onlyDigitsRegex) -> {PathTokenType.ARRAY_INDEX}it.matches(wordRegex) -> {PathTokenType.PROPERTY}else -> {throw IllegalArgumentException("propertyPath must contain only [A-Za-z0-9] chars")}},)}val sourceJsonNode = mapper.valueToTree<JsonNode>(this)val newValueJsonNode = mapper.valueToTree<JsonNode>(newValue)var parentNode: JsonNode = sourceJsonNodepathTokens.dropLast(1).forEach {parentNode = if (it.type == PathTokenType.ARRAY_INDEX) {parentNode.get(it.intValue) ?: error("Bad index in propertyPath")} else { // propertyparentNode.get(it.stringValue) ?: error("Bad property in propertyPath")}}val lastPathToken = pathTokens.last()when (parentNode.nodeType) {JsonNodeType.ARRAY -> {check(lastPathToken.type == PathTokenType.ARRAY_INDEX) {"Bad propertyPath. Expected array index at the end."}val parentArrayNode = parentNode as ArrayNodeval index = lastPathToken.intValueif (index > parentArrayNode.size()) {throw IndexOutOfBoundsException("Can't set/add/insert element at index $index. Check propertyPath.")}when (arrayModificationMode) {REPLACE -> {parentArrayNode.set(index, newValueJsonNode)}INSERT_APPEND -> {parentArrayNode.insert(index, newValueJsonNode)}REMOVE -> {parentArrayNode.remove(index)}}}JsonNodeType.OBJECT -> {check(lastPathToken.type == PathTokenType.PROPERTY) {"Bad propertyPath. Expected property name at the end."}(parentNode as ObjectNode).set(lastPathToken.stringValue, newValueJsonNode)}else -> error("Unexpected parent JsonNode type: ${parentNode.nodeType}, raw value: $parentNode")}val resultJson = mapper.writeValueAsString(sourceJsonNode)return mapper.readValue(resultJson, type)}data class PathToken(val type: PathTokenType,val stringValue: String) {val intValue: Intget() = if (type == PathTokenType.ARRAY_INDEX)stringValue.toInt()elseerror("PathToken $stringValue is property, not array! Check propertyPath.")}enum class PathTokenType {/** Property name, to be specific */PROPERTY,/** Integer, starting from 0 */ARRAY_INDEX}enum class ArrayModificationMode {REPLACE,INSERT_APPEND,REMOVE}
