美文网首页Kotlin从入门到放弃
Kotlin 之旅3--程序结构

Kotlin 之旅3--程序结构

作者: 小楠总 | 来源:发表于2017-07-21 10:51 被阅读224次

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<String>) {

    val arr = intArrayOf(1, 2, 3, 4, 5)

    arr.forEach {
        if (it == 4) {
            //这里直接把main方法返回了
            return
        }
        println(it)
    }

    println("这句话不会被执行")
}

正确的返回是添加标签,返回的是标签,例如我们添加一个自定义的标签ForEach,然后返回的时候声明返回的是ForEach标签:

fun main(args: Array<String>) {

    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<in P1, in P2, out R> : Function<R> {
    /** 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<String>) {

    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<String>) {

    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<String>) {

    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 <T> Iterable<T>.withIndex(): Iterable<IndexedValue<T>> {
    return IndexingIterable { iterator() }
}

实际上是每一次返回了一个IndexedValue,而IndexedValue是一个data class,因此可以写成第二种形式:

public data class IndexedValue<out T>(public val index: Int, public val value: T)

for循环中in的本质是Iterable的iterator方法,返回的是一个Iterator:

public interface Iterable<out T> {
    /**
     * Returns an iterator over the elements of this object.
     */
    public operator fun iterator(): Iterator<T>
}

其中Iterator包含一个next与hasNext方法,分别用于判断下一个元素以及是否还有下一个元素:

public interface Iterator<out T> {
    /**
     * 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<String>) {
    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生成可执行程序,我们只需要先给脚本添加可执行权限,然后就可以执行了。

如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:

公众号:Android开发进阶

我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

相关文章

网友评论

    本文标题:Kotlin 之旅3--程序结构

    本文链接:https://www.haomeiwen.com/subject/azwskxtx.html