重拾Kotlin-1. 基础

317 阅读7分钟

包的定义与导入:

  • 定义: 包的声明应处于源⽂件顶部
package com.example.ljy.kotlin001
  • 包的导入
import java.math.BigDecimal
  • 如果出现名字冲突,可以使⽤ as 关键字在本地重命名冲突项来消歧义:
// Message 可访问
import android.os.Message
// NotifiMessage 代表“android.app.Notification.MessagingStyle.Message”
import android.app.Notification.MessagingStyle.Message as NotifiMessage

程序⼊⼝点

fun main() {
    println("Hello world!")
}

注释

 // 这是一个单行注释
 
 /* 
 这是一个多行的 /*abc*/块注释。
 与 Java 不同, Kotlin 中的块注释允许嵌套
 */

变量与常量

  1. 声明:var/val关键字 变量名 :变量类型
    • 类型可以省略, 语句结尾分号可省略
var aaa: Int=0;
val bbb =1
  1. 可变变量定义:var 关键字
 var x = 5  // 系统自动推断变量类型为Int
 x += 1     // 变量可修改
  1. 不可变变量定义:val 关键字,只能赋值一次的变量
    • 类似Java中final修饰的变量
    • 优先使⽤不可变数据
val a: Int = 0
val b = 1       // 系统自动推断变量类型为Int
val c: Int      // 如果不在声明时初始化则必须提供变量类型
c = 2

数字:

  • 常用类型: Byte,Short,Int,Long,Float,Double
  • 常用进制
//十进制: 
var num1=123
//十六进制: 
var num2=0x2f
//二进制: 
var num3=0b00001011
//不支持八进制
  • 可以使⽤下划线使数字常量更易读:
val oneMillion = 1_000_000
  • 与⼀些其他语⾔不同,Kotlin 中的数字没有隐式拓宽转换。 例如,具有 Double 参数的函数只能对 Double 值调⽤,⽽不能对 Float 、 Int 或者其他数字值调⽤
 fun printDouble(d: Double) {
     println(d)
 }

    val n1 = 1
    val n2 = 1.1
    val n3 = 1.1f
    printDouble(n2)
//        printDouble(n1)//错误:类型不匹配
//        printDouble(n3)//错误:类型不匹配
  • 数字装箱不⼀定保留同⼀性,保留了相等性
val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
val b: Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
//数字装箱不⼀定保留同⼀性:
//===类似于equals
println(boxedA === anotherBoxedA) // true
println(boxedB === anotherBoxedB) // false
//保留了相等性:
println(b == b) // true
println(boxedB == anotherBoxedB) // true
  • 显式转换
// 假想的代码,实际上并不能编译:(较⼩类型并不是较⼤类型的⼦类型)
val a1: Int? = 1 // ⼀个装箱的 Int (java.lang.Integer)
// val b1: Long? = a1 // 隐式转换产⽣⼀个装箱的 Long (java.lang.Long)
//  print(b1 == a1) // 惊!这将输出“false”鉴于 Long 的 equals() 会检测另⼀个是否也为 Long
//可以显式转换来拓宽数字
val b1: Long? = a1?.toLong()
println(b1?.equals(a1) ?: (a1 == null))
  • 整数间的除法总是返回整数
val x1 = 5 / 2
println("x1 == 2:${x1 == 2}")
//如需返回浮点类型,请将其中的⼀个参数显式转换为浮点类型
val x2 = 5 / 2.toDouble()
println("x2 == 2.5:${x2 == 2.5}")
val x3 = 5.toDouble() / 2
println("x3 == 2.5:${x3 == 2.5}")
  • 位运算:对于位运算,没有特殊字符来表⽰,⽽只可⽤中缀⽅式调⽤具名函数
 val x = (1 shl 2) and 0x000FF000
//完整的位运算列表(只⽤于 Int 与 Long):
//shl(bits) ‒ 有符号左移
//shr(bits) ‒ 有符号右移
//ushr(bits) ‒ ⽆符号右移
//and(bits) ‒ 位与
//or(bits) ‒ 位或
//xor(bits) ‒ 位异或
//inv() ‒ 位⾮
  • NaN
与其⾃⾝相等
println("Float.NaN== Float.NaN:${Float.NaN == Float.NaN}")
println("Float.NaN=== Float.NaN:${Float.NaN === Float.NaN}")
println("Float.NaN.equals( Float.NaN):${Float.NaN.equals(Float.NaN)}")

println("Float.NaN - Float.POSITIVE_INFINITY: ${Float.NaN - Float.POSITIVE_INFINITY}")
//-0.0 ⼩于 0.0
println("-0.0 ⼩于 0.0:${-0.0 < 0.0}")
  • 数组
数组在 Kotlin 中使⽤ Array 类来表⽰,它定义了 get 与 set 函数
(按照运算符重载约定这会转变为 [])以及size 属性,以及⼀些其他有⽤的成员函数
//使⽤库函数 arrayOf() 来创建⼀个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array
//[1, 2, 3] 。 或者,库函数 arrayOfNulls() 可以⽤于创建⼀个指定⼤⼩的、所有元素都为空的数组
//另⼀个选项是⽤接受数组⼤⼩以及⼀个函数参数的 Array 构造函数,⽤作参数的函数能够返回给定索引的每个元素 初始值:
// 创建⼀个 Array<String> 初始化为 ["0", "1", "4", "9", "16"]
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { println(it) }
//原⽣类型数组: Kotlin 也有⽆装箱开销的专⻔的类来表⽰原⽣类型数组: ByteArray 、 ShortArray 、IntArray 等等。这些类与
//Array 并没有继承关系,但是它们有同样的⽅法属性集。它们也都有相应的⼯⼚⽅法
val aar: IntArray = intArrayOf(1, 2, 3)
aar[0] = aar[1] + aar[2]
// ⼤⼩为 5、值为 [0, 0, 0, 0, 0] 的整型数组
val arr1 = IntArray(5)
// 例如:⽤常量初始化数组中的值
// ⼤⼩为 5、值为 [42, 42, 42, 42, 42] 的整型数组
val arr2 = IntArray(5) { 42 }
// 例如:使⽤ lambda 表达式初始化数组中的值
// ⼤⼩为 5、值为 [0, 1, 2, 3, 4] 的整型数组(值初始化为其索引值)
var arr3 = IntArray(5) { it * 1 }
  • 无符号整型
//⽆符号整型:
//kotlin.UByte : ⽆符号 8 ⽐特整数,范围是 0 到 255
//kotlin.UShort : ⽆符号 16 ⽐特整数,范围是 0 到 65535
//kotlin.UInt : ⽆符号 32 ⽐特整数,范围是 0 到 2^32 - 1
//kotlin.ULong : ⽆符号 64 ⽐特整数,范围是 0 到 2^64 - 1

//kotlin.UByteArray : ⽆符号字节数组
//kotlin.UShortArray : ⽆符号短整型数组
//kotlin.UIntArray : ⽆符号整型数组
//kotlin.ULongArray : ⽆符号⻓整型数组
//与有符号整型数组⼀样,它们提供了类似于 Array 类的 API ⽽没有装箱开销

//字⾯值:为使⽆符号整型更易于使⽤,Kotlin 提供了⽤后缀标记整型字⾯值来表⽰指定⽆符号类型(类似于 Float/Long)
// 后缀 u 与 U 将字⾯值标记为⽆符号
val b: UByte = 1u // UByte,已提供预期类型
val s: UShort = 1u // UShort,已提供预期类型
val l: ULong = 1u // ULong,已提供预期类型
val a1 = 42u // UInt:未提供预期类型,常量适于 UInt
val a2 = 0xFFFF_FFFF_FFFFu // ULong:未提供预期类型,常量不适于 UInt
//后缀 uL 与 UL 显式将字⾯值标记为⽆符号⻓整型。
val a = 1UL // ULong,即使未提供预期类型并且常量适于 UInt

字符串模版

  • 优先使⽤字符串模板⽽不是字符串拼接
  • 优先使⽤多⾏字符串⽽不是将 \n 转义序列嵌⼊到常规字符串字⾯值中
  1. 模版中的简单名称(省略花括号)
var num = 101
val s1 = "num is $num"
println(s1)
  1. 模版中的任意表达式
num = 102
val s2 = "${s1.replace("is", "was")},but now is $num"
println(s2)
  1. 多行字符串
//如需在多⾏字符串中维护缩进,
// 当⽣成的字符串不需要任何内部缩进时使⽤ trimIndent,
// ⽽需要内部缩进时使⽤trimMargin :
val a1 =
    """
        Foo
        Bar
    """.trimIndent()
println("a1:\n$a1")

val a2 = """if(a > 1) {
        |   return a
        |}""".trimMargin()
println("a2:\n$a2")
  1. 字符串的元素⸺字符可以使⽤索引运算符访问
for (c in s1) {
    println(c)
}
  1. 字符串字⾯值:(两种)
//1. 转义字符串: 可以有转义字符
val s = "Hello, world!\n"
//2. 原始字符串: 使⽤三个引号( """ )分界符括起来
val text = """
    for (c in "foo")
    print(c)
"""

条件表达式和循环表达式

  • if判断
if (num > 0) {
    println(num)
} else {
    println("num < 0")
}

val data = mapOf("name" to "ljy", "email" to null)
// if null 缩写 ?:
data["name"] ?: println("name is null")
data["email"] ?: println("email is null")
// if not null 缩写 ?.
println("name len = ${data["name"]?.length}")
println("email len = ${data["email"]?.length}")
// if not null and else 缩写 ?. :
println(data["name"]?.length ?: "empty")
println(data["email"]?.length ?: "empty")

// 使用if作为表达式
//在 Kotlin 中,if是⼀个表达式,即它会返回⼀个值。
//因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if 就能胜任这个⻆⾊。
fun maxOf(a: Int, b: Int) = if (a > b) a else b
val num = maxOf(a, b)

 //if 的分⽀可以是代码块,最后的表达式作为该块的值
 //如果你使⽤ if 作为表达式⽽不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有 else 分⽀
val max1 = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}
  • when表达式
fun describe(obj: Any) =
    when (obj) {
        1 -> "one"
        "Hello" -> "greeting"
        //如果很多分⽀需要⽤相同的⽅式处理,可以把多个分⽀条件放在⼀起,⽤逗号分隔
        0, 2 -> "obj == 0 or obj == 2"
        //在不在⼀个区间或集合中
        in 1..10 -> "obj is in the range"
        is Long -> "Long"
        !is String -> "not a string"
        else -> "Unknown"
    }

println("-->${describe(1)}")
println("-->${describe("Hello")}")
println("-->${describe(1L)}")
println("-->${describe(321)}")
println("-->${describe("abc")}")
  • for循环
val items = listOf("a", "ca", "ac", "bb", "ab", "ba", "c")
//for 循环可以对任何提供迭代器(iterator)的对象进⾏遍历,相当于foreach
for (item in items)
    println("for-->$item")
//如果你想要通过索引遍历⼀个数组或者⼀个 list:
for (index in items.indices)
    println("for-->item at $index is ${items[index]}")
//或者⽤库函数 withIndex :
for ((index, value) in items.withIndex()) {
    println("the element at $index is $value")
}
//可以⽤标签限制 break 或者continue:
loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (i + j == 200) break@loop
    }
}
//返回到标签: Kotlin 有函数字⾯量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层 函数返回
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // ⾮局部直接返回到 foo() 的调⽤者
        print(it)
    }
    println("this point is unreachable")
}

fun foo2() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // 局部返回到该 lambda 表达式的调⽤者,即 forEach 循环
        print(it)
    }
    print(" done with explicit label")
}

//通常情况下使⽤隐式标签更⽅便。 该标签与接受该 lambda 的函数同名
fun foo3() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // 局部返回到该 lambda 表达式的调⽤者,即 forEach 循环
        print(it)
    }
    print(" done with implicit label")
}

//或者,我们⽤⼀个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数⾃⾝返回
fun foo4() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) return // 局部返回到匿名函数的调⽤者,即 forEach 循环
        print(value)
    })
    print(" done with anonymous function")
}

// 请注意,前⽂三个⽰例中使⽤的局部返回类似于在常规循环中使⽤ continue。并没有 break 的直接等价形式,不过
//可以通过增加另⼀层嵌套 lambda 表达式并从其中⾮局部返回来模拟:
fun foo5() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop // 从传⼊ run 的 lambda 表达式⾮局部返回
            print(it)
        }
    }
    print(" done with nested loop")
}
//当要返⼀个回值的时候,解析器优先选⽤标签限制的 return,即
//return@a 1
//意为“返回 1 到 @a ”,⽽不是“返回⼀个标签标注的表达式 (@a 1) ”。
  • while循环
var index = 0
while (index < items.size) {
    println("while-->item at $index is ${items[index]}")
    index++
}
var index2 = 0
do {
    println("while-->item at $index2 is ${items[index2]}")
    index++
} while (index2 < items.size)

使用区间(range)

 //- 1.区间外
num = 100
if (num !in 0..10) {
    println("num 不在010范围内")
}
//- 2.区间内
val max = 100
if (num in 1..max + 1) {//闭区间:包含101
    println("num 在1101范围内")
}
if (num in 1 until max) {//半开区间:不包含 100
    println("num 在199范围内")
} else {
    println("num 不 在199范围内")
}
//for (i in 0..n - 1) { /*……*/ } // 不良
//for (i in 0 until n) { /*……*/ } // 良好

//- 3.区间迭代
for (it in 1..10) {
    println("x = $it")
}

//- 4.数列迭代 (步长)
for (y in 1..10 step 2)
    println("y = $y")
for (z in 9 downTo 0 step 3)
    println("z = $z")

其他

  • 可空值和null检测
 fun printNums(vararg numArr: Int, userName: String?) {
    //上面str: String?中的?表示可为空
    for (n in numArr) {
        println("numArr-->$n")
    }
    if (userName != null)//null检测
        println("userName--->$userName")
 }
 
  printNums(num, userName = null)
  • 使用类型检测及自动类型转换
fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // `obj` 在该条件分支内自动转换成 `String`
        return obj.length
    }
    return null
}

val len = getStringLength(s1)
println("$s1<--len-->$len")
  • 类型别名:
//如果有⼀个在代码库中多次⽤到的函数类型或者带有类型参数的类型,
// 那么最好为它定义⼀个类型别名:
typealias MouseClickHandler = (Any, MouseEvent) -> Unit
typealias PersonIndex = Map<String, Person>
  • 延迟属性
//相当于:if(p==null) { p=1+2+3+4+5 }, 且是第一次用到时才加载
val p: Int by lazy {
    1 + 2 + 3 + 4 + 5
}
println("p --> $p")
  • 扩展函数
 /**
 * 放⼿去⽤扩展函数
 * 每当你有⼀个主要⽤于某个对象的函数时,可以考虑使其成为⼀个以该对象为接收者的扩展函数。
 * 为了尽量减少 API 污染,尽可能地限制扩展函数的可⻅性
 * 下例为给String类添加一个扩展函数add
 */
fun String.add(s: String): String {
    return "$this$s"
}

println("abc".add("def"))
  • 创建单例
 /**
 * 创建单例
 */
object StrConfig {
    //经过const修饰的常量,才是java中理解的常量
    const val name = "Mr.L"
    var s = "str"
}

println(StrConfig.name)
println(StrConfig.s)
  • try/cache表达式
fun test(): Int {
    var result = try {
        12 / 0
    } catch (e: Exception) {
        -1
    }
    return result
}

println("result=${test()}")
  • 对一个对象实例调用多个方法(with)
//1.1 创建一个类:见Person.kt
/**
 * 创建 DTOs(POJOs/POCOs)
 * 会为 Person 类提供以下功能:
 * — 所有属性的 getters(对于 var 定义的还有 setters)
 * — equals()
 * — hashCode()
 * — toString()
 * — copy()
 * — 所有属性的 component1() 、component2() ......等等(参⻅数据类)
 */
data class Person(val name: String, var email: String)
//1.2 添加一个扩展函数
fun Person.sayHi() {
    println("$name say: Hello World")
}

 //2.创建类对象
val person = Person("ljy", "123456@qq.com")
//3.with调用一个对象对多个方法
with(person)
{
    val s = toString()
    println(s)
    hashCode()
    println(name)
    println(email)
    sayHi()
}

person.email = "aaa"
//        person.name="aaa"
  • 对于需要泛型信息的泛型函数的适宜形式
//1. java
  public final class Gson {
 ......
 public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
 ......
//2. kotlin
inline fun <reified T: Any> Gson.fromJson(json: JsonElement): T
        = this.fromJson(json, T::class.java)
  • 使用可空布尔
val bol: Boolean? = null
if (bol == true) {
    println("bol = true")
} else {
    println("`bol` 是 false 或者 null ")
}
  • TODO():将代码标记为不完整
/** Kotlin 的标准库有⼀个 TODO() 函数,该函数总是抛出⼀个 NotImplementedError 。 其返回类型为 Nothing,
 * 因此⽆论预期类型是什么都可以使⽤它。 还有⼀个接受原因参数的重载:
 */
fun testTODO(): BigDecimal = TODO("waiting for feedback from accounting")
  • 注解格式化
 //注解通常放在单独的⾏上,在它们所依附的声明之前,并使⽤相同的缩进:
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude

//⽆参数的注解可以放在同⼀⾏:
@JsonExclude @JvmField
var x: String

//⽆参数的单个注解可以与相应的声明放在同⼀⾏:
@Test fun foo() { /*……*/ }

//⽂件注解: ⽂件注解位于⽂件注释(如果有的话)之后、package 语句之前,
// 并且⽤⼀个空⽩⾏与 package 分开(为了强调其针 对⽂件⽽不是包)。
///** 授权许可、版权以及任何其他内容 */
@file:JvmName("FooBar")

package foo.bar
我是今阳,如果想要进阶和了解更多的干货,欢迎关注公众号”今阳说“接收我的最新文章