开始学习Kotlin是因为Google将Kotlin作为Android开发的官网语言,现在市面也有一些公司面试的时候开始将Kotlin作为一个加分项,其实可以想想多学一点东西,多一点傍身的技巧,O(∩_∩)O哈哈~。
希望在看文章的时候,一定要把注释一起看,一定要把注释一起看,一定要把注释一起看,重要的事情说三遍,很多解释都是直接写在注释中的,因为我觉得把一些解释跟代码放在一起要稍微好理解一些,O(∩_∩)O哈哈~。
Kotlin 中使用的基本类型:数字、字符、布尔值、数组与字符串。
类型 | 大小(比特数) | 最小值 | 最大值 |
Byte | 8 | -128 | 127 |
Short | 16 | -32768 | 32767 |
Int | 32 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
Long | 64 | -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 提供了双精度。
类型 | 大小(比特数) | 有效数字比特数 | 指数比特数 | 十进制位数 |
Float | 32 | 24 | 8 | 6-7 |
Double | 64 | 53 | 11 | 15-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中浮点数的常规表示
数字字面值中的下划线(自 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的补码)
各位取反:01000000
加1:01000001(+65)
所以介绍一下位移运算的步骤
如何求补码不理解的直接参考上面的概念,例子,自己手动去操作一遍,其实理解了东西还是蛮简单的。
Int类型是32位,其实我们这里应该操作32位去进行位运算
例子就简单的介绍下具体的用法
有符号左移:<<
例如:20 << 2,表示将整数20有符号左移两位,正整数的符号位是0
原码:0001 0100
补码:0001 0100
左移两位:00 0101 0000
去掉最高位的两位:0101 0000 2的6次方 + 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 2的6次方 + 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
结果:-5(2的二次方 + 2的0次方 = 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运算符重载
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() // 显式转换为数字
}
// 创建一个 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 }
字符串可以使用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}")
// 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
}
// 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是没有封装类的
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();
// 没有返回值的函数,其实是有一个默认返回值: 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()
}
// 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);
例子:
/**
* @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);
//最原始
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闭包的类型(下面的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表达式
* 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,被打印出来
// 如果传递函数的执行,其实是传递的函数的返回值
注意:我们平时使用的 对象.方法名() 是函数的执行
inline fun onlyIf(isDebug: Boolean, block: () -> Unit) {
if (isDebug) {
block()
}
}
// Kotlin的类跟Java一样都是class来修饰,不同的是Kotlin的类默认会在前面加上public final修饰
class Test{}
// 如果需要去掉public final,使用open修饰
open class Test{}
// Kotlin的继承和接口的实现都是用冒号(:)来分割,多个接口实现用逗号(,)隔开,接口和父类的实现没有先后顺序
class Dog: Aniaml, TestDao {}
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
) {
}
}
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!!
}*/
}
}
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中也提供了枚举类,但是我们一般使用密闭类
/**
* 枚举类,跟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)
}
/**
* 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")
}
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
}
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() //降序
/**
* 中缀表达式,我们对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中文网