blow your mind
bym系列意在除开技术分享,还分享下思路,不止是做一个代码的搬运工。
1. Int 替换Integer
面对一些后端返回的接口,以及列表筛选的定义,我们通常会定义Integer类型的数据。
例如:
Integer userId;//后端返回的用户id
Integer state;//null,0,1,2,3 //某些列表的状态筛选
复制代码
在kt中,我们不要再是用Integer这种包装类型。直接使用Kt的基础类型kt来替换,如果坚持使用Integer类型,则会出现类型不兼容的情况。
例如:
//枚举类java文件
public enum CheckStatus {
WAIT(1),
...
private final Integer value;
CheckStatus(Integer value) {
this.value = value;
}
}
//kt文件中
var checkStatus: Integer? = null
1 -> checkStatus = CheckStatus.WAIT.value
~~~~~~~~~~~~~~~~~~~~~~
Type mismatch.
Required:
Integer?
Found:
Int!
复制代码
很奇怪是不是,为什么这里会报错类型不匹配。因为kt本身不存在基础类型int,double…等,那么kt在处理java的数据类型时,自动就封装成了kt自己的包装类型Int,Short,Long,Float,Double等。所以当我们在kt文件中继续是用Integer时,通过kt代码调用获取java中的Integer就被包装成Int类型,所以与Integer类型不匹配。
那么我们直接将原有的Integer替换成Int类型使用就行了。
2. interface中的val与var
我们在定义一个interface时,希望继承该接口的类能够返回某个字段作为关键字段。例如:列表,筛选框显示的文字,Java中我们一般会这样来定义
interface SimpleText{
String getTitle();
}
//实现子类
class TestText implements SimpleText{
String name
@Override
public String getTitle() {
return name;
}
}
复制代码
而在kt中,如果我们只需要定义一个get方法,则使用val声明该字段就行了,因为val意味着常量,但是val修饰的字段允许初始化一次。则满足我们上述场景的需求,所以应该这么写
interface SimpleText {
val title: String
}
//实现子类
//java中使用时写法与上面实现一致
//kt如下
class TestText : SimpleText {
var name: String = ""
override val title: String
get() = name
}
复制代码
那么如果你将interface中的字段定义成var就意味着这个参数是可变的,那么就需要在实现子类中重载get/set方法。
3. 伴生对象与newInstance
在我们使用Fragment的过程中,往往会定义newInstance方法来实现创建Fragment,并向Fragment的传参。如下所示
public class MyFragment extends Fragment {
private static final String ARG_CAUGHT = "myFragment_caught";
public static MyFragment newInstance(Pokemon caught) {
Bundle args = new Bundle();
args.putSerializable(ARG_CAUGHT, caught);
MyFragment fragment = new MyFragment();
fragment.setArguments(args);
return fragment;
}
...
}
复制代码
那么在kt中,我们需要用到
class MyFragment: Fragment(){
companion object{
private val ARG_CAUGHT = "myFragment_caught"
@JvmStatic //在java中调用MyFragment.newInstance()需要加上此注解
fun newInstance(caught: Pokemon):MyFragment{
val args: Bundle = Bundle()
args.putSerializable(ARG_CAUGHT, caught)
val fragment = MyFragment()
fragment.arguments = args
return fragment
}
...
}
fun test(){
val caught = arguments?.getString(ARG_CAUGHT)
}
}
复制代码
可以看到示例中有一个常量ARG_CAUGHT,该常量一样可以在MyFragment中使用。
注意事项:
- 在 JVM 平台,如果使用 @JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
4 Parameter type must not include a type variable or wildcard
在改造Retrofit + Kotlin请求接口时,Log中报错
Parameter type must not include a type variable or wildcard:
java.util.Map<java.lang.String, ? extend okhttp3.RequestBody> (parameter #1)
复制代码
报错场景场景如下:
@Multipart
@POST("xxx/addTestProject")
fun addTestProject(@PartMap paramsMap: Map<String, RequestBody>?):Observable<BaseApiEntity<String?>?>
复制代码
咋一看没什么错,但是在运行时,RequestBody做为java的抽象类,被kt当做了Any来看待,而在这里kt语法不允许声明一个通配参数。这个时间只需要在参数前面添加注解 @JvmSuppressWildcards
这里可以引申出Android和kt互相调用中需要用到的两个注解
JvmWildcard
以及 JvmSuppressWildcards
@JvmWildcard
这个注解主要用于处理泛型参数,这涉及到两个新的知识点:逆变与协变。由于Java语言不支持协变,为了保证安全地相互调用,可以通过在泛型参数声明的位置添加该注解使用Kotlin编译器生成通配符形式的泛型参数(?extends ...)
。
看下面这段代码:
class Box<out T>(val value: T)
interface Base
class Derived : Base
fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
复制代码
按照正常思维,下面的两个方法转换到Java代码应该是这样:
Box<Derived> boxDerived(Derived value) { …… }
Base unboxBase(Box<Base> box) { …… }
复制代码
但问题是,Kotlin泛型支持型变,在Kotlin中,我们可以这样写unboxBase(Box(Derived())),而在
Java语言中,泛型参数类型是不可变的,按照上面的写法显然已经做不到了。
正确转换到Java代码应该是这样:
Base unboxBase(Box<? extends Base> box) { …… }
复制代码
为了使这样的转换正确生成,我们需要在泛型参数的位置添加上面的注解:
fun unboxBase(box: Box<@JvmWildcard Base>): Base = box.value
复制代码
@JvmSuppressWildcards
这个注解的作用与@JvmWildcard恰恰相反,它是用来抑制通配符泛型参数的生成,即在不需要型变泛型参数的情况下,我们可以通过添加这个注解来避免生成型变泛型参数。
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// 生成的代码相当于下面这段Java代码
Base unboxBase(Box<Base> box) { …… }
复制代码
正确使用上述注解,可以抹平Kotlin与Java泛型处理的差异,避免出现安全转换问题。