基础语法
1、包引用
和Java一样,包名称使用package声明,包的引入使用import。
package com.runoob.main
import java.util.*
如果没有指定包,默认为 default 包。
默认导入
有多个包会默认导入到每个 Kotlin 文件中:
- kotlin.*
- kotlin.annotation.*
- kotlin.collections.*
- kotlin.comparisons.*
- kotlin.io.*
- kotlin.ranges.*
- kotlin.sequences.*
- kotlin.text.*
2.函数
函数使用fun关键字定义
fun sum(a:Int, b:Int) : Int {
return a + b
}
sum是函数名称,括号中定义函数的参数和参数类型,a为参数名,a:Int,Int为参数a的数据类型。
kotlin中参数的定义是 参数名:参数类型的方式。
括号后边的 : Int 定义函数返回值类型。此函数的返回值为 Int。
注意:与Java不同,kotlin行结尾不需要使用分号。
2.1 无返回值的函数
无返回的类型,使用 Unit。或者不需要声明
private fun printSum(a: Int, b: Int): Unit {
println(a + b)
}
2.2 public 函数需要声明返回类型
如果public方法是无返回的类型,返回值可以使用Unit或者不申明。
public fun sum(a: Int, b: Int): Int {
return a + b;
}
如果返回类型是Unit,可以不用声明
public fun printSum(a:Int, b:Int) {
println("${a} + ${}b = ${a+b}")
}
2.3 可变长参数函数
函数的变长参数可以用 vararg 关键字进行标识:
fun vars(vararg v:string){
for(vt in v){
print(vt)
}
}
2.4 lambda(匿名函数)
sumLamdba是一个匿名函数,输入参数有两个,参数类型为Int和Int,->表示返回值,类型是Int。
{}中定义匿名函数的方法体。
// 测试
fun main(args: Array<String>) {
val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
println(sumLambda(1,2)) // 输出 3
}
3 变量和常量
3.1 可变变量定义:var 关键字
var [<标识符>] : <类型> = <初始化值>
可变变量相当于Java中的普通变量。
可变变量声明和初始化的时候,可以省略类型,编译器会根据初始化值的类型作为变量的类型。
3.2 不可变变量:val关键字
val [<标识符>] : <类型> = <初始化值>
不可变变量相当于Java中final修饰的变量,值初始化以后,不允许改变。
和Java一样,变量声明和变量初始化可以不同时。
可以先声明变量,然后再进行初始化化。但是变量使用之前必须初始化。
val a: Int = 2 // 指定变量a类型是Int,并且初始化为2
val b = 2 // 根据初始化的值的类型,系统自动推断变量类型为Int
val c: Int // 如果不在声明时初始化则必须提供变量类型
c = 2 // 明确赋值
var x = 5 // 系统自动推断变量类型为Int
3.3 常量
可以通过val定义常量。
4 注释
和Java一样,支持单行注释和多行注释。
// 单行注释
/**
* 多行注释
*/
与 Java 不同, Kotlin 中的块注释允许嵌套。
5 NULL处理机制
kotlin NULL安全机制设计,标记为可为NULL的参数/变量,在使用时要进行空判断处理,有两种处理方式:
抛出异常:参数后增加“!!”。
// 抛出空指针异常
val ages = age!!.toInt()
NULL处理:可为NULL的参数增加?,处理NULL情况
定义变量的时候,如果变量可以为NULL,声明时需要声明可为NULL
var a:String? = "abc" // a为字符串变量,初始值为“abc”,a可以为NULL
// 不做处理返回 null
val ages1 = age?.toInt()
在函数体内,对于可为NULL的参数/变量,NULL的时不进行处理,防止出现NullPonterExceptiop
age为NULL时,直接返回NULL, ages=NULL
age不为NULL时,返回age.toInt()
如果需要定义NULL场景的处理,后面使用 : 定义为NULL的处理。
// age不为空,返回age.toInt();为空返回-1
val ages2 = age?.toInt() ?: -1
如果函数的返回值可能为NULL,定义函数的时候需要声明
/**
* 方法parseInt的返回值可能为NULL,返回值需要声明NULL。"Int? "表示返回值类型为Int,返回值可以为
* NULL。使用者需要处理返回值为NULL的情况
*/
fun parseInt(str: String): Int? {
// ...
}
6 类型检测和自动类型转换
判断数据类型,使用 is (相当于Java的instanceof)
if (obj is String) {
// 这个分支内部obj自动转换为String
return obj.length
}
7 区间表达式
区间表达式由具有操作符形式 .. 的 rangeTo 函数辅以 in 和 !in 形成。
for (i in 1..4) print(i) // 从1到4的循环,输出“1234”
可以使用step指定步长
// 使用 step 指定步长,第一轮为1,第二轮为1+2=3,第三轮为5
for (i in 1..5 step 2) print(i) // 输出“135”
从大到小的循环可以使用downTo
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
不包括最后一个元素[1,10),排除10
// 使用 until 函数排除结束元素
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
8 Kotlin基本数据类型
8.1 基本数值类型

字面常量
- 十进制:123
- 长整型以大写的 L 结尾:123L
- 16 进制以 0x 开头:0x0F
- 2 进制以 0b 开头:0b00001011
- 注意:8进制不支持
Kotlin支持传统的数值写法:
Doubles 默认写法: 123.5, 123.5e10
Floats 使用 f 或者 F 后缀:123.5f
8.2 比较两个大小
Kotlin 中没有基础数据类型,只有封装的数字类型,你每定义的一个变量,其实 Kotlin 帮你封装了一个对象,这样可以保证不会出现空指针。数字类型也一样,所以在比较两个数字的时候,就有比较数据大小和比较两个对象是否相同的区别了。
kotlin比较大小包括比较数值地址和比较数值大小。
三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小。
val a: Int = 10000
println(a === a) // true,值相等,对象地址相等
// 经过了装箱,创建了两个不同的对象
val boxedA: Int? = a
val anotherBoxedA: Int? = a
// 虽然经过了装箱,但是值是相等的,都是10000
println(boxedA === anotherBoxedA) // false,值相等,对象地址不一样
println(boxedA == anotherBoxedA) // true,值相等
8.3 类型转换
类型转换可以通过toXX()进行转换。
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK。不能直接将b赋值给i,因为两者类型不相同。
每种数据类型都有下面的转换放大:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
8.4 位操作符
shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向
8.5 字符类型
和Java不一样,kotlin字符不能和数字直接操作,字符需要通过单引号包括起来
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of char range")
return c.toInt() - '0'.toInt() // 通过toInt显式转换为数字
}
8.6 布尔类型
和Java一样,布尔类型Boolean,有两个值:true和false
布尔运算逻辑与 && 逻辑或 || 逻辑非 !
8.7 数组
kotlin创建数组,有两种方式。
arrayOf和Array工厂模式
//[1,2,3,4]
val a = arrayOf(1, 2, 3,4)
//[0,2,4] 3个元素,第i个元素的值为 (i* 2)
val b = Array(3, { i -> (i * 2) })
元素的访问,和Java一样,通过下标访问,可以使用a[i]的方式。
8.8 字符串
和Java一样,kotlin中String也可以通过字符遍历。
Kotlin 支持三个引号 """ 扩起来的字符串,支持多行字符串,
fun main(args: Array<String>) {
val text = """
这是多行字符串
第二行多行字符串
"""
println(text) // 输出有一些前置空格
}
8.9 字符串模板
字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成:
类型与Java的格式化输出
var a = 5;
var b = 7;
var str = "$a + $b = ${a+b}"
原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义)
val price = """
${'$'}9.99
"""
println(price) // 求值结果为 $9.99
9 条件控制
9.1 IF表达式
if表达式和Java使用相同
var a = 5
// 测试IF表达式
if (a > 60) {
println("我及格了")
} else {
println("真糟糕,我没及格")
}
9.2 When表达式
when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。
when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。
类似于Java的switch-case,不过分支判断可以支持表达式。
// 测试WHEN表达式
var x = 88
when (x) {
in 0 until 60 -> println("还是没及格,加油呀")
in 60 until 80 -> println("及格了")
in 80 until 90 -> println("良好,还可以")
in 90..100 -> println("你真优秀")
else -> println("无效分数")
}
10 循环语句
kotlin循环有for循环和while循环
val items = listOf("大毛", "二毛", "明明")
for (item in items) {
println(item)
}
也可以根据索引进行循环遍历
// 根据索引循环遍历
for (index in items.indices) {
// items[index]按照索引获取元素值,或者使用items.get(index)
println("item $index is ${items[index]}")
}
和Java一样,kotlin也支持迭代器
// 使用迭代器循环遍历
var it = items.iterator()
while (it.hasNext()) {
println("item ${it.next()}")
}
do...while循环,与while循环类似,不过至少执行一次。
// 测试do...while循环
println("===测试do...while循环===")
var i = 0
do {
println("第 ${i + 1} 次循环 i=$i")
i++
} while (i < 5)
11 类和对象
11.1 类成员(类属性)
类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
11.2 类的成员的set和get方法
/**
* 基类
*
* @author zhouronghua
* @time 2021/6/7 6:11 PM
*/
open class Person {
/**
* 姓名
*/
var name:String = ""
/**
* 年龄
*/
var age:Int = 0
/**
* 性别
*/
var sex:String = ""
get() = field // filed指示为当前属性sex
set(value) {
field = value
}
constructor(name: String, age: Int) {
this.name = name
this.age = age
}
/**
* 类方法:toString
*/
override fun toString(): String {
return "Person(name='$name', age=$age, sex='$sex')"
}
}
kotlin提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,field 关键词只能用于属性的访问器
11.3 主构造函数
主构造函数不能包含任何代码,初始化代码可以放在初始化方法init中。
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
class A 修饰符 constructor(当前类的属性) { }
如果主构造函数没有任何修饰符,可以省略 constructor 关键字,如果没有任何属性,可以 省略小括号()
11.4 次构造函数
次构造函数 使用 constructor 关键字声明。如果有柱构造函数,次构造函数必须调用主构造函数(直接或者间接调用)
11.5 类方法
类方法采用fun定义
12 密闭类(sealed)
密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类 的一个子类可以有可包含状态的多个实例。
声明一个密封类,使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
sealed不能修饰interface, abstract class。
/**
* 密闭类测试
*
* @author zhouronghua
* @time 2021/6/10 8:43 AM
*/
sealed class Gender(val type: String) {
/**
* 男人
*/
class Man() : Gender("男人")
/**
* 女人
*/
class Woman() : Gender("女人")
}
/**
* 测试密封类
*/
private fun testSealedClass() {
println("===测试密闭类===")
var man = Gender.Man()
println("密闭类 ${man.type}")
}
Gender的子类,只有Man和Woman。其他的类不能继承Gender。
使用密封类的关键好处在于使用 when 表达式 的时候,如果能够 验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。
/**
* 打印性别
*/
private fun printGender(gender: Gender) {
// 密闭类判读的时候只需要枚举子类情形,不必要处理else
when (gender) {
is Gender.Man -> println("是个男人")
is Gender.Woman -> println("是个女人")
}
}
13 数据类
13.1 数据类特性:
- 主构造函数至少包含一个参数。
- 所有的主构造函数的参数必须标识为val 或者 var ;
- 数据类不可以声明为 abstract, open, sealed 或者 inner;
- 数据类不能继承其他类 (但是可以实现接口)。
13.2 数据类属性
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
- equals() / hashCode()
- toString() 格式如 "User(name=John, age=42)"
- componentN() functions 对应于属性,按声明顺序排列
- copy() 函数
/**
* 数据类
*/
data class User(var name:String, val gender:String) {
/**
* 注册时间
*/
var registerTime: String = ""
}
// 数据类自动实现toString()。User(name=jakson, gender=男)
// 更新注册时间
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
val date = LocalDate.now()
val fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val dateStr = date.format(fmt)
println("dateStr $dateStr")
jakson.registerTime = dateStr
}
println("jakson info: ${jakson.toString()}")
数据类根据主构造函数的属性自动实现了toString方法。因此能够直接输出toString内容。
jakson.toString不会打印注册时间,因为注册时间属性不在主构造函数中。
因为数据类自动实现了toString,打印$jackson的内容和打印$jakson.toString内容相同。
// 测试数据类拷贝
println("===测试数据类拷贝===")
var peter = jakson.copy(name = "peter")
println("peter info: ${peter} registerTime:${peter.registerTime}")
peter info: User(name=peter, gender=男) registerTime:
我们看到jakson的注册时间属性没有拷贝到peter,因为registerTime不是主构造函数的属性。
14 枚举类
14.1 枚举类定义
枚举类最基本的用法是实现一个类型安全的枚举。
枚举常量用逗号分隔,每个枚举常量都是一个对象。枚举类采用enum定义
/**
* 枚举类:形状
*
* @author zhouronghua
* @time 2021/6/9 11:26 AM
*/
enum class Shape(val desc: String) {
/**
* 椭圆
*/
OVAL("椭圆"),
/**
* 圆形
*/
CIRCLE("圆形"),
/**
* 矩形
*/
RECTANGLE("矩形");
}
14.2 枚举类内置成员
枚举类包含内置成员name和ordinal。参考Enum.kt
name对应枚举类常量名称。

// 测试枚举类
println("===测试枚举类===")
var shape = Shape.CIRCLE
// 图形 CIRCLE ordinal 1
println("图形 ${shape.name} ordinal ${shape.ordinal}")
14.3 枚举类对象获取
枚举类对象可以通过valueOf获取
// 根据枚举类名称获取枚举类
shape = Shape.valueOf("OVAL")
// 打印枚举类名称(OVAL)
println("获取的枚举类型对象为 ${shape.name} ordinal ${shape.ordinal}")
枚举类名称没有匹配上的时候会抛出异常
// 枚举类名没有匹配上的话则会抛出异常
shape = Shape.valueOf("BOX")
println("获取的枚举类型对象为 ${shape.name}")
14.4 枚举类遍历
可以通过enumValues获取枚举类常量列表;
通过enumValueOf("RECTANGLE")查找对应的枚举类常量
// 枚举类常量遍历
println("===枚举类常量遍历===")
var items: Array<Shape> = enumValues<Shape>()
for (item: Shape in items) {
println("枚举元素 ${item.name}")
}
// 通过枚举名称查找枚举类常量
println("===查找枚举类===")
var child: Shape = enumValueOf("RECTANGLE")
println("枚举元素 ${child.name}")
15 嵌套类
kotlin中的嵌套类,嵌套类不能访问外部类的属性,因为嵌套类实际上是一个静态内部类
嵌套类相当于Java的静态内部类,不持有外部类的对象
/**
* 内部类举例
*
* @author zhouronghua
* @time 2021/6/10 9:42 AM
*/
class Car(val model:String) {
class Petrol() {
fun getPetrolName() : String {
return ""
}
}
}
16 内部类
内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
/**
* 内部类举例
*
* @author zhouronghua
* @time 2021/6/10 9:42 AM
*/
class Car(val model: String) {
/**
* 汽油
*/
class Petrol() {
fun getPetrolName(): String {
return ""
}
}
/**
* 车轮
* 说明: 内部类。可以访问外部类对象
*/
inner class Wheel(val brand: String) {
/**
* 打印车轮信息
*/
fun printWheel() {
// 能够访问到外部类的对象实例
println("$model wheel is $brand")
}
}
}
17 匿名内部类
使用对象表达式来创建匿名内部类:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.button_first).setOnClickListener {
// findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
testDemo()
}
view.findViewById<Button>(R.id.button_loop).setOnClickListener {
// 测试循环
testLoop()
}
view.findViewById<Button>(R.id.button_class).setOnClickListener {
// 使用匿名内部类
object : View.OnClickListener {
override fun onClick(v: View?) {
// 测试类
testClass()
}
}
}
}
18 Object对象
18.1 object对象声明
object声明的对象相当于单例。
/**
* 直接声明对象
*/
object XBus {
/**
* 颜色
*/
var color: String = "绿色"
/**
* 座位
*/
var seat: Int = 30
}
println("===测试object对象===")
// object声明的对象相当于一个单例
println("XBus 颜色:${XBus.color} 座位: ${XBus.seat} 个")
// 指向同一个单例对象结果相等
var obj1 = XBus
obj1.color = "红色"
var obj2 = XBus
println("${obj1 == obj2}")
// XBus 颜色:红色 座位: 30 个
println("XBus 颜色:${obj2.color} 座位: ${obj2.seat} 个")
18.2 object函数返回对象
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
不能访问到publicFoo().x
19 类型
19.1 空安全
kotlin在定义参数和方法的输入参数时,如果是可以为空的参数,需要显式声明,在参数后面增加?
代表是它的值是可空的, 这样能够减少很多空判断,还能有效避免空指针。
// 声明变量name是可以为空的变量
var name: String? = null
// 判断参数name是否为空,不为空则打印name的长度,为空则输出0
println(name?.length ?: 0)

19.2 Elvis操作符
Elvis操作符使用?:表示。如果操作符满足,则使用前面的表达式,否则使用:后面的表达式。类似Java的三目操作符。
// 判断参数name是否为空,不为空则打印name的长度,为空则输出0
println(name?.length ?: 0)
name不为空,则打印name.length。如果name.length则打印,否则打印“0”
19.3 非空断言
断言表达式不为空,为空则会抛出KotlinNullPointerException
// 非空断言(str为空则抛出空指针)
println(name!!.length)
19.4 对象类型判断
使用is 判断对象类型,对应Java的instanceOf
// 类型转换
var obj: Any? = "abc"
if (obj is String) {
// 会智能转化为对应类型的实例
println("obj is String length: " + obj.length)
}
在if (obj is String) 中,obj.length,此时obj已经判断了是String类型,在代码块中不需要进行强制转换,会智能转换为String。这个与Java的使用有点差异。
kotlin使用 as 进行类型强制转换
// 强制转换
println("===测试强制转换===")
// 如果转换成功则转化为String,转换失败则为null
val str = obj as? String
// str为非空字符串则打印str长度,否则打印null
println(str?.length)
因为obj是String,因此强制转换成功,输出str的长度。
强制转换失败,返回的结果时null。
// 如果转换成功则转化为String,转换失败则为null
val str = obj as? Int
// str为非空字符串则打印str长度,否则打印null
println(str?.toChar())
此时obj类型是String, 强制转换为Int,转换失败,结果为null.
19.5 数组类型
可以通过arrayOf构建数组。
println("===测试arrayOf数组===")
var array2 = arrayOf("1", 2, 5)
for (it in array2) {
println("$it")
}
arrayOf中的元素类型可以不相同。
如果构建同一类型元素的数组,可以使用intArrayOf、booleanArrayOf\byteArrayOf。
// 测试数组类型
println("===测试intArrayOf数组===")
var array1 = intArrayOf(1, 2, 5)
for (it in array1) {
println("$it")
}
20 泛型
泛型与java类型。用<T>,<E>等表示泛型
/**
* 泛型类
*/
class TestGen<E> {
/**
* 对应元素
*/
var elment: E? = null
/**
* 定义泛型方法,输入参数e,输出参数为泛型E类型
*/
fun <E> add(e: E) {
}
}
可以为泛型类或者泛型方法指定限定类,这叫做泛型上限。
/**
* 限定参数只能接收 Number 的子类
*/
fun <E : Number> add(e: E) {}
使用此方法时,输入的参数类型,必须是Number的子类。
泛型通配符 Java 中使用?代表通配符,? extends 类型表示通配符上限,? super 类型表示通配 符下限

21 集合
kotlin集合分为可变集合和只读集合。可变集合采用Mutable定义

可变集合是指集合声明和初始化以后,可以改变集合中元素的值。
// 测试只读列表
var readList = listOf("aaa", "bbb", "cccc")
// 只读集合不允许改变元素的值,编辑器报错
readList[0] = "1111"
可变集合允许改变元素的值
// 测试可变集合
var writeList = mutableListOf(1, 2, 3, 4, 5, 6)
println("${writeList[0]}")
writeList[0] = 125
println("改变后的值${writeList[0]}")
22 序列
// 测试可变集合
var writeList = mutableListOf(1, 2, 3, 4, 5, 6)
println("${writeList[0]}")
// writeList[0] = 125
println("改变后的值${writeList[0]}")
// 测试序列
val result = writeList.filter {
println("filter $it")
it > 2
}.map {
// 进行变换,原来的值进行映射变化后,返回新的值
println("map $it")
it * 2
}.toList()
// 打印变换后的结果
println("===测试序列结果===")
for (it in result) {
println("$it")
}
序列的操作分为两类,中间操作和末端操作,中间操作就是 filter,map 等这些转化,末端操 作就是调用 toList 将序列转化为集合.
序列和集合的使用总结:

1072

被折叠的 条评论
为什么被折叠?



