Kotlin (一)基础
基础语法
1.1 增强类型推导
fun main() {
// 类型推断
val string= "I am kotlin"
val int = 1314
val long = 1314L
val float = 13.14f
val double = 13.14
val double2 = 10.1e6
println(string.javaClass.name)
println(int.javaClass.name)
println(long.javaClass.name)
println(float.javaClass.name)
println(double.javaClass.name)
println(double2.javaClass.name)
}
类型推导是Kotlin在java语言上的增强。编译器可以在不显示声明情况推导出类型。Kotlin 是属于静态语言,所以编写中会写很多类型,类型推导改善这点,提高开发效率。
1.2 声明函数返回类型
fun sum(x:Int,y:Int): Int { return x + y }
必须返回类型:Int 否则 报错,默认是Unit
fun sum(x:Int,y:Int) = x + y
我们可以这么写
但是别高兴太早
// 递归函数,全局类型推导不支持
fun foo(n:Int) = if (n==0) 1 else n * foo(n -1)
Type checking has run into a recursive problem. Easiest workaround: specify types of your declarations explicitly
所以例如递归复杂的情况,使用表达式函数也必须有返回值声明
fun foo(n:Int):Int = if (n==0) 1 else n * foo(n -1)
原因是Kotlin,Scala支持的是子类型和继承,很难做到全局类型推导
1.3 val 和var 的使用规则
var ===> variable 可变
val ===> 相当于 variable + final 不可变
// val var
val x = intArrayOf(1,2)
// x = intArrayOf(2,3)-> error
x[0] = 2
不同语言对数组设计不同,在Swift 中 let 是不能像我们这样改变值的。Swift 数组看做值类型,提一下作对比,具体去看Swift语言
kotlin 设计考虑数组这种大数据结构拷贝成本,所以数组存在堆内存。kotlin val 引用不能更改,但是引用的对象可以重新赋值
class Book(var name:String){
fun printName(){
println(name)
}
}
val book = Book("Thinking in Java")
book.name = "I use kt"
book.printName()
优先使用val来避免副作用
尽可能采用val,不可变对象以及纯函数设计程序。
副作用,就是修改了某处的东西,比如:
修改外部变量的值
IO, 写到磁盘
UI,修改一个按钮的操作状态
副作用与可变数据和共享状态相关
var a = 1
//val a = 1 -> error
fun count(x:Int){
a = a +1
println(x + a)
}
- val 防御编程思维,更安全可靠
- 不可变对于复杂的业务逻辑优势会更大
var 使用场景
var 一定成都兼容java
在一些遍历中,使用var 会更加简洁,节省内存
kotlin底层变量也大量使用了var,针对数据结构,业务中需要大量存储数据var 似乎起了更大作用
fun cal(list:List<Int>):Int{
return list.fold(0){res,el->res * el + 1}
}
fold 底层实现
/**
* Accumulates value starting with [initial] value and applying [operation] from left to right to current accumulator value and each element.
*/
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
var accumulator = initial
for (element in this) accumulator = operation(accumulator, element)
return accumulator
}
1.4 抽象和高阶函数
高阶函数---> 以其他函数作为参数或者返回值的函数
kotlin 中函数类型非常简单,
如 (int)->Unit
() -> Unit
(Int,String) -> Unit
(errorCode:Int,errMsg:String) -> Unit
- -> 符号 左边是参数类型,右边是返回值
- 必须用括号包裹参数类型
- 返回值 即使是Unit 也必须显示声明
方法和成员的引用---> 通过两个冒号来实现对于某个类的方法引用,Kotlin 沿用了java8 的习惯
class Book2(var name:String)
// 沿用java8 语法风格双冒号引用类和成员
val getBook = ::Book2
println(getBook("David is good").name)
Book::name 形式
val bookNames = listOf(
Book2("David is good"),
Book2("Json is good")
).map(Book2::name)
println(bookNames)
匿名函数----> 缺省函数名情况下,直接定义一个函数
fun filterCustom(names:List<String>,test:(name:String)->Boolean){
}
匿名函数作为参数
filterCustom(bookNames,fun(name:String):Boolean{
return name.contains("David")
})
1.5 Lambda 语法糖
java8 中也有类似,可以理解为简化的匿名函数
如上匿名函数作为参数
filterCustom(bookNames, { name -> name.contains("David") })
->
filterCustom(bookNames) { name -> name.contains("David") }
- 一个lambda 表达式 必须用”{}“ 包裹
- 如果lambda声明了参数部分类型,并且返回值类型支持类型推导,那么Lambda可以省略函数类型声明。
- 如果lambda声明了函数的类型,lambda参数类型就可以省略
- 如果lambda 返回值不是Unit,那么默认最后一行表达式类型,就是返回值类型
lambda 语法糖 it
listOf(1,2,3).forEach{
println(it)
}
Function 类型
JVM 层设计了Function类型(Function0 ......Function22)兼容java Lambda表达式-----> kotlin.jvm.functions
每个Function类型都有一个invoke()
fun bar(int: Int): () -> Unit = { println(int) }
//Function 类型 invoke ()
listOf(7, 8, 9).forEach {
bar(it)
--> 不会打印
}
listOf(7, 8, 9).forEach {
bar(it).invoke()
}
listOf(7, 8, 9).forEach {
bar(it)()
}
1.6 函数,lambda 和 闭包
fun
- fun 在没有等号,只有花括号的情况下,使我们最常见的代码块函数体,如果返回非Unit 那么必须带有return
fun foo2(x : Int){ println(x) }
fun foo3(x : Int,y : Int) :Int {return x * y}
- fun 带有符号,是单表达式函数体。该情况下可以省略return。
fun sum(x: Int, y: Int) = x + y
lambda
- 无论是val 还是 fun,如果是等号+花括号语法,那么构建的就是lambda表达式,lambda的参数在花括号里面声明。所以如果左侧是fun 那么就是lambda 函数体,也必须用() 或者invoke()调用lambda
val foo5 = { x: Int, y: Int -> x + y }
fun foo4(x: Int) = { y: Int -> x + y }
// lambda fun val
foo5.invoke(100,200)
foo4(1_000).invoke(2_000)
闭包
匿名函数体,lambda(以及局部函数,object表达式)在语法上都存在”{}“,由花括号包裹内容访问外部环境变量被称为闭包
----> "访问外部环境变量的函数" l
kotlin中闭包不仅能访问外部变量,还能对其修改
//闭包
var sum = 0
listOf(1,2,3).filter{it > 1}.forEach { sum += it }
println("sum = $sum")
还有一种自运行lambda 语法
{x:Int -> println(x)}(1_000)
1.7 “柯里化” 风格,扩展函数
fun sum2(x: Int, y: Int, z: Int) = x + y + z
fun sum(x:Int)={
y:Int -> { z:Int -> x+y+z}
}
val testsum1 = sum2(1,2,3)
val testsum2 = sum(1)(2)(3)
println("testsum1=${testsum1} testsum2=${testsum2}")
柯里化就是为了简化Lambda演算理论接受多参数,简化多元函数为一元
Lambda 特殊用法如果一个函数,只有一个参数,并且是函数类型。那么在调用的时候,外面的括号可以省略
fun omitParenthese(block:()->Unit){
block()
}
omitParenthese{
println("Parenthese is omited")
}
如果参数不只有一个,而且最后一个参数为函数类型,就可以采用类似柯里化风格调用。
fun curringLike(current: String, block: (String) -> Unit) {
block(current)
}
curringLike("like style") {
content ->
println(content)
}
等价
curringLike("like style",
{ content -> println(content) }
)
kotlin 允许我们在不修改原有类情况下,给他增加新的方法
android 中View
fun View.invisible(){
visibility = View.INVISIBLE
}
// 语法演示
fun main() {
var views = listOf<View>()
views.forEach { it.invalidate() }
}
fun <A, B> Array<A>.corresponds(that: Array<B>, p: (A, B) -> Boolean): Boolean {
val i = this.iterator()
val j = that.iterator()
while (i.hasNext() && j.hasNext()) (
if (!p(i.next(), j.next())) {
return false
}
)
return !i.hasNext() && !j.hasNext()
}
// 扩展函数
val a = arrayOf(1, 2, 3)
val b = arrayOf(2, 3, 4)
val test1 = a.corresponds(b) { x, y -> x + 1 == y }
val test2 = a.corresponds(b) { x, y -> x + 2 == y }
println("test1 test2 : $test1 --- $test2")
1.8 面向表达式编程
语句的作用服务于创建副作用 (就是可以改或者null)
表达式的目的为了创造新的值。函数式编程中,原则上表达式不允许包含副作用
Unity 类型: 让函数调用皆为表达式
java 中Void 对于void ,没有返回值Void 不具有实例
Unit在kotlin不代表任何信息,用面向对象立即就是一个单例可以写为“()”
对比 Java8 添加Action<T> 这种函数式接口来解决问题
Consumer<T> 接收一个参数,返回无结果;
BiConsumer<T,U> 接收两个参数,返回无结果;
ObjDoubleConsumer<T> 接收一个object参数和一个double参数,返回无结果;
ObjIntConsumer<T> 接收一个object参数和一个int参数,返回无结果;
ObjLongConsumer<T> 接收一个object参数和一个long参数,返回无结果;
符合表达式 try catch
val res:Int? = try {
if (result.success){
jsonDecode(result.response)
} else null
}catch (e:JsonDecodeException){
null
}
try 也是表达式,返回值由try和cach决定
枚举和when 提示一下,具体参照官网
- kotlin枚举是类实现的
- 用when代替if-else
for 循环表达式
in 检查成员关系
范围表达式 "..."
infix 中缀表达式
A 中缀方法 B
定义一个中缀函数,必须满足下面条件:
- 中缀函数必须是某类型的扩展函数后者成员成员方法
- 中缀函数只有一个参数
- kotlin虽然参数有默认,但是中缀函数不能有默认值,否则B会缺失,造成语义破坏
- 该参数不能是可变参数,我们保持参数始终是1个
中缀表达式,支持类更贴近自然语言
class Person{
infix fun called(name:String){
println("My name is $name")
}
}
val p = Person()
p called "David"
Kotlin中用varargs关键字表示可变参数,类似java中“..."
我们可以用*号来传入多个参数
fun printletters(vararg letters:String,count:Int){
println("$count letters are $letters")
}
val letters = arrayOf("a","b","c")
printletters(*letters,count = 3)
to 返回Pair 键值对,常和map结合在一起
mapOf(
1 to "one",
2 to "two"
)
类似的还有in,step,downTo,until