历时七天,我将我的两万行java项目转kotlin项目

数天前我将我java开发的工程,全部转换成了kotlin形式的工程。如果你也想做,本身也有一定的java开发安卓程序的功底。本文将比较适合你。

创建kotlin工程,拷贝类文件xml文件等核心文件到工程目录下,形成一个kotlin底子的java代码组成的工程,然后通过ctrl +shift +alt +k 快捷代码逐个转换为kotlin。转换的结果相对比较成功。将转换中遇到的问题总结如下。

1. 循环,改成filter map的使用

那些只是听说过kotlin的小白,在转换前,请参考:最适合Android程序员的kotlin笔记——集合操作
很多普通的操作,AS可以帮我们自动转,但是唯独集合(AS工具的薄弱环节)这里可以优化的比较多。所以建议熟悉。

2. 字符串非空判断改成kotlin形式。

快捷键转换后,还是equal("")形式,改成纯正的kotlin风格:
一下方法为kotlin特有:

.isEmpty()
.isBlank()
.isEmptyOrNull()
.isNullOrBlank()

类似的,点出来,自己看用那个合适。

3. 不必要的bindview

原来的:

   @BindView(R.id.ad_image)
    ImageView ad_image;
    @BindView(R.id.countdownProgressView)
    TextView countdownProgressView;

问题: 类似此类的代码,转换后还会存在。

解决办法: 转换前,对齐控件的变量名和id名。转换后,直接删掉。

你会惊奇的发现,.kt 文件直接引用了xml文件。代码中可以直接使用id来对view进行操作了。

4.丑陋的equals

    if (!StringUtils.equals(password, password2)) {
        toast(resources.getString(R.string.two_password_input_error))
        return
    }

改成等号,不等号。

java中的 equals ,kotlin中可用 == 替代,java中的比较对象的引用,kotlin中,用===替代。

5. 容易错转的map

在进行此类型的转换和修正前,请百度一下kotlin map集合操作。
以下是原java代码:

 private String getTypemane(int id) {
        String aa = "";
        List> typelist = listDataSaveUtil.getDataList();
        Map map = typelist.get(0);
        for (Map.Entry str : map.entrySet()) {
            if (String.valueOf(id).equals(str.getKey() + "")) {
                aa = str.getValue();
            }
        }
        return aa;
    }

ktlin错误转换为:

   private fun getTypemane(id: Int): String {
        var aa = ""
        val typelist = listDataSaveUtil!!.getDataList>()
        val map = typelist[0]
        for ((key, value) in map) {
            if (id == key) {
                aa = value
            }
        }
        return aa
    }

运行报错,不能将int 转number,我给他改成了:

 private fun getTypemane(id: Int): String {
        var aa = ""
        val typelist = listDataSaveUtil!!.getDataList>()
        val map = typelist[0]
       if(map.contains(id)){
            aa=map.getValue(id)
        }
        return aa
    }

6.不够清爽的if else 转换

java代码:

 private void isShowRv(AllTypeBean body) {
        if (getAreaList(body).size() == 1) {
            mRvArea.setVisibility(View.GONE);
        } else {
            mRvArea.setVisibility(View.VISIBLE);
        }

        if (body.getClassX().size() == 1) {
            mRvStyle.setVisibility(View.GONE);
        } else {
            mRvStyle.setVisibility(View.VISIBLE);
        }

        if (body.getType().size() == 1) {
            mRvAllTvStyle.setVisibility(View.GONE);
        } else {
            mRvAllTvStyle.setVisibility(View.VISIBLE);
        }


        if (body.getYear().size() == 1) {
            mRvYear.setVisibility(View.GONE);
        } else {
            mRvYear.setVisibility(View.VISIBLE);
        }

    }

使用快捷键转换,怎么结果还是一堆if?!!
还是难看,能否用when(kotlin最擅长的)改造下,动手,效果如下:

  when {
            getAreaList(body!!).size == 1 -> mRvArea!!.visibility = View.GONE
            getAreaList(body!!).size != 1 -> mRvArea!!.visibility = View.VISIBLE
  
            body!!.classX!!.size == 1 -> mRvStyle!!.visibility = View.GONE
            body.classX!!.size != 1 -> mRvStyle!!.visibility = View.VISIBLE
     
            body!!.type!!.size == 1 -> mRvAllTvStyle!!.visibility = View.GONE
            body.type!!.size != 1 -> mRvAllTvStyle!!.visibility = View.VISIBLE
    
            body!!.year!!.size == 1 -> mRvYear!!.visibility = View.GONE
            body.year!!.size != 1 -> mRvYear!!.visibility = View.VISIBLE
        }

运行,程序业务逻辑出错!!!!,断点研究发现,代码只走到when中第二行,就break了。后面没走。好傻!生手!

正确的写法:如下:


  when {
            getAreaList(body!!).size == 1 -> mRvArea!!.visibility = View.GONE
            getAreaList(body!!).size != 1 -> mRvArea!!.visibility = View.VISIBLE
        }
        when {
            body!!.classX!!.size == 1 -> mRvStyle!!.visibility = View.GONE
            body.classX!!.size != 1 -> mRvStyle!!.visibility = View.VISIBLE
        }
        when {
            body!!.type!!.size == 1 -> mRvAllTvStyle!!.visibility = View.GONE
            body.type!!.size != 1 -> mRvAllTvStyle!!.visibility = View.VISIBLE
        }
        when {
            body!!.year!!.size == 1 -> mRvYear!!.visibility = View.GONE
            body.year!!.size != 1 -> mRvYear!!.visibility = View.VISIBLE
        }

一堆when,不爽。能否再简化下,聪明的你一定想到了,还是用回if else

if在 java中是语句,kotlin中是表达式(表达式有值,语句不返回值)

改!:

        mRvArea!!.visibility = if (getAreaList(body!!).size == 1) View.GONE else View.VISIBLE
        mRvStyle!!.visibility = if (body!!.classX!!.size == 1) View.GONE else View.VISIBLE
        mRvAllTvStyle!!.visibility = if (body!!.type!!.size == 1) View.GONE else View.VISIBLE
        mRvYear!!.visibility = if (body!!.year!!.size == 1) View.GONE else View.VISIBLE

总结: when结构,替换的时候,典型的场景应该是switch case 那种(一旦某一个条件满足,直接跳出函数体)
普通的if根据需求,千万别像我一样转错了。但是如果是连续的else if结构,也可以替换。
 if (customView != null) {
                hideCustomView()
            } else if (wv_web_view!!.canGoBack()) {
                wv_web_view!!.goBack()
            } else {
                finish()
            }

转成如下:

    when{
                customView != null-> hideCustomView()
                wv_web_view!!.canGoBack()->  wv_web_view!!.goBack()
                else->finish()
            }

还有这种单if的垃圾代码,都很适合转when

注意这种单if和上面的单if的不同,这正是我这个条目想强调的部分。
    if (VipType == HdVipType) {
            userMessage!!.vip!!.isHdvip = true
        }
        if (VipType == DlanVipType) {
            userMessage!!.vip!!.isDlanvip = true
        }
改动秘诀:

看是否能转换为else if 能转换成else if的 ,在转when,遵循这个原则。

7、漏失的toString()

例子:

  private Map getIntegerStringMap(HomeTypeBean body) {
        Map map = new HashMap<>();
        for (HomeTypeBean.TypeBean categoryBean : body.getType()) {
            map.put(categoryBean.getType_id(), categoryBean.getType_name());
        }
        return map;
    }

转换Kotlin后:


  private fun getIntegerStringMap(body: HomeTypeBean): Map {
        val map = HashMap()
        for (categoryBean in body.type!!) {
            map[categoryBean.type_id] = categoryBean.type_name
        }
        return map
    }

编译器出现报错将for循环中map一行,下划线标红。


   private fun getIntegerStringMap(body: HomeTypeBean): Map {
        val map = HashMap()
        for (categoryBean in body.type!!) {
            map[categoryBean.type_id] = categoryBean.type_name.toString()
        }
        return map
    }

原来是没有添加toString(); 这种情况,as有报错,并有提示。但此处Studio给出的提示不易懂,注意!

8. 集合的转换不够彻底:

例子java代码:

 private List bornTypeList(HomeTypeBean body) {
        List typeList = new ArrayList<>();
        for (HomeTypeBean.TypeBean categoryBean : body.getType()) {
            typeList.add(categoryBean.getType_name());

        }
        return typeList;
    }

as工具转换为kotlin代码后:


   private fun bornTypeList(body: HomeTypeBean): List {
        val typeList = ArrayList()
        for (categoryBean in body.type!!) {
            typeList.add(categoryBean.type_name.toString())

        }
        return typeList
    }

太丑了,羞于使用,优化,代码如下:


   private fun bornTypeList(body: HomeTypeBean): List { 
        return body.type!!.map { it.type_name.toString() }
    }

9. 失灵的onclick

使用bindview的条件下,如下转换后的kotlin代码,出现业务问题,点击事件失灵:

@OnClick(R.id.searcher_view, R.id.play_history_image, R.id.all)
    fun onClick(v: View) {

        //搜索
        if (v === searcher_view) {
            ActivityUtils.startActivity(Intent(activity, SearcherActivity::class.java))
            return
        }
        //播放记录
        if (v === play_history_image) {
            ActivityUtils.startActivity(PlayHistoryActivity::class.java)
            return
        }

        //全部
        if (v === all) {
            val intent = Intent(activity, AllTypeActivity::class.java)
            intent.putExtra(ConstantUtils.TYPE_NAME, videoType)
            intent.putExtra(ConstantUtils.TYPE_ID, typeId)
            ActivityUtils.startActivity(intent)
        }
    }

10. 无法优雅转换的单例

在java中我有如下代码:


public class DataBeanModel {

    private SparseArray> listHashMap = new SparseArray<>();

    private static class SingletonHolder {
        private static final DataBeanModel INSTANCE = new DataBeanModel();
    }

    private DataBeanModel() {
    }

    public static final DataBeanModel getInstance() {
        return SingletonHolder.INSTANCE;
    }

    public void clearAll(){
        listHashMap.clear();
    }

    public void putValue(int Page, ArrayList dataBeans){
        listHashMap.put(Page, dataBeans);
    }
    public List getSetByPage(int position){
        return  listHashMap.get(position);
    }

    public SparseArray> getAll(){
        return listHashMap;

    }
}

as工具转换后:


class DataBeanModel private constructor() {

    val all = SparseArray>()

    private object SingletonHolder {
        private val INSTANCE = DataBeanModel()
    }

    fun clearAll() {
        all.clear()
    }

    fun putValue(Page: Int, dataBeans: ArrayList) {
        all.put(Page, dataBeans)
    }

    fun getSetByPage(position: Int): List {
        return all.get(position)
    }

    companion object {

        val instance: DataBeanModel
            get() = SingletonHolder.INSTANCE
    }
}

已经非常臃肿,浓浓的java式kotlin,怎么办?
正确的做法:

  1. 注释和单例相关的方法:

    private static class SingletonHolder {
        private static final DataBeanModel INSTANCE = new DataBeanModel();
    }

    private DataBeanModel() {
    }

    public static final DataBeanModel getInstance() {
        return SingletonHolder.INSTANCE;
    }

  1. 按下ctrl shift alt k 转换成kotlin,将类关键字class 替换为Object
  2. 删除注释部分

object DataBeanModel {

    val all = SparseArray>()
    fun clearAll() {
        all.clear()
    }

    fun putValue(Page: Int, dataBeans: ArrayList) {
        all.put(Page, dataBeans)
    }

    fun getSetByPage(position: Int): List {
        return all.get(position)
    }
}
  1. 调用的部分,改为(中间加INSTANCE):

    DataBeanModel.INSTANCE.clearAll();

值得注意的是,当我在运行业务逻辑的时候,提示我,getSetByPage 这个方法: return all.get(position)
这个代码,里面all已经强制要求为空。导致我程序报错。不知道all为什么会为空。回滚吧。

10. 无法正确转换+=1

我有如下java代码:


    newHost = appMessageBean.getHostUrlList().get(hostPosition += 1);

直接as转换后,在代码下面直接爆红。

解決方案:在外面写hostPosition+=1, 然后再用as工具转换,正确代码。
   hostPosition = hostPosition + 1
                    newHost = appMessageBean!!.hostUrlList!![hostPosition]

11. 变异的trim方法:

我在java中的trim方法,kotlin转换为了如下形式:

    result.addProperty("mobile", etPhone!!.text.toString().trim { it <= ' ' })
注意:kotlin的trim()方法意思发生了变异,是指去除前后的空格。和java中的trim方法等价的就是:.trim { it <= ' ' } 转换并没有发生错误,慢慢学习吧。编译器的快捷键转换是正确的。这个给编译器点个赞。

12. 不够优雅的工具类

kotlin中,方法不一定会写在类内部。操作方法,
  • 工具类去掉类的包裹,直接全部都是方法
  • 类文件的第一行,指明名字即可,如下:

@file:JvmName("AesUtils")
package com.play.myapplication.helper

  • java文件中该怎么用怎么用
  • 如果是在kotlin文件中,直接写方法即可,前面的“类点”也给省略了

13. 错误转换的get 方法。

默认as的转换工具,会对,get 开头的方法,进行重新梳理。

有以下java代码:

    /**
     * 标题栏id
     */

    protected abstract int getTitleBarId();

    /**
     * 引入布局
     */
    protected abstract int getLayoutId();

用来子类复写,父类回调。结果AS自动转换:

    /**
     * 标题栏id
     */
    protected abstract val titleBarId: Int

    /**
     * 引入布局
     */
    protected abstract val layoutId: Int

荒唐呀,我这个是一个父类方法,子类需要复写。这时候就会导致更多的转换异常,解决办法,在java阶段,改成重命名成其他(比如got)开头的方法。然后再用工具转换。

注意不要用get set开头,除非确定是当成成员处理。

13. 不够优雅的弱引用。

有弱引用代码,转换后,累赘不够优雅,并且
弱引用使用处,如下的位置,还会爆红。

     weakReference.get().setAppMessageBeanFromSP()

添加双感叹号。

     weakReference.get()!!.setAppMessageBeanFromSP()

爆红解决,单仍不够优雅。
解决方案:
采用委托机制处理
第一步,创建weak类:


class Weak(initializer: () -> T?) {
    var weakReference = WeakReference(initializer())

    constructor() : this({
        null
    })

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
        return weakReference.get()
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        weakReference = WeakReference(value)
    }
}

第二步
生命的地方这样写,

        var act:MyActivity? by Weak()

引用的地方:

act.setAppMessageBeanFromSP()
14. 意外的问号
 OkGoUtils.postRequest(ConstantUtils.getUrlConstant().getaskvip, JsonObject.toString(), new JsonCallBack(UserandVip.class, this) {

            @Override
            public void onSuccess(Response response) {
}
)}

转换完之后,给我添加一个:

 OkGoUtils.postRequest(
            ConstantUtils.getUrlConstant().getaskvip,
            JsonObject.toString(),
            object : JsonCallBack(
                *UserandVip::class.java, this*
            ) {
                *override fun onSuccess*(response: Response) {
                    super.onSuccess(response)
}
)}

很奇怪,导致斜体的部分都在飘红。
去掉问号就可以了。这里没必要if null

15. 错误转换的json文件:重要#############巨坑巨坑#################

image.png

之前我们项目中存在一个签到的问题,原来里面是用json转换的,字段是signin , 使用kotlin转换后,kotlin自动会为boolean类型的字段加上is,变成如上图所示,结果倒是json变换的时候,请求返回的bean中,接收不到新的数据singin=true。获取的isSignin=false。破坏了原来代码的逻辑。大罪大罪!!!!!!!!!!!!!!!!!转换方法要坑满才能明白过来呀

16. 点击事件
源代码:
 btn_stop_dlan.setOnClickListener(this::onClick);
        btn_iv_dlan_full.setOnClickListener(this::onClick);
        btn_iv_add_volume.setOnClickListener(this::onClick);
        btn_iv_delete_volume.setOnClickListener(this::onClick);

        btn_ad_x.setOnClickListener(this::onClick);
        iv_pause_ad.setOnClickListener(this::onClick);
        btn_video_tv.setOnClickListener(this::onClick);
        btn_video_hd.setOnClickListener(this::onClick);
        btn_video_next.setOnClickListener(this::onClick);
        btn_video_skip.setOnClickListener(this::onClick);
        rl_close_father.setOnClickListener(this::onClick);
        rl_pause_father.setOnClickListener(this::onClick);
        rl_close_father.setOnClickListener(this::onClick);
        btn_video_start.setOnClickListener(this::onClick);
转换后的代码:

但是下面报红线

  btn_stop_dlan.setOnClickListener(OnClickListener { v: View ->
            onClick(
                v
            )
        })
        btn_iv_dlan_full.setOnClickListener(OnClickListener { v: View ->
            onClick(
                v
            )
        })
        btn_iv_add_volume.setOnClickListener(OnClickListener { v: View ->
            onClick(
                v
            )
        })
        btn_iv_delete_volume.setOnClickListener(OnClickListener { v: View ->
            onClick(
                v
            )
        })
        btn_ad_x.setOnClickListener(OnClickListener { v: View ->
            onClick(
                v
            )
        })
17. 缺失的hanlder类型

java代码(减缩版)

  private Handler mDialogHandler;   
  public void initTimerDialog() {
       btn_btn_finish_0.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               
                    mDialogHandler.removeCallbacksAndMessages(null);
             
            }
        });
        };

快捷键转换(减缩版)

  private var mDialogHandler: Handler? = null
   fun initTimerDialog() {
     
        btn_btn_finish_0!!.setOnClickListener(object : OnClickListener {
            override fun onClick(v: View) {
              
                    mDialogHandler.removeCallbacksAndMessages(null)
             
            }
        })
}

转换后:此行报错!
mDialogHandler.removeCallbacksAndMessages(null)
订正:
(mDialogHandler as Handler).removeCallbacksAndMessages(null)

Alt+enter中有,只要合理选择就行。

第二个坑:
简化后的java代码如下:

  public Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            String s = msg.obj.toString();
            runCount();
        }
    };

转换后,第一行,handler名字这里报错:


image.png

怎办?看了alt+enter的提示,看不懂。
解决方案就是:
不用用handler做名字,换个hanlderOne啥的,handler这个名字好像已经被占用了。

18.IDE 自带的插件转换 Java 代码, 项目上线后后台空指针Error增加(重要#############巨坑巨坑#################

IDE 里面的插件 "Covert Java File To Kotlin File" 早已被大家熟知,要是不知道的小伙伴,赶紧写个 Java 文件,尝试点击 Android Studio 工具栏的 Code 下面的 "Convert Java File To Kotlin File"。
这样的方式足够地快,但却会出现很多很多的 !!,这是由于 Kotlin 的 null safety 特性。这是 Kotlin 在 Android 开发中的很牛逼的一大特性,想必不少小伙伴都被此 Android 的 NullPointException 困扰许久。我们直接转换 Java 文件造成的各种 !! ,其实也就意味着你可能存在潜在的未处理的 KotlinNullPointException。

19. 资源id和object对象的变量名重名

if (FilmAllModel.INSTANCE.getTotal() == 1)
转换为kotlin
if(total==1)
此处直接报错,阅读提示,发现,我们的一个资源id也叫total,重名导致,报错。

20. 不变的枚举

枚举对开发者非常友好。数量有限的元素,描述性的名字,因此大幅提高了代码的可读性。另外他还支持多态。由于这些原因枚举在代码中被广泛使用。——摘自作者:《Android 高性能编程》(西班牙)([恩里克·洛佩斯·马尼亚斯](意)([迪戈·格兰奇尼]

正如摘要所言,枚举很好。但我不推荐枚举,缘由有两个:

  1. 枚举通常会占用两倍于静态常亮的内存,在朱凯翻译的android delveop官方性能优化里已经不推荐(Enums usually request twice as much memory as static constants)
  1. 枚举在有几个成员,就分别会被转换为几个对象, 并且分贝为他们的name 参数分配String 。。。——《Adnroid 高性能编程》也正因为如此,以简化著称的kotlin语言,少见的为enum添加一个class ,比java的创建还多一个关键字。就是因为他本质是一个类,而且还不是一个省心的class ,是class里面还有class。

在安卓高性能编程这本书里,作者推荐,用静态常量代替枚举类的成员,将枚举类前面的enum,换成class,回归一个正常的类。

说了这么多有什么用呢? 因为kotlin自动转换,枚举类还是枚举类,若要转换的优雅,还得自己操作。
怎么办呢?

  • 我们在转换的时候也可以遵循这个步骤:
  1. 先将类转换 2 . 再转kotlin。
    java代码:
public enum EventType { 
    LOG_MINE, 
    UD_HEADER, 
    INVISIBLE_RED_DOT,
}

kotlin转换:

object EventType {    
    const val LOG_MINE = 0 
    const val UD_HEADER = 1 
    const val INVISIBLE_RED_DOT = 2
}

Tips:安卓版本后来,在形如上面代码的简单枚举,在proguard的时候,已经转换成了静态常量,所以性能无影响。
参考链接:https://www.jianshu.com/p/3b0c10c08227
https://www.jianshu.com/p/c7f9444e6d12

更新日期:2020.05.13

你可能感兴趣的:(历时七天,我将我的两万行java项目转kotlin项目)