美文网首页Kotlin
Kotlin 入门(一):基本语法

Kotlin 入门(一):基本语法

作者: 孤独的根号十二 | 来源:发表于2019-02-02 10:49 被阅读64次

    前言:

    在android studio中已经支持kotlin,只需要在android studio中下载kotlin的插件,就可以使用kotlin进行app的开发了,本系列主要讲一些kotlin的语法规范,案例及使用场景。

    基本类型

    在 Kotlin 中,所有东西都是对象,在这个意义上讲所以我们可以在任何变量上调用成员函数和属性。有些类型是内置的,因为他们的实现是优化过的。但是用户看起来他们就像普通的类

    数字

    Kotlin 处理数字在某种程度上接近 Java,但是并不完全相同。例如,对于数字没有隐式拓宽转换(如 Java 中 int 可以隐式转换为 long)

    Kotlin 提供了如下的内置类型来表示数字(与 Java 很相近):

    kotlin内置数字.png
    注意在 Kotlin 中字符不是数字,kotlin支持十进制(123),十六进制(0x0F),二进制(0b00001011),不支持八进制

    从kotlin1.1起,支持使用下划线使数字常量更易读

    val oneMillion = 1_000_000
    val creditCardNumber = 1234_5678_9012_3456L
    val socialSecurityNumber = 999_99_9999L
    val hexBytes = 0xFF_EC_DE_5E
    val bytes = 0b11010010_01101001_10010100_10010010
    

    在 Java 平台数字是物理存储为 JVM 的原生类型,除非我们需要一个可空的引用(如 Int? )或泛型。 后者情况下会把数字装箱
    注意数字装箱不必保留同一性:

    val a:  Int =   10000
     print(a    === a)  //  输出“true” 
    val boxedA: Int?    =   a 
    val anotherBoxedA:  Int?    =   a 
    print(boxedA    === anotherBoxedA)  //  !!!输出“false”!!!
    
    

    另一方面,它保留了相等性:

    val a:  Int =   10000 
    print(a ==  a)  //  输出“true” 
    val boxedA: Int?    =   a 
    val anotherBoxedA:  Int?    =   a 
    print(boxedA    ==  anotherBoxedA)  //  输出“true”
    

    因此较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能 把 Byte 型值赋给一个 Int 变量。

    val b:  Byte    =   1   //  OK, 字面值是静态检测的
     val    i:  Int =   b   //  错误
    
    

    我们可以显式转换来拓宽数字

    val i:  Int =   b.toInt()   //  OK: 显式拓宽
    

    基本语法

    1.包的声明

    和java包的生命相同,都处于源文件顶部

    package abc.demo
    import java.util.*
    import net.println.kotlin.simple.A  as B //给A类起别名叫B
    

    2.定义函数

    使用关键字fun,返回写在函数声明的右边,以连接,且kotlin的函数声明和java有些不同,kotlin可以将表达式作为函数体、返回值类型自动推断的函数,例如下面定义sum函数两种方式结果是一样的

    //普通的写法
    fun sum(a: Int, b: Int): Int {
    return a + b
    }
    
    //简写
    fun sum(a: Int, b: Int) = a + b
    
    //kotlin的main函数
    fun main(args: Array<String>) {
    print("sum of 3 and 5 is ")
    println(sum(3, 5))
    }
    

    kotlin函数没有返回值,可以返回Unit,相当于java的void,也可以省略不写

    fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
    }
    //不写Unit
    fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
    }
    

    kotlin支持函数的默认参数

    fun foo(a: Int = 0, b: String = "") { …… }
    

    3.定义常量/变量

    kotlin提供两种定义变量的方式,分别使用关键字valvar,val定义只读常量,相当于java中使用final修饰的属性,var定义变量

    fun main(args: Array<String>) {
    //val
    val a: Int = 1 // 立即赋值
    val b = 2 // 自动推断出 `Int` 类型
    val c: Int // 如果没有初始值类型不能省略
    c = 3 // 明确赋值
    
    //var
    var x = 5 // 自动推断出 `Int` 类型
    x += 1
    }
    

    4.关于注释

    与java相同,唯一的区别在于,Kotlin 的块注释可以嵌套

    5.字符串模板

    kotlin提供了相对java更灵活的字符串处理方式,字符串模版,在字符串中使用$a符直接输出字符串a的值,如果是表达式,可以使用${a+1}的方式

    val a=10
    val s1 = "a is $a"
    val s2="a+1 is ${a+1}"
    println(s1)
    println(s2)
    

    6.条件表达式

    基本使用与java相同,不同在于,kotlin的条件表达式可以充当返回值,例如:

    fun maxOf(a: Int, b: Int) = if (a > b) a else b
    

    7.可空类型检测

    kotlin可以大幅度减少空指针错误的发生,当某个变量的值可以为 null 的时候,必须在声明处的类型后添加 ? 来标识该引用可为
    空,不为null的时候,才会调用对象的方法

    val student:Student?=null
    
    student=Student("xiao ming")
    
    val name:String=student?.name?:""
    //相当于
    val name:String=if(student!=null){
    student.name
    }else{
    ""
    }
    
    //可以用于函数返回值
    fun parseInt(str: String): Int? {
    // ……
    }
    

    8.类型检测及自动类型转换

    is 运算符检测一个表达式是否某类型的一个实例。 如果一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换

    //Any相当于java的Object
    fun getStringLength(obj: Any): Int? {
    if (obj is String) {
    // `obj` 在该条件分支内自动转换成 `String`
    return obj.length
    }
    // 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
    return null
    }
    

    9.循环结构

    for循环
    fun main(args: Array<String>) {
    val items = listOf("apple", "banana", "kiwi")  //kotlin的一个函数
    val map=mapOf("a"  to "A", "b" to "B","c" to "C") //to 只是用来定义map的键值对
    for (item in items) {    //in是kotlin的一个关键字,用来检测某个数字是否在指定区间内
    println(item)
    }
    //遍历map的key和value,k  、 v  可以改成任意名字,后面会讲到原理
    for ((k, v) in map) {
    println("$k -> $v")
    }
    
    }
    

    Break 和 Continue 标签:

    loop@ for(i in  1..100) {       
            for (j  in  1..100) {   
                if  (……)    break@loop          
        } 
    }
    
    
    while循环
    fun main(args: Array<String>) {
    val items = listOf("apple", "banana", "kiwi")
    var index = 0
    while (index < items.size) {
    println("item at $index is ${items[index]}")
    index++
    }
    }
    

    10.when分支结构表达式

    与java中的switch case类似,不同点在于when中可以是任意类型参数,java中只能是整数类型,而且when中内容可以省略,判断放在分支中,比java更加灵活而且when表达式跟if表达式一样,可以直接当返回值

    fun describe(obj: Any): String =
    when (obj) {
    1 -> "One"
    "Hello" -> "Greeting"
    is Long -> "Long"
    !is String -> "Not a string"
    else -> "Unknown"
    }
    
    fun main(args: Array<String>) {
    println(describe(1))
    println(describe("Hello"))
    println(describe(1000L))
    println(describe(2))
    println(describe("other"))
    }
    

    is表达式用在分支结构

    when (x) {
    is Foo //-> ……
    is Bar //-> ……
    else //-> ……
    }
    

    11.区间(range)

    上面在for循环的时候用到过in关键字,它的作用是检测某个数字是否在指定区内,后面通常跟一个具体的容器类,或者和..,until,搭配使用
    检测某个数字是否在指定区间:

    fun main(args: Array<String>) {
    val x = 10
    val y = 9
    if (x in 1..y+1) {
    println("fits in range $x")
    }
    }
    

    区间迭代:

    fun main(args: Array<String>) {
    for (x in 1..5) {
    print(x)
    }
    }
    

    数列迭代:

    fun main(args: Array<String>) {
    for (x in 1..10 step 2) {   //step用于指定步长
    print(x)
    }
    for (x in 9 downTo 0 step 3) {  //downTo  用于指定递减
    print(x)
    }
    }
    

    在分支结构中使用in:

    fun main(args: Array<String>) {
    val items = setOf("apple", "banana", "kiwi")
    when {  //when中内容省略了
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
    }
    }
    

    until:与..类似,但是..表示闭区间,而until表示左闭右开区间

    for (i in 1 until 100) { …… }// 半开区间:不包含 100
    

    12.“try/catch”表达式

    fun test() {
    val result = try {
    count()
    } catch (e: ArithmeticException) {
    throw IllegalStateException(e)
    }
    }
    

    13.运算

    Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相 应的指令).
    对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数,例如:

    val x   =   (1  shl 2)  and 0x000FF000
    

    这是完整的位运算列表(只用于 Int 和 Long ):
    shl(bits) – 有符号左移 (Java 的 << )
    shr(bits) – 有符号右移 (Java 的 >> )
    ushr(bits) – 无符号右移 (Java 的 >>> )
    and(bits) – 位与
    or(bits) – 位或
    xor(bits) – 位异或
    inv() – 位非

    14.字符

    字符用 Char 类型表示。它们不能直接当作数字

    fun check(c:    Char)   {           
        if  (c  ==  1)  {   //  错误:类型不兼容                                //  ……              
    } }
    
    

    字符字面值用单引号括起来: '1' 。 特殊字符可以用反斜杠转义。 支持这几个转义序 列: \t 、 \b 、 \n 、 \r 、 ' 、 " 、 \ 和 $ 。 编码其他字符要用 Unicode 转义序 列语法: '\uFF00'
    我们可以显式把字符转换为 Int 数字:

    fun decimalDigitValue(c:    Char):  Int {   
                if  (c  !in '0'..'9')   
        throwIllegalArgumentException("Out  of  range")         
        return  c.toInt()   -   '0'.toInt() //  显式转换为数字
     }
    
    

    当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。

    15.数组

    数组在 Kotlin 中使用 Array 类来表示,我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 或者,库函数 arrayOfNulls() 可以用于创建一个指定大小、元素都 为空的数组。
    注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。这意味着 Kotlin 不让我们把 Array<String> 赋值给 Array<Any> ,以防止可能的运行时失败.
    Kotlin 也有无装箱开销的专门的类来表示原生类型数组: ByteArray 、 ShortArray 、 IntArray 等等。这些类和 Array 并没有继承关系,但是 它们有同样的方法 属性集。它们也都有相应的工厂方法:

    val x:  IntArray    =   intArrayOf(1,   2,  3)
     x[0]   =   x[1]    +   x[2]
    
    

    类和接口

    class的定义

    命名风格:

    1.使用驼峰法命名(并避免命名含有下划线)
    2.类型名以大写字母开头
    3.方法和属性以小写字母开头
    4.使用 4 个空格缩进
    5.公有函数应撰写函数文档,这样这些文档才会出现在 Kotlin Doc 中

    kotlin类的定义比java要简洁很多,有少数几个参数的类可以写成一行:

    class Person(id: Int, name: String)
    

    具有较长类头的类应该格式化,以使每个主构造函数参数位于带有缩进的单独一行中。 此外,右括号应该另起一行。如果我们使用继承,那么超类构造函数调用或者实现接口列表 应位于与括号相同的行上:

    class Person(
    id: Int,
    name: String,
    surname: String
    ) : Human(id, name) {
    // ……
    }
    
    

    对于多个接口,应首先放置超类构造函数调用,然后每个接口应位于不同的行中:

    class Person(
    id: Int,
    name: String,
    surname: String
    ) : Human(id, name),
    KotlinMaker {
    // ……
    }
    
    

    对于Java用户:Kotlin 中外部类不能访问内部类的 private 成员。
    如果你覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可 见性。

    构造函数

    在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函,如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。

    要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个 显式 constructor 关键字):

    class   C   private constructor(a:  Int)    {   ……  }
    
    

    这里的构造函数是私有的。默认情况下,所有构造函数都是 public ,这实际上 等于类可见 的地方它就可见(即 一个 internal 类的构造函数只能 在相同模块内可见).

    class   Customer(name:  String) {
    init{logger.info("Customer  initialized with    value   ${name}")} 
    }
    主构造函数中声明的属性可以是  可变的(    var )或只读的(  val )。 如果构造函数有注解或可见性修饰符,这个      constructor     关键字是必需的,并且  这些修饰符 在它前面:
    

    主构造函数不能包含任何的代码。初始化的代码可以放 到以 init 关键字作为前缀的初始化 块(initializer blocks)中:

    class   Customer    public  @Inject constructor(name:   String) {   ……  }
    
    

    次构造函数
    类也可以声明前缀有 constructor 的次构造函数:

    class Person    {   
    constructor(parent: Person) {
    parent.children.add(this)           
        }
     }
    
    

    如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过 别的次构造函数间接委托。委托到同一个类的另一个构造函数 用 this 关键字即可:

    class   Person(val  name:   String) {
    constructor(name:   String, parent: Person) :   this(name)  {
    parent.children.add(this)
    } }
    
    

    继承

    在 Kotlin 中所有类都有一个共同的超类 Any ,这对于没有超类型声明的类是默认超类, Any 不是 java.lang.Object ;尤其是,它除了equals() 、 hashCode() 和 toString() 外没 有任何成员。
    默认情况下,在 Kotlin 中所有的类都是final, open 标注允许其他类 从这个类继承。 如果函数没有标注 open ,则子类中不允许定义相同签名的函数.
    如果一个类从它的直接超类继承相同成员的多个实 现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用 从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super ,如 super<Base> :

    抽象类

    类和其中的某些成员可以声明为abstract ,不需要用 open标注一个抽象类或者函数——因为这不言而喻。

    伴生对象

    与 Java 或 C# 不同,在 Kotlin 中类没有静态方法。在大多数情况下,它建议简单地使用 包级 函数。如果在你的类内声明了一个伴生对象, 你就可以使用像在 Java/C# 中调用静态 方法相同的语法来调用其成员,只使用类名 作为限定符。

    属性和字段

    Kotlin的类可以有属性。 属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 。

    Getters 和 Setters:

    var <propertyName>[:<PropertyType>] [=<property_initializer>]
    [<getter>]  
    [<setter>]
    
    
    var stringRepresentation:   String
    get()   =   this.toString() 
    set(value)  {                               setDataFromString(value)    //  解析字符串并赋值给其他属性               }
    
    

    幕后字段:
    Kotlin 中类不能有字段。然而,当使用自定义访问器时,有时有一个幕后字段(backing field)有时是必要的。为此 Kotlin 提供 一个自动幕后字段,它可通过使用 field 标识符访 问。

    var counter =   0   
    //  此初始器值直接写入到幕后字段
    set(value){
    if  (value  >=  0)
    field   =   value
    }
    

    幕后属性:

    private var _table: Map<String, Int>?=null 
    public  val table:  Map<String, Int>    
    get()   {
    if  (_table ==  null)   {
    _table  =   HashMap()
    //  类型参数已推断出    
    }
    return  _table  ?:  throw   AssertionError("Set to  null    by  another thread")
    }
    
    

    编译期常量:
    已知值的属性可以使用 const 修饰符标记为 编译期常量。 这些属性需要满足以下要求:
    位于顶层或者是 object 的一个成员
    用 String 或原生类型 值初始化
    没有自定义 getter

    惰性初始化属性:
    一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性 可以通过依赖注入来初始化,为处理这种情况,你可以用 lateinit 修饰符标记该属性:

    public  class   MyTest  {
    lateinit    var subject:    TestSubject
            @SetUp  fun setup() {
    subject =   TestSubject()               }
         @Test  fun test()  {
    subject.method()        //  直接解引用               } }
    

    该修饰符只能用于在类体中(不是在主构造函数中)声明的 var 属性,并且仅 当该属性没 有自定义 getter 或 setter 时。该属性必须是非空类型,并且不能是 原生类型

    模块

    可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译 在一起的一套 Kotlin 文件:
    一个 IntelliJ IDEA 模块;
    一个 Maven 或者 Gradle 项目;
    一次 <kotlinc> Ant 任务执行所编译的一套文件。

    相关文章

      网友评论

        本文标题:Kotlin 入门(一):基本语法

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