这个系列是扔物线 码上开学系列课程 的课后练习题,记录的也主要是一个java程序员在转kotlin 时重点关注的一些用法的比较。

本节内容目录

《Kotlin 的变量、函数和类型》

Q1:子类重写父类的 override 函数,能否修改它的可见性

Q2:以下的写法有什么区别?

Q3:可空变量的声明、初始化和传参

《Kotlin 里那些「不是那么写的」》

Q1:单例练习

Q2:数组和集合的练习

《Kotlin 里那些「更方便的」》

Q1: 主构造器和字符串模板$的练习

Q2 : 循环的用法

Q3: 为什么数组的 filter 之后变成 List


  • 《Kotlin 的变量、函数和类型》

Q1:子类重写父类的 override 函数,能否修改它的可见性

父类是 internal 可以修改为public,但是父类为public时无法修改为private/internal。

分析:只要记住子类不能降低父类的可见性即可。至于为什么不讨论protect,是因为Kotlin 里的 override 函数的可见性是继承自父类的,父类public子类就public,父类private方法子类就无法复写。也就是说,对于kotlin来说,写不写protect都是继承自父类。

延申1:如果不是继承自父类的话,Kotlin 函数默认可见性都是 public (和变量一样)。

延申2:关于能不能继承,Kotlin 里的类和方法默认是 final ,不能继承的(而 Java 里只有加了 final 才是无法继承 的)。如果想创建一个类的子类,需要使用open 关键字来修饰这个类(方法)。

Q2:以下的写法有什么区别?

  • 1. activity as? NewActivity
  • 2. activity as NewActivity?
  • 3. activity as? NewActivity?

1. 如果 activity 是 NewActivity,转换成功,否则返回空,当 activity 为空的时候,返回也是为 null。

2. 如果 activity 是NewActivity 类型,就转换成功,否则抛出 java.lang.ClassCastException 异常。问号的作用是当 activity 为 null 的时候,不会抛出异常,返回 null。

3. 如果 activity 是 NewActivity 类型,就转换成功,否则返回空。

分析:as 是强转关键字,强转成一个错误的类型的时候抛异常, as? 就是来解决强转失败的问题的。推荐写法应该是:3或者

(activity as? NewActivity)? 

这里 第一个? 确保强转能成功才往下执行,第二个? 表示成功且强转结果不为null的话,才会继续往下执行。

Q3:可空变量的声明、初始化和传参

使用 Android Studio 创建一个基于 Kotlin 的新项目(Empty Activity),添加一个新的属性(类型是非空的 View),在 onCreate 函数中初始化它。声明一个参数为 View? 类型的方法,传入刚才的 View 类型属性,并在该方法中打印出该 View? 的 id。

class MainActivity : AppCompatActivity() {
    //因为没办法再声明的时候给一个非null的初始值,所以用lateinit 
    lateinit var view: View;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        view = findViewById(R.id.my_view);
        print(view);
    }

    // 参数view是可空的
    protected open fun print(view: View?) {
        //可空变量调用要加 ?,或者 !!
        Log.d("tag", "id = " + view?.id);
    }
}

  • 《Kotlin 里那些「不是那么写的」》

Q1:单例练习

创建一个 Kotlin 类,这个类需要禁止外部通过构造器创建实例,并提供至少一种实例化方式。

package com.example.demo1.testkotlin

import android.view.View
import java.time.LocalDateTime
import java.util.*

class Sample private constructor() {
    companion object {
        fun getInstance(): Sample {
            return Sample()
        }
    }

}

Q2:数组和集合的练习

分别用 Array、IntArray、List 实现 「保存 1-100_000 的数字,并求出这些数字的平均值」,打印出这三种数据结构的执行时间

package com.example.demo1.testkotlin

import android.view.View
import java.util.*

class Sample private constructor() {

    public fun compare() {
        println("ArrayCal : " + getTimeLin { ArrayCal() } + " ms,")
        println("IntArrayCal : " + getTimeLin { IntArrayCal() } + " ms,")
        println("ListCal : " + getTimeLin { ListCal() } + " ms,")
    }

    private fun getTimeLin(cal: () -> Unit): Long {
        val startTime = System.nanoTime()
        print(cal().toString() + "")
        return System.nanoTime() - startTime;
    }


    private fun ArrayCal(): Int {
        var result = 0
        var array: Array<Int> = Array(100000) { it -> it + 1 }
        for (item in array) {
            result += item
        }
        return result / 100000
    }

    private fun IntArrayCal(): Int {
        var result = 0
        var arrays: IntArray = IntArray(100000) { it -> it + 1 }
        for (item in arrays) {
            result += item
        }
        return result / 100000
    }

    private fun ListCal(): Int {
        var result = 0
        var list = mutableListOf(0)
        for (i in 1..100000) {
            list.add(i - 1)
        }
        return result / 100000
    }
}

返回结果:

Array<Int>: 75657200 ms,
IntArray :  13486500 ms  // ①
List<Int> : 44496400 ms

知识点:

① Kotlin 中要用专门的基本类型数组类 (IntArray FloatArray LongArray) 才可以免于装箱,因此IntArray 在这里运行效率比Array<Int>高。

补充: 原先在 Java 里的基本类型,类比到 Kotlin 里面,条件满足如下之一就不装箱:

  • 不可空类型。

  • 使用 IntArray、FloatArray 等。

  • 《Kotlin 里那些「更方便的」》

Q1: 主构造器和字符串模板$的练习

请按照以下要求实现一个 Student 类:
写出三个构造器,其中一个必须是主构造器
主构造器中的参数作为属性
写一个普通函数 show,要求通过字符串模板输出类中的属性

fun print(view: View?) {
     var stu: Student = Student("cy", 1)
     stu.show()
}
//主构造器 primary constructor,写在类名之后。主构造函数不是必须要写的
class Student(name:String){
    // 主构造器中的值可以直接赋值给成员变量
    var name = name;
    var id = 0;
    //如果写了主构造,次构造函数就必须 this(),调用主构造
    constructor(name:String ,id:Int):this(name){
        this.id = id;
    }

    fun show(){
        // 注意 $的用法。
        Log.d("tag","id = $id ,name = $name");
    }
}

Q2 : 循环的用法

编写程序,找出集合 {21, 40, 11, 33, 78} 中能够被 3 整除的所有元素,并输出。

fun show() {
     val intArray = intArrayOf(21, 40, 11, 33, 78);
     // 写法一:最接近java 的写法,Kotlin 的 in 后面的变量可以是任何实现 Iterable 接口的对象。
     for (i in intArray) {
         if (i % 3 == 0) {
             Log.d("tag", "$i");
         }
     }
     //写法二:IntArray的操作函数forEach 
     intArray.forEach { i ->
        // i-> 是lambda 表达式,i 表示数组的每个元素
        if (i % 3 == 0) {
            Log.d("tag", "$i");
        }
    }
}

Q3:为什么数组的 filter 之后变成 List

val newList: List = intArray.filter { i ->
    i != 1 // 👈 过滤掉数组中等于 1 的元素
}

过滤之后,intArray这个数组类型的对象,变成了一个List。原因可能是Kotlin中数组的元素个数不能变,再filter之前并不知道结果数组大小,所以用的是一个List。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐