Kotlin学习笔记

2022-06-25 11:01:39 浏览数 (1)

文章目录
  • 一. Kotlin 基础知识
    • 1.1 函数结构:
    • 1.2 变量
    • 1.3 when、循环语句
      • 1.3.1 when
      • 1.3.2 循环语句
    • 1.4 Kotlin 异常处理
    • 1.5 "?" 和 "!!"
    • 1.6 重载调用函数
    • 1.7 顶级函数和属性(静态的)
    • 1.8 可变参数 和 展开操作符
    • 1.9 中缀调用infix
    • 1.10 本地函数
    • 1.11 访问修饰符

一. Kotlin 基础知识

1.1 函数结构:

区块式体:

代码语言:javascript复制
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

上式可以写成如下表达式体(Kotlin 会通过类型推导来得知该表达式的类型):

代码语言:javascript复制
fun max(a:Int, b:Int) = if (a > b) a else b
1.2 变量

Kotlin 定义变量的语法为: var/val name:Type

  • var (variable ),可以修改
  • val ( value ),不能修改
代码语言:javascript复制
var age : Int = 17    // 定义一个可以被修改的变量
val ID : String= "1000"    // 定义一个不可修改的变量
// 还可以省略变量类型,Kotlin会类型推导出变量的类型
var age = 17
val id = "1000"

注意:val 表示该变量 引用不可变,但是对象里的内容可以变

1.3 when、循环语句
1.3.1 when

在 Java 中有 switch 语句,在 Kotlin 中使用 when 来代替 switch

代码语言:javascript复制
when(参数){
    参数1 -> {
        //...
    }
    参数1 -> {
        //...
    }}
1.3.2 循环语句

Kotlin 中的 while 和 do…while 循环和 Java 没有区别:

代码语言:javascript复制
while (condition) {
    //
}
do {
    //
} while (condition)

for 循环和 Java 中的是区别:

代码语言:javascript复制
// Java for 循环
for (int i = 0; i <= 100; i  ) {
    System.out.println(i);
}
// 对应 Kotlin 版本
for(i in 0..100){
    println(i)
}

1)、闭区间:使用 … 操作符 表示一个区间,该区间是闭区间,包含开始和结束的元素,然后使用 in 操作符来遍历这个区间 2)、半闭区间 :即只包含头部元素,不包含尾部:until

代码语言:javascript复制
for(i in 0 until 100){
    println(i)
}

3)、倒序遍历:downTo(闭区间)

代码语言:javascript复制
for(i in 100 downTo 0){
    println(i)
}

4)、步长:遍历的时候 步长(step) 默认是 1,可以通过 step 关键字来指定步长

代码语言:javascript复制
for( i in 100 downTo 0 step 2){
    println(i)
}
1.4 Kotlin 异常处理

1)、throw 关键字在 Kotlin 中是 表达式,有返回值

代码语言:javascript复制
val percentage = if (number in 0..100)
        number
    else
        throw IllegalArgumentException(
                "A percentage value must be between 0 and 100: $number")

2)、Kotlin 中可以不必向上抛出异常(java中必须抛出,不然编译不过)

代码语言:javascript复制
fun readNumber(reader: BufferedReader): Int?{
    try {
        val line = reader.readLine()   // throws IOException
        return Integer.parseInt(line)  // throws NumberFormatException
    } catch (e: NumberFormatException) {
        return null
    } finally {
        reader.close()   // throws IOException
    }
}

java中则:

代码语言:javascript复制
int readNumber( BufferedReader reader) throws IOException {
    try {
        String line = reader.readLine(); // throws IOException
        return Integer.parseInt(line);   // throws NumberFormatException
    } catch (NumberFormatException e) {
        return -1;
    } finally {
        reader.close(); // throws IOException
    }
}
1.5 “?” 和 “!!”

1. 声明对象时(包括方法的参数):

  • 把"?"跟在类名后面,表示这个对象允许为null;
  • 把"!!"跟在类名后面,表示这个对象不允许为null; 2. 调用对象时:
  • 把"?"跟在对象后面,表示如果为null,程序就会视而不见,不会空指针。
  • 把"!!"跟在对象后面,表示如果为null,那么系统会报异常。
代码语言:javascript复制
  // 这是声明一个变量,问号跟在类名后面
    var room: Room? = Room()
    private fun checkRoom() {
        // 因为加上了问号,所以可以任意的把room变成空
        room = null
        // 因为在调用时加上了问号,所以程序不会抛出异常
        Log.d("TAG", "-->> room name = ${room?.roomName}")
    }

方法参数也和声明一个变量类似

代码语言:javascript复制
fun test(){
    heat(null)     //编译过
    heat1(null)    //编译不过
}

//参数str可以传null
fun heat(str: String?): String{
     return str   "热"
}

//参数str 不可以传null
fun heat1(str: String): String{
    return str   "热"
}
代码语言:javascript复制
var str: String? = "ds";
var length = str?.length;

var str2: String? = null
var length2 = str2?.length;

var str1: String = "ds";
var length1 = str1!!.length;

var str3: String? = null
var length3 = str3!!.length;

//编译后
String str = "ds";
int length = str.length();

String str2 = (String)null;
Integer length2 = null;

String str1 = "ds";
int length1 = str1.length();

String str3 = (String)null;
Intrinsics.throwNpe();
int length3 = str3.length();
  1. 如果不用"?"
代码语言:javascript复制
  // 这样程序就默认的给room加上了!!,从此以后room不允许为null
    var room: Room = Room()
    private fun checkRoom() {
        // 当把null赋给room时,编译不会通过
        room = null
        // 并且编译器建议把对象后面的问号删除,因为这个对象永远不为空
        Log.d("TAG", "-->> room name = ${room.roomName}")
    }
  1. 定义变量时"?"的使用
代码语言:javascript复制
  val room: Room? = Room()    // 先实例化一个room,并且room可以为空
    val room: Room? = null      // 不实例化了,开始room就是空的

    val room: Room = Room()   // 实例化一个room,并且room永远不能为空
    val room = Room()         // 和上一行代码一样,是简写语法
  1. 使用了 “?” 就真的不会NullPointerException了么?
代码语言:javascript复制
  val roomList: ArrayList<Room>? = null
   if (roomList?.size > 0) {
        Log.d("TAG", "-->> 房间数不是0")
   }

编译器会告诉我们:当roomList为null的时,它的size返回就是"null",但是"null"不可以和int值比大小,所以编译器建议我们写成roomList?.size!! > 0,但这样一定会报NullPointerException。

那是不是必须得在外面套一层if(roomList != null)这种Java常见语句才能避免异常吗? 不过Kotlin不会让程序出现这种啰嗦的代码,所以里面提供了对象A ?: 对象B表达式, ?:表示的意思是,当对象A值为null的时候,那么它就会返回后面的对象B,所以可以写为:

代码语言:javascript复制
 val roomList: ArrayList<Room>? = null
    if (roomList?.size ?: 0 > 0) {    // 这一行添加了?:
        Log.d("TAG", "-->> 房间数不是0")
    }

就目前为止使,用上面的?和?:基本上能避免程序中出现的所有NullPointerException。

  1. 如果变量可以为null(使用操作符"?"),则编译后是包装类型
代码语言:javascript复制
//因为可以为 null,所以编译后为 Integer
var width: Int? = 10
var width: Int? = null


//编译后的代码
@Nullable
private static Integer width = 10;
@Nullable
private static Integer width;

//再来看看方法返回值为整型:
//返回值 Int 编译后变成基本类型 int
fun getAge(): Int {
    return 0
}
//返回值 Int 编译后变成 Integer
fun getAge(): Int? {
    return 0
}
1.6 重载调用函数

假设我们有如下的函数:

代码语言:javascript复制
fun <T> joinToString(collection: Collection<T>,
    separator: String, 
    prefix: String, 
    postfix: String): String

1、调用(为参数值指定参数名称):

代码语言:javascript复制
joinToString(collection, separator = " ", prefix = " ", postfix = ".")

2、为函数参数指定默认值:

代码语言:javascript复制
fun <T> joinToString(collection: Collection<T>, 
   separator: String = ", ", 
    prefix: String = "", 
    postfix: String = "" ): String

3、重载调用(有默认值的情况下才可以)

代码语言:javascript复制
joinToString(list)
joinToString(list, prefix = "# ")
1.7 顶级函数和属性(静态的)

在 Java 中我们需要把函数和属性放在一个类中,在 Kotlin 中我们可以把某个函数或属性直接放到某个 Kotlin 文件中,把这样的函数或属性称之为 顶级函数或属性。

例如在 join.kt 文件中:

代码语言:javascript复制
package strings
fun joinToString(...): String { 
    ... 
}

1)、但是在 Java 代码中如何调用该方法呢? 因为 JVM 虚拟机只能执行类中的代码,所以 Kotlin 会生成一个名叫 JoinKt 的类,并且顶级函数是静态的,所以可以在 Java 中这样调用顶级函数:

代码语言:javascript复制
JoinKt.joinToString(...)

2)、在Kotlin中如何调用,如果在不同的包,需要把这个顶级函数导入才能调用:

代码语言:javascript复制
//相当于 import strings.JoinKt.joinToString
import strings.joinToString 
//相当于 import strings.JoinKt.*
import strings.* 

3)、顶级属性 同样也是 static 静态的 如果使用 var 来定义会生成对应的静态setter、getter函数 如果使用 val 来定义只会生成对应的静态getter函数

4)、Kotlin文件名被修改怎么办? 如果所在的Kotlin文件名被修改,编译生成的类名也会被修改,可以通过注解的方式来固定编译生成的类名:

代码语言:javascript复制
@file:JvmName("StringFunctions")
package stringsfun joinToString(...): String { 
    ... 
}
代码语言:javascript复制
   调用的时候:
代码语言:javascript复制
import strings.StringFunctions; 

StringFunctions.joinToString(list, ", ", "", "");
1.8 可变参数 和 展开操作符

1)、可变参数,可传递任意数量参数 java中使用…来声明可变参数,如:

代码语言:javascript复制
public static <T> List<T> listOf(T... items) {
    System.out.println(items.getClass()); //数组类型
    return Arrays.asList(items);
}

Kotlin 和 Java 不一样,Kotlin 使用 vararg 关键来定义可变参数:

代码语言:javascript复制
fun <T> listOf(vararg items: T): List<T> {
    println(items.javaClass)     //数组类型
    return Arrays.asList(*items) // * spread operator
}

2)、展开操作符 通过上面的两段代码比较我们发现:Kotlin 需要显示的将可变参数通过 * 展开,然后传递给 asList 函数。这里的 * 就是 展开操作符,在 Java 中是没有 展开操作符 的。

  • 不使用展开操作符时:
代码语言:javascript复制
val intArr: Array<Int> = arrayOf(1, 2, 3, 4)
Arrays.asList(0, intArr).run {
    println("size = $size")}
//输出结果:
size = 2
  • 使用展开操作符时:
代码语言:javascript复制
val intArr: Array<Int> = arrayOf(1, 2, 3, 4)
Arrays.asList(0, *intArr).run {
    println("size = $size")}
//输出结果:
size = 5
1.9 中缀调用infix

使用关键字 infix 修饰的函数都能够 中缀调用, 被关键字 infix 修饰的函数只能有一个参数。

Kotlin 中的 to 就是一个中缀函数:

代码语言:javascript复制
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

下面我们来对比下 to 函数的常规调用和中缀调用:

代码语言:javascript复制
1.to("one")  //普通的函数调用
1 to "one"   //函数的中缀调用

除了 to 函数,还有我们介绍 循环 的时候讲到的 until、downTo、step 也是中缀函数:

代码语言:javascript复制
public infix fun Int.until(to: Int): IntRange {
    if (to <= Int.MIN_VALUE) return IntRange.EMPTY
    return this .. (to - 1).toInt()
}

public infix fun Int.downTo(to: Int): IntProgression {
    return IntProgression.fromClosedRange(this, to, -1)
}

public infix fun IntProgression.step(step: Int): IntProgression {
    checkStepIsPositive(step > 0, step)
    return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}

//使用示例:
for(i in 0 until 100){}

for (i in 100 downTo 0 step 2) {}
1.10 本地函数

本地函数(local function) 是在函数里面定义函数,本地函数只能在函数内部使用 什么时候使用本地函数?当一个函数里的逻辑很多重复的逻辑,可以把这些逻辑抽取到一个本地函数。

代码语言:javascript复制
fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Cannot save user ${user.id}: Name is empty")
    }
    if (user.address.isEmpty()) { 
        throw IllegalArgumentException("Cannot save user ${user.id}: Address is empty")
    }
}

这个 saveUser 函数里面有些重复逻辑,如果 name 或 address 为空都会抛出异常 可以使用本地函数优化下:

代码语言:javascript复制
fun saveUser(user: User) {
    fun validate(value: String, fieldName: String) { 
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id}: "   "$fieldName is empty")
        }
    }
    validate(user.name, "Name") 
    validate(user.address, "Address") 
}

本地函数避免了模板代码的出现。如果不使用本地函数,我们需要把 validate函数 定义到外面去,但是这个函数只会被 saveUser函数 使用到,从而污染了外面的全局作用域。通过本地函数使得代码更加清晰,可读性更高。 注意:虽然 Kotlin 允许在函数内部定义函数,但是不要嵌套太深,否则会导致可读性太差

1.11 访问修饰符
  1. 类访问修饰符如下:
  1. 类成员访问修饰符:
  • 只有 ‘’protected‘’ 对应的 ‘’Kotlin可访问级别‘’ 有区别:只有子类可访问

1.12 声明类的几种方式

  1. class className : 这个类是 public final 的
代码语言:javascript复制
class Person
编译后:
public final class Person {
}
  1. class className([var/val] property : Type…)
  • 1、会生成一个构造方法,参数就是括号里的那些参数
  • 2、会根据括号的参数生成对应的属性
  • 3、会根据 val 和 var 关键字来生成 setter、getter 方法,var 表示该属性可以修改;val 表示该属性不能被修改
代码语言:javascript复制
class Person(val name: String) //name属性不可修改

---编译后---

public final class Person {
   //1. 生成 name 属性
   @NotNull
   private final String name;

   //2. 生成 getter 方法
   //由于 name 属性不可修改,所以不提供 name 的 setter 方法
   @NotNull
   public final String getName() {
      return this.name;
   }
   
   //3. 生成构造函数
   public Person(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}
  1. data class className([var/val] property: Type)
  • 新建 bean 类的时候,常常需要声明 equals、hashCode、toString 等方法,我们需要写很多代码。
  • 在 Kotlin 中,只需要在声明类的时候前面加 data 关键字就可以完成这些功能。
  • 注意 : 哪些属性参与 equals、hashCode、toString 方法呢?primary constructor 构造函数里的参数,都会参与 equals、hashCode、toString 方法里。
  1. object className 这种方法声明的类是一个单例类,以前在Java中新建一个单例类,需要写一些模板代码,在Kotlin中一行代码就可以了(类名前加上object关键字)。
  2. 内部类
  • 在 Kotlin 中内部类默认是静态的( Java 与此相反),不持有外部类的引用:
代码语言:javascript复制
class OuterClass {
    //在 Kotlin 中内部类默认是静态的,不持有外部类的引用
    class InnerStaticClass{
    }
    //如果要声明非静态的内部类,需要加上 inner 关键字
    inner class InnerClass{
    }
}

编译后代码如下:
class OuterClass {
   public static final class InnerStaticClass {
   }
   public final class InnerClass {
   }
}

1.13 静态变量和静态方法

0 人点赞