# Kotlin教程 - 2 基础语法

# 2.1 字面量

Kotlin 是一种静态类型的编程语言,它具有丰富的数据类型来表示不同种类的数据,先介绍一下 Kotlin 中基本的数据类型。

如果没学过其他语言,看数据类型可能比较懵逼,数据类型就是说数据有哪些类型,例如我们保存姓名,使用的的是字符串,也就是文本,但是保存年龄,是数字类型,金额可能使用的小数的类型,不同的数据会使用不同的数据类型来保存。

整数类型

下面这些都是整数:

123
1234567
123456789101112L  		// 表示长整型,需要以L作为后缀
123L  								// 表示长整型
0x0F									// 十六进制形式整数
0b00001011						// 二进制形式的整数
1
2
3
4
5
6

Kotlin 中表示整数的类型有如下几种,他们的长度不同,所以可以表示的范围不同。

  • Byte:8 位有符号整数,取值范围为 -128 到 127。
  • Short:16 位有符号整数,取值范围为 -32768 到 32767。
  • Int:32 位有符号整数,通常是最常用的整数类型。
  • Long:64 位有符号整数,用于表示大整数值,例如时间戳(时间的毫秒数)。

上面这样在代码中直接书写的数据,被称为字面量。

如何使用数据类型,讲到变量的时候再讲解,不必着急。

浮点数类型

123.5					// 默认就是双精度浮点数
123.5e10			// 科学计数法,表示123.5乘以10的10次方,平时用的不多
123.5f				// 单精度浮点数,后面需要加f后缀
1
2
3

Kotlin 中表示浮点数的类型有如下两种:

  • Float:32 位单精度浮点数(小数),用于表示小数,有约 6-7 位有效数字。
  • Double:64 位双精度浮点数(小数),用于表示双精度小数,有约 15-16 位有效数字。

为了便于阅读,还可以在字面量重添加下划线

100_000_001
1234_5678_9012L
0xFF_EC_DE_5E					// 十六进制
0b11010010_01101011_10011100_10010011			// 二进制
123_4567_7890.123			// 双精度浮点数
123_4567.1f						// 单精度浮点数
1
2
3
4
5
6

字符类型

'A'
'a'
'#'
'3' 	// 这不是整数,是一个字符
1
2
3
4
  • Char:16 位 Unicode 字符,用于表示单个字符,例如字母、数字或符号。

和Java不同,Kotlin中Char只能表示单个字符,不能和数字直接进行转换。

布尔类型

true
false
1
2
  • Boolean:表示布尔值,只能取 truefalse

字符串类型

还有最常用的数据类型,就是字符串类型,用来存储文本数据。在Kotlin中,可以使用双引号 " 或三个双引号 """ 来定义字符串:

"abc"

"""
你好,
China
"""
1
2
3
4
5
6
  • String:表示字符串,可以包含任何字符序列。

Kotlin中还有一些其他的数据类型,例如空类型、数组类型、自定义数据类型(类、枚举类等),这些后面在讲解,这里就不说了。

在 Java 中,数据类型分为基本数据类型和引用数据类型,但是在 Kotlin 中只有引用数据类型,只是Byte、Short、Int、Long、Float、Double、Boolean、Char ,在编译的时候,还是会编译为基本数据类型来处理。

# 2.2 注释

我们在学习任何语言,都会有注释,注释的作用就是向别人解释我们编写的代码的含义和逻辑,使代码有更好的可读性,注释不是程序,是不会被执行的

在Kotlin中注释分类三类,单行注释多行注释文档注释

# 1 单行注释

单行注释以 // 开头,// 号右边为注释内容。

例如:

// 我是单行注释,打印Hello World
print("Hello World!")
1
2

注意:// 号和注释内容一般建议以一个空格隔开,这是代码规范,建议大家遵守。

单行注释一般用于对一行或一小部分代码进行解释。

# 2 多行注释

多行注释是以 /* */ 括起来,中间的内容为注释内容,注释内容可以换行。

/*
我是多行注释,
可以跨越多行
*/
print("Hello World!")
1
2
3
4
5

多行注释一般用于描述一段代码或者变量的详细信息。

# 3 文档注释

文档注释使用 /** 开始,*/ 结束。这种注释方式常用于生成API文档,可以包含对类、方法和属性的描述。

举个栗子:

/**  
 * 这是一个Javadoc注释  
 * 用于描述类、方法和属性的详细信息  
 */  
class MyClass {  
    /**
     * 这是一个方法的注释示例
     *
     * @param name 用户的姓名
     * @return 一个欢迎消息
     */
    fun sayHello(name: String): String {
        return "Hello, $name!"
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

文档注释中的标签通常以 @ 符号开头,用于描述函数、参数、返回值等详细信息。这些注释可以由文档生成工具(如KDoc)用来生成文档以供开发者参考。如果没有编程基础,上面的代码看起来比较懵逼,先不用过多理会。

简单的说,就是可以使用工具生成代码的说明文档,这些说明文档就是根据代码中的 文档注释 来生成的。

如果你已经学习过 Java 了,和 Java 一样,赶紧继续学吧。

# 2.3 打印语句

print()和println()

print() 和 println() 都是打印数据的,println() 表示打印完数据后,会换行,所以如果在 println() 后面继续打印的话,会在第二行打印。

举个栗子:

print("name:")
print("DouBi")
1
2

执行结果为:

name:Doubi

println("name:")		// 打印完换行
print("DouBi")
1
2

执行结果为:

name:

Doubi

# 2.4 变量和常量

变量是在程序运行的时候存储数据用的,可以想象变量为一个盒子。如果我们要存储数据,就需要先定义变量,将数据存储的变量中。

# 1 定义变量

定义变量的语法 var 变量名: 数据类型 = 值

举个栗子:

fun main() {
    // 定义整数变量,变量名称为age
    var age: Int = 18
    println(age)   // 打印age
}
1
2
3
4
5

在上面,我们定义了一个整形的变量,变量名称为age,然后将整数18存储到变量age中,然后使用打印语句println()将变量打印到控制台,输出的结果是18。

同样,我们可以定义各种不同类型的变量,存储各种不同类型的数据:

fun main() {
    // 定义字符串变量,变量名称为name,用来存储姓名
    var name: String = "Doubi"
    print(name)   // 打印name

    // 定义整数变量,变量名称为age
    var age: Int = 18

    // 定义长整数变量,使用 'L' 后缀表示长整数
    var time: Long = 1234567890L

    // 定义浮点数变量,需要添加 'F' 后缀表示单精度浮点数
    var floatVariable: Float = 3.14F

    // 定义浮点数变量,默认是双精度浮点数
    var doubleVariable: Double = 2.71828

    // 定义字符变量,字符类型只能表示单个字符,使用'括起来
    var charVariable: Char = 'A'

    // 定义布尔变量,只有两个值,true和false
    var booleanVariable: Boolean = true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

上面分别定义了不同数据类型的变量,将变量的值存储在不同的变量中。

和 Java 相比,这定义变量的方式看上去恶心至极,突然想吐,但其实数据类型是可以省略的。

举个栗子:

fun main() {
    // 定义字符串变量,变量名称为name,用来存储姓名
    var name = "Doubi"
    print(name)   // 打印name

    // 定义整数变量,变量名称为age
    var age = 18

    // 定义长整数变量,使用 'L' 后缀表示长整数
    var time = 1234567890L
}
1
2
3
4
5
6
7
8
9
10
11

省略了数据类型,这样看上去好多了。Kotlin 会根据后面的值自动推断变量的类型

# 2 变量的操作

变量变量,是可以变化的量,所以我们可以随时修改变量的值。

例如:

fun main() {
    var name = "DouBi"     // 定义一个变量,用来表示姓名
    name = "ShaBi"         // 重新修改变量的值
    // name = 123 // 错误, name是字符串类型的,无法赋值其他类型数据
    println(name)
    
    var height = 170
    height = height + 10   // 将height在原来的基础上加 10
    println(height);
}
1
2
3
4
5
6
7
8
9
10

需要注意,var name="DouBi"; 已经确定了变量name的类型为字符串,无法使用 name = 123; 赋值其他类型数据。

# 3 常量

变量的值是可以变的,就像上面我们定义了变量,后面可以修改它的值。

常量就是值不能改变的。

在 Kotin 中,使用 valconst val 关键字来定义常量。

两者的区别在于:

  • val 关键字定义的常量是在第一次使用时初始化的,初始化后不可改变,又叫只读变量
  • const val 关键字定义的常量在程序运行之前,在程序编译时就已经被初始化了,永远不会改变,而且 const val 只能定义在顶级作用域,或者在对象声明和伴生对象中使用(后面再讲解)。

举个栗子:

const val PI = 3.141592653      // 在全局使用const定义常量,程序运行之前就知道了值
//const val nowTime = System.currentTimeMillis()  // 错误:System.currentTimeMillis()是获取当前时间,需要运行的时候才能确定值,所以不能赋值给const val常量

fun main() {
    // const val PI = 3.141592653       // 错误:无法在这里使用const val定义常量,这在方法里,不是全局作用域
    val PI2 = 3.141592653		            // 使用val来定义常量
    val nowTime: Long = System.currentTimeMillis() 		// val定义常量,需要等到程序第一次运行的时候才能知道值
}
1
2
3
4
5
6
7
8

从上面可以看到 const val 定义常量不能在方法中定义,需要在全局定义,并且值需要在运行前确定,而 val 定义常量没有这些要求。

常量和变量的类型都可以显式指定,也可以通过类型推断来自动推断。

通过查看字节码,反编译成对应的java代码,可以看到,val其实就是final修饰变量,const val其实就是static final修饰的变量。

# 2.5 数据类型与转换

# 1 Any

在前面我们介绍了很多的数据类型,这些数据类型有一个共同的父类型 AnyAny 类型可以包含任何非空的Kotlin值。

举个栗子:

fun main() {
    var name: Any = "Doubi"
    var age: Any = 18

    var value: Any = "Shabi"
    value = 123                // 可以接收任何非空类型
}
1
2
3
4
5
6
7

在上面定义了 Any 类型的变量,Any 类型的变量因为是 Int 和 String 等类的父类,所以这些子类都是 Any 类。就像学生类和老师类都是人类,那么定义张三这个变量也肯定是人类。

所以在上面的代码中,字符串和整数都可以赋值给 Any 类型的变量,同时可以修改一个 Any 类型变量的值为不同的数据类型。

# 2 获取数据类型

在 Kotlin 中,可以使用 ::class::class.java 来获取一个变量的数据类型。

举个栗子:

使用 ::class

fun main() {
    val num = 10
    val str = "Hello, World!"

    println(num::class.simpleName)  // 输出: Int
    println(str::class.simpleName)  // 输出: String
}
1
2
3
4
5
6
7

首先使用 ::class 获取它的数据类型。然后可以使用 simpleName 属性来获取类型的简单名称。

# 3 Any转换为其他类型

相同数据类型的变量可以互相赋值,但是一个存储整数的 Any 类型的变量,如何赋值给另一个整形的变量呢?

fun main() {
    var age: Any = 18
    var value: Int = age       // 报错,类型不匹配
}
1
2
3
4

那么如何将 Any 类型赋值给其他类型呢?

可以使用 as 关键字进行转换

举个栗子:

fun main() {
    var age: Any = 18
    var value: Int = age as Int     // 将age转换为Int类型
}
1
2
3
4

上面使用 as 关键字将 Any 转换为 Int 类型。

这里如果 age 不是整数,那么转换为 Int 类型是会报错的,所以推荐在转换之前先进行类型的判断,判断 age 是否是整数,如果是整数在转换。

fun main() {
    var age: Any = 18

    if (age is Int) {       // 先判断age是否是Int,如果不是Int,转换为Int会报错
        var value: Int = age as Int     // 将age转换为Int类型
    }
}
1
2
3
4
5
6
7

上面用到了 if 判断,后面会讲解,age is Int 表示判断 age 是否是 Int 类型,如果是 Int 类型,则会执行后面 {} 中的代码。

# 4 数字类型之间的转换

Byte、Short、Int、Long、Float、Double、Char 之间如何转换呢?

他们都有提供下面的这些方法,可以转换为对应的类型:

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
1
2
3
4
5
6
7

举个栗子:

double 转 int

fun main() {
    var a: Double = 12.89
    var b: Int = a.toInt()
    print(b)		// 输出:12
}
1
2
3
4
5

double 类型转 int ,小数点后去掉,只保留整数部分。

如果要进行四舍五入,可以使用roundToInt()方法:

import kotlin.math.roundToInt		// 需要引入roundToInt

fun main() {
    var a: Double = 12.89
    var b: Int = a.roundToInt()
    print(b)    // 13
}
1
2
3
4
5
6
7

# 5 String与数字相互转换

数字转换为String

直接使用数字的 toString() 方法将数字转换为字符串。

fun main() {
    val intValue = 123
    val intStr = intValue.toString()        // 整数转换为字符串
    
    val doubleValue = 3.14
    val doubleStr = doubleValue.toString()  // 浮点数转换为字符串
}
1
2
3
4
5
6
7

String转换为数字

String 类型提供了 toInt()toDouble()toFloat()toLong() 方法,可以将字符串转换为数字。

举个栗子:

fun main() {
    val str = "123"
    val intValue = str.toInt()
    val doubleValue = str.toDouble()

    println(intValue)
    println(doubleValue)
}
1
2
3
4
5
6
7
8

需要注意的是,如果字符串不能被解析为数字,则会报错,导致程序终止运行。

如果字符串不能解析为数字,不想程序报错,可以使用 toXXXOrNull() 方法。

举个栗子:

fun main() {
    val str = "abc"
    val intValue = str.toIntOrNull()
    val doubleValue = str.toDoubleOrNull()

    println(intValue)			// 输出:null
    println(doubleValue)	// 输出:null
}
1
2
3
4
5
6
7
8

null 是一特殊的值,表示没有值,是空的,因为没有能把字符串转换为数字,所以得到的结果是空。

关于空(null)的问题,在后面空安全中再讲解。

# 2.6 标识符

什么是标识符?

标识符就是名字,例如变量的名字、方法的名字、类的名字。

起名字肯定会有限制,肯定不能 张Three 这样起名字,所以标识符也有限制。

# 1 标识符命名规则

标识符需要遵守一下规则:

  1. 只能是英文、数字、下划线,其他任何内容都不允许;
  2. 不能使用数字开头,可以英文或下划线开头;
var a = "Doubi"					// 可以
var a_b = "Doubi"				// 可以
var _a = "Doubi"				// 可以
var a1 = "Doubi"				// 可以
var a_b_a = "Doubi"			// 可以

var 1 = "Doubi"					// 错误
var 1_ = "Doubi"				// 错误
var 1_a = "Doubi"				// 错误
1
2
3
4
5
6
7
8
9
  1. 大小写敏感,大小写不同是不同的标识符;
var name = "Doubi"					
var Name = "Doubi"				    // 和 name 是不同的标识符
1
2
  1. 不能使用关键字,关键字就是 Kotlin 中保留的一些单词,有特殊的用途,不能被用作标识符;

Kotlin 中常用的关键字和保留字如下:

as break class continue do
else enum false field finally
for fun if in interface
internal is lateinit null object
package return super this throw
true try typealias val var
when while with annotation const
crossinline data inline infix noinline
open operator reified sealed suspend
tailrec vararg

如果标识符中只是包含了关键字,是没有关系的。

这么多怎么记?不用记!不用记!不用记!后面每个关键字都会学到,自然知道每个关键字是做什么用的,不用记!

其实你如果名字不对,编译会报错,无法运行,自然就知道不对了。

# 2 变量命名规范

使用上面说的规则,我们可以定义变量名了。

但是为了优雅、统一、规范,我们在定义变量名时,还应该遵守以下规范,虽然你不遵守,没人开枪打死你,但是建议你遵守。

  1. 见名知意

看见一个变量名,就知道这个变量名是干嘛的。

var a = "Doubi"									// 看到a,鬼知道a是干嘛的
var name = "Doubi"							// 看到name,就知道这是个名字,简单明了
var personName = "Doubi"		    // 在确保明了的前提下,尽量减少长度,这个有点不够简洁
1
2
3
  1. 变量使用首字母小写的驼峰规则
var name = "Doubi"
var accountNumber = "Doubi"
1
2
  1. 常量名应该全部使用大写字母,如果常量名由多个单词组成,单词之间应该使用下划线 _ 分隔
val MAX_VALUE = 123456
const val MIN_VALUE = 1
1
2

其他的命名,例如类名,学到的时候再讲解。

# 2.7 字符串

# 1 字符串定义方式

在上面我们使用双引号 " 来定义字符串,但其实字符串有2种不同的定义方式:

方式一:双引号定义法

val str1 = "我是一个字符串"
1

方式二:三引号定义法

使用3个引号定义字符串,还可以换行:

val str2: String = """
    这是一个
    多行字符串
    """
1
2
3
4

因为字符串是不可变的,一旦创建了就无法修改了,如果要修改,需要重新创建一个新的字符串。

所以在定义字符串的时候,建议使用 val 来定义。

# 2 转义字符

如果我们的字符串中包含引号,该怎么处理呢?

如果是用的是双引号定义的字符串,需要使用转义符 \ ,在引号前面加上 \ 表示后面的字符是普通字符串:

val str = "Hello \"Doubi\""
1

同样,如果你想在字符串中输出 \n ,你可能会这样写:

val str = "Hello \n Doubi";
println(str);
1
2

但运行完成结果却没有 \n ,因为 \n 是换行符,同样 \t 是制表符,如果想在字符串中输出 \t\n 等特殊字符,也是需要对 斜杠 \ 进行转义:

val str = "Hello \\n Doubi";
println(str);
1
2

如果使用的是三引号定义法,那么就不用转义了,直接写就可以了。

val str: String = """Hello \n "Doubi""""
println(str)		// 输出:Hello \n "Doubi"
1
2

但是如果其中有三个引号连起来,并且换行了那就不行了,也是需要使用 斜杠 \ 进行转义的。

val str1: String = """Hello \n "Doubi""""""           // 可以
val str2: String = """
    Hello \n "Doubi""\""
"""
1
2
3
4

# 3 字符串拼接

# 方式一:使用字符串模板

Kotlin 支持字符串模板,你可以在字符串中使用 ${} 来嵌入变量或表达式的值。

举个栗子:

fun main() {
    val name = "Doubi"
    val age = 18

    // 使用 ${} 进行字符串插值
    val message = "我的名字是${name}, 今年${age}岁, 明年${age + 1}岁。"
    print(message) // 输出:我的名字是Doubi, 今年18岁, 明年19岁。
}
1
2
3
4
5
6
7
8

可以看到在 ${} 括号内填写变量和执行任意的表达式

如果直接拼接变量,还可以直接使用 $

fun main() {
    val name = "Doubi"
    val age = 18

    val message1 = "我的名字是$name, 今年$age 岁, 明年${age + 1}岁。"		// $age后面不能直接跟汉字
}
1
2
3
4
5
6

但是这里需要注意,$age 后面必须是符号或空格,不能是汉字,否则会被识别为变量名,比较坑爹。所以建议使用 ${变量} 的方式

# 方式二:使用加号拼接

如果是字符串或字符串变量之间的拼接,直接使用 加号+ 拼接即可,例如:

val name = "zhangsan"
println("我的名字是" + name + ", 我是法外狂徒")
1
2

如果拼接的太多,就有点麻烦:

fun main() {
    val name = "Doubi"
    val age = 18

    val message = "我的名字是" + name + ", 今年" + age + "岁, 明年" + (age + 1) +"岁。"
    println(message)
}
1
2
3
4
5
6
7

所以建议使用字符串模板。

# 方式三:使用StringBuilder

如果要进行大量的字符串拼接,多次进行拼接,建议使用 StringBuilder

fun main() {
    val stringBuilder = StringBuilder()
    stringBuilder.append("Hello, ")
    stringBuilder.append("world!")
    stringBuilder.append("I ame ")
    stringBuilder.append("Doubi")

    val result = stringBuilder.toString()		// 转换为字符串
    println(result)
}
1
2
3
4
5
6
7
8
9
10

如果要进行频繁的拼接,使用StringBuilder 可以提高性能。

# 方式四:使用字符串插值函数

上面使用 StringBuilder 有点麻烦,Kotlin 还提供了 buildString 函数,可以更方便地构建字符串。

fun main() {
    val result: String = buildString {
        append("Hello, ")
        append("world!")
        append("I ame ")
        append("Doubi")
    }

    println(result)
}
1
2
3
4
5
6
7
8
9
10

上面也是使用 StringBuilder 来构建的,使用方便,如果频繁拼接,推荐时候用这种方式。

# 4 判断字符串非空

可以使用 .isEmpty.isNotEmpty 来判断字符串是否为空字符串。

空字符串就是没有内容的字符串。

举个栗子:

val str = ""
print(str.isEmpty())        // 输出: true
print(str.isNotEmpty())     // 输出: false
1
2
3

# 5 去掉字符串前后空格

用户输入的内容,前后有空格,可以使用 trim() 方法去掉前后的空格。

fun main() {
    val str = "   Hello, Kotlin   "
    val trimmedStr = str.trim()
    println(trimmedStr) // 输出 "Hello, Kotlin"
}
1
2
3
4
5

注意:它返回一个新的字符串,原始字符串不会被修改。

如果想单独去掉前面或后面的空格,可以使用 trimStart()trimEnd() 函数。

# 5 浮点数格式化

在输出浮点数的时候,我们可以指定输出的浮点数的精度。

  1. 使用字符串模板

    可以使用字符串模板来将浮点数格式化为字符串,并指定小数位数。以下是一个示例:

    val number = 123.456789
    val formattedString = "格式化后字符串: %.2f".format(number)
    println(formattedString) // 输出:格式化后字符串: 123.46
    
    1
    2
    3

    上面的代码使用 %.2f 格式字符串来指定要显示的小数位数为 2。

  2. 使用 String.format() 函数

    还可以使用 Java 的 String.format() 函数,因为 Kotlin 可以与 Java 互操作。以下是示例代码:

    val number = 123.456789
    val formattedString = String.format("格式化后:%.2f", number)
    println(formattedString) // 输出:格式化后:123.46
    
    1
    2
    3
  3. 使用 DecimalFormat

    可以使用 Java 的 DecimalFormat 类,它提供了更多的格式化选项。首先,需要导入 Java 的 DecimalFormat 类,然后使用它来格式化浮点数。

    import java.text.DecimalFormat
    
    fun main() {
        val number = 123.456789
        val decimalFormat = DecimalFormat("格式化后:#.##")
        val formattedString = decimalFormat.format(number)
        println(formattedString) // 输出:格式化后:123.46
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

上述示例中,#.## 是格式化模式,它表示最多保留两位小数。可以根据需要调整格式化模式以获得所需的格式。

# 2.8 键盘输入

下面介绍一下如何接收键盘输入的内容。

在 Kotlin 中可以使用标准库中的 readLine() 函数来接收键盘输入。

举个栗子:

fun main() {
    print("请输入一个字符串:")
    val input = readLine()

    println("您输入的字符串是:$input")
}
1
2
3
4
5
6

在上面的代码中,通过 readLine() 函数读取到键盘的输入,然后将读取的结果打印了出来。

运行后输入abc,执行结果:

请输入一个字符串:abc 您输入的字符串是:abc

# 2.9 扩展

Java 中有两种数据类型:基本数据类型、引用类型。

Kotlin 只提供了引用类型一种数据类型,但是出于性能的需要,Kotlin 编译器会在Java字节码中将一些数据类型(Byte、Short、Int、Long、Float、Double、Boolean、Char)改用基本数据类型。