数据解析与定义
1. Json解析与Bean的定义
Json转Bean的时候,不会进行空安全判断,当设置参数类型为非空类型,如果Json中指定为null,
参数会被赋值为null,后面引用此参数时,会进行空安全检查checkNull,
此时就会执行kotlin.jvm.internal.Intrinsics.checkNotNullParameter
抛出Parameter specified as non-null is null
;
例如:要解析的实体Bean为
@Keep
data class ResponsePaging(
@JvmField var paging: Paging? = null,
@JvmField var timestamp: Long = 0,
@JvmField var data: List<String> = arrayListOf()
) : Serializable
接口数据为:
{
"data": null,
"paging": {
"hasMore": true,
"limit": 10,
"offset": 10,
"total": 100
}
}
此时,ResponsePaging 的 data = null,如果在调用data的地方没有使用data?.
或者判空,此时就会抛出异常;
所以接口实体类,所有的参数都应该声明为可空,因为不确定服务器返回的数据是否全部有值;
⚠注意
num 和 boolean 如果声明为非空类型,如果指定了默认值,那么值就等于其指定的默认值,即使 Json 的数据明确指定为 null,解析时也会为其赋值为默认值;
如果想要接收 Json 的数据 null,需要声明为可空类型,并且不设置默认值,如:offset: Int? = null
2. JvmField 在通用基类 Bean 定义中的注意事项
定义了分页相关的接口解析通用实体ResponsePaging
如下:
@Keep
open data class ResponsePaging<T>(
@JvmField open var paging: Paging? = null,
@JvmField open var timestamp: Long = 0,
@JvmField open var data: List<T> = arrayListOf()
) : Serializable
此时对应接口响应的Json应为:
{
"data": ...,
"paging": {
"hasMore": true,
"limit": 10,
"offset": 10,
"total": 100
}
}
如果某个接口响应的data
参数换名了,例如换成具体的名称feedList
,此时我们希望通过继承ResponsePaging
重写data
来增加例如SerializedName
来兼容新的名称,Example:
open data class CustomBean(
@SerializedName("feedList")
@JvmField
override var data: List<String>? = null,
) : ResponsePaging<String>()
这时候问题就出现了:接口解析成功,并且调用CustomBean.data
可以获取到正确的值,但如果(CustomBean as ResponsePagingdata).data
,此时就不能获取到正确的值;
原因:增加了@JvmField
之后,相当于Java中声明字段为public
,此时,字段是不能被重写的;
建议:声明通用基类时,尽量不要添加@JvmField
注解,并且,重写基类字段时,被重写的字段同样不要增加@JvmField
,Kotlin转Class时,子类和父类都会自动生成getter、setter方法,而方法是可以重写的,就可以达到我们做兼容不同字段名的目的;
附Java测试用例:
class SuperClass {
public String str = "SuperClass";
}
class SubClass extends SuperClass {
// @Override 加上会报错
public String str = "SubClass";
}
public static void main(String[] args) {
SubClass subClass = new SubClass();
// 将子类的对象赋值给父类的引用
SuperClass superClass = subClass;
System.out.println("-----------------字段------------------");
System.out.println("superClass.str " + superClass.str);
System.out.println("subClass.str " + subClass.str);
}
// 输出
-----------------字段------------------
superClass2.str SuperClass2
subClass2.str SubClass2
要不要广泛使用 Data Class ?
kotlin data class 会自动生成以下函数:
getter
、setter
方法;componentN
解构方法,有几个属性,就会生成几个结构方法;copy
浅拷贝方法;constructor
构造函数,如果有Nullable
属性,将会生成多个重载构造函数;toString
,数据实体应重写;equals
,数据实体应重写;hashCode
,数据实体应重写; 由此看到,getter
、setter
、componentN
、copy
都是使用频率比较低的函数,并且会增加大量方法数,
而toString
、equals
、hashCode
可以使用快捷键生成,并不影响开发效率和使用;
所以 非必要情况下,不建议使用data class;
要不要广泛使用内联函数 inline ?
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包,即那些在函数体内会访问到的变量。
内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。
内联函数主要的使用场景是在循环或者尾递归中的函数调用,所以不应该随意在函数上加上inline修饰;
内联函数也有相应的局限性,例如不能使用return,需要用crossInline标记函数作为参数传递到另一个函数中或者嵌套函数调用,需要使用noinline标记需要作为返回值的函数参数;
大量使用内联函数可能会导致字节码的激增,增加包体积;