Kotlin
Kotlin包含了面向对象编程和函数式编程两种方式
函数式编程
- 头等函数——把函数当成值来用,可以用变量来表示,当做参数传入某个函数或者作为返回值。
- 不可变性——使用不可变对象,保证它们的状态在创建之后不会被改变。(这不就是final吗)
- 无副作用——使用的是纯函数,此类函数在输入相同时会产生相同的效果,不会修改其他对象的状态,也不会和外面的世界交互。(感觉上就是没有面向对象的多态而已)
函数
函数以fun
标识,紧随其后的是函数名,接下来括号里的是函数参数,最后使用:
隔开的是函数的返回值类型,不需要返回值则不写,默认返回unit
。
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
语句和表达式
-
语句和表达式区别在于表达式是有值的,类似于数学公式总是会有一个结果。而语句则是包围着它的代码块中的顶层元素,类似一种规则制定者,负责维持秩序,其本身没有值。
-
在kotlin中,除了循环(
for、do和do/while
)以外大多数控制结构都是表达式,这种模式可以让你的编码风格更加灵活和多变。-
例如上述函数,其实
if
就是一个表达式,因此才可以直接返回if语句。既然if是一个表达式,而在kotlin
中大多数控制结构都是以表达式实现的,那么函数本身其实也可以是个表达式。所以我们可以将函数改写成如下:fun max(a: Int, b: Int): Int = if (a > b) a else b
-
-
这里我之所以要解释语句和表达式的区别而不是直接告诉大家有哪些写法,是因为如果在不知道一个编程语言的理念和风格的情况下就盲目的进行学习,到后面就是
知其然而不知其所以然
,这样导致的最直接的结果就是在别人不告知你某个特定语法的情况下你是几乎不能联想到其可能的实现方法的。相反,如果你理解了其实现原理,其实很多地方即便不用特别告诉你,你也能推测出一些在已有认知中理所应当
的实现。
变量
-
Kotlin是一门强类型语言,因此必须明确每个变量的具体类型才能够通过编译,但是,Kotlin是一门很智能的语言,它不用像
Java
一样一开始就声名出变量的类型,而是统一使用var/val
进行表示,最后根据所装载的值自动识别其变量类型(当然一些不确定的情况下还是需要声明类型)。val a = 1 //int val b = "two" //String val c = 3.0 //double val d = 4.0f //float var e: Long? = null //可变long
-
可变变量和不可变量
-
val——不可变引用
-
var——可变引用
尽可能使用
val
声明所有变量,具体原因就不多说了。不可变量的定义大致说下,在Java中变量分为引用
和值(内存空间)
,在C语言里面由于指针的关系我们是可以通过直接操作内存来改变某个变量的值的,但是在Java中我们只能改变某个变量引用的指向来改变变量的值。举个栗子,一个Int型的变量a的初始值为1,我们重新将a重新赋值成为了2。这中间我们其实只是改变了变量a的引用,让它指向了2的内存空间。而不是在1的内空间上上将值改成了2。因此这时候1和2在内存中都是存在的。
所以这里的不可变量的意义就是该引用的指向关系不可变,但是其内存中的数据是可变的。
不知道能不能听懂,但是这种基础的概念应该都知道的。
-
字符串模板
-
在Java中我们将某个变量和固定字符串一起显示时是通过“+”来完成的,在Kotlin中,为我们带来了更加优美的实现方式。
val name = "张三" log("我的名字叫$name") 输出:我的名字叫张三
如上,当编译器发现有" " 时 , 会 将 其 识 别 为 变 量 带 入 。 还 可 以 使 用 " "时,会将其识别为变量带入。还可以使用" "时,会将其识别为变量带入。还可以使用"${}"引入一个表达式。
循环/迭代器
循环
循环语法是<item>
in
<elements>
,前者为循环项,后者为迭代体,中间用in
关键字链接。kotlin我感觉比较方便的一点在于循环语法中的item
不需要为它主动设置类型,它会根据上下文进行智能。
for(i in <elements>)
这里in
可以当做一个逻辑判断符来使用,可以用来表示某个变量在某个elements
之中,因此也会有非的情况。
if(i in 0..10)
//判断变量i在区间0-10之间
if(i !n 0..10)
//判断变量i不在0-10之间
区间
kotlin保留了Java的循环语句while/do和for
,但不同的是加入了区间的定义。
区间的本质上就是两个值之间的间隔,在kotlin中用..
表示,这里需要注意的是在这里的区间都是指的双向闭合区间,例如0..10
,按照以往的思想我们会觉得是0到9的数字,一共有10个。但实际上在这里表示的是0到10的数字,一共有11个。
因此如果需要循环n次而坐标又要从0开始,那么需要写成这样:
for(i in 0..n-1){
...
}
这样是不是感觉更麻烦了,当然我第一次也是这么觉得,但是,并不是这么简单,通常我们的循环大多都是通过数字的加减来控制的,而在kotlin中,区间不仅能够使用数字区间,还能创建字符区间。不过经过测试,字符区间的局限性还是很大的。以下是支持的区间:
- Int型
- 字符‘a’…‘z’
- 字符’A’…‘Z’
迭代器
在Java中我们要循环一个实现了迭代器的集合,通常是使用的foreach
语句,在这里我们可以直接将<elements>
替换为实现了Iterable
借口的集合对象。
for(i in list)
其中,kotlin对map类型的迭代器作了专门的优化,可以直接迭代出map里面的键值。
val map = HashMap<Int,String>()
for((key,value) in map){
log("key:$key value:$value")
}
同样的写法甚至可以用于List,有人会问List就一个value打印什么,我只能说太天真了,还有下标啊。
for((index,value) in list.withIndex()){
}
这个确实很方便,赞一个。
另外既然说到了Map,还有个特性也顺便说下,在这里要通过key
来得到某个value
有一种简便的写法:
map[key] = value
Lambda
官方也封装了对应的Lambda使用方式,以最基础的遍历为例
list.forEach{it->
it.xxxx
}
这种方式下如果需要break
、continue
就会比较麻烦一些
- break
// 不局限于run 也可用其他例如let、apply,或者直接return整个方法
run{
list.forEach{it->
if(it.xxx)return@run
}
}
- continue
list.forEach{it->
if(it.xxx)return@forEach
}
可变参数
可变参数的语法也有一些不同,主要区别在于kotlin函数内引用需要在变量名前面加上*
。
Java:
void variableParameters(String... strings) {
variableParameters(strings);
}
kotlin:
fun variableParameters(vararg strings: String) {
variableParameters(*strings)
}
逻辑判断
这里就主要说重点,在kotlin中舍弃了switch
的使用,从而带来了新的判断词when
。
-
首先,
when
可以完成switch
能完成的所有功能。when(int){ 1 -> log(1) 2 -> log(2) }
-
然后是支持支持所有数据类型。
var any: Any? = null when (any) { Int -> log("haha") String -> log("nima") }
-
并且可以当做
if
来使用,而且kotlin也推荐我们这么做。if(i==1) xxx else if(i==2) xxx else xxx //以上是传统的if/else写法 //接下来就是见证奇迹的时刻了 when(i){ 1 -> xxx 2 -> xxx else -> xxx }
-
拓展,
when
甚至不用传入参数就可以使用。fun insert(name: String, age: Int) { when { name == "张三" -> log(name) age == 10 -> log("10") name == "张三" || age == 10 -> log("name:$name age:10") } }
异常
kotlin中的异常机制应该是我目前看到的最大的败笔了。
-
所以用法和Java中差不多,由于表达式的特性,会有一些细小的差别。
var i = try { Integer.parseInt("不要在意这些细节") } catch (e: NumberFormatException) { null } finally { log("over") } log("i: $i")
-
然后就是其中的败笔了。
- 我们都知道在Java中异常是分为两类的:
运行时异常
,非运行时异常
。简单的说就是有一类异常在编码阶段,如果被编译器发现了可能会发生,那么编译器就会强制让我们使用try/catch或者throws
对该异常进行处理。然而在kotlin中却去掉了这种机制,将所有的异常都作为运行时异常,可以人为选择是否处理,编译器不会给予任何意见。 - 其解释是“基于Java中使用异常的实践做出的决定,经验显示这些规则常常导致许多毫无意义的重新抛出或者忽略异常的代码”。大概意思就是他们总结出来这样做效果更好。
- 但是这样就导致了很多问题,让我在写代码的时候随时都在担心某个地方是不是会抛出异常。
- 在我自己的理解中Java之所以定义了非运行时异常的意义和泛型的初衷相似,就是为了在编译阶段找出代码可能存在的问题并且给予解决方案。
- 我们都知道在Java中异常是分为两类的:
-
败笔说完了,那么现在不区分异常后,如果发生了异常会怎么办呢?
- 根据我个人的测试,在没有
try/catch
的情况下,当发生了某个原非运行时异常,程序不会报错,而且直接打印了异常log,目测是直接抛给了虚拟机处理。 - 其导致的效果就是发生异常之后的代码都没有执行,而且在我没有看log之前完全不知道发生了什么,在哪里发生的。发现之后还要自己傻逼的手写上对应的
try/catch
。总之这点非常反人类。
- 根据我个人的测试,在没有
-
写在最后,对于异常这个机制,从理性上来说,确实这样可以节约很多不必要的代码量,而且看上去代码也会更简洁,但是在一些不可避免
try/catch
的地方会增加无意义的工作量。因此如果有大神能开发个让编译器提示非运行时异常却不会强制让程序员处理的插件就太完美了。