Kotlin

Kotlin 入门(一):基本语法

2019-02-02  本文已影响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 任务执行所编译的一套文件。

上一篇下一篇

猜你喜欢

热点阅读