Kotlin的程序结构
变量与常量
变量
Kotlin中,用var代表一个变量,意思为variable:
//用var表示一个变量,变量可以再次赋值
var str = "haha"
str = "enen"
println(str)
Tips:编译器会对类型进行推荐
常量
Kotlin中,用val代表一个常量,意思为value,也叫值类型,例如:
//常量,不可变
val NAME = "lubaobao"
那么这样的常量与Java中的final有什么区别呢?
下面先来看Java的代码:
public class TestJava {
public static final String NAME = "lubaobao";
public static void main(String[] args) {
//使用final常量的时候,会自动把常量替换成它的值
String name = NAME;
}
}
- Java中的final类型是编译期常量,也就是说在编译的时候就能确定值,而在使用final常量的时候,会自动把常量替换成它的值。
- 而Kotlin中的val不是编译期常量,编译期的时候不能确定值,在运行的时候才可以确定。所以说在使用的时候,不是像Java一样替换成值的。因此还是可以通过反射等手段去修改val常量的值。
如果想要在Kotlin中定义编译期常量的话,就需要使用const关键字了:
//编译期常量,并且不能在方法体中定义
const val NAME = "lubaobao"
函数
函数的基本定义
最基本的方式:
fun sum(a:Int,b:Int):Int{
return a + b
}
省略版(编译器会对返回值进行推导):
fun toLong(a: Int) = a.toLong()
用val去接收一个匿名函数(使用的时候与一般的函数使用一样):
val toLong = fun(a: Int):Long{
return a.toLong()
}
val toLong = fun(a: Int) = a.toLong()
函数的注意事项
- 功能单一
- 函数名要顾名思义
- 参数个数不要太多
Lambda表达式
函数可以是匿名函数,因此Lambda正是利用了这一点。
例子1,Lambda表达式的定义:
//有参数,有函数体,最后一行作为返回值
val sum = { a: Int, b: Int ->
println("a=$a,b=$b")
a + b
}
//无参数,大部分可省略
val printHello = {
println("hello")
}
println(sum(1, 2))
printHello()
括号是相当于对变量的某个方法的调用,也就是相当于:
printHello.invoke()
Tips:invoke是运算符重载的方法。
例子2,使用Lambda表达式进行数组的遍历:
//用Lambda表达式进行遍历
arr.forEach {
println(it)
}
//参数重命名
arr.forEach { element ->
println(element)
}
Tips:Lambda只有一个参数的话,默认就叫做it,就是Itertor的意思。
forEach的源码如下:
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
for (element in this) action(element)
}
forEach是IntArray的一个扩展方法,参数是一个Lambda表达式,这个Lambda表达式的参数是Int,返回值是Unit(类似于Java中的void)。
Lambda表达式的简化过程
以我们对函数调用的理解,最完整的写法如下:
arr.forEach({ element ->
println(element)
})
但是,当一个函数的最后一个参数Lambda表达式的时候,可以把()提前(注意:这里我们顺便把表达式的参数名省略了):
arr.forEach(){
println(it)
}
然后我们发现,如果Lambda参数前面没有任何参数,那么()根本没用用处,那么也可以省略:
arr.forEach{
println(it)
}
最后,如果我们传入的函数与Lambda接收返回的类型一样,我们可以进一步省略成引用(Reference)的形式,用两个冒号来引用println参数(但是参数只有一个,那么参数也可以省略),如果是Lambda表达式,可以直接传入,如果是非匿名函数,那么需要加上两个冒号:
arr.forEach(::println)
即入参与形参一致的函数可以用函数引用的方式作为实参的传入。
如果我们想要在Lambda表达式中return的话就需要注意了,Lambda表达式中return的话返回的是所在的函数(因为Lambda说白了是一段代码块):
fun main(args: Array) {
val arr = intArrayOf(1, 2, 3, 4, 5)
arr.forEach {
if (it == 4) {
//这里直接把main方法返回了
return
}
println(it)
}
println("这句话不会被执行")
}
正确的返回是添加标签,返回的是标签,例如我们添加一个自定义的标签ForEach,然后返回的时候声明返回的是ForEach标签:
fun main(args: Array) {
val arr = intArrayOf(1, 2, 3, 4, 5)
arr.forEach ForEach@{
if (it == 4) {
return@ForEach
}
println(it)
}
println("这句话会被执行")
}
函数的类型
例如:
() -> Unit
(Int) -> Int
//参数是String、Lambda表达式,返回值是Bool额按
(String, (String)->String) -> Boolean
Kotlin中为我们定义了FunctionX(0~22,一共23种)的类型的接口,用来代表Lambda表达式的类型,例如:
public interface Function2 : Function {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2): R
}
运算符
Kotlin中,所有的运算符本质上是一个函数。都有对应的关系,比如说:
+ -- plus
in -- contains
运算符重载,例如我们可以重载+号:
class Point(var x: Int, var y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
override fun toString(): String {
return "$x,$y"
}
}
fun main(args: Array) {
var p1 = Point(1, 2)
var p2 = Point(2, 3)
var sum = p1 + p2
println(sum)
}
- 任何类都可以定义或者重载父类的基本运算符运算符
- 运算符的定义通过具体的函数名字来定义
- 运算符的定义,参数只能有1个,但是参数类型以及是否返回或者返回值类型不作要求
中缀表达式
当然我们也可以通过使用中缀表达式,定义其他运算符,例如:
class Desk
class Book {
infix fun on(desk: Desk): Boolean {
return false
}
}
fun main(args: Array) {
if (Book() on Desk()) {
}
}
中缀表达式只支持一个参数的函数,这样的话在使用函数的时候,就可以省略一般的.以及()了。
Tips:这种方式在DSL中用得比较多,一般的开发不推荐使用,因为会使得代码可读性变差。
分支表达式
if语句可以有返回值,那就是分支表达式,内部我们需要传入Lambda表达式即可:
//注意这里可以使用val
val i = if (args[0].toInt() == 1) {
println("1")
1
} else {
println("2")
2
}
- 分支表达式需要使用Lambda表达式
- 使用返回值的时候,需要有完备的分支(IDE会提示并且编译不通过)
when表达式
when表达式是Java的switch表达式的增强版:
fun main(args: Array) {
val x = 5
val i = when (x) {
is Int -> {
println("哈哈")
1
}
in 1..100 -> 2
else -> 3
}
}
- when表达式实现switch的任意功能
- when表达式可以有返回值
- 与分支表达式一样,when表达式也是需要使用Lambda表达式
循环语句
for循环
val arr = 1..5
for (i in arr) {
println(i)
}
for ((index, value) in arr.withIndex()) {
println("$index->$value")
}
for (indexValue in arr.withIndex()) {
println("${indexValue.index}->${indexValue.value}")
}
arr.withIndex()的实现如下:
public fun Iterable.withIndex(): Iterable> {
return IndexingIterable { iterator() }
}
实际上是每一次返回了一个IndexedValue,而IndexedValue是一个data class,因此可以写成第二种形式:
public data class IndexedValue(public val index: Int, public val value: T)
for循环中in的本质是Iterable的iterator方法,返回的是一个Iterator:
public interface Iterable {
/**
* Returns an iterator over the elements of this object.
*/
public operator fun iterator(): Iterator
}
其中Iterator包含一个next与hasNext方法,分别用于判断下一个元素以及是否还有下一个元素:
public interface Iterator {
/**
* Returns the next element in the iteration.
*/
public operator fun next(): T
/**
* Returns `true` if the iteration has more elements.
*/
public operator fun hasNext(): Boolean
}
while循环
使用方法与一般的语言一样:
while () {
}
do {
} while (){
}
异常捕获
基本使用与Java一样:
try {
val x = 1 / 0
} catch (e: Exception) {
e.printStackTrace()
}finally {
}
try/catch也是一个表达式:
val x = try {
1 / 0
} catch (e: Exception) {
0
} finally {
1
}
//输出的是0
println(x)
try/catch可以有返回值,没有抛出异常则返回try的返回值,否则返回catch的返回值,但是要先执行finally的代码,才返回。
具名参数、可变长参数、默认参数
例子代码如下:
fun main(args: Array) {
val arr = intArrayOf(1, 2, 3, 4, 5)
test(i = 1, arr = *arr)
}
fun test(i: Int, vararg arr: Int, d: Double = 5.0) {
}
- 具名参数就是在函数调用的时候指定函数的形参。
- 具名参数可以解决可变参数的位置问题,在Java中可变参数只能是最后一个,但是Kotlin中不需要。反正唯一的标准就是不能有歧义。
- 默认参数就是函数定义的时候指定参数的默认值。
- *arr叫做Spread Operator,作用是把Array展开然后作为可变参数的实参,Spread Operator不能重载。
导出可执行程序
在build.gradle中添加:
apply plugin: 'java'
mainClassName = "xxxxx"
然后同步,同步完成之后选择gradle面板,选择Tasks->distribution->installDist,最终会在build->install->项目名->bin生成可执行程序,我们只需要先给脚本添加可执行权限,然后就可以执行了。
如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:
我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)。