Kotlin简单入门

开始学习Kotlin是因为Google将Kotlin作为Android开发的官网语言,现在市面也有一些公司面试的时候开始将Kotlin作为一个加分项,其实可以想想多学一点东西,多一点傍身的技巧,O(∩_∩)O哈哈~。

希望在看文章的时候,一定要把注释一起看一定要把注释一起看一定要把注释一起看,重要的事情说三遍,很多解释都是直接写在注释中的,因为我觉得把一些解释跟代码放在一起要稍微好理解一些,O(∩_∩)O哈哈~。

Kotlin的基本数据类型

Kotlin 中使用的基本类型:数字、字符、布尔值、数组与字符串。

  1. 数字
    整型
    Kotlin 提供了一组表示数字的内置类型。 对于整数,有四种不同大小的类型,因此值的范围也不同。
类型大小(比特数)最小值最大值
Byte8-128127
Short16-3276832767
Int32-2,147,483,648 (-231)2,147,483,647 (231 - 1)
Long64-9,223,372,036,854,775,808 (-263)9,223,372,036,854,775,807 (263 - 1)

所有以未超出 Int 最大值的整型值初始化的变量都会推断为 Int 类型。如果初始值超过了其最大值,那么推断为 Long 类型。 如需显式指定 Long 型值,请在该值后追加 l 或 L 后缀。

val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1
浮点型

对于浮点数,Kotlin 提供了 Float 与 Double 类型。 根据 IEEE 754 标准, 两种浮点类型的十进制位数(即可以存储多少位十进制数)不同。 Float 反映了 IEEE 754 单精度,而 Double 提供了双精度。

类型大小(比特数)有效数字比特数指数比特数十进制位数
Float322486-7
Double64531115-16

对于以小数初始化的变量,编译器会推断为 Double 类型。 如需将一个值显式指定为 Float 类型,请添加 f 或 F 后缀。 如果这样的值包含多于 6~7 位十进制数,那么会将其舍入。

val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float,实际值为 2.7182817

注意:与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换。
例如:具有 Double 参数的函数只能对 Double 值调用,而不能对 Float、 Int 或者其他数字值调用。

fun main() {
    val i = 1    
    val d = 1.1
    val f = 1.1f 

    printDouble(d)
//    printDouble(i) // 错误:类型不匹配
//    printDouble(f) // 错误:类型不匹配
}

fun printDouble(d: Double) { print(d) }

如果要将数值转换为不同类型,使用显式转换

显式转换
val a: Int? = 1 
// 这样是不正确的,编译器也会给你报错,Kotlin是没有隐式转换,我们必须去显式转换
// val b: Long? = a
// 如下才是正确的使用
val b: Long? = a!!.toLong()

// 这里也是正确的使用
val c: Long = 1000
val d: Int = c.toInt()

每个数字类型支持如下的转换:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

缺乏隐式类型转换很少会引起注意,因为类型会从上下文推断出来,而算术运算会有重载做适当转换。

val e = 1L + 3
// 其实Kotlin会从上下文推断出e是一个Long类型,完整代码如下
val e: Long = 1L + 3
字面常量

字面常量有如下几种:
十进制: 123,Long 类型用大写 L 标记: 123L
十六进制: 0x0F
二进制: 0b00001011
注意: 不支持八进制

Kotlin中浮点数的常规表示

  • 默认 double:123.5、123.5e10
  • Float 用 f 或者 F 标记: 123.5f

数字字面值中的下划线(自 1.1 起开始支持,方便阅读)

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
表示方式

在 Java 平台数字是物理存储为 JVM 的原生类型,除非我们需要一个可空的引用(如 Int?)或泛型。 后者情况下会把数字装箱。

注意数字装箱不一定保留同一性:

val a: Int = 10000
println(a === a) // 输出“true”
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA === anotherBoxedA) // !!!输出“false”!!!

但是保留了同一性

val a: Int = 10000
println(a == a) // 输出“true”
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA == anotherBoxedA) // 输出“true”
运算

Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相应的指令)

介绍下位运算符(只应用于Int和Long)

1、 shl(bits) - 有符号左移 ==>对应就Java的:<<:解释:不分正负数,低位补0; 
2、shr(bits) – 有符号右移 ==>对应就Java的:>>:解释:如果该数为正,则高位补0,若为负数,则高位补1;
3、ushr(bits) – 无符号右移 ==>对应就Java的:>>>:解释:无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0。
4、and(bits) – 位与 ==> &:解释:两个数转二进制,从高位开始比较,都为1才是1,否则为0
5、or(bits) – 位或 ==> | :解释:两个数转二进制,从高位开始比较,只要有一个为1就为1,否则为0
6、xor(bits) – 位异或 ==> ^:解释:两个数转二进制,从高位开始比较,相同为0,相异为1。
7、inv() – 位非 ==> ~:解释:如果位为0,结果是1,如果位为1,结果是0.

// 位运算符操作的是补码
// 正整数的补码是其二进制表示,与原码相同。
// 求负整数的补码,将其原码除符号位外的所有位取反(0变1,1变0,符号位为1不变)后加1。-5的补码,十进制转2进制使用除2取余,然后将余数倒过来连接就是他的二进制
原码:1000 0101
反码:符号未不变,其余位数取反 1111 1010
补码:反码+1 1111 1011

// 数0的补码表示是唯一的。
[+0]=[+0]=[+0]=00000000
[ -0]=11111111+1=00000000


补码转换为原码
已知一个数的补码,求原码的操作其实就是对该补码再求补码(补码的补码就是原码):
⑴如果补码的符号位为“0”,表示是一个正数,其原码就是补码。
⑵如果补码的符号位为“1”,表示是一个负数,那么求给定的这个补码的补码就是要求的原码。
例子:已知一个补码为11111001,则原码是10000111-7)。
因为符号位为“1”,表示是一个负数,所以该位不变,仍为“1”,然后其余7位取反
反码:1000 0110
补码:1000 0111  
2的平方 + 2的一次方 + 2的零次方,补上符号位 = -7

补码的绝对值
例子:-65的补码是10111111
若直接将10111111转换成十进制,发现结果并不是-65,而是191。
事实上,在计算机内,如果是一个二进制数,其最左边的位是1,则我们可以判定它为负数,并且是用补码表示。
若要得到一个负二进制补码的数值,只要对补码全部取反并加1,就可得到其数值。

如:二进制值:10111111-65的补码)
各位取反:01000000101000001+65

所以介绍一下位移运算的步骤

如何求补码不理解的直接参考上面的概念,例子,自己手动去操作一遍,其实理解了东西还是蛮简单的。

Int类型是32位,其实我们这里应该操作32位去进行位运算
例子就简单的介绍下具体的用法

有符号左移:<<
例如:20 << 2,表示将整数20有符号左移两位,正整数的符号位是0
原码:0001 0100
补码:0001 0100
左移两位:00 0101 0000
去掉最高位的两位:0101 0000   26次方 + 2的四次方 = 64 + 16 = 80

-20 << 2,表示将负整数20左移两位,符号位为1
原码:1001 0100
反码:1110 1011
补码:1110 1100
左移两位:11 1011 0000
去掉最高位的两位:1011 0000 
补码的补码就是原码:
补码:1011 0000
取反:1100 1111
求补码:1101 0000
所以原码:1101 0000 26次方 + 2的四次方 = 80,然后补上符号位-80

有符号右移:>>,正数高位补0,负数补1
20 >> 2,表示将正整数20右移两位
原码:0001 0100
补码:0001 0100
右移两位:0000 0101 00
去掉最后两位:0000 0101
结果:5

-20 >> 2,表示将负整数20右移两位
原码:1001 0100
反码:1110 1011
补码:1110 1100
右移两位:1111 1011 00
去掉最后两位:1111 1011
取反:1000 0100
求补码:1000 0101
原码:1000 0101 
结果:-52的二次方 + 20次方 = 5,补上符号位-5)

无符号右移
20 >>> 2 还是5
但是-20 >>> 2就不是-5了
原码:10000000 00000000 00000000 00010100
反码:11111111 11111111 11111111 11101011
补码:11111111 11111111 11111111 11101100
右移两位:00111111 11111111 11111111 11111011 00
去掉最右边两位:00111111 11111111 11111111 11111011
结果:最高位是0,所以是正数 00111111 11111111 11111111 11111011 是一个很大的正整数

其他的运算符的使用,参考运算符重载
Kotlin运算符重载

  1. 字符
    字符用 Char 类型表示。
fun check(c: Char) {
    // 错误:类型不兼容,应该使用'1'
    if (c == 1) {
        
    }
}

字符字面值用单引号括起来: ‘1’。 特殊字符可以用反斜杠转义。 支持这几个转义序列:\t、 \b、\n、\r、’、"、\ 与 $。 编码其他字符要用 Unicode 转义序列语法:’\uFF00’。

显式把字符转换为 Int 数字

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9')
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() // 显式转换为数字
}
  1. 布尔类型
    布尔用 Boolean 类型表示,它有两个值:true 与 false。
    若需要可空引用布尔会被装箱。

    内置的布尔运算有:
    || – 短路逻辑或
    && – 短路逻辑与
    ! - 逻辑非

  2. 数组
    数组在 Kotlin 中使用 Array 类来表示,它定义了 get 与 set 函数(按照运算符重载约定这会转变为 [])以及 size 属性,以及一些其他有用的成员函数:

    1、可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。
    2、库函数 arrayOfNulls() 可以用于创建一个指定大小的、所有元素都为空的数组。
    3、另一个选项是用接受数组大小以及一个函数参数的 Array 构造函数,用作参数的函数能够返回给定索引的每个元素初始值。

// 创建一个 Array<String> 初始化为 ["0", "1", "4", "9", "16"]
val array: Array<String> = Array(5) { i -> (i*i).toString()}
array.forEach {
        Log.d("", it)
}

Kotlin 中数组是不型变的(invariant)
这意味着 Kotlin 不让我们把 Array< String> 赋值给 Array< Any>,以防止可能的运行时失败(但是你可以使用 Array< out Any>, 参见类型投影)。

原生数组类型

Kotlin 也有无装箱开销的专门的类来表示原生类型数组:
ByteArray
CharArray
ShortArray
IntArray
LongArray
FloatArray
DoubleArray
BooleanArray
等等,这些类与 Array 并没有继承关系,但是它们有同样的方法属性集。

val intArray: IntArray = intArrayOf(0, 1)
// Array of int of size 5 with values [0, 0, 0, 0, 0]
val intArray1: IntArray = IntArray(5)
// Array of int of size 5 with values [42, 42, 42, 42, 42]
val intArray2: IntArray = IntArray(5) { 20 }
// Array of int of size 5 with values [0, 1, 4, 6, 16] (values initialised to their index value)
val intArray3: IntArray = IntArray(5) { it * it }
  1. 字符串
    字符串用 String 类型表示。
    字符串是不可变的。
    字符串的元素——字符可以使用索引运算符访问: s[i]。

字符串可以使用for循环进行输出

fun main() {
    val str = "abcd"
    for (c in str) {
        println(c)
    }
}

可以用 + 操作符连接字符串。这也适用于连接字符串与其他类型的值, 只要表达式中的第一个元素是字符串,一般使用字符串模板代替。

// 这里必须明确用+进行连接,表达式第一个元素必须是字符串,下面注释的这句代码是错误的,正确的写法是下面一句
// val str: String = 1 + "00"
val str: String = "00" + 1

字符串字面值
Kotlin 有两种类型的字符串字面值: 转义字符串可以有转义字符, 以及原始字符串可以包含换行以及任意文本。

// \n 就是一个换行,是一个转义字符
// 转义采用传统的反斜杠方式
// 几个特殊的转移字符 \t、 \b、\n、\r、\'、\"、\\ 与 \$
val s = "Hello, world!\n"

原始字符串 使用三个引号(""")分界符括起来,内部没有转义并且可以包含换行以及任何其他字符:

val str1: String = """
    array.forEach {
        Log.d("", it)
    }
""".trimIndent()

// 通过 trimMargin() 函数去除前导空格
// 默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")。
val text = """
    |Tell me and I forget.
    |Teach me and I remember.
    |Involve me and I learn.
    |(Benjamin Franklin)
    """.trimMargin()
字符串模板

字符串字面值可以包含模板表达式 ,即一些小段代码,会求值并且把结果拼接到字符串中的相应位置。 模板表达式以美元符($)开头:

// $it,由$符号引用的一个简单的变量
fun `1`() {
    array.forEach {
        Log.d("", "$it==>$str")
    }
}

// 或者由${}引用的任意表达式
// 输出001.length is 3
Log.d("","$str.length is {str.length}")
  1. 无符号整型和其他无符号的特殊类
    无符号整型和无符号的特殊类是Kotlin1.3才加入的

    Kotlin 为无符号整数引入了以下类型:
    kotlin.UByte: 无符号 8 比特整数,范围是 0 到 255
    kotlin.UShort: 无符号 16 比特整数,范围是 0 到 65535
    kotlin.UInt: 无符号 32 比特整数,范围是 0 到 2^32 - 1
    kotlin.ULong: 无符号 64 比特整数,范围是 0 到 2^64 - 1
    注意:请注意,将类型从无符号类型更改为对应的有符号类型(反之亦然)是二进制不兼容变更
    无符号类型是使用另一个实验性特性(即内联类)实现的。

    与原生类型相同,每个无符号类型都有相应的为该类型特化的表示数组的类型:
    kotlin.UByteArray: 无符号字节数组
    kotlin.UShortArray: 无符号短整型数组
    kotlin.UIntArray: 无符号整型数组
    kotlin.ULongArray: 无符号长整型数组
    与有符号整型数组一样,它们提供了类似于 Array 类的 API 而没有装箱开销。

    此外,区间与数列也支持 UInt 与 ULong(通过这些类 kotlin.ranges.UIntRange、 kotlin.ranges.UIntProgression、 kotlin.ranges.ULongRange、 kotlin.ranges.ULongProgression)

基本用法
  1. 基础语法
// Kotlin是空安全的,需要我们为变量赋一个初值
var name: String = "张三"
var age: Int = 0

// Kotlin允许我们使用 类型?(String?)的形式修饰变量,告诉程序允许这个变量为空
var name1: String? = null

// Kotlin的不可变变量,类似Java的final修饰的变量,但是这并不是常量,只是一个不可赋值的变量
val age1: Int = 0

// 使用,讲解
fun main() {
    // 不允许赋值,因为age1是不可变变量
    //age1 = 2
    
    // 因为Kotlin的空安全
    // 不允许将可以为空的变量直接赋值给不可为空的变量,必须使用!!修饰,告诉程序name1不为空
    // 如果name1为空,会直接抛出NullPointException
    name = name1!!
    // 允许将不能为空的变量直接赋值给可以为空的变量
    // 因为name1既可以接收空值,也可以接收不为空的值
    name1 = name
}
  1. Kotlin与Java的互调
// Kotlin中创建匿名内部类的写法
object Test1 {
    fun log(str: String) {
        Log.d("", str)
    }
}

fun main() {
    // kotlin中的调用,可以直接使用类名的方法名调用
    Test1.log("")
    // Java中调用,kotlin在转换为Java代码时在Test1中构建了一个单例模式
    //Test1.INSTANCE.log("")
    // 所以从上面可以看出object也是一种单例的写法,我们日常使用的工具类也可以使用   object修饰

    // 调用Java的class对象,需要在最后加上.java
    testClass(UserBean2::class.java)
    // 调用Kotlin的class
    testClass(UserBean::class)
    
    // kotlin中in,object是关键字,这里我们可以使用两个反引号去引用``
    Log.d("", UserBean2.`in`)
    // kotlin中在字符串中引用变量值和方法值
    // 可以使用$变量值,${对象.方法名}
    Log.d("", "输出一个值:${UserBean2.`object`}")
    
    // 调用方法,这种不正确不合法的方法名称定义平常最好不要写,虽然这里用反引号引用是没有问题的
    `1`()
}

// 调用Java的class对象
fun testClass(clazz: Class<UserBean2>) {

}
// 调用Kotlin的class,Kotlin的class是KClass
fun testClass(clazz: KClass<UserBean>) {

}


// 反引号可以使用一些不合法的方式,但是平常最好还是避免
fun `1`() {

}
  1. 才开始学习Kotlin容易遇到的问题

1、Kotlin是没有封装类的
2、Kotlin类型对空值敏感
3、Kotlin没有静态变量和静态方法

例如:Kotlin是没有封装类的

// 我们在Java中定义一个接口
public class TestDaoImpl implements TestDao {
    private static final String TAG = "TestDaoImpl";

    @Override
    public void printNumber(int number) {
        Log.d(TAG, "输出int类型的数字:" + number);
    }

    @Override
    public void printNumber(Integer number) {
        Log.d(TAG, "输出Integer封装类型的数字:" + number);
    }
}

// 最终我们去Kotlin中调用输出
val testDaoImpl = TestDaoImpl()
testDaoImpl.printNumber(123)
// 最终你会发现打印走的是基本数据类型的方法
// 因为Kotlin没有封装类,其实在你调用的时候,你会看到编译器的提醒其实是同一个方法,你点击方法进去也是走的基本数据类型的方法。
// 你还可以用Kotlin的类去实现TestDao,编译器会告诉你实现的这两个方法是相同的。你需要删除一个。比如下面将其中一个注释掉。
class TestDaoImpl1: TestDao {
    override fun printNumber(number: Int) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    /*override fun printNumber(number: Int?) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }*/
}

例如:Kotlin是空敏感的

// 我们让编译器来推断,编译器会推断为String!,这样只会出现在Java和Kotlin互调
    val format = testDaoImpl.format("")
    // 这里会抛出NullPointException
    // String!是一个临时类型,我们只能临时去使用他,如果我们尝试调用他的方法,它就会像Java语法一样去调用,然后给我们抛出一个空指针异常
    Log.d("", "字符串长度:${format.length}")
    // 这里会抛出异常,告诉我们返回的值是空,我们的接收对象是一个不为空的类型
    // val format1: String = testDaoImpl.format("")
    val format2: String? = testDaoImpl.format("")
    // 正确,因为Kotlin是空安全的
    // 所以在Java和Kotlin互调的时候
    // 如果在接收一个Java类对象的时候,不确定接收类型是否为空,一定要将它声明成一个可空类型的
    Log.d("", "字符串长度:${format2?.length}")

例如:Kotlin是没有静态变量和静态方法的

object Test1 {
    fun log(str: String) {
        Log.d("", str)
    }

    // 使用@JvmStatic修饰,在Java中调用staticM方法跟Kotlin中保持一致,Test1.staticM()
    // 不用在使用Test1.INSTANCE.staticM()这种方式
    @JvmStatic
    fun staticM() {

    }
}

// Java中调用
Test1.INSTANCE.log("");
Test1.staticM();
  1. 函数与Lambda闭包
  • 函数的特性语法
// 没有返回值的函数,其实是有一个默认返回值: Unit,默认会省略
fun test1() {
}

// 有参,有返回值的方法
// Kotlin中,方法的参数声明参数名在前,类型在后,参数名:类型
// 方法返回值也跟Java不一样,用:隔开,然后跟在后面
fun test1(data: String?): String? {
    return data ?: "NULL"
}

// 如果其中只有一句语句,Kotlin还允许这样简写
fun add(a: Int, b: Int): Int = a + b 
// Kotlin中允许给函数的参数设置默认值
fun add(a: Int = 20, b: Int = 10): Int = a + b
  • 嵌套函数
    正常情况下,不推荐使用嵌套函数,除非在下面介绍的一些特殊情况
/**
 * 用途:在某些条件下触发递归的函数,或者不希望被外部函数访问到的函数
 */
fun test2() {
    val data = "2019"


    fun test3(number: Int = 10) {
        println("访问外部函数的局部变量:$data")

        if (number > 0) {
            println(number - 1)
        }
    }

    test3()
}
  • 扩展函数
    扩展函数用途:对于第三方SDK或者或者你不能控制的类型新增你需要使用的方法。扩展函数它是静态的给一个类添加成员变量和成员方法
// fun + 扩展的类名 + .(用来分割类名和方法名) + 方法名,以及方法参数,返回值,方法体
public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset)

// 当我们对这个类声明了扩展函数之后,我们就可以直接使用类的对象调用这个方法
// 使用
val file: File = File("")
file.readBytes()
file.readText()

// 在Java中调用
File file = new File("");
// 第一个参数file是我们需要扩展的对象,因为这个方法是我们给File类扩展的
String text = FilesKt.readText(file, Charsets.UTF_8);
Kotlin的扩展函数是静态扩展

例子:

/**
 * @author 86351
 * @date 2019/9/28
 * @description
 * Kotlin中如果类要被继承,需要使用open修饰符修饰,因为Kotlin中的类
 * 默认会被声明为final修饰的
 */
open class Animal
class Dog: Animal()

// Animal和Dog对象都扩展了一个name方法
fun Animal.name() = "Animal"
fun Dog.name() = "Dog"

// 然后给Animal扩展一个打印方法
fun Animal.printName(animal: Animal) {
    println(animal.name())
}

fun main() {
    //val age = 20
    //val name = "张三"

    // 下面语句是会编译是出错的
    //println("我叫%d,我今年%d岁", name, age)
    //println("我叫$name,我今年${age}岁")

    val animal = Animal()
    //animal.name()
    animal.printName(animal)

    val dog = Dog()
    //dog.name()
    // 最终此处打印出来的是Animal,而不是Dog类扩展的dog的name
    dog.printName(dog)
}

我们观察Kotlin被编译成Java之后的代码

@NotNull
   public static final String name(@NotNull Animal $this$name) {
      Intrinsics.checkParameterIsNotNull($this$name, "$this$name");
      return "Animal";
   }

   @NotNull
   public static final String name(@NotNull Dog $this$name) {
      Intrinsics.checkParameterIsNotNull($this$name, "$this$name");
      return "Dog";
   }

   // 第一个参数是我们需要扩展的类的对象,第二个是我们声明扩展函数传入的参数
   public static final void printName(@NotNull Animal $this$printName, @NotNull Animal animal) {
      Intrinsics.checkParameterIsNotNull($this$printName, "$this$printName");
      Intrinsics.checkParameterIsNotNull(animal, "animal");
      String var2 = name(animal);
      boolean var3 = false;
      System.out.println(var2);
   }


// 最后在main方法中调用,最终在调用的时候,dog对象被强转成了Animal对象,所以调用的是Animal的name方法,而不是我们觉得的Dog的name方法。
Dog dog = new Dog();
printName((Animal)dog, (Animal)dog);

注意:从这里我们就知道了Kotlin的扩展函数是静态扩展的

如果希望Dog对象的name方法被调用,就给Dog对象扩展一个printName方法

// 如果我们希望Dog的name方法被调用,我们就需要对给Dog对象去扩展一个printName方法
fun Dog.printName(dog: Dog) {
    println(dog.name())
}

// 最终编译成Java代码,在main方法中调用的就是
Dog dog = new Dog();
printName(dog, dog);
  • Lambda闭包语法
    Kotlin的Lambda允许我们省略很多无用的冗余信息
//最原始
val thread = Thread (object :Runnable {
    override fun run() {
        
    }
})

// 省略run方法
val thread = Thread ({ -> Unit 
})

// 如果Lambda没有参数,可以省略箭头符号->
val thread = Thread ({
    
})

// 如果Lambda是函数的最后一个参数,我们可以将大括号放在小括号外面
val thread = Thread (){

}

// 如果函数只有一个参数,并且这个参数Lambda表达式,则可以省略小括号
val thread = Thread {

}
Lambda闭包的声明
// Lambda闭包的类型(下面的val),变量名(下面的print),然后闭包需要用{}括起来,同时闭包也可以有参数(name: String),然后在参数声明结束的时候需要加上->,表示后面是Lambda闭包的闭包体
val print = { name: String ->
    println(name)
}

// 调用
print("测试下lambda")

Kotlin的Lambda 1.3之前和之后的问题,就是对方法有限制,因为在Kotlin 1.3之前Kotlin源码中只定义了22个方法,我们可以根据它源码的定义方式从新去定义一个方法,但是在1.3之后我们就不需要关心这个问题,因为Kotlin已经帮我们解决了,可以看下面的解释。


  Kotlin的lambda闭包
  kotlin 1.3之前是有参数限制的(如果参数超过限制会抛出一个方法未定义的异常,kotlin/Function22),上限是22,具体可以参考Functions.kt文件
     解决方式: 参考本项目kotlin包下面的JAVA类,为什么不用kotlin的方式,因为kotlin中只有系统标准库才允许
              使用kotlin的包名,所以根据JAVA和kotlin可以互相调用,所以声明成JAVA类
  kotlin 1.3之后,如果参数上限超过了22个,会走FunctionN,查阅编译后的java类
  lambda22 = (Function22)null.INSTANCE;
  lambda23 = (FunctionN)null.INSTANCE;
  this.lambda22 = (Function22)null.INSTANCE;
  this.lambda23 = (FunctionN)null.INSTANCE;
  print = (Function1)null.INSTANCE;
  具体可以参考FunctionN.kt类,通过源码可以看到这个方法是在1.3以及之后再新增的
  (就是官方解决了这个问题,我们在使用的时候就不用去考虑解决的问题)
 

Kotlin 1.3之前解决

// 定义一个23个参数的Lambda表达式
val lambda23 =
    { param1: Int, param2: Int, param3: Int, param4: Int, param5: Int, param6: Int, param7: Int, param8: Int, param9: Int, param10: Int,
      param11: Int, param12: Int, param13: Int, param14: Int, param15: Int, param16: Int, param17: Int, param18: Int, param19: Int, param20: Int,
      param21: Int, param22: Int, param23: Int ->
        println("我刚好23个参数")
    }

// Lambda闭包编译后是走Functions中的对应的多少个参数的方法
// 调用lambda23,编译器就会抛出异常,说找不到这个方法,因为Kotlin源码中只给我们定义了22个方法

// 解决方法,我们通过查看源码,发现Function方法是放在kotlin这个包下的,但是我们自建一个kotlin包,然后自己在这个包下去定义Function的方法的时候,会报错,告诉我们kotlin这个包是只有Kotlin原生代码的才可以用。
// 因为Java和Kotlin是可以互相调用的,所以我们通过Java的方式去使用

// 因为我的Kotlin版本是在1.3之后,所以不用考虑这个问题,如果你的Kotlin版本在1.3之后再这么写,运行就会报错
// 通过定义这个方法就解决了参数超过限制的问题,如果有24个参数,就继续加方法
public interface Function23<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, P23, R> extends Function<R> {
    R invoke(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10, P11 p11, P12 p12, P13 p13, P14 p14, P15 p15, P16 p16, P17 p17, P18 p18, P19 p19, P20 p20, P21 p21, P22 p22, P23 p23);
}
  • 高阶函数
    指函数或者Lambda的参数又是一个函数或者Lambda

声明一个高阶函数

/**
 * 函数的第二个参数是一个Lambda表达式
 * block参数的类型是:一个参数为空,返回值是Unit的函数
 */
fun onlyIf(isDebug: Boolean, block: () -> Unit) {
    if (isDebug) {
        block()
    }
}

// 调用
onlyIf(true) {
     println("我是Lambda的高阶函数")
}

注意:在Kotlin中函数是“一等公民”

val runnable = Runnable {
    println("runnable::run")
}
val function: () -> Unit
// 函数的声明
function = runnable::run

// 在高阶函数中如果需要将函数作为参数传递,必须传递的是函数的声明
// 调用,function传入的是函数的声明
onlyIf(true, function)
// 结果 runnable::run,被打印出来
// 如果传递函数的执行,其实是传递的函数的返回值

注意:我们平时使用的 对象.方法名() 是函数的执行

  • 内联函数
    Kotlin中的Lambda表达式会被编译成匿名内部类
    可以使用inline关键字去修饰高阶函数,这样当方法在编译时就会拆解方法的调用为语句的调用,进而减少创建不必要的临时对象。
inline fun onlyIf(isDebug: Boolean, block: () -> Unit) {
    if (isDebug) {
        block()
    }
}
  1. 类和对象
    Kotlin的类,如果没有父类对象,默认的父类Any对象。
    Kotlin的类默认是public final的,是不能继承的,如果不希望我们的类是public final的,就需要我们手动给类加open修饰符
// Kotlin的类跟Java一样都是class来修饰,不同的是Kotlin的类默认会在前面加上public final修饰
class Test{}

// 如果需要去掉public final,使用open修饰
open class Test{}

// Kotlin的继承和接口的实现都是用冒号(:)来分割,多个接口实现用逗号(,)隔开,接口和父类的实现没有先后顺序
class Dog: Aniaml, TestDao {}
  • 构造函数
    Kotlin的构造函数,不用额外的去声明构造函数,而是直接在类名后面跟参数就可以。
class Pig constructor(val name: String, age: Int) : Animal()

// 一般情况下是省略 constructor 关键字的,
class Pig(val name: String, age: Int) : Animal()

// Kotlin中给我们提供了需要在构造函数中执行的init{}方法块
class Pig(val name: String, age: Int) : Animal() {
    // 执行我们需要在构造函数中执行的语句
    init {
        
    }
}

Kotlin中是分主构造函数和次级构造函数的,如果一个类有多个构造函数,我们需要显式的去声明次级构造函数

// TestView后,默认跟了一个主构造函数
class TestView : View {
    
    // 下面是次级构造函数
    constructor(context: Context) : super(context) {}

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
    }
}
  • 访问修饰符
    private:private修饰的成员变量和方法只有在当前的类才能访问
    public :public修饰的成员变量和方法在所有的类都可以访问
    protected :protected修饰的成员变量和方法在当前类和它的子类都可以访问
    internal :Kotlin特有的,internal修饰的成员变量和方法在同一个模块中都可以访问,跨模块就允许方法。使用IDEA的应该都知道模块(Module)

  • 伴生对象
    伴生对象一定要写在一个类的内部,使用companion object声明,单例模式就可以使用kotlin的伴生对象来实现

class DynamicProxyFragment : BaseFragment() {
   companion object {
         fun getInstance(): DynamicProxyFragment = DynamicProxyFragment()
   }
}

// Kotlin中伴生对象中的方法的调用,就是类名.方法名,跟Java中调用类的静态方法是一样的
DynamicProxyFragment.getInstance()

// Java中调用,会先调用伴生对象,然后调用它的方法
// 伴生对象在编译好之后,会在这个类的内部生成一个静态对象Companion,然后通过这个对象去调用伴生对象的方法
DynamicProxyFragment.Companion.getInstance()
  • 单例类(单例模式)
/**
 * 单例模式,Kotlin的单例模式就是这么简单
 */
class SingleMode private constructor() {
    // 利用伴生对象实现单例模式,其实这种写法跟JAVA的静态内部类实现单例模式类似
    private object SingleHolder {
        val instance = SingleMode()
    }

    companion object {

        fun getInstance(): SingleMode {
            return SingleHolder.instance
        }

        // 简写
        //fun getInstance(): SingleMode = SingleHolder.instance

        //val singleMode: SingleMode by lazy { SingleHolder.instance}

        // JAVA的双重判断
        /*private var instance: SingleMode? = null
        fun getInstance(): SingleMode {
            if (instance == null) {
                synchronized(SingleMode::class) {
                    if (instance == null) {
                        instance = SingleMode()
                    }
                }
            }
            return instance!!
        }*/

        /*private var instance: SingleMode? = null
        val INSTANCE: SingleMode
            get() {
                if (instance == null) {
                    synchronized(SingleMode::class) {
                        if (instance == null) {
                            instance = SingleMode()
                        }
                    }
                }
                return instance!!
            }*/
    }

}
  • 动态代理
    1、Kotlin的语法层面就帮我们实现了动态代理,只需要使用by关键字修饰。
    2、Kotlin的动态代理编译后是会转变成静态代理去调用,而Java的动态代理是通过反射去执行的,所以本质上Kotlin的动态代理要比Java的动态代理效率高。
private interface Animal {
        fun eat()
    }

private class Dog : Animal {
    override fun eat() {
        LogUtils.d(TAG, "狗吃肉")
    }
}
    
// 动态代理
private class Pig(animal: Animal) : Animal by animal

// 调用了Dog的eat方法
// 如果Pig类实现了eat方法,就会调用Pig的eat方法
// 这里实际执行的就是它代理的方法,输出  "狗吃肉"
Pig(Dog()).eat()
  • Kotlin特有的类

密闭类
Kotlin的加强版的枚举类(密闭类),Kotlin中也提供了枚举类,但是我们一般使用密闭类

    /**
     * 枚举类,跟Java中定义是一样的,但是可以对比下面的密闭类
     * 密闭类比枚举类拥有更强大的扩展性,我们可以做一些特有的操作
     */
    enum class EnumMode {
        A, B, C
    }
/**
     * 密闭类,kotlin中更强大的enum,Kotlin中一般使用密闭类,很少使用枚举
     * 因为密闭类有更强大的扩展性
     */
    sealed class SealedMode {
        object A : SealedMode() 
        object B : SealedMode()
        object C : SealedMode()

        class E(var age: Int) : SealedMode() {
           // 比如我们可以自这个类的包体中做一些操作,比枚举类更智能
        }
    }

    fun exec(age: Int, sealMode: SealedMode) {
        when (sealMode) {
            SealedMode.A -> {

            }
            SealedMode.B -> {

            }
            SealedMode.C -> {

            }
            is SealedMode.E -> {
                //val eMode = sealMode
                //eMode.age
                sealMode.age
            }
        }
    }
数据类

数据类我们通常用来替换 Java的 JavaBean

/**
 * 数据类是Kotlin独有的类,它是被public final修饰的,是不可以被继承的,不能用open去修饰
 * 默认会帮我们实现getter,setter方法
 * 默认帮我们实现了copy(int age),toString(),hashCode(),equals()方法,可以通过查看编译之后的JAVA文件发现
 */
data class DataClassTest (var age: Int)

总结性的例子:涉及到单例,密闭类,动态代理等等
题目:定义一个播放器,内置两种皮肤颜色,用户可以选择自己的播放器皮肤,不同用户登录显示不同的皮肤,同时注意皮肤的可扩展性。

一、首先我们需要定义一个用户类

// val type: PlayerViewType = PlayerViewType.YELLOW  我们可以给参数设置一个默认值
data class User(val id: Int, val name: String, val type: PlayerViewType = PlayerViewType.YELLOW)

二、其次我们需要定义皮肤类型

sealed class PlayerViewType {
    object GREEN : PlayerViewType()
    object YELLOW : PlayerViewType()
    // 密闭类的扩展性就在这里得到了提现,比枚举更强大
    class VIP(val title: String?, val content: String?) : PlayerViewType()
}

fun getPlayViewType(type: PlayerViewType): PlayerView = when (type) {
    PlayerViewType.GREEN -> GreenPlayerView()
    PlayerViewType.YELLOW -> YellowPlayerView()
    is PlayerViewType.VIP -> VipPlayerView(type.title, type.content)
}

三、定义播放器皮肤

interface PlayerView {
    fun showView()
}

/**
 * 黄色播放器
 */
class YellowPlayerView : PlayerView {
    override fun showView() {
        println("黄色播放器")
    }
}

/**
 * 黄色播放器
 */
class GreenPlayerView : PlayerView {
    override fun showView() {
        println("绿色播放器")
    }
}

private const val defaultTitle = "VIP用户"
private const val defaultContent = "欢迎VIP用户"
/**
 * vip播放器
 */
class VipPlayerView(var title: String?, var content: String?) : PlayerView {

    // 需要在构造方法中完成的操作,可以放在init代码块中完成
    init {
        // 表示如果title不为空返回title本身的内容,为空就返回默认title
        title = title ?: defaultTitle
        content = content ?: defaultContent
    }

    override fun showView() {
        println("标题:$title  ==>  内容:$content")
    }

}

// 动态代理
class MediaPlayerView(playerView: PlayerView) : PlayerView by playerView

四、播放工具类

class PlayerUI {

    companion object {
        fun getInstance() = PlayerHolder.palyerUI
    }

    private object PlayerHolder {
        val palyerUI = PlayerUI()
    }

    // 根据不同传入的播放器类型去获取不同的播放器
    fun showPlayer(user: User) {
        MediaPlayerView(getPlayViewType(user.type)).showView()
    }
}

五、调用

fun main() {
    val user = User(1, "张三", PlayerViewType.VIP(null,null))
    PlayerUI.getInstance().showPlayer(user)
}

  1. Kotlin的高级特性
  • 1、解构
/**
 * operator:将一个函数标记为重载一个操作符或者实现一个约定
 * 下面就是实现解构
 */
class OperatorBean(var name: String, var age: Int) {
    // 这里是固定写法component1(),必须是component+一个数字
    operator fun component1() = name
    operator fun component2() = age
}

// 调用
val operatorBean = OperatorBean("张三", 20)
// 解构,其实这里kotlin具体操作,具体可以看编译后的JAVA文件
// name = operatorBean.component1()
// age = operatorBean.component2()
// 这里就实现了解构,我们可以直接获取到name和age的值
val (name, age) = operatorBean
tv_content.text = "姓名:$name===>年龄:$age"

// 遍历map的时候比较常用
for ((key, value) in map) {
    println("键:$key==>值:$value")
}
  • 2、循环与集合操作符
循环遍历
val builder = StringBuilder()
        builder.append("for (i in 0..10)会输出0-10:")
        for (i in 0..10) {
            builder.append("$i").append(" ")
        }

        builder.append("\nfor (i in 0 until 10)会输出0-9:")
        for (i in 0 until 10) {
            builder.append("$i").append(" ")
        }

        builder.append("\nfor (i in 10 downTo 0)会输出10-0:")
        for (i in 10 downTo 0) {
            builder.append("$i").append(" ")
        }

        builder.append("\nfor (i in 0..10 step 2)走两步正序:")
        for (i in 0..10 step 2) {
            builder.append("$i").append(" ")
        }

        builder.append("\nfor (i in 10 downTo 0 step 2)走两步倒序:")
        for (i in 10 downTo 0 step 2) {
            builder.append("$i").append(" ")
        }

        builder.append("\nrepeat(times,action:(Int) -> Unit)会输出0-9,查看源码可以知道就是实现的for (i in 0 until 10):")
        /*val action: (Int) -> Unit
        action = testOutput::output*/
        //repeat(10, testOutput::output)
        // 这是Kotlin给我们提供的集合操作符,traverList就是repeat的实现方式
        repeat(10) {
            builder.append("$it").append(" ")
        }
        /*traverList(10) {
            builder.append("$it").append(" ")
        }*/

        builder.append("\nfor (num in testList)遍历集合:")
        val testList = arrayListOf<String>("a", "b", "c", "d")
        for (str in testList) {
            builder.append(str).append(" ")
        }

        builder.append("\nfor ((index, value) in testList.withIndex())遍历集合会同时输出下标和数值,其实就是解构:")
        for ((index, value) in testList.withIndex()) {
            builder.append("下标:$index").append("==>值:$value").append(" ")
        }
        tv_content.text = builder.toString()

下面将上面例子中注释的两段代码自定义的方式贴出来,其实kotlin的操作符也是这个实现

// 高阶函数声明中用inline关键字,在编译的时候回拆解函数的调用为语句的调用
// traverList(10,testOutput::output),高阶函数具体的调用,调用的是方法的引用,对象名::方法名
// traverList(10) {} lambda的调用方式,是因为最后一个参数是一个lambda表达式,所以可以写成这样
private inline fun traverList(times: Int, action: (Int) -> Unit) {
    for (index in 0 until times) {
        action(index)
    }
}
// 我们看到了repeat的第二个参数是一个参数为int,返回值为空的lambda表达式(action: (Int) -> Unit)。具体来说就是调用的一个参数为int,返回值为空的方法
private interface TestOutput {
     fun output(num: Int)
}

private val testOutput = object : TestOutput {
    override fun output(num: Int) {
       LogUtils.d(TAG, "输出参数:$num")
    }
}

遍历List集合

val list: MutableList<OperatorBean> = arrayListOf()
list.add(OperatorBean("张三", 20))
list.add(OperatorBean("李斯", 40))
// ForEach的遍历,只是对象实现了解构
for ((name, age) in list) {

}
// 获取集合对象和集合下标,解构
for ((operatorBean, i) in list.withIndex()) {
            
}
// 获取集合对象,跟Java的ForEach一样
for (operatorBean in list) {
}
// 获取集合的下标,使用的kotlin提供的操作符,其余方式看上面集合的遍历,是一样的
for (i in list.indices) {
}
// 看名字就应该知道了,ForEach的遍历
list.forEach {
}
集合操作符
    /**
     * 集合操作符,跟RxJava类似
     */
    private fun listOpe() {
        val builder = StringBuilder()
        val list = arrayListOf('a', 'b', 'c', 'd', 'e')
        // map操作符就是在其中做相应的操作,将数据转变为我们想要的结果
        val findValue = list.map {
            it - 'a'
        }.filter {
            it > 0
        }.find {
            // find操作符返回的是符合这个lambda闭包操作的第一个值
            // findLast操作符是返回符合lambda闭包操作的最后一个值
            it > 1
        }
        builder.append("测试简单操作符:$findValue\n")

        val strList = arrayListOf("4", "0", "7", "i", "f", "w", "0", "9")
        val indexList = arrayListOf(5, 3, 9, 4, 8, 3, 1, 9, 2, 1, 7)
        indexList.filter {
            it < strList.size
        }.map {
            strList[it]
        }.reduce { acc, s ->
            // 组合
            "$acc$s"
        }.also {
            builder.append("拼装结果:$it\n")
        }

        val cusList = arrayListOf("a", "b", "c", "d")
        cusList.convert {
            "2$it"
        }.also {
            builder.append("自定义操作符转换结果:")
            for (result in it) {
                builder.append(result).append(" ")
            }
        }

        tv_content.text = builder.toString()
    }

自定义集合操作符,就是集合的map的功能,将经过转换的list集合返回

/**
     * 自定义操作符,去扩展的Iterable,将集合转变为另一个集合
     */
    private inline fun <T, R> Iterable<T>.convert(action: (T) -> R): Iterable<R> {
        //private inline fun <T, R> Iterable<T>.convert(action: (T) -> R): MutableList<R> {
        val list: MutableList<R> = arrayListOf()
        for (item: T in this) {
            list.add(action(item))
        }
        return list
    }
  • 3、运算符重载
    Kotlin的运算符重载是通过operator这个关键字去实现的,Kotlin的运算符重载必须是Kotlin中提前定义好的运算符。

  • 4、作用域函数

let 和 run:let和run都会返回lambda闭包的执行结果,区别在于let有闭包参数,run没有闭包参数。

我们通过查看源码来看我们的结论是否正确
let操作符源码

// 我们可以看到let操作符是有闭包参数的,并且有返回值,就是lambda表达式转换后的结果
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    // 从这里我们可以看到闭包参数是调用者,也就是T
    return block(this)
}

run操作符源码:

// 通过源码我们可以明确的看到,lambda表达式是没有闭包参数的,我们可以通过this去得到run的调用者,
//从let源码可以看出,let就是将调用者传给闭包参数的。
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

例子:

val builder = StringBuilder()
// let和run都会返回lambda闭包的执行结果,区别在于let有闭包参数,run没有闭包参数
val user = UserBean("张三")
// let有闭包参数,比如user调用了let,闭包参数就是user
val letName = user.let { userBean ->
    "let:${userBean.name}"
}
// 而run是没有闭包参数,比如user调用了run,this就是用来指代调用者user
val runName = user.run {
    "run:${this.name}"
}
builder.append(letName).append("==>$runName\n")

also 和 apply:also和apply都不会返回闭包的执行结果(返回的是本身的调用对象),区别在于also有闭包参数,apply没有闭包参数。
主要适用于可以连续调用的操作,比如下面的例子

// also和apply都不会返回闭包的执行结果(返回的是本身的调用对象),区别在于also有闭包参数,apply没有闭包参数
/*user.also {
      builder.append("also:${it.name}")
}
user.apply {
     builder.append("==>apply:${this.name}\n")
}*/
user.also {
      builder.append("also:${it.name}")
}.apply {
      builder.append("==>apply:${this.name}\n")
}.name = "李斯"
user.also {
     builder.append("also:${it.name}")
}.apply {
     builder.append("==>apply:${this.name}\n")
}

takeIf 和 takeUnless
takeIf 返回一个判断结果,为false时,takeIf会返回空。
takeUnless跟takeIf刚好相反,闭包的返回结果为true时,会返回空。
所以我们在使用的时候会使用?.去调用takeIf和takeUnless后面的函数

看一下源码验证我们结论是否正确

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
// 给T进行了一个扩展函数操作
// takeIf是一个参数为T,返回为Boolean的lambda闭包
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    // 我们可以看到当lambda表达式返回true的时候,就返回调用对象,否则直接返回null
    return if (predicate(this)) this else null
}

例子:

user.takeIf {
     it.name.isNotEmpty() && it.name.isNotBlank()
}?.also {
    builder.append("takeIf:${it.name}")
} ?: builder.append("takeIf:姓名为空")

user.takeUnless {
     it.name.isNotEmpty() && it.name.isNotBlank()
}?.also {
    builder.append("takeUnless:姓名为空")
} ?: builder.append("takeUnless:${user.name}")

with操作符
with比较特殊,它不是以扩展函数存在的,而是一个顶级函数
with操作符的源码就可以看出

// reveiver就是我们需要操作的对象
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    // 返回的是reveiver调用block方法之后的值
    return receiver.block()
}

例子

// with比较特殊,它不是以扩展函数存在的,而是一个顶级函数
// Android中可以使用with对各个View赋值
with(user) {
     //user.name = "王五"
     this.name = "张三"
}
tv_content.text = builder.toString()

常用的集合操作符
        val list = arrayListOf(0, 1)
        // 常用的集合操作符:
        // 元素操作类
        list.contains(0) // 判断是否有指定元素
        list.elementAt(0) // 返回对应的元素
        list.firstOrNull {
            it > 0
        } // 返回符合条件的第一个元素,没有则返回null
        list.lastOrNull {
            it > 0
        } // 返回符合条件的最后一个元素,没有则返回null
        list.indexOf(1) // 返回指定元素的下标
        list.singleOrNull {
            it != 0
        } // 返回符合条件的单个元素,没有或者超过一个,返回null

        // 判断类
        list.any {
            it != 0
        } // 判断集合中是否有满足条件的元素
        list.all {
            it > 0
        } // 判断集合中的元素是否都满足条件
        list.none {
            it < 0
        } // 判断结合中的元素是否都不满足条件
        list.count {
            it > 0
        } // 判断集合中满足条件元素的个数
        list.reduce { acc, i ->
            acc + i
        } // 从第一项到最后一项进行累计

        // 过滤类
        list.filter {
            it > 0
        } // 过滤掉满足条件的所有元素
        list.filterNot {
            it > 0
        } // 过滤掉不满足条件的所有元素
        list.filterNotNull() // 过滤Null
        list.take(2) // 返回前n个元素

        // 转换类
        list.map {
            it.toString()
        } // 将集合转换成另一个集合
        // list.flatMap {} // 自定义逻辑合并两个集合
        list.groupBy {
            it > 0
        } // 按照某个条件进行分组,返回map

        // 排序类
        list.reversed() // 反序
        list.sorted() // 升序排序
        // list.sortedBy {  } // 自定义排序
        list.sortedDescending() //降序

  • 5、中缀表达式
    Kotlin中使用 infix 关键字修饰的函数就可以使用中缀表达式的方式调用,上面集合中的step就是一个中缀表达式。
     /**
     * 中缀表达式,我们对Int定义了一个中缀表达式
     * Int.vs 就是我们给Int扩展了一个vs的函数,Int就称为vs的函数接收者类型
     * (或者可以说只有Int才有vs这个函数)
     */
    private infix fun Int.vs(num: Int): CompareResult {
        return when {
            this - num > 0 -> CompareResult.MORE
            this - num < 0 -> CompareResult.LESS
            else -> CompareResult.EQUAL
        }
    }

    sealed class CompareResult {
        object LESS : CompareResult() {
            override fun toString(): String {
                return "小于"
            }
        }

        object MORE : CompareResult() {
            override fun toString(): String {
                return "大于"
            }
        }

        object EQUAL : CompareResult() {
            override fun toString(): String {
                return "等于"
            }
        }
    }

//调用,下面的 5 vs 6 中vs就是我们定义的中缀表达式
tv_content.text = (5 vs 6).toString()

注意:一个函数只有用于两个角色类似的对象时才将其声明为中缀表达式

重要:Kotlin和Java比较对象
Kotlin的 == 对应Java的equals(),Kotlin的 === 对应Java的 ==

代码

项目github地址

参考

Kotlin中文网

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃骨头不吐股骨头皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值