kotlin创建
如果要新建一个支持kotlin的Android项目,在创建工程时选择kotlin语言即可。
此时项目中两个build.gradle
文件会多了kotlin依赖代码:
项目根目录下build.gradle:
buildscript {
ext.kotlin_version = 'xxx'
repositories {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:xxx'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
app目录下build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
...
android {
...
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
...
}
如果希望在现有项目上支持kotlin,即在项目中添加上述依赖即可。
一、变量与常量
1.1 变量
Kotlin变量声明与Java存在较大区别,需使用var
或val
关键字
var
:用此关键字声明的变量相当于Java中普通变量val
:用此关键字声明的变量相当于Java中用final
修饰的变量
变量声明格式
// 关键字 变量名 : 数据类型 = xxx
var var_a: Int = 10 //等价于Java中 int var_a = 10;
//Kotlin可以自动推导数据类型
var x = 5 //自动推导其类型为Int,(快捷键显示ctrl + shift + p)
Kotlin具有类型推导机制,初始化时可以根据变量或常量右边的值来推导出其类型
Kotlin中默认访问的修饰符是public
,且如果变量没有初始化,需要显示声明类型并指明类型是否可null
kotlin中语句不需要在行末加分号(加了也不报错)
可空变量
Kotlin中,所有的变量默认都是不允许为空,如果给它赋值null,会报错;但在有些场景需要其允许为空,如从服务端获取一个JSON
数据并解析,这时候可以使用可空变量解除限制
如果我们不能确定一个属性或变量一定不为空,将其定义为可空变量
可空变量声明格式:
var/val 变量名 : 类型? = null/确定的值
class Test{
var var_a : Int? = 0
var var_b : Int = 12
val val_a : Int? = null
var_a = null
var_b = null //报错,不能为null
}
对可空变量调用可能引起空指针,kotlin提供一些方法进行相关处理
可空变量空指针处理:
Kotlin中对可空变量的空指针的处理方法有:判空处理、?.
处理、使用Elvis
操作符
-
使用前判空处理
可以在调用可空变量前进行空判断
var str : String? = null if (str == null){ println("变量str为空") }else{ println("str.length => ${str.length}") }
-
使用
?.
处理即使调用前检查非空,也可能因为在多线程情况下被其他线程改成空。
Kotlin通过?.
解决,保证变量非空,线程安全。如果可空类型变量为null时,返回nullvar str : String? = null println(str?.length) // 当变量str为null时,会返回空(null)
-
使用
let
操作符
let操作符的作用是用符号?.
验证的时候忽略掉null
情况val arrTest : Array
= arrayOf(1,2,null,3,null,5,6,null) for (index in arrTest) { index?.let { println("index => $it") } //1、2、3、5、6,忽略null的情况 } -
使用
Elvis
操作符Elvis
操作符表示屏蔽,安全的操作符,这种操作符有三种,分别是:?:
、!!
、as?
。其优先级较低,比加减乘除运算符低,使用时最好记得加上括号?:
操作符对一个可空类型变量,如果变量不为空则使用,为空则使用设定好的默认值
val testStr : String? = null var length = 0 length = testStr?.length ?: -1 //testStr为null,返回默认值-1
?:
一般与?.
连用!!
操作符使用可空变量后加上
!!
操作符,如果为空会抛出NullPointException
异常val testStr : String? = null println(testStr!!.length) //抛出 NullPointException 异常
as?
操作符as
表示类型转换,如果不能正常转换会抛出ClassCastException
异常,使用as?则会返回null
,不会抛出异常val num : Int? = "Kotlin" as Int //抛出异常 val num1 : Int? = "Kotlin" as? Int println("num1 = $num1") //num1 = null ,不会抛出异常
延迟初始化属性
如果变量一定不为空,但无法立即赋值,例如findViewById()
需要在onCreate
之后才能调用。Kotlin
提供了延迟初始化功能
延迟初始化属性特点:
- 使用
lateinit
关键字且必须是var
声明的变量 - 不能声明于可空变量和基本数据类型,如
Int、Float
等 - 必须在使用变量前赋值,不然抛
UninitializedPropertyAccessException
异常。 - 检测一个
lateinit var
是否已经初始化,可在属性应用上使用.isInitialized
- 该修饰符只能用于类属性
private lateinit var mTabLayout : TabLayout
lateinit val a : Int // 报错,关键字必须是var,不能为基本数据类型
if (::mTabLayout.isInitialized) { //使用前最好检测一下是否已经赋值,防止抛异常
......
}
延迟属性
延迟属性即在程序第一次使用到这个变量的时候初始化
延迟属性特点:
- 使用
lazy{}
高阶函数,不能用于类型推断,用于变量数据类型后,用by
链接 - 只能是用
val
声明的变量 - 可以使用于类属性与局部变量
val name : String by lazy {
"nihao"
}
//声明一个延迟初始化数组
val mTitles : Array by lazy {
arrayOf(
"hello",
"world",
"haha"
)
}
1.2 常量
常量修饰定义格式:
const val NUM = 6
常量的注意事项:
-
const
只能修饰val
,不能修饰var
- 常量必须声明在对象(包括伴生对象)或者顶层中,因为常量是静态的。
- 只有基本类型和
String
类型可以声明为常量
// 1. 顶层声明
const val NUM_A : String = "顶层声明"
// 2. 在object修饰的类中
object TestConst{ const val NUM_B = "object修饰的类中"}
// 3. 伴生对象中
class TestClass{
companion object {
const val NUM_C = "伴生对象中声明"
}
}
二、数据类型
在Kotlin中,需要牢记一点:所有东西都是对象
2.1 基本类型
数值类型
Kotlin中数字内置类型关键字与Java大致相同
类型 | Kotlin | Java |
---|---|---|
布尔 | Boolean | Boolean |
字节 | Byte | Byte/byte |
短整型 | Short | short/Short |
整型 | Int & Long | int/Integer & long/Long |
浮点型 | Float & Double | float/Float & double/Double |
字符 | Char | char/Character |
字符串 | String | String |
var a: Byte = 2
var b: Short = 2
var c: Int = 2
var d: Long = 2L //长整型由大写字母L标记,小写的l会报错
var e: Float = 2f //单精度浮点型由小写字母f或大写字符F标记
var f: Double = 2.0
进制数
Kotlin支持进制数,但不支持八进制数
var g = 0x0F //十六进制数
var h = 0b00001011 //二进制数
var k = 123 //十进制数
println(" g => $g \n h => $h \n k => $k)
// g => 15
// h => 11
// k => 123
数字分组
Kotlin支持分割数字进行分组,使数字常量更易读
val oneMillion = 1_000_000
println("oneMillion => $oneMillion") //oneMillion => 1000000
装箱与数值比较
在Kotlin中,存在数字装箱,但不存在拆箱,因为Kotlin中没有基本数据类型。在kotlin中,是否装箱根据场合来定
val numValue: Int = 123 //数值类型,未进行装箱操作
val numValueBox: Int? = numValue //装箱操作,可空引用会把数字进行装箱
println("装箱后: numValueBox => $numValueBox") //装箱后: numValueBox => 123
Kotlin中数值比较通过(==
)判断。
数字装箱后值不变,然其内存中地址是根据其数据类型的数值范围而定,可能发生变化,两个数值在内存中的地址是否相等用(===
)判断
类型转换
Kotlin提供显式转换和隐式转换
-
显式转换
数字类型支持
toByte()
、toShort()
、toInt()
、toLong()
、toFloat()
、toDouble()
、toChar()
显示转换val numA: Int = 97 println(numA.toByte()) //Int 显式转换为 Byte println(numA.toShort()) //.......
-
隐式转换
其中较小的类型不能隐式转换为较大类型
val b: Byte = 1 // Int 隐式转换为Byte类型 val i: Int = b //报错,Byte不能隐式转换为Int类型 val num = 30L + 12 // 30L + 12 -> Long + Int => Long
位运算
Kotlin中没有特殊字符来表示,只可用中缀方式调用具名函数(只用于Int
和Long
)
shl(bits)
– 有符号左移(类似Java
的<<
)
shr(bits)
– 有符号右移(类似Java
的>>
)
ushr(bits)
– 无符号右移(类似Java
的>>>
)
and(bits)
– 位与(同Java
中的按位与)
or(bits)
– 位或(同Java
中的按位或)
xor(bits)
– 位异或(同Java
中的按位异或)
inv()
– 位非(同Java
中的按位取反)
val x = (1 shl 2) and 0x000FF000
布尔类型
布尔用 Boolean
类型表示,它有两个值:true
与 false
。
布尔运算
||
– 短路逻辑或,与Java一致
&&
– 短路逻辑与,与Java一致
!
- 逻辑非,与Java一致
字符型
字符用 Char
类型表示,字符变量用单引号(' '
)表示,与Java不同,它们不能直接当作数字
fun check(c: Char) {
if (c == 1) { // 错误:类型不兼容,不能直接当作数字使用,需要进行类型转换
// ……
}
}
字符类型转换
字符型变量不能直接当作数字,可以转换为数字,也可转换为其他类型和英文大小写转换
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // 显式转换为数字
}
//大小写转换
var charA: Char = 'a'
var charB: Char = 'B'
var charNum: Char = '1'
result = charA.toUpperCase()
result = charB.toLowerCase()
result = charNum.toLowerCase() //字符变量不是英文时,转换无效,还为1
字符转义
Kotlin同Java一样,支持以下转义序列:
\t
=> 表示制表符
\n
=> 表示换行符
\b
=> 表示退格键(键盘上的Back建)
\r
=> 表示键盘上的Enter
键
\\
=> 表示反斜杠
\'
=> 表示单引号
\"
=> 表示双引号
\$
=> 表示美元符号,如果不转义在kotlin
中就表示变量的引用了其他的任何字符请使用Unicode转义序列语法。例:'\uFF00'
println("\n 换行符")
println("\t 制表符")
println(" \b 退格键")
2.2 字符串
定义
字符串用 String
类型表示,字符串的元素——字符可以使用索引运算符访问: s[i]
val str: String = "kotlin"
for (s in str){
print(s)
print("\t")
}
// k o t l i n
字符串字面量
Kotlin 有两种类型的字符串字面值:
- 转义字符串可以有转义字符
- 原始字符串可以包含换行以及任意文本
val s = "Hello, world!\n" //字符串含有转义字符
//字符串包含换行
val text = """
for (c in "foo")
print(c)
"""
可以使用trimMargin()
函数删除前导空格 ,默认使用符号(|
)作为距前缀,当然也可以使用其他字符。例:右尖括号(>
)、左尖括号(<
)等
val str3: String = """
> I`m like Kotlin .
> I`m like Java .
> I`m like Android .
> I`m like React-Native.
""".trimMargin(">")
println(str3)
// I`m like Kotlin .
// I`m like Java .
// I`m like Android .
// I`m like React-Native.
也可以使用trimIndent()
函数删除共同的缩进空格
字符串模板
字符串字面值可以包含模板表达式*
,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($
)开头,在$
符号后面加上变量名或大括号中的表达式
val text1: String = "我来了!"
var text2: String = "$text1 kotlin" //我来了! kotlin
var text3: String = "$text2 ${text1.length} 哈哈!!!!" //我来了! kotlin 4 哈哈!!!!
大多数情况下,优先使用字符串模板或原始字符串,而不是字符串连接
2.3 数组
Kotlin | Java | |
---|---|---|
整型 | IntArray |
int[] |
整型装箱 | Array |
Integer[] |
字符 | CharArray |
char[] |
字符装箱 | Array |
Character[] |
字符串 | Array |
String[] |
... | ... | ... |
Java中的数组创建方式示例:
//java
int[]c = new int[]{1,2,3,4};
Kotlin中不装箱的写法(等价表示java中的原生类型数组):
val c0 = intArrayOf(1,2,3,4,5) //[1,2,3,4,5]
val arr = IntArray(5) //[0, 0, 0, 0, 0]
val arr1 = IntArray(5) { 42 } //[42, 42, 42, 42, 42]
val c1 = IntArray(5){it + 1} //[0, 1, 2, 3, 4]
println(c1.contentToString)
同理,可以使用
FloatArray
,BooleanArray
等,其中contentToString
是kotlin中数组的便捷打印方法
除却前述原型数组的创建,Kotlin中还有装箱类型数组和对象类型数组,常用方法有arrayOf()
、arrayOfNulls()
、Array()
、emptyArray()
var arr1 = arrayOf(1,2,3,4,5) // Array,等价于[1,2,3,4,5]
var arr2 = arrayOfNulls(3) //Array,其arr3[0]、arr3[1]、arr3[2]皆为null
arr3[0] = 10
var arr3 = Array(5,{index -> (index * 2).toString() }) //等价于 ["0","2","4","6","8"]
val arr4 = Array(5) { i -> (i * i).toString() } //等价于 ["0", "1", "4", "9", "16"]
val arrayEmpty = emptyArray() //相当于arrayOfNulls(0)
与Java一样,Kotlin的array默认大小就是给定的,不过kotlin提供了一个添加元素的api,plus()
public actual operator fun Array.plus(elements: Array): Array {
val thisSize = size
val arraySize = elements.size
val result = java.util.Arrays.copyOf(this, thisSize + arraySize)
System.arraycopy(elements, 0, result, thisSize, arraySize)
return result
}
Kotlin数组的plus()
函数其实是拷贝原来的数组,返回一个新数组
var array4 = arrayOf("1",2,3)
array4.plus("1")
for (i in array4){
println(i) //array4不变,所以打印结果是1、2、3,没有4
}
fill()
数组填充
array
提供了fill
函数,给原有的数组填充数组(如果原来有数据将会被覆盖)
var array4 = arrayOf("1","2","3")
array4.fill("4",0,2) //往数组里填入数据"4",从下标0到2,不包含2,
//打印出来结果为4、4、3
数组遍历
kotlin中数组的遍历方式主要如下三种:
val e = floatArrayOf(1f,2f,3f,4f)
//方式一,使用for in方式
for(element in e) {
println(element)
}
//方式二,使用forEach高阶函数方式遍历
e.forEach{element ->
println(element)
}
//方式三,使用数组下标遍历
for(index in e.indices) {
println("index=$index") //输出0、1、2、3,输出数组下标
}
2.4 Range
Kotlin中Range
表示区间的意思,即范围,其中除了until是前闭后开,其他都是前闭后闭。
val range: IntRange = 0..1000 //表示[0,1000],前闭后闭区间
val range1 = 0..1000 step 2 //表示[0,1000],步长为2
val range1: IntRange = 0 until 1000 //表示[0,1000),前闭后开
val charRange = 'a'..'z'
val intRangeReverse = 10 downTo 1 //[10,1]
除了IntRange
,还有CharRange
和LongRange
val floatRange = 1f..2f //连续区间,不可数
val doubleRange = 2.0..4.0 //连续区间,不可数
Range
一般是用于遍历
val range = 0..1000
// 步长为 2,输出:0, 2, 4, 6, 8, 10,....1000,
for (i in range step 2) {
print("$i, ")
}
//输出:4, 3, 2, 1,
for (i in 4 downTo 1) {
print("$i, ")
}
三、控制流
[图片上传失败...(image-c1b5dc-1644831342962)]
Kotlin中的控制流语句大体与Java相似,废除了switch
语句,新增了When
分支语句
if else语句
var max = a
if (a < b) max = b
if (a > b) {
max = a
} else {
max = b
}
需要特别注意的是,Kotlin中不存在三元运算,直接用if....else替代
var numB: Int = (numA > 2) ? 3 : 5 //直接报错
val max = if (a > b) a else b
Kotlin
中的if
可以作为一个表达式并返回一个值,每一个if分支都是一个代码块,并且返回了一个值。
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
for循环语句
Kotlin
废除了Java
中的for(初始值;条件;增减步长)
这个规则,其提供了迭代器用来遍历任何东西
语法格式为:
for (item in collection) print(item)
-
数字区间上迭代遍历
// 递增,范围:until[n,m),关键字until,左闭右开
for (i in 0 until 5){
print("i => $i \t")
}
//递减,范围:downTo[n,m],关键字downTo,左右闭合
for (i in 15 downTo 11){
print("i => $i \t")
}
// 递增,范围..[n,m],关键符号 .. ,左右闭合
for (i in 20 .. 25){
print("i => $i \t")
}
//设置循环步长,关键字step
for (i in 10 until 16 step 2){
print("i => $i \t") //10 12 14
}
- 遍历字符串
```kotlin
for (i in "abcdefg"){
print("i => $i \t") //i => a i => b ......
}
-
遍历数组
var arrayListOne = arrayOf(10,20,30,40,50)
for (i in arrayListOne){
print("i => $i \t") //i => 10 i => 20 ......
}
var arrayListTwo = arrayOf(1,3,5,7,9)
for (i in arrayListTwo.indices){
println("arrayListTwo[$i] => " + arrayListTwo[i])
}
// arrayListTwo[0] => 1
// arrayListTwo[1] => 3
//......
var arrayListTwo = arrayOf(1,3,5,7,9)
for ((index,value) in arrayListTwo.withIndex()){
println("index => value")
}
//index => 0 value => 1
//index => 1 value => 3
//......
- 列表或数组扩展函数遍历
```kotlin
var arrayListThree = arrayOf(2,'a',3,false,9)
var iterator: Iterator = arrayListThree.iterator()
while (iterator.hasNext()){
println(iterator.next())
}
when表达式
Kotlin
中废除掉Java
中的switch
语句,新增when
语句
简单形式
when (2) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}
// x == 2
多分支处理
//x = 0,1,输出内容一样
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
分支条件
可以用任意表达式(而不只是常量)作为分支条件
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
检查特定类型值
可以检测一个值是(is
)或者不是(!is
)一个特定类型的值,类似于Java中的instanceof
关键字
when("abc"){
is String -> println("abc是一个字符串")
else -> {
println("abc不是一个字符串")
}
}
不使用表达式,取代if
when
也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
while语句
同Java中while循环一样
do...while语句
同Java中do...while循环一样
break与continue
Kotlin 支持传统的 break 与 continue 操作符
-
return
:默认从最近的封闭函数或匿名函数返回 -
break
:终止最近的闭合循环 -
continue
:前进到最近的封闭循环的下一个步骤(迭代)。
四、函数
Kotlin中,函数有自己的类型,是“一等公民”,其可以赋值给变量,可以作为参数传递,也可以直接调用
4.1 函数声明
//java
Food cook(String name) {
//......
}
// kotlin
fun cook(name : String) : Food {
// .....
}
函数声明
Kotlin中函数声明的关键字为:fun
,声明格式为
// 可见性修饰符 fun 函数名(参数名 : 类型,...) : 返回值{方法体}
fun double(x: Int): Int {
return 2 * x
}
在Kotlin中定义的函数无返回值时,返回Unit或忽略即可
fun abc(): Unit{}
fun abc(){}
Receiver
接收者
这里只要牢记一点:函数的Receiver
是它的调用者
class Foo {
fun bar() = println("bar")
}
Foo foo = Foo()
foo.bar() // foo实例对象就是bar方法的Receiver
函数类型
顶级函数的类型
// 顶级函数的类型
fun foo() {...} // () -> Unit
fun foo(p0: Int): String {...} // (Int) -> String
成员函数的类型
// 类中函数的类型
class Foo {
fun bar(p0: String, p1: Long): Any {...} // Foo.(String, Long) -> Any
}
4.2 函数调用
在前述我们就说了,在Kotlin
中函数是“一等公民”,可以赋值,可以作为参数传递
函数的引用
这里要注意的是,函数名不是函数引用,Kotlin中提供了双冒号::
限定符,将函数名转成函数引用。
关于函数的引用,主要分为顶层、局部、扩展函数引用与类成员函数引用
// 定义一个顶级函数
fun foo() {...} //() -> Unit
// 获取顶级函数foo的引用
val block = ::foo //block的数据类型为:KFunction0,其中0表示没有函数参数,Unit表示无返回值
private fun test(aa:Int) :String{...}
val block = ::test //block数据类型为:KFunction1
// 定义一个成员函数
class Foo {
fun bar(p0: String, p1: Long): Any {...}
}
// 获取成员函数的引用,通过类名::方法名
val block = Foo::bar // KFunction3 ,其函数引用对应的类型就会在参数列表第 1 位多一个 Receiver 参数(用于接收方法依赖的对象实例)
//赋值给变量
val f1: () -> Unit = ::foo
val f2: (Int) -> String = ::foo
val f3: (Foo, String, Long) -> Any = Foo::bar
// 因为类型可推导,可以去掉函数类型声明
val f = ::foo
val f = ::foo
val f = Foo::bar
对于object
或者companion object
中的方法,可以像顶层函数一样直接调用,不依赖对象实例。
注意:以下二者是等价的
Foo.(String, Long) -> Any
(Foo, String, Long) -> Any
函数使用与调用
传统方法调用函数:
val result = double(2)
使用点表示法调用成员函数:
class Test{
fun foo(){}
}
Test().foo() //创建类Test实例并调用foo()
将函数对象作为函数的某个参数进行调用:
class Foo {
fun bar(p0: String, p1: Long): Any {
println(p0 + p1)
return p0
}
}
fun test(block: Foo.(String, Long) -> Any) {
// 调用p函数
block(Foo(), "Ryan", 3L)
// 或者
block.invoke(Foo(), "Ryan", 3L)
}
val block: (Foo,String,Long) -> Any = Foo::bar
test(block)
函数的引用可以通过invoke()
操作符调用:block.invoke(...)
或者block(...)
fun add(arg0: Int,arg1: Int): Int {
return arg0 + arg1
}
fun test(int: Int,block:(Int,Int) -> Int): Int{
return int + block.invoke(12,13)
}
fun main(args: Array) {
val addVariant : (Int,Int) -> Int = ::add
println(test(12,addVariant))
println(test(12){arg1:Int,arg2:Int ->
arg1 + arg2
})
}
下面再通过一个示例重点演示函数的调用
class Bird {
var name:String = "喜鹊"
fun fly() {
println("$name fly")
}
fun bar(p0: String, p1: Long): Any {
return p0 + name + p1
}
}
fun main() {
val tt = Bird::bar
test(tt)
test { bird, string, long ->
return@test bird.name + string + long
}
test1(tt)
test1 { s, l ->
return@test1 this.name + s + l
}
val ff = Bird::fly
test2(ff)
test2 {
this.name = "老鹰"
println(this.name + "fly")
}
}
fun test(a:(Bird, String, Long) -> Any) {
println(a.invoke(Bird(),"春天",1L))
}
fun test1(b: Bird.(String, Long) -> Any) {
println(b(Bird(),"夏天",12))
}
fun test2(b: Bird.() -> Unit) {
println("test2")
val bird = Bird()
bird.name = "麻雀"
b.invoke(bird)
}
春天喜鹊1
喜鹊春天1
夏天喜鹊12
喜鹊夏天12
test2
麻雀 fly
test2
老鹰fly
通过前述示例可知
- 对于成员函数的引用,
(Bird, String, Long) -> Any
与Bird.(String, Long) -> Any
是等价的 - 对于
A.(B) -> C
这种形式的引用,可以使用this
表达式访问接收者对象
4.3 函数参数
在Kotlin中,函数参数是一个值得重点关注的部分
参数
函数参数使用 Pascal
表示法定义,即 name: type
。参数用逗号隔开,每个参数必须有显式类型:
fun powerOf(number: Int, exponent: Int) { /*……*/ }
函数的参数也可以为可空类型
var myName : String? = "rengwuxian"
fun cook(name: String) : Food {}
cook(myName) //报错,传了一个可空参数
var myName : String? = "rengwuxian"
fun cook(name: String?) : Food {}
cook(myName) //正常运行
默认参数
函数参数可以有默认值,省略相应的参数时使用默认值。默认值通过类型后面的 =
及给出的值来定义。
fun read(b: Array, off: Int = 0, len: Int = b.size) { /*……*/ }
具有默认参数时,可以对参数有默认值的参数不传递参数值
覆盖方法总是使用与基类型方法相同的默认参数值。 当覆盖一个带有默认参数值的方法时,必须从签名中省略默认参数值
open class A {
open fun foo(i: Int = 10) { /*……*/ }
}
class B : A() {
override fun foo(i: Int) { /*……*/ } // 不能有默认值
}
具名参数
即在使用函数时显示使用参数名
= 参数值
这种方式传递参数
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
/*……*/
}
reformat(str) //使用默认参数调用
reformat(str, true, true, false, '_') //使用非默认参数调用
//使用具名参数调用
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
reformat(str, wordSeparator = '_') //使用部分具名参数调用
关于默认参数与具名参数,需要注意的是:如果一个默认参数在一个无默认值参数前,该无默认值参数的使用只能通过具名参数调用,所有位置参数要放在第一个具名参数之前
fun foo(bar: Int = 0, baz: Int) { /*……*/ }
foo(baz = 1) // 使用默认值 bar = 0
简单来说就是,允许调用 f(1, y = 2)
但不允许 f(x = 1, 2)
如果在默认参数之后最后一个参数是lambda表达式,那么它既可以作为具名参数在括号内传入,也可以在[括号外]传入
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*……*/ }
foo(1) { println("hello") } // 使用默认值 baz = 1
foo(qux = { println("hello") }) // 使用两个默认值 bar = 0 与 baz = 1
foo { println("hello") } // 使用两个默认值 bar = 0 与 baz = 1
变长参数
函数中的参数是不定数量的个数并且是同一个类型,则可是使用vararg
修饰符去修饰这个变量,则被vararg
修饰的参数相当于一个固定类型的数组
fun 函数名(vararg 参数名 : 类型,...) :返回值{}
fun asList(vararg ts: T): List {
val result = ArrayList()
for (t in ts) // ts is an Array
result.add(t)
return result
}
val list = asList(1, 2, 3)
4.4 本地函数
本地函数即可以在函数内部再声明一个函数仅供外部函数使用
假设一个普通登录校验函数如下:
fun login(user: String, password: String, illegalStr: String) {
// 验证 user 是否为空
if (user.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
// 验证 password 是否为空
if (password.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
}
若检查参数这部分有点冗余,又不想作为一个单独函数对外暴露,可以使用嵌套函数,在内部声明一个函数:
fun login(user: String, password: String, illegalStr: String) {
fun validate(value: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(illegalStr)
}
}
validate(user)
...
}
前述的嵌套函数在login函数外是无法访问的
一般情况下不建议使用本地函数,具体原因可以反编译为java函数看其内部实现可知
4.5 重载函数
在Java中是没有默认值参数的概念,当我们需要从Java
中调用Kotlin
中的默认值重载函数的时候,必须显示的指定所有参数值
在kotlin中
fun f(a: String, b: Int = 0, c: String="abc"){
...
}
相当于在java中声明:
void f(String a, int b, String c){
}
kotlin中给出了一种方案,就是使用@JvmOverloads
注解,这样就会自动生成多个重载方法供Java调用
@JvmOverloads fun f(a: String, b: Int=0, c:String="abc"){
}
相当于在java中声明了如下三个方法:
void f(String a)
void f(String a, int b)
void f(String a, int b, String c)
该注解也可以用在构造方法和静态方法中
4.6 其他
单表达式
当函数返回单个表达式时,可以省略花括号并且在 =
符号之后指定代码体即可
fun test1() = 2 // 自动推断为:返回类型为Int
fun test2(num : Int) = num * 2 // 自动推断为:返回类型为Int
fun test3(x : Float, y : Int = 2) = x * y // 和默认参数一起使用,返回值为Float型
多返回值
Kotlin中可以使用Pair
或Triple
实现函数多返回值,最后通过结构声明将值取出。
fun multiReturnValues(): Triple {
return Triple(1, 3L, 4.0)
}
// 通过解构语法获取多返回值
val (a, b, c) = multiReturnValues()
五、字符串
关于字符串的基本使用在前述数据类型时已经介绍过了,这里简单介绍一下一些常用方法,方法使用时查阅
字符串属性
属性 | 描述 |
---|---|
length: Int |
它返回字符串序列的长度。 |
indices: IntRange |
它返回当前char 序列中有效字符索引的范围。 |
lastIndex: Int |
它返回char 序列中最后一个字符的索引。 |
val str : String = "Hello world!"
println("strLength = ${str.length}") //strLength = 12
println("strIndices = ${str.indices}") //strIndices = 0..11
println("strLastIndex = ${str.lastIndex}") //strLastIndex = 11
字符串查找
函数 | 描述 |
---|---|
first() |
获取第一个元素,为空抛出异常 |
firstOrNull() |
获取第一个元素,为空返回null |
last() |
获取最后一个元素,为空抛出异常 |
lastOrNull() |
获取最后一个元素,为空返回null |
find{} |
高阶函数,对firstOrNull() 进行处理 |
findLast{} |
高阶函数,对lastOrNull() 进行处理 |
indexOf() |
查找某一个元素或字符串在原字符串中第一次出现的下标。 |
indexLastOf() |
查找某一个元素或字符串在原字符串中最后一次出现的下标。 |
indexOfFirst{} |
同indexOf() |
indexOfLast{} |
同indexLastOf() |
val str : String = "Hello world!"
println("strFirst = ${str.first()}") // strFirst = H
println("strFind = ${str.find { it == 'H' }}") // strFind = H
println("strFind = ${str.find { it == 'W' }}") // strFind = null
println("strLast = ${str.last()}") // strLast = !
println("strIndexOfFirst = ${str.indexOfFirst { it == 'H'}}") //strIndexOfFirst = 0
println("strIndexOf = ${str.indexOf("Hello",0)}") //strIndexOf = 0
字符串截取
函数 | 描述 |
---|---|
subString() |
截取字符串 |
subSequence() |
截取字符串 |
val str = "Kotlin is a very good programming language"
// 当只有开始下标时,结束下标为length - 1
println("subStr = ${str.substring(10)}") //subStr = a very good programming language
println(str.substring(0,15)) // Kotlin is a ver
println(str.substring(IntRange(0,15))) //Kotlin is a very
println(str.subSequence(0,15)) //Kotlin is a ver
println("subSeq = ${str.subSequence(IntRange(0,15))}") //subSeq = Kotlin is a very
字符串替换
函数 | 描述 |
---|---|
replace(oldChar,newChar,ignoreCase = false) |
把原字符串中的某一个字符全部替换成新的字符。然后返回新的字符串 |
replace(oldValue,newValue,ignoreCase = false) |
把原字符串中的某一个字符全部替换成新的字符。然后返回新的字符串 |
replace(regex,replacement) |
根据定义的正则规则去匹配源字符串,把满足规则的字符串替换成新的字符串。 |
replace(regex: Regex, noinline transform: (MatchResult) -> CharSequence) |
根据定义的正则规则去匹配源字符串,把满足规则的字符串通过transform{} 高阶函数映射的新字符串替换。 |
replaceFirst() |
满足条件的第一个字符或字符串替换成新的字符或字符串 |
replaceBefore() |
截取满足条件的第一个字符或字符串后面的字符串,包含满足条件字符或字符串自身,并在其前面加上新的字符串。 |
replaceBeforeLast() |
截取满足条件的最后一个字符或字符串后面的字符串,包含满足条件字符或字符串自身,并在其前面加上新的字符串。 |
replaceAfter() |
截取满足条件的第一个字符或字符串前面的字符串,包含满足条件字符或字符串自身,并在其后面加上新的字符串。 |
replaceAfterLast() |
截取满足条件的最后一个字符或字符串前面的字符串,包含满足条件字符或字符串自身,并在其后面加上新的字符串。 |
// 字符替换
val str = "Kotlin is a very good programming language"
println(str.replace('a','A')) //Kotlin is A very good progrAmming lAnguAge
//字符串替换
val str = "Kotlin is a very good programming language"
println(str.replace("Kotlin","Java")) //Java is a very good programming language
//正则匹配替换字符串
val str = "1234a kotlin 5678 3 is 4"
println(str.replace(Regex("[0-9]+"),"kotlin")) //kotlina kotlin kotlin kotlin is kotlin
//正则匹配并通过transform{}映射替换字符串
val str = "1234a kotlin 5678 3 is 4"
val newStr = str.replace(Regex("[0-9]+"),{
"abcd "
}) //abcd abcd abcd abcd a kotlin abcd abcd abcd abcd abcd is abcd
// replaceFirst()
val str = "Kotlin is a very good programming language"
println(str.replaceFirst('a','A')) //Kotlin is A very good programming language
println(str.replaceFirst( "Kotlin","Java")) //Java is a very good programming language
//replaceBefore()
val str = "Kotlin is a very good programming language"
println(str.replaceBefore('a',"AA")) //AAa very good programming language
println(str.replaceBefore("Kotlin","Java")) //JavaKotlin is a very good programming language
//replaceBeforeLast()
val str = "Kotlin is a very good programming language"
println(str.replaceBeforeLast('a',"AA")) //AAage
println(str.replaceBeforeLast("Kotlin","Java")) //JavaKotlin is a very good programming language
//replaceAfter()
val str = "Kotlin is a very good programming language"
println(str.replaceAfter('a',"AA")) //Kotlin is aAA
println(str.replaceAfter("Kotlin","Java")) //KotlinJava
//replaceAfterLast()
val str = "Kotlin is a very good programming language"
println(str.replaceAfterLast('a',"AA")) //Kotlin is a very good programming languaAA
println(str.replaceAfterLast("Kotlin","Java")) //KotlinJava
字符串分割
函数 | 描述 |
---|---|
split() |
分割函数 |
splitToSequence() |
分割函数 |
//使用正则表达式分割
var str2 = "1 kotlin 2 java 3 Lua 4 JavaScript"
val list3 = str2.split(Regex("[0-9]+"))
for (str in list3){
print("$str \t")
}
//kotlin java Lua JavaScript
val list4 = str2.split(Pattern.compile("[0-9]+")) //与前述等价
//使用字符或字符串分割
val str1 = "Kotlin is a very good programming language"
val list2 = str1.split(" ")
val str3 = "a b c d e f g h 2+3+4+5"
val list5 = str3.split(' ','+')
splitToSequence()
使用方法与split()
一样
other
函数 | 描述 |
---|---|
count() |
获取字符串长度 |
count{} |
高阶函数 |
isEmpty() |
判断其length 是否等于0 ,不可用于可空字符串 |
isNotEmpty() |
判断其length 是否大于0 ,不可用于可空字符串 |
isNullOrEmpty() |
判断该字符串是否为null 或者其length 是否等于0 |
isBlank() |
判断其length 是否等于0 ,或者判断其包含的空格数是否等于当前的length 。不能直接用于可空的字符串 |
isNotBlank() |
isBlank() 函数取反 |
isNotOrBlank() |
判断该字符串是否为null 。或者调用isBlank() 函数 |
plus() |
字符串链接 |
reversed() |
字符串反转 |
startsWith() |
判断字符串的起始 |
endsWith() |
判断字符串的结尾 |
// count()
val str = "kotlin very good"
println("str.count() => ${str.count()}") //str.count() => 16
// count{}
val str = "kotlin very good"
val count = str.count { it == 'o' }
println("count : $count") //count : 3
//字符串验证
val str : String? = "kotlin"
println(str?.isEmpty()) //false
println(str?.isNotEmpty()) //true
println(str.isNullOrEmpty()) //false
println(str?.isBlank()) //false
println(str?.isNotBlank()) //true
println(str.isNullOrBlank()) //false
//字符串链接
val oldStr = "kotlin"
println(oldStr.plus(" very good")) //kotlin very good
println(oldStr + " very good") //kotlin very good
//判断字符串起始
val str = "kotlin"
str.startsWith('k') // 是否有字符`k`起始
str.startsWith("Kot") // 是否由字符串`kot`起始
str.startsWith("lin",3) // 当起始位置为3时,是否由字符串`lin`起始
// true true true
//判断字符串结尾
val str = "kotlin"
println(str.endsWith("lin")) // 是否由字符串`lin`结尾
println(str.endsWith('n')) // 是否由字符`n`结尾
// true ture
更多字符串操作可以参看标准库
六、顶层函数
在前述内容介绍时也曾多次涉及到顶层函数,那么什么是顶层函数?为什么用顶层函数替代Java
中static
函数?
在Java
中有静态函数和静态属性概念,它们一般作用就是为了提供一个全局共享访问区域和方法。我们一般的习惯的写法就是写一个类包裹一些static
修饰的方法,然后在外部访问的直接利用类名.方法名访问。
静态函数内部是不包含状态的,它的输入仅仅来自于它的参数列表,而它的输出也仅仅依赖于它参数列表。
在Kotlin
中则认为一个函数或方法有时候并不是属于任何一个类,它可以独立存在。所以在Kotlin中类似静态函数和静态属性会去掉外层类的容器,一个函数或者属性可以直接定义在一个Kotlin
文件的顶层中,在使用的地方只需要import
这个函数或属性即可。
如果你的代码还存在很多以"Util"后缀结尾的工具类,是时候去掉了。
基本使用
创建一个顶层文件:
在顶层文件定义函数
//这个顶层函数不属于任何一个类,不需要类容器,不需要static关键字
fun formateFileSize(size: Double): String {
if (size < 0) {
return "0 KB"
}
val kBSize = size / 1024
if (kBSize < 1) {
return "$size B"
}
val mBSize = kBSize / 1024
if (mBSize < 1) {
return "${BigDecimal(kBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} KB"
}
val mGSize = mBSize / 1024
if (mGSize < 1) {
return "${BigDecimal(mBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} MB"
}
val mTSize = mGSize / 1024
if (mTSize < 1) {
return "${BigDecimal(mGSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} GB"
}
return "${BigDecimal(mTSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} TB"
}
顶层函数实质原理
- 顶层文件会反编译成一个容器类。
- 顶层函数会反编译成一个static静态函数
七、中缀调用
中缀调用:对只有一个参数的函数调用进行简化,省略了类名或者对象名+"."+函数名调用方式
val map = mapOf(Pair(1, "A"), Pair(2, "B"), Pair(3, "C"))
//前述等价于
val map = mapOf(1.to("A"), 2.to("B"), 3.to("C"))
//前述亦等价于 to函数的中缀调用
val map = mapOf(1 to "A", 2 to "B", 3 to "C")
//to实际上一个返回Pair对象的函数
val strA = "A"
val strB = "B"
if (StringUtils.equals(strA, strB))
//前述等价于 sameAs的中缀调用
if (strA sameAs strB)
private infix fun String.sameAs(strB: String): Boolean {
return this == strB
}
val list = listOf(1, 3, 5, 7, 9)
val element = 2
if (list.contains(element))
//前述等价于 into的中缀调用
if (element into list)
基本使用
中缀调用类似运算符使用,调用结构为:A (中缀函数名) B
例如: element into list
实质原理
to函数
public infix fun A.to(that: B): Pair = Pair(this, that)
使用
infix
关键字修饰的函数,传入A,B两个泛型对象,“A.to(B)”结构是一种特殊结构暂时把它叫做带接收者的结构,后面的this就是指代A,并且函数的参数只有一个,返回的是一个Pair对象。
this
指代A,that
就是传入的B类型对象。
into函数
infix fun T.into(other: Collection): Boolean = other.contains(this)
使用
infix
关键字修饰的函数,泛型T元素是否存在于泛型T集合之中。
从前述可知,使用中缀调用的函数都是infix
修饰的函数,也称为infix函数。
如何写一个infix函数?必须满足如下要求:
- 必须是成员函数或扩展函数
- 必须只有一个参数
- 方法前必须加
infix
关键字 - 其参数不得接收可变数量参数且不能有默认值
/**
* 中缀成员函数
*/
class Student {
var kotlinScore = 0.0
infix fun addKotlinScore(score: Double): Unit {
this.kotlinScore = kotlinScore + score
}
}
val student = Student()
student addKotlinScore 95.00
print(student.kotlinScore) // will print "95.0"
/**
* 中缀扩展函数
*/
package foo.bar; //将扩展方法放到包内,作用域将是整个包
infix fun Int.ride(num: Int): Int{
println("num= $num")
return 2 * num
}
import foo.bar.* //使用时需要导包
object MyTest {
@JvmStatic
fun main(arg: Array) {
println(3.ride(2))
}
}
中缀函数优先级
-
中缀函数调用的优先级低于算术操作符、类型转换以及 rangeTo 操作符
1 shl 2 + 3 等价于 1 shl (2 + 3) 0 until n * 2 等价于 0 until (n * 2) xs union ys as Set<*> 等价于 xs union (ys as Set<*>)
-
中缀函数调用的优先级高于布尔操作符 && 与 ||、is- 与 in- 检测以及其他一些操作符。
a && b xor c 等价于 a && (b xor c) a xor b in c 等价于 (a xor b) in c
八、运算符重载
首先看一下常见的运算符
== // 对应 equals
+ // 对应 plus
- // 对应 minus
* // 对应 times
/ // 对应 div
% // 对应 rem
a++ // 对应 inc
a..b // 对应 rengeTo
a in b // 对应 contains
a[i] // 对应 get
a() // 对应 invoke
...
Kotlin支持运算符重载,可重载的运算符列表参见官方文档
Kotlin通过调用自己代码中定义特定的函数名的函数(成员函数或者扩展函数),并且用
operator
修饰符标记,来实现特定的语言结构,例如:如果你在一个类上面定义了一个特定函数命名plus
的函数,那么按照Kotlin的约定,可用在这个类的实例上使用+
运算符。
运算符重载示例:
// 重载String中的-运算符
operator fun String.minus(other: String): String {
return this.replaceFirst(other.toString(), "")
}
val a = "HelloWorld"
val b = "World"
val c = a - b // "Hello"