[BYM] Android To Kotlin 使用笔记

1,215 阅读5分钟

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泛型处理的差异,避免出现安全转换问题。

这里建议大家看看文末的Kotlin的泛型,比Kotlin官网讲的要通俗易懂些

5.待更新

参考文献