Kotlin笔记--持续更新
类和函数
类
默认任何类都是基础继承自 Any (与java中的 Object 类似),但是我们可以继承其它类。
所有的类默认都是不可继承的(final),所以我们只能继承那些明确声明 open 或者abstract 的类:
open class Animal(name: String)
class Person(name: String, surname: String) : Animal(name)
数据类
Kotlin可以创建一个只包含数据的类,关键字为 data :
data class User(val name: String, val age: Int)
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
equals() / hashCode()
toString() 格式如 “User(name=John, age=42)”
componentN() functions 对应于属性,按声明顺序排列(解构声明)
copy() 函数
组件函数允许数据类在解构声明中使用:
val jane = User("Jane", 35)
val (name, age) = jane //解构声明
println("$name, $age years of age") // prints "Jane, 35 years of age"
函数
通过使用 fun 关键字定义:
fun onCreate(savedInstanceState: Bundle?) {}
如果你没有指定它的返回值,它就会返回 Unit ,与Java中的 void 类似,但是 Unit 是一个真正的对象。你当然也可以指定任何其它的返回类型:
fun add(x: Int, y: Int) : Int {
return x + y
}
然而如果返回的结果可以使用一个表达式计算出来,你可以不使用括号而是使用等号:
fun sum(a: Int, b: Int) = a + b
public fun sum(a: Int, b: Int): Int = a + b // public 方法则必须明确写出返回类型
可变长参数函数
函数的变长参数可以用 vararg 关键字进行标识:
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
vars(1,2,3,4,5) // 输出12345
属性
如果没有任何指定,属性会默认使用 getters 和 setters
public class Person {
var name: String = ""
}
...
val person = Person()
person.name = "name"
val name = person.name
如果需要在getter和setter中访问这个属性自身的值,它需要创建一个 backing field 。可以使用field 这个预留字段来访问
public classs Person {
var name: String = ""
get() = field.toUpperCase()
set(value){
field = "Name: $value"
}
}
NULL检查机制
//类型后面加?表示可为空
var age: String? = "23"
//age如果为空,抛出空指针异常
val ages = age!!.toInt()
//age如果为空,不做处理返回 null
val ages1 = age?.toInt()
//age如果为空,返回-1,埃尔维斯表达式 ?:
val ages2 = age?.toInt() ?: -1
类型检测及自动类型转换
使用 is 运算符检测一个对象是否是某类型的一个实例(类似于Java中的instanceof关键字)。
fun getStringLength(obj: Any): Int? {
// 在 && 运算符的右侧, obj 的类型会被自动转换为 String
if (obj is String && obj.length > 0)
return obj.length
return null
}
区间
区间表达式由具有操作符形式 … 的 rangeTo 函数辅以 **in ** 形成,是闭区间,使用 **until ** 为左闭右开区间:
for (i in 1..4) print(i) // 输出“1234”
for (i in 4..1) print(i) // 什么都不输出
// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
// 使用 until 函数排除结束元素
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
基本数值类型
Kotlin 的基本数值类型包括 Byte 、 Short 、 Int 、 Long 、 Float 、 Double
java多了boolean和char
扩展
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。有扩展属性和扩展函数。
形式
扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:
fun receiverType.functionName(params){
body
}
//receiverType:表示函数的接收者,也就是函数扩展的对象
//functionName:扩展函数的名称
//params:扩展函数的参数,可以为NULL
特点
1.扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:
通过使用 fun 关键字定义:
open class C
class D: C()
fun C.foo() = "c" // 扩展函数 foo
fun D.foo() = "d" // 扩展函数 foo
fun printFoo(c: C) {
println(c.foo()) // 类型是 C 类
}
fun main(arg:Array<String>){
printFoo(D()) //实例执行输出结果为:c
}
2.若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
3.扩展属性不支持默认初始化,需要明确定义 getters 和 setters
var View.padLeft: Int
set(value) {
setPadding(value, paddingTop, paddingRight, paddingBottom)
}
get() {
return paddingLeft
}
顶层函数和伴生对象
kotlin没有静态成员或静态函数之说,推荐使用顶层函数代替,但是顶层函数不能访问类的private成员,所以有伴生对象 ompanion object
顶层函数
1.一个函数或者属性可以直接定义在一个Kotlin文件的顶层中,在使用的地方只需要import这个函数或属性即可
package util
fun joinToStr(collection: Collection<string>): String{
//....
return "";
}
import util.joinToStr //通过import 包名.函数名来导入我们将要使用的函数,然后就可以直接使用了
fun main(args: Array<string>){
joinToStr(collection = listOf("123", "456"))
}
2.如果觉得Kotlin为你自动生成的这个类名不好,那你可以通过 @file:JvmName 注解来自定义类名,就像下面这样。
而且要注意,这个注解必须放在文件的开头,包名的前面。
@file:JvmName("StrUtil")
package util
fun joinToStr(collection: Collection<string>): String{
//....
return "";
}
import util.StrUtil; //通过import 包名.自定义类名来进行操作
fun main(args: Array<string>){
StrUtil.joinToStr(collection = listOf("123", "456")) //自定义类名.函数名
}
伴生对象
通过 companion object 关键字,在一个类中定义
class A {
companion object {
fun bar() {
println("111");
}
}
}
A.bar() //输出111
}
中缀调用
一种特殊的函数调用,使用 infix 关键字修饰函数,该函数只能有一个参数
infix fun String.和(uniqueParam: String): String { //和扩展函数结合
return this + "+" + uniqueParam
}
val resultInfix = "北京" 和 "上海"
println(resultInfix) //输出结果:北京+上海
解构声明
解构声明是把一个对象看成一组单独的变量
支持解构声明的对象的类必须是数据类(使用 data 关键字修饰的类),因为只有data class才会生成每个属性对应的 componentN() 方法
data class Student(var name: String, var age: Int, var grade: Double)
val student = Student("mikyou", 18, 99.0)
val (name, age, grade) = student//将一个student对象解构成一组3个单独的变量
println("my name is $name , I'm $age years old, I get $grade score")//解构后的3个变量可以脱离对象,直接单独使用
序列Sequence
除了集合, Kotlin 还引入了一个新的容器类型 Sequence ,它和 Iterable 一样用来遍历一组数据并可以对每个元素进行特定的处理
创建
//第一种方式
val sequence1 = sequenceOf(1, 2, 3, 4)
//第二种方式
val list = listOf("a", "b", "c")
val sequence2 = list.asSequence()
特点
惰性:在「?」标注之前的代码运行时不会立即执行,它只是定义了一个执行流程,只有 result 被使用到的时候才会执行,即执行到「? 」标注的代码
顺序:集合和序列在链式的执行顺序是不一样的,集合在链式中先处理完第一步所有的元素,再处理第二步所有的元素,以此类推。而序列不是,序列是对每个元素做链式操作,只有第一个元素执行完所有的链式操作,才执行第二个元素的链式操作。
所以下面的例子,执行顺序是:
1.取出元素 1 -> map 为 2 -> filter 判断 2 是否能被 3 整除 ->first()为空执行下一步
2.取出元素 2 -> map 为 4 -> filter 判断 4 是否能被 3 整除 ->first()为空执行下一步
3.取出元素 3 -> map 为 6 -> filter 判断 6 是否能被 3 整除 ->first()为6,不会执行下一步,即跳过了 4 的遍历
val sequence = sequenceOf(1, 2, 3, 4)
val result: List = sequence
.map { i ->
println("Map $i")
i * 2
}
.filter { i ->
println("Filter $i")
i % 3 == 0
}
?
println(result.first()) ?
如果是list,如下例,它的执行顺序是:
1.在「? 」标注的代码,list已经开始执行map和filter函数了
2.先会对所有元素做map,即{1, 2, 3, 4} -> {2, 4, 6, 8}
3.然后遍历判断是否能被 3 整除,即 {2, 4, 6, 8}-> {6}
4.执行到「?」标注的代码,即first,取出6
val list = listOf(1, 2, 3, 4)
val result: List = list ?
.map { i ->
println("Map $i")
i * 2
}
.filter { i ->
println("Filter $i")
i % 3 == 0
}
?
println(result.first())
好处和使用场景
1.像 List 这种实现 Iterable 接口的集合类,每调用一次函数就会生成一个新的 Iterable,下一个函数再基于新的 Iterable 执行,每次函数调用产生的临时 Iterable 会导致额外的内存消耗,而 Sequence 在整个流程中只有一个。
2.Sequence 这种数据类型可以在数据量比较大或者数据量未知的时候,作为流式处理的解决方案
委托
未完待续
闭包
闭包就是能够读取其他函数内部变量的函数。函数里声明函数,函数里面返回函数,就是闭包
未完待续
内联函数
1.inline 在编译时,会将此修饰符修饰的函数复制到调用处(称为内联),避免创建 Function 对象,以减少创建对象的内存开销。
2.noinline 需要配合 inline 使用,使用在内联函数的形参函数上,告诉编译器当前这个形参函数不能内联
3.crossinline 需要配合 inline 使用。非局部返回(non-local return): kotlin中,如果一个函数中,存在一个lambda表达式,在该lambda中不支持直接通过return退出该函数的,只能通过return@XXXinterface这种方式指定外层。
但是如果这个函数是内联的却是可以的。这种直接从lambda中return掉函数的方法就是非局部返回,crossinline就是为了让其不能直接return