文章目录
- 第1章 基础类型
- 第2章 运算符和表达式
- 第3章 流程控制语句(顺序,分支,循环)
- 第4章 数组和集合
- 第5章 函数和lambda表达式
- 第6章 面向对象
- 第7章 异常
- 第8章 泛型
- 第9章 注解
- 第10章 Kotlin和Java互相调用
- Kotlin图片处理
第1章 基础类型
1.1 注释
1.1.1单行注释
// 单行注释
1.1.2多行注释
/*
多行注释
多行注释
*/
1.1.3文档注释
/**
* 我是
* 文档
* 注释
*/
1.2. 变量
1.2.1 分隔符
分号(😉
作用:分割 结尾
单行内有一条语句:可以以分号结尾,也可以不以分号结尾
单行内超过一条语句:语句分割的必须加;最后一条语句可以不加;
一条语句跨行的三种方式:
//1.表达式跨行
var str = "hello"
str +=
"world"
//2.调用方法跨行
var method = str
.startWith("fk")
//3.访问属性跨行
var len = str
.length
字符串不能跨行:
var a = "sss
ssss" //这种形式的错误的
花括号({})
作用:定义一个代码块,类体部分,枚举,方法体,条件语句中的执行体,循环语句中的循环体放在花括号里面
方括号([])
作用:索引运算符,访问数组元素,访问List集合元素和Map集合元素
圆括号(())
作用:定义函数,定义方法,声明形参,调用函数时使用圆括号来传入实参值,运算式中优先计算
空格( )
作用:分隔一条语句的不同部分
分类:空格符(Space)/制表符(Tab)/回车符(Enter)
圆点(.)
作用:用作类/结构体/枚举/实例和他的成员属性和方法之间的分隔符
1.2.2 标识符规则
定义:所谓标识符,就是用于给变量/类/枚举/函数等命名的名字.
规则:
- 字符/数字/下划线_组成,不能以数字开头
- 可以包含中文字符/日文字符
- 不能是kotlin关键字,但可以包含关键字
- 不能包含空格
- 不能包含@#等特殊字符
1.2.3 关键字
分类:
- 硬关键字
- 软关键字
- 修饰符关键字
1.2.4 声明变量
先声明,后使用,必须显示或隐式指定变量的类型
声明方式:
使用var或val修饰
- var为值可变,可以被多次赋值
- val为值不可变,只可以赋值一次,如果再被const修饰那就 相当于常量
var|val 变量名 [:类型] [=初始值]
程序要么通过":类型"的形式显示指定该变量的类型,要么为该变量直接指定初始值(kotlin会根据值确定变量类型)
var b :Int
var name = "张三" //String类型
var age :Int = 25
var sun : String = 500 //错误
val book = "你好世界"
book = "世界你好啊啊啊" //错误,不可以重新赋值
Kotlin常量有两种:
- 局部范围常量:允许在声明时不指定初始值,第一次使用之前指定即可.
- 类的常量属性:既可以在声明时指定初始值,也可在类或结构体的构造中指定初始值.
val maxAge = 120 //没有显示指定类型,编译器根据初始值指定常量的类型
val eduName : String = "成长" //执行初始值和类型
val herName : String //不指定初始值
1.3. 整型(4种)
Kotlin的整型不是基本类型,而是引用类型,都可以调用方法,访问属性,通过类型.MIN_VALUE和MAX_VLAUE来获取对应类型的最大值和最小值
- Kotlin不支持八进制正数
- 支持二进制 0b 或 0B开头
- 支持十进制
- 支持十六进制 0x 或 0X
类型名 | 占位 | 表示范围 |
---|---|---|
Byte | 8位 | -128 ~ 127 |
Short | 16位 | -32768~32767 |
Int | 32位 | -2147483648~2147483647 |
Long | 64位 | -9223372036854775808~9223372036854775807 |
定义方式
var a : Int = 56 //正确
var bigValue : Int = 2999999999 //错误,因为Int存不下这个数字
var bigValue2 : Long = 299999999 //正确
var binValue1 = 0b1010101 //定义了二进制数字
var binValue2 = 0B1010101 //定义了二进制数字
var hexValue1 = 0x13
var hexValue2 = 0X13
允许Int支持空,默认是不支持的,在类型后面加? 就代表允许为空,变成了包装类型Integer类型
var int1 : Int = null //错误,不允许为空
var int1 : Int? = null //正确,不允许为空 类型为 Integer
//===========================================
var num1 : Int = 100 //类型为Int
var num2 : Int = 100 //类型为Int
println(num1 === num2) //基本类型之间比较,输出true
var num3 : Int = 100 //类型为Int
var num4 : Int? = 100 //类型为Integer
println(num3 === num4) //基本类型和包装类型之间比较,输出false 因为不是一个类型
===运算符代表要求两个变量引用同一个对象
查看最大值和最小值
//Byte的最大值和最小值
val aByte: Byte = Byte.MAX_VALUE
val bByte: Byte = Byte.MIN_VALUE
println("Byte的最大值" + aByte)
println("Byte的最大值${bByte}")
println("=================")
//Short的最大值和最小值
val aShort: Short = Short.MAX_VALUE
val bShort: Short = Short.MIN_VALUE
println("Short的最大值" + aShort)
println("Short的最大值${bShort}")
println("=================")
//Int的最大值和最小值
val aInt: Int = Int.MAX_VALUE
val bInt: Int = Int.MIN_VALUE
println("Int的最大值" + aInt)
println("Int的最大值${bInt}")
println("=================")
//Long的最大值和最小值
val aLong: Long = Long.MAX_VALUE
val bLong: Long = Long.MIN_VALUE
println("Long的最大值" + aLong)
println("Long的最大值${bLong}")
1.4. 浮点型(2种)
浮点型数值可以包含小数部分,可以存储比Long更大或更小的数
分类:
类型名 | 占位 | 表示范围 |
---|---|---|
Float | 32位 | 小数,精确到6位 |
Double | 64位 | 小数,精确到15-16位 |
定义
var a = 5.123456f
var b : Float = 25.678f
var c = 0.0
查看最大值和最小值
//Float的最大值和最小值
val aFloat: Float = Float.MAX_VALUE
val bFloat: Float = Float.MIN_VALUE
println("Float的最大值" + aFloat)
println("Float的最大值${bFloat}")
println("=================")
//Double的最大值和最小值
val aDouble: Double = Double.MAX_VALUE
val bDouble: Double = Double.MIN_VALUE
println("Double的最大值" + aDouble)
println("Double的最大值${bDouble}")
三个特殊的浮点型数值
类型 | 获取方式 |
---|---|
正无穷大 | 一个正数除以0.0 |
负无穷大 | 一个负数除以0.0 |
非数 | 0.0除以0.0 |
注意:(如果是除以0会引发错误,只可以用浮点数除以0.0)
1.5. 字符型 Char
字符型用于表示单个字符,必须用单引号( ’ )括起来,Kotlin使用16位的Unicode字符集作为编码方式.
字符型值的3种表示形式:
- ‘A’ 直接单引号包裹字符
- ‘\n’ 通过反斜杠转义字符形式
- ‘\u0008’ 通过Unicode编码形式
var aChar : Char = 'a'
var enterChar : Char = '\r'
var ch : Char = '\u0008'
Char类型和Java中的Char类型不一样,它就是简简单单的字符串,不能当成整数值使用.但是可以运算
但是可以通过Int类型调用toChar()来进行算术计算
并且Kotlin为两个Char类型提供了 加减运算支持:
- Char型值加/减一个整型值
- 两个Char型值进行相减
var c1 = "i"
var c2 = "k"
println(c1 + 4) //m
println(c1 - 4) //e
println((c1 - c2)) //-2
1.6. 数值之间的类型转换
Kotlin是强类型语言,编译时对类型检查很严格.
1.6.1 整型之间的转换
类型转换时必须选择合适的类型,避免类型转换带来的数据损失
Kotlin为所有数据类型都提供了如下的方法进行转换:
方法名 | 作用 |
---|---|
toByte() | 转换为Byte类型 |
toShort() | 转换为Short类型 |
toInt() | 转换为Int类型 |
toLong() | 转换为Long类型 |
toFloat() | 转换为Float类型 |
toDouble() | 转换为Double类型 |
toChar() | 转换为Char类型 |
Kotlin要求不同整型的变量或值之间必须进行显示转换.
var book : Byte = 79
var item : Short = 120
//var a : Short = book //错误,类型转换错误
var a : Short = book.toShort() //正确,通过方法显示转换
//============================
var c : Int = 233
//将Int类型转换为Byte类型,发生溢出
val byteVolue : Byte = c.toByte()
1.6.2 浮点型与整形之间转换
Float和Double之间需要显示转换,浮点型和整型之间也需要显示转换
Float转换为Double
var width : Float = 2.3f
var height : Double = 4.5
var a : Double = width.toDouble()
Double转换为Float
var width : Float = 2.3f
var height : Double = 4.5
var a : Float = height.toFloat()
自动类型提升为Double
var width : Float = 2.3f
var height : Double = 4.5
var a = width * height //自动提升为Double类型
1.6.3 表达式类型的自动提升
当一个算术表达式包含多个数值型的值时,整个算术表达式的数据类型将发生自动提升.
规则:
- 所有的Byte/Short类型被提升到Int类型
- Byte -> Short -> Int -> Long -> Float ->Double
如果表达式中包含String字符串,字符串和数值相加时候,这个时候就变成了字符串连接,而不是加法运算.
要注意分清楚前面是字符和数字运算 还是 字符串情况:
println("Hello!" + 'a' + 7) //输出Hello!a7
println('a' + 7 + "Hello!") //输出hHtllo! 因为前面先计算 ,后 和字符串进行的相加
1.7 Boolean类型
真 和 假 true和false
用在if-else判断里面
1.8 null安全
null安全是Kotlin语言对Java的重大改进之一,避免了NullPointerException
1.8.1 非空类型和可空类型 ?
非空类型定义方式
var int1 : Int = null //非空类型
可空类型定义方式
var int1 : Int? = null //可空类型,类型为 Integer
判断是否为可空类型
var n = str.toIntOrNull() //调用字符串的toIntOrNull()方法
1.8.2 先判断后使用
可空类型的变量,不允许直接调用方法和属性,可以先判断,该变量不为空,然后再调用方法和属性.
var a : String? = "hahaha"
//先判断a不为空,然后访问a的length属性
var len = if(a != null) a.length else -1
1.8.3 安全调用 ?.
变量的安全调用
var str : String? = "hahaha"
println(str?.length) //输出6
str = null
println(str?.length) //输出null
数组变量的安全调用
val names : Array<String?> = arratOf("张三","李四",null,"王五")
for(name in names){
//当name不为空的时候才能调用let函数
name?.let{
println(it)
}
)
1.8.4 Elvis运算 ?: (if-else的简化)
var str : String? = "hahaha"
//先判断str不为空,然后访问b的length属性
var len1 = if(str != null) str.length else -1
str = null
var len2 = str?.length ?: -1
Elvis表达式的含义是:
如果?:左边的表达式不为空,则返回左边表达式的值,否则返回右边的值
用途:return throw 都可以用
val data = ......
val email = data["email"] ?: throw IllegalArgumentException("email为空")
1.8.5 强制调用 !!. (可能引发空指针)
变量的强制调用
var str : String? = "hahaha"
println(str?.length) //输出6
str = null
println(str!!.length) //空指针异常(NPE)
数组变量的强制调用
val names : Array<String?> = arratOf("张三","李四",null,"王五")
for(name in names){
//当name不为空的时候才能调用let函数
name!!.let{
println(it) //当调用到索引为2时候,空异常
}
)
1.9 字符串String
1.9.1 字符串类型
字符串类型分为两种:
- 转义字符
- 原始字符串 “”“三重引号引起来的内容”“”
转义字符
var a = "\n"
原始字符串
var text = """
|白日依山尽
|黄河入海流
|欲穷千里目
|更上一层楼
""".trimMargin()
默认情况下kotlin使用( | )作为边界符,也就是说 使用 trimMargin()方法后所有( | )之前的内容都被去掉
1.9.2 字符串模板 “”“字符串内容”“”
在字符串中嵌入变量或表达式,只要将变量或表达式放入 ${} 中即可
val bookPrice = 80
var s1 = "图书的价格是:${boojPrice}"
var rand = java.util.Random()
var s2 = "伪随机数是:${rand.nextInt(10)}"
var str = "123456789"
println("${str}的长度是${str.length}")
1.9.3 Kotlin字符串的方法
方法名 | 作用 |
---|---|
toDouble() | 字符串转Double |
toInt() | 字符串转Int |
capitalize() | 首字母大写 |
decapitalize() | 首字母小写 |
commonPrefixwith() | 返回两字符串相同前缀 |
commonSufixwith() | 返回两字符串相同后缀 |
contains(Regex(“\\d{3}”)) | 正则表达式 |
substring(start,end) | 截取字符串从start到end,返回新的字符串 |
substring(start) | 截取字符串从start到最后,返回新的字符串 |
1.10 类型别名 typealias
Kotlin提供了类似于C语言中的typedf功能,为已有的类型指定另一个可读性更强的名字
Kotlin使用了typealias来定义类型别名
语法格式为:
typealias 类型别名 = 已有类型
给集合泛型形式起别名
//定义两个集合的别名
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K,MutableList<File>>
//使用集合别名
var set : NodeSet
var table : FileTable<K>
给内部类起别名
class A{
inner class Inner
}
class B{
inner class Inner
}
//为A.Inner内部类指定别名
typealias AInner = A.Inner
//为B.Inner内部类指定别名
typealias BInner = B.Inner
fun main(args:Array<String>){
//使用定义的内部类别名
var a : AInner = A().AInner()
var b : B().BInner()
}
给Lambda表达式起别名
typealias Predicate<T> = (T) -> Boolean
val p : Predicate<String> = {it.length > 4}
//使用数组的过滤器执行Lambda表达式
println(arrayOf("Java","Kotlin","Object-c","C++").filter(p))
第2章 运算符和表达式
2.1 和Java相同的运算符
Kotlin的运算符都是以方法形式来实现的,都具有特定的符号和固定的优先级
2.1.1 单目前缀运算符( + - ! )
运算符 | 对应的方法 | 作用 |
---|---|---|
+a | a.unaryPlus() | 正号 |
-a | a.unaryMinus() | 负号 |
!a | a.not() | 相反 |
2.1.2 自加自减运算符( ++ – )
运算符 | 对应的方法 | 作用 |
---|---|---|
a++ | a.inc() | 自加 |
a– | a.dec() | 自减 |
2.1.3 双目算术运算符
运算符 | 对应的方法 | 作用 |
---|---|---|
a+b | a.plus() | 加 |
a-b | a.minus(b) | 减 |
a*b | a.times(b) | 乘 |
a/b | a.div(b) | 除 |
a%b | a.rem(b) | 取余 |
a…b | a.range(b) | 范围 |
2.1.4 in和!in
运算符 | 对应的方法 | 作用 |
---|---|---|
a in b | b.contains(a) | 是否在里面 |
a !in b | !b.minus(a) | 是否不在里面 |
2.1.5 索引访问运算符 ( [] )
运算符 | 对应的方法 | 作用 |
---|---|---|
a[i] | a.get(i) | 获取一维数组索引为 i 的元素 |
a[i,j] | a.get(i,j) | 获取二维数组所谓为i,j的元素 |
a[i_1,…,i_n] | a.get(i_1,…,i_n) | 获取n维数组元素 |
a[i] = b | a.set(i,b) | 设置一维数组索引为 i 的元素 |
a[i,j] = b | a.set(i,j,b) | 设置二维数组索引为i,j的元素 |
a[i_1,…i_n] = b | a.set(i_1,…,i_n,b) | 设置n维数组元素 |
2.1.6 调用运算符 ( invoke() )
运算符 | 对应的方法 | 作用 |
---|---|---|
a() | a.invoke() | 反射时候用来插入方法参数 |
a(b) | a.invoke(b) | 反射时候用来插入方法参数 |
a(b1 , b2) | a.invoke(b1,b2) | 反射时候用来插入方法参数 |
a(b1 , b2 , b3, …) | a.invoke(b1,b2,b3,…) | 反射时候用来插入方法参数 |
val s = "java.long.String"
val mtd = Class.fromName(s).getMethod("length")
//使用反射
println(mtd.invoke("java")) //输出4
//使用调用运算符
println(mtd("java")) //输出4
2.1.7 广义赋值运算符 ( +=,-=,*=,/=,%= )
运算符 | 对应的方法 | 作用 |
---|---|---|
a += b | a.plusAssign(b) | 加等 |
a -= b | a.minusAssign(b) | 减等 |
a *= b | a.timesAssign(b) | 剩等 |
a /= b | a.divAssign(b) | 除等 |
a %= b | a.remAssign(b) | 区余等 |
2.1.8 相等于不等运算 ( == , !=)
运算符 | 对应的方法 | 作用 |
---|---|---|
a == b | a?.equals(b) ?: (b===null) | 判断a和b是否是引用了同一个对象 |
a != b | !a?.equals(b) ?: (b===null) | 判断a和b是否不是引用了同一个对象 |
2.1.9 比较运算符( > , < , >= , <= )
比较后,返回值的是Boolean类型
运算符 | 对应的方法 | 作用 |
---|---|---|
a > b | a.compareTo(b) >0 | 判断a是否大于b |
a < b | a.compareTo(b) <0 | 判断a是否小于b |
a >= b | a.compareTo(b) >=0 | 判断a是否大于等于b |
a <= b | a.compareTo(b) <=0 | 判断a是否小于等于b |
2.2 位运算符(and,or,inv,xor,shl,shr,ushr)
运算符 | 名字 | 作用 |
---|---|---|
and(bits) | 按位与 | 两位同时为1才返回1 |
or(bits) | 按位或 | 只要有一位为1就返回1 |
inv(bits) | 按位非 | 单目运算符,每个位数全部取反 |
xor(bits) | 按位异或 | 两位相同时返回0,不同时返回1 |
shl(bits) | 左移运算符 | |
shr(bits) | 右移运算符 | |
ushr(bits) | 无符号右移运算符 |
2.3 区间运算符 ( … , a until b , downTo)
kotlin提供了两个区间运算符:闭区间运算符合半开区间运算符
2.3.1 闭区间运算符 …
var nums = 1..10
for(num in nums){
println(unm)
}
2.3.2 半开区间运算符 a until b
最后那个数字没有,用于数组的索引,从0开始
var books = arrayOf("Swift","Kotlin","C","C++")
//使用半开区间运算符定义区间
for(index in 0 until books.size){ //0,1,2,3 没有4
println()
}
2.3.3 反向区间 downTo
如果有希望元素从大到小的情况,可以使用downTo运算符(其实是一个infix函数),构建一个闭区间
对于a downTo b 而言,要求b不能大于a
var rangel = 6 downTo 2
for(num in rangel){
println(num) // 6,5,4,3,2
}
2.3.4 区间步长 Step
设置区间步长差值,默认为1,可以通过step运算符(其实是一个infix函数)显示指定区间步长
//为反向闭区间指定步长为2
for(num in 8 downTo 2 step 2){
println(num) //8,6,4,2
}
2.4 运算符重载 operator
kotlin的运算符都是靠特性名称的方法制成的
只要重载这些名称的方法,我们就可以为任意类型添加这些运算符
2.4.1 重载单目前缀运算符
单目运算符方法名:
unaryPlus() 加
unaryMinus() 减
Not() 取反
//定义一个Data数据类
data class Data(val x: Int, val y: Int) {
operator fun unaryMinus(): Data {
return Data(-x, -y)
}
}
//以扩展放的形式为Data类定义not方法
operator fun Data.not(): Data {
return Data(-x, -y)
}
fun main(args: Array<String>) {
val d = Data(4, 10)
println(-d) //输出 Data(x = -4, y = -10)
println(!d) //输出 Data(x = -4, y = -10)
}
2.4.2 重载自加自减运算符
inc() 自加
dec()自减
//定义一个Data数据类
data class Data(val x: Int, val y: Int) {
operator fun inc(): Data {
return Data(x + 1, y + 1)
}
}
//以扩展放的形式为Data类定义not方法
operator fun Data.dec(): Data {
return Data(x - 1, y - 1)
}
fun main(args: Array<String>) {
val d = Data(4, 10)
println(d++) //先调用
println(d) //后输出 Data(x = 5 , y = 11)
val dd = Data(9, 20)
println(dd--) //先调用
println(dd) //后输出 Data(x = 8 , y = 19)
}
2.4.3 重载双目算术运算符
plus() 加
minus() 减
times() 乘
div() 除
rem() 取余
rangeTo() 范围
data class Point(val x: Int, val y: Int) {
operator fun minus(target: Point): Double {
return Math.hypot((this.x - target.x).toDouble(), (this.y - target.y).toDouble())
}
}
operator fun Point.times(target: Point): Int {
return Math.abs(this.x - target.x) * Math.abs(this.y - target.y)
}
fun main(args: Array<String>) {
var p1 = Point(4, 10)
var p2 = Point(5, 15)
var dis = p1 - p2
println("p1和p2之间的距离为:${dis}") //p1和p2之间的距离为:5.0990195135927845
var area = p1 * p2
println("p1和p2围成矩形的面积为:${area}") //p1和p2围成矩形的面积为:5
}
第3章 流程控制语句(顺序,分支,循环)
3.1 顺序结构
顺序结构就是从上到西一行一行地执行,中间没有任何判断和跳转
3.2 分支结构(if,when)
kotlin提供了两种常见的分值控制结构:if和when分支
3.2.1 if分支
if
if(expresion){
}
//如果只有一条语句情况可以省略{}
if(true)
println("true")
else
println("false")
if else
if(expresion){
}else{
}
if elseif
if(expresion){
}else if(expresion){
}else if(expresion){
}else{
}
3.2.2 if表达式
if表达式最终返回一个值,用来代替Java中的三目运算符
if(条件) "为true返回的" else "为false返回的"
if(条件) "情况1" else if "情况2" else "情况3"
3.2.3 when分支语句
when代替了Java原有的switch语句
条件可以为:基本数据类型,表达式,类
内容可以为:基本数据类型(直接返回),代码块
char score = 'C'
val str = "GHIJKLMN"
when(score){
'A' -> println("优秀")
'B' -> println("良好")
'C' -> println("中等")
'D' -> println("及格")
'E' -> {
//代码块形式
}
'F','G' ->{
//多个条件配合代码块形式
}
str[4]-3 -> {
//条件为表达式形式
}
str[4]-3,str[5]-2 -> {
//条件为多个表达式形式
}
Dae() -> {
//条件为类的形式
}
else -> println("不及格")
}
3.2.4 when表达式
char score = 'C'
val str = "GHIJKLMN"
var result = when(score){
'A' -> "优秀"
'B' -> "良好"
'C' -> "中等"
'D' -> "及格"
else -> "不及格"
}
println(result)
3.2.5 when分支处理范围
val age = java.util.Random().nextInt(100)
println(age)
var str = when (age) {
in 10..20 -> "范围在10-20"
in 21..30 -> "范围在21-30"
in 31..40 -> "范围在31-40"
else -> "其他"
}
println(str) //其他
3.2.6 when分支处理类型
is / !is运算符用来表达是否为指定类型
fun realPrice(inputPrice: Any) = when (inputPrice) {
is String -> inputPrice.toDouble()
is Int -> inputPrice.toDouble()
is Double -> inputPrice
else -> 0
}
fun main(args: Array<String>) {
var inputPrice = 26
println(realPrice(inputPrice)) //26.0
}
3.2.7 when条件分支
可以用来取代if…else if链
fun main(args: Array<String>) {
var ln = readLine()
if (ln != null) {
when {
ln.matches(Regex("\\d+")) -> println("输入的全是数字")
ln.matches(Regex("[a-zA-Z]+")) -> println("输入的全是字母")
ln.matches(Regex("[a-zA-Z0-9]+")) -> println("输入的是数字和字母")
else -> println("输入的包含特殊字符")
}
}
}
3.3 循环结构(while,do-while,for-in)
循环语句包含4个部分:
- 初始化语句: 一条或多条语句,用于完成初始化工作,初始化语句在循环开始之前执行
- 循环条件: 布尔表达式,用于决定是否执行循环体
- 循环体: 循环主体,花括号内的
- 迭代语句: 在一次循环体执行结束后,对循环条件求值之前执行,控制循环条件的变量,让循环在合适时候结束
3.3.1 while循环
var count = 0
while(count < 10){
println(count)
count++
}
println("循环结束")
循环体只有一行语句时
var count = 0
while(count < 10){
println(count)
}
println("循环结束")
3.3.2 do-while循环
var count = 1
do{
println(count)
count++
} while(count < 10)
println("循环结束")
循环体只有一行语句时
var count = 1
do
count++
while(count < 10)
println("循环结束")
3.3.3 for-in循环
常用于遍历范围,序列,集合等
for-in循环的计数器相当于val变量,不允许被重新赋值
for(常量名 in 字符串|范围|集合){
//循环体
}
for(i in 0..100){
println(i)
}
3.3.4 嵌套循环
就是循环内还有循环
for(i in 0 until 5){ //0,1,2,3,4 没有5
var j = 0
while(j < 3){
println("i的值为${i},j的值为${j++}")
}
}
3.4 控制循环结构(beak,continue,return)
Kotlin语言没有goto语句来控制程序
3.4.1 使用break结束循环
跳出方法了(运行完了程序结束)
break不仅可以结束本层循环,也可以结束外层循环
fun main(args: Array<String>) {
for (i in 0..10){
if (i==2){
break //跳出main方法了
}
}
println("我可以继续执行")
}
//0
//1
//我可以继续执行
outher@ 关键字 结束所修饰指定的循环
fun main(args: Array<String>) {
outher@ for (i in 0..10) {
if (i == 2) {
break@outher
}
println(i)
}
println("我可以继续执行")
}
//0
//1
//我可以继续执行
3.4.2 使用continue忽略本次循环
continue用于结束本次循环继续下面的循环
for (i in 0 until 5){
if (i == 2){
continue //跳过索引为2的本次
}
println(i)
}
outher@ 关键字 跳过所指定循环层数的本次循环 ,继续下面的循环
outher@ for (i in 0 until 5){
if (i == 2){
continue@outher //跳过for循环索引为2
}
println(i)
}
3.4.3 使用return结束方法
return直接结束整个函数或方法,不管有多少层循环,return 方法后面的语句不能执行了
如果是在main函数中,就相当于停止工作了
for (i in 0 until
if (i == 2){
return
}
println(i)
}
println("我不能执行!!!")
第4章 数组和集合
4.1数组
Kotlin的数组使用Array类代表,属于一个类的实例,算引用类型.
4.1.1 创建数组
创建数组有两种方式:
方式 | 作用 |
---|---|
arrayOf() | 无须显示指定数组的长度,依次列出每个数组元素 |
arrayOfNulls() | 需要显示指定数组的长度,要指定泛型 |
emptyArray() | 创建长度为0的空数组,需要泛型来指定数组元素类型 |
Array( size : Int , init : (Int) -> T) 构造器 | 需要显示指定数组的长度,通过Lambda来动态计算个数组元素的值 |
fun main(args: Array<String>) {
//创建数组
var arr1 = arrayOf("java", "kotlin", "swift", "go")
//创建没有元素,类型为Double,长度为10的空数组
var arr2 = arrayOfNulls<Double>(10)
//创建没有元素,类型为String,长度为0的空数组
var arr3 = emptyArray<String>()
//创建指定长度,使用Lambda表达式初始化数组元素的数组
var arr4 = Array(5, { (it * 2 + 97).toChar() })
for (num in arr4) {
println(num) // a c e g i
}
//通过构造器创建组
var arr5 = Array(6, { "kotlin" })
for (num in arr5) {
println(num) //kotlin kotlin kotlin kotlin kotlin kotlin
}
}
4.1.2 使用数组
访问数组元素,对元素进行赋值,取出数组元素.
Kotlin的方括号运算符其实是get(index) 和 set(index, value)方法.
访问到数组元素后,就可以吧数组元素变成普通变量来使用.
数组元素的下标是从0开始的.
fun main(args: Array<String>) {
var strArr = arrayOf("java", "Java", "Go", "Swift")
//获取数组元素的两种方式
println(strArr[2])
println(strArr.get(2))
//使用两种方式修改数组元素
strArr[1] = "hahaha"
strArr.set(1, "xixixi")
}
数组索引小于0,或者大于或等于数组长度时候,会出现异常:java.lang.ArrayIndexOutOfBoundsException:N
var strArr = arrayOf("java", "Java", "Go", "Swift")
for (i in 0 until strArr.size) {
println(i)
}
4.1.3 使用for-in循环遍历数组
fun main(args: Array<String>) {
var books = arrayOf("java", "Java", "Go", "Swift")
for (book in books) {
println(book)
}
}
4.1.4 使用数组索引
数组.indices 返回数组索引区间
fun main(args: Array<String>) {
var books = arrayOf("java", "Java", "Go", "Swift")
for (i in books.indices) {
println(books[i])
}
}
使用indices判断是否在这个区间内
fun main(args: Array<String>) {
var books = arrayOf("java", "Java", "Go", "Swift")
var i = java.util.Random().nextInt(10)
//判断生成的随机数 是否在 books数组的区间内
println(i in books.indices)
}
使用withIndex() 同时获取索引和值
fun main(args: Array<String>) {
var books = arrayOf("java", "Java", "Go", "Swift")
//通过withIndex()方法可同时访问你数组的索引和元素
for ((index, value) in books.withIndex()) {
println("索引为${index}的元素是:${value}")
}
}
4.1.5 数组的常用方法
方法 | 作用 |
---|---|
all(predicate: (T) -> Boolean) | 使用Lambda表达式要求所有元素都满足该表达式,如果都满足返回true,反之false |
any(predicate: (T) -> Boolean) | 使用Lambda表达式要修任一元素都满足该边大师,如果都满足返回true,反之false |
asList() | 将数组转换成List集合 |
associate(transform: (T) -> Pair<K,V>) | 使用Lambda表达式根据数组元素进行计算,返回元素是<K,V>的Map集合 |
associateBy(keySelector: (T) -> K) | 使用Lambda表达式根据数组元素进行计算,返回元素是<K,V>的Map集合 |
associateBy(keySelector: (T) -> K,valueTransform: (T) -> V) | 使用Lambda表达式根据数组元素进行计算,返回元素是<K,V>的Map集合 |
associateByTo(dest: M, keySelector: (T) -> K) | 使用Lambda表达式根据数组元素进行计算,返回元素是<K,V>的Map集合 |
associateByTo(dest: M, keySelector: (T) -> K, valueTransform: (T) -> V) | 使用Lambda表达式根据数组元素进行计算,并将计算得到的<K,V>对 添加到可变Map集合中,该方法返回被修改的dest集合 |
associateTo(dest: M, transform: (T) -> Pair<K, V>) | 使用Lambda表达式根据数组元素进行计算,并将计算得到的<K,V>对 添加到可变Map集合中,该方法返回被修改的dest集合 |
average() | 计算数组平均值 |
binarySearch(element: T, comparator: Comparatorsin T>, fromlndex: Int = 0,tolndex: Int =size) | 使用二分法查询element元素值在数组中出现的索引,如果不包含element元素值,则返回负数(调用本方法时候要求数组中元素已经按升序排列) |
int binarySearch(element: T, fromlndex: Int = 0, tolndex: Int = size) | 与上一个类似(本方法要求数组元素按自然排序,数组元素要实现Comparable接口升序排血) |
contains(element: T) | 判断该数组是否包含某个元素,该方法可用in !in运算符执行。 |
contentEquals(other: Array): | 比较两个数组是否相等 |
contentToString() | 把数组转换成字符串(相当于Arrays类的toString()方法) |
copyOf(newSize: Int) | 把数组复制成一个新数组 newSize是新数组的长度,不足则补0,如果不传入这个参数,则新数组与原数组长度相同 |
copyOfRange(fromlndex: Int, tolndex: Int) | 与上一个类似,只复制该数组的from到to的索引 |
count(predicate: (T)-> Boolean | 返回该数组符合Lambda表达式的元素个数,如果不指定predicate参数,则直接返回数组中元素的个数,相当于size属性的返回值 |
distinct() | 去掉数组中重复的元素 |
drop I dropLast(n: Int) | 去掉数组前或后n个元素 |
dropWhile | dropLastWhile(predicate: (T)-> Boolean) | 去掉数组前或后的某些元素,直到前或后的元素不再符合predicatet参数的条件 |
fill (element: T, fromlndex: Int = 0, tolndex: Int = size) | 把数组中从fromlndex到tolndex索引的数组元素赋值为element |
filterXxx() | 对数组元素进行过滤 |
findxxx() | 用于查找元素的方法 |
first | last(predicate: (T) -> Boolean | 获取数组中第一个或最后一个符合predicate条件的元素 |
fold(initial: R, operation: (acc: R, T) | 将initial,数组元素作为参数传入operation表达式进行计算,将计算得到的结果作为下一个数组元素的initial,依此类推,直到使用最后一个数组元素计算,该方法返回使用最后一个数组元素计算得到的值。 |
indexOf | lastIndexOf(element: T) | 获取从前搜索 或者从后搜索是元素element在数组中的索引 |
indexOfFirst I indexOfLast(predicate: (T) -> Boolean) | 返回第一个或最后一个符合predicate条件的元素的索引 |
intersect | plus(other: Iterable) | 获取两个数组的交集或并集 |
max l min() | 找出最大值或最小值(要求元素必须实现Comparable接口,并且按照自然排序规则) |
sort(fromlndex: Int=0, tolndex: Int = size) | 该方法对数组的元素按自然排序(元素实现Comparable接口) |
sortWith(comparator: Comparatorkin T>) | 对数组的所有元素按comparator排序(定制排序)进行排列 |
fun main(args: Array<String>) {
//定一个数组
var arr = arrayOf(2, 4, 5, 6)
//(判断是否近有元素的平方都大于20
println(arr.all({ it * it > 20 })) //输出false
//(判断是否任一元素的平方大于20
println(arr.any({ it * it > 20 })) //输出true
//根据数组元素来计算<K, V>对,返回所有<K, V>对组成的Map集合//下面的算法规则是: K是数组元素+2, V是数组元素的平方
var result1 = arr.associate({ it + 10 to it * it })
println(result1)
//创建一个可变Map集合,用于追加根据数组计算出来的key-value对
var map = mutableMapOf(1 to 100, 2 to 120, -1 to 130)
//将计算出来的key (元素的平方)、value (元素)对添加到map集合中
arr.associateByTo(map, { it * it })
println(map)
//计算数组所有元素的总和
println(arr.fold(0, { acc, e -> acc + e })) //输出17
//定义一个a数组
var a = arrayOf(3, 4, 5, 6)
//定义一个a数组vara=arrayof(3,4,5,6)//定义一个a2数组
var a2 = arrayOf(3, 4, 5, 6)
//a数组和a2数组的长度相等,每个元素依次相等,将输出trueprintIn ("a数组和a2数组是否相等: $(a.contentEquals (a2) }")//通过复制a数组,生成一个新的b数组
var b = a.copyOf(6)
println("a数组和b数组是否相等: $(a.contentEquals (b) }") //输出b数组的元素,将输出[3, 4, 5, 6, null, nul1]
println("b数组的元素为: ${b.contentToString()}")
//将b数组的第5个元素(包括)到第7个元素(不包括)赋值为1
b.fill(1, 4, 6)
//输出b数组的元素,将输出[3, 4, 5, 6, 1, 1]
println("b数组的元素为: s(b.contentTostring () }")
//对b数组进行排序
b.sort() //输出b数组的元素,将输出[1, 1, 3, 4, 5, 6]
println("b数组的元素为: $(b.contentTostring () }")
}
4.1.6 多维数组
fun main(args: Array<String>) {
//把a当成一维数组进行初始化,初始化a是一个长度为4的数组//a数组的元素又是Array<Int>类型
var a = arrayOfNulls<Array<Int>>(4)
//把a数组当成一维数组,遍历a数组的每个数组元素
for (i in a.indices) {
println(a[i])
}
//初始化a数组的第一个元素
a[0] = arrayOf(2, 5)
//访问a数组的第一个元素所指数组的第二个元素
a[0]?.set(1, 6)
//a数组的第一个元素是一个一维数组,遍历这个一维数组
for (i in a[0]!!.indices) {
println(a[0]?.get(i))
}
}
4.1.7 数组的应用举例
五子棋,连连看,俄罗斯方块,扫雷等小游戏.
4.2 Kotlin集合概述
Kotlin的集合类同样由两个接口派生 Collection 和 Map
Collection是单列集合根接口
Kotlin集合分为可变集合和不可变集合两种,只有可变集合才能添加,删除,修改元素,不可变集合只能读取元素.
Kotlin的Collection的集合有三个接口:
- MutableCollection 可变
- MutableSet 可变
- MutableList 可变
- Set 不可变
- HashSet
- LinkedHashSet
- List 不可变
- ArrayList
Map集合:
- MutableMap 可变
- HashMap
- LinkedHashMap
4.3 Set集合
元素不允许重复
4.3.1 声明和创建Set集合
创建方式
方法名 | 作用 | 是否维护元素顺序 | 是否可变 | 类型 |
---|---|---|---|---|
setOf() | 创建 不可变Set集合 | 维护 | 不可变 | ArrayList |
mutableSetOf() | 创建 可变Set集合 | 维护 | 可变 | ArrayList |
hashSetOf() | 创建 可变HashSet集合 | 不维护 | 可变 | ArrayList |
linkedSetOf() | 创建 可变LinkedHashSet集合 | 维护 | 可变 | ArrayList |
sortedSetOf() | 创建 可变TreeSet集合 | 按照大小顺序排列 | 可变 | ArrayList |
fun main(args: Array<String>) {
//创建不可变集合,返回值是Set
var set = setOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
println(set) //[java, kotlin, object-c, c, c++, python]
println("setOf集合实际返回类型:${set.javaClass}") //setOf集合实际返回类型:class java.util.LinkedHashSet
//创建可变集合,返回值是MutableSet
var mutableSet = mutableSetOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
println(mutableSet) //[java, kotlin, object-c, c, c++, python]
println("mutableSet集合实际返回类型:${mutableSet.javaClass}") //mutableSet集合实际返回类型:class java.util.LinkedHashSet
//创建HashSet集合,不能保证元素顺序
var hashSet = hashSetOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
println(hashSet) //[object-c, c++, python, java, c, kotlin]
println("hashSet集合实际返回类型:${hashSet.javaClass}") //hashSet集合实际返回类型:class java.util.HashSet
//创建LinkedHashSet集合
var linkedHashSet = linkedSetOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
println(linkedHashSet) //[java, kotlin, object-c, c, c++, python]
println("linkedHashSet集合实际返回类型:${linkedHashSet.javaClass}") //linkedHashSet集合实际返回类型:class java.util.LinkedHashSet
//创建TreeSet集合,自动按照字母顺序排序
var treeSet = sortedSetOf("java", "kotlin", "object-c", "c", "c++", "python")
println(treeSet) //[c, c++, java, kotlin, object-c, python]
println("treeSet集合实际返回类型:${treeSet.javaClass}")//treeSet集合实际返回类型:class java.util.TreeSet
}
4.3.2 使用Set的方法
方法 | 作用 |
---|---|
all | 判断是否所有集合元素都满足规则 |
any | 判断是否任一元素都满足指定规则 |
associate | 根据Set集合生成Map集合 |
drop | 按规则取出集合的部分元素 |
filter | 对集合元素进行过滤 |
find | 查找集合元素 |
fold | 将set集合中的所有String元素拼接起来 |
indexOf | 元素在集合中第一次出现的索引 |
map | 将每个元素映射成新值,返回set新组成的set集合 |
max | 求最大值 |
min | 求最小值 |
reversed | 反转 |
intersect | 交集 |
union | 并集 |
+ | 交集 |
- | 并集 |
fun main(args: Array<String>) {
//创建不可变集合,返回值是Set
var set = setOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
//判断所有元素和任一元素的方法
println(set.all({ it.length > 4 })) //判断是否所有的元素长度都大于4 //false
println(set.any({ it.length > 4 })) //判断是否任一的元素长度都大于4 //true
//判断元素是否存在集合中的方法
println("java" in set) //true
println("java" !in set) //false
//截取元素,返回新的集合的方法
var dropSet = set.drop(2) //删除前两个元素
println(dropSet) //[object-c, c, c++, python]
//过滤出符合条件的元素,返回新的集合
var filterSet = set.filter({ "li" in it }) //过滤出元素中有li的
println(filterSet) //[kotlin]
//选出符合条件的元素,返回新的集合
var findSet = set.find({ "li" in it })
println(findSet) //kotlin
//将集合中的所有字符串拼接在一起
var foldSet = set.fold("", { acc, e -> acc + e })
println(foldSet) //javakotlinobject-ccc++python
//查找某个元素出现的位置
println(set.indexOf("kotlin")) //1
//将Set集合元素映射成新值,返回所有新值组成的Set集合
var mappedList = set.map({ "我喜欢学习" + it + "语言" })
println(mappedList)//[我喜欢学习java语言, 我喜欢学习kotlin语言, 我喜欢学习object-c语言, 我喜欢学习c语言, 我喜欢学习c++语言, 我喜欢学习python语言]
//获取最大值
println(set.max()) //python
//获取最小值
println(set.min()) //c
//反转集合序列
var reversedList = set.reversed()
println(reversedList)
//计算两个集合的交集
var newSet = setOf<String>("object-c", "c", "c++", "python", "English")
println(set intersect newSet) //[object-c, c, c++, python]
//计算两个集合的并集
println(set union newSet) //[java, kotlin, object-c, c, c++, python, English]
//集合相加,相当于并集
println(set + newSet) //[java, kotlin, object-c, c, c++, python, English]
//集合相减,减去它们公共的元素
println(set - newSet) //[java, kotlin]
}
4.3.3 遍历Set
for-in来遍历
var books = setOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
for (book in books){
println(book)
}
set集合继承了Iteratable,因此可以用forEach来遍历
var books = setOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
books.forEach({ println(it)})
通过Set集合提供的indices方法返回索引的区间,然后通过for - in 遍历,elementAt(index)来获取
var books = setOf<String>("java", "kotlin", "object-c", "c", "c++", "python")
for (i in books.indices){
println(books.elementAt(i))
}
迭代器循环
Set 和 MutableSet都包含一个iterator()方法,
- Set方法的intertar()返回的是Iterator对象,只有hasNext和next两个方法
- MutableSet方法的intertar()返回的是MutableIterator对象,除了hasNext()和next()两个方法外,还提供了一个remove()方法,用于在遍历时删除元素
fun main(args: Array<String>) {
//创建不可变集合,返回值是Set
var books = mutableSetOf<String>("java", "kotlin", "object-c", "c", "c++", "python", "php")
var iterator = books.iterator()
while (iterator.hasNext()){
var next = iterator.next()
println(next)
//遍历时候删除元素
if (next.startsWith("p")){
iterator.remove()
}
}
println(books)//[java, kotlin, object-c, c, c++]
}
4.3.4 可变的Set
创建方法
方法名 | 作用 | 是否维护元素顺序 | 是否可变 |
---|---|---|---|
setOf() | 创建 不可变Set集合 | 维护 | 不可变 |
mutableSetOf() | 创建 可变Set集合 | 维护 | 可变 |
hashSetOf() | 创建 可变HashSet集合 | 不维护 | 可变 |
linkedSetOf() | 创建 可变LinkedHashSet集合 | 维护 | 可变 |
sortedSetOf() | 创建 可变TreeSet集合 | 按照大小顺序排列 | 可变 |
可变Set方法
添加元素
- add 添加单个
- addAll 添加全部
fun main(args: Array<String>) {
//创建不可变集合,返回值是Set
var books = mutableSetOf<String>("Kotlin")
//添加单个
books.add("Go")
books.add("C++")
books.add("Java")
//添加全部
books.addAll(setOf("Python","English"))
}
删除元素
- remove 删除指定元素
- removeAll 批量删除元素
- retainAll 只保留和参数的共有元素
- clear 清空集合
fun main(args: Array<String>) {
//创建不可变集合,返回值是Set
var books = mutableSetOf<String>("java", "kotlin", "object-c", "c", "c++", "python", "php")
//删除指定元素
books.remove("php")
//批量删除多个元素
books.removeAll(setOf("c", "c++"))
//只保留某个元素
books.retainAll(setOf("java"))
println(books) //java
//清空set集合
books.clear()
println(books) //null
//使用迭代器遍历时候,删除元素
var myBooks = mutableSetOf<String>("java", "kotlin", "object-c", "c", "c++", "python", "php")
var iterator = myBooks.iterator()
while (iterator.hasNext()){
var next = iterator.next()
println(next)
//遍历时候删除元素
if (next.startsWith("p")){
iterator.remove()
}
}
println(books)//[java, kotlin, object-c, c, c++]
}
4.4 List集合
List集合最大特征就是元素都有对应的顺序索引,允许使用重复元素,可以通过索引来访问指定位置的集合元素.
4.4.1 声明和创建List集合
创建方法
方法名 | 是否可变 | 作用 | 类型 |
---|---|---|---|
listOf() | 不可变 | 可接受0个或多个集合元素 | ArrayList类型 |
listOfNotNull() | 不可变 | 创建不包含Null值的集合(会去掉Null的元素) | ArrayList类型 |
mutableListOf() | 可变 | 可接受0个或多个集合元素 | ArrayList类型 |
arrayListOf() | 可变 | 创建ArrayList集合可接受0个或多个集合元素 | ArrayList类型 |
fun main(args: Array<String>) {
//创建不可变集合,返回值是List
var list1 = listOf("Java", "Kotlin", null, "Go")
println(list1)//[Java, Kotlin, null, Go]
println("listof的返回对象的实际类型:${list1.javaClass}")//listof的返回对象的实际类型:class java.util.Arrays$ArrayList
//集合包含nul1值//创建不可变集合,返回值是List
var list2 = listOfNotNull("Java", "Kotlin", null, "Go")
println(list2) //[Java, Kotlin, Go]
println("listOfNotNull的返回对象的实际类型:${list2.javaClass}")//listOfNotNull的返回对象的实际类型:class java.util.ArrayList
//集合不包含nul1值//创建可变集合,返回值是MutableList
var mutablelist = mutableListOf("Java", "Kotlin", null, "Go")
println(mutablelist)//[Java, Kotlin, null, Go]
println("mutableListOf的返回对象的实际类型:${mutablelist.javaClass}")//mutableListOf的返回对象的实际类型:class java.util.ArrayList
//创建Arraylist集合
var arrayList = arrayListOf("Java", "Kotlin", null, "Go")
println(arrayList)//[Java, Kotlin, null, Go]
println("arrayListOf的返回对象的实际类型:${arrayList.javaClass}")//arrayListOf的返回对象的实际类型:class java.util.ArrayList
}
4.4.2 使用List的方法
方法
方法名 | 作用 |
---|---|
get | 根据索引访问集合元素(带operator修饰的方法,可用[]运算符访问集合元素) |
indexOf | 返回元素在List中的索引 |
lastIndexOf | 返回最后一次出现的索引 |
subList | 截取集合,返回截取后的子集合 |
4.4.3 可变的List
mutableList() 和 arrayList()函数返回的List集合都是都是可变的.
fun main(args: Array<String>) {
//集合不包含nul1值//创建可变集合,返回值是MutableList
var mutablelist = mutableListOf("Java", "Kotlin", null, "Go")
println(mutablelist)//[Java, Kotlin, null, Go]
println("mutableListOf的返回对象的实际类型:${mutablelist.javaClass}")//mutableListOf的返回对象的实际类型:class java.util.ArrayList
//添加一个元素
mutablelist.add("PHP")
//根据索引删除元素
mutablelist.removeAt(1)
//修改索引处的元素值
mutablelist[2] = "hahaha"
//清空集合
mutablelist.clear()
//创建Arraylist集合
var arrayList = arrayListOf("Java", "Kotlin", null, "Go")
println(arrayList)//[Java, Kotlin, null, Go]
println("arrayListOf的返回对象的实际类型:${arrayList.javaClass}")//arrayListOf的返回对象的实际类型:class java.util.ArrayList
//添加一个元素
arrayList.add("PHP")
//根据索引删除元素
arrayList.removeAt(1)
//修改索引处的元素值
arrayList[2] = "hahaha"
//清空集合
arrayList.clear()
}
4.5 Map集合
Kotlin集合同样用于保存key-value对,也分为可变和不可变
方法 | 是否可变 | 作用 | 是否维护添加顺序 | 类型 |
---|---|---|---|---|
mapOf() | 不可变 | 可接受0个或多个key-valued键值对 | 维护 | LinkedHashMap |
mutableMapOf() | 可变 | 可接受0个或多个key-valued键值对 | 维护 | LinkedHashMap |
hashMapOf() | 可变 | 可接受0个或多个key-valued键值对 | 按照key大小排序 | HashMap |
linkedMapOf() | 可变 | 可接受0个或多个key-valued键值对 | 维护 | LinkedHashMap |
sortedMapOf | 可变 | 可接受0个或多个key-valued键值对 | 按照key大小排序 | TreeMap |
4.5.1 声明和创建Map集合
使用方法
var map = mapOf("key1" to "value1" , key2" to "value2" , key3" to "value3")
fun main(args: Array<String>) {
//创建不可变集合,返回值是Map
var map = mapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
println("mapOf 的返回对象实际类型是:${map.javaClass}")//mapOf 的返回对象实际类型是:class java.util.LinkedHashMap
//创建可变集合,返回值是MutableMap
var mutableMap = mutableMapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
println("mutableMapOf 的返回对象实际类型是:${mutableMap.javaClass}")//mutableMapOf 的返回对象实际类型是:class java.util.LinkedHashMap
//创建HashMap集合
var hashMap = hashMapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
println("hashMapOf 的返回对象实际类型是:${hashMap.javaClass}")//hashMapOf 的返回对象实际类型是:class java.util.HashMap
//创建LinkedHashMap集合
var linkedhashMap = linkedMapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
println("linkedMapOf 的返回对象实际类型是:${linkedhashMap.javaClass}")//linkedMapOf 的返回对象实际类型是:class java.util.LinkedHashMap
//创建TreeMap集合
var ltreeMap = sortedMapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
println("sortedMapOf 的返回对象实际类型是:${ltreeMap.javaClass}")//sortedMapOf 的返回对象实际类型是:class java.util.TreeMap
}
4.5.2 使用Map的方法
Map的方法
方法 | 作用 |
---|---|
all() | 判断是否所有的key-value都符合条件 |
ant() | 判断是否任一的key-value都符合条件 |
in | 判断某个元素在map集合里面 |
!in | 判断某个元素不在map集合里面 |
filter() | 按条件过滤集合,并返回新集合 |
map() | 将每个键值对应生成新值,返回所有新值组成的map集合 |
maxBy() | 获取key或value的最大值(map.macBy({it.key})) |
minBy() | 获取key或value的最小值(map.macBy({it.value})) |
+ | 取两个集合的并集 ( map1 + map2 ) |
- | 减去两个集合的并集 ( map1 - map2 ) |
fun main(args: Array<String>) {
var map = mapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
//判断是否所有的key的长度都大于4 和 value 的长度都大于80
println(map.all({ it.key.length > 4 && it.value > 80 })) //false
//判断是否任一的key的长度都大于4 和 value 的长度都大于80
println(map.any({ it.key.length > 4 && it.value > 80 })) //true
//判断元素是否在map集合中
println("java" in map)//true
println("java" !in map)//false
//对集合元素进行过滤,要求ket包含li
var filterMap = map.filter({ "li" in it.key })
//将每个key-valued对 映射成新值,返回新值的Map集合
var arrayList = map.map({ "我的书名字叫${it.key} 价格为 ${it.value}" })
println(arrayList)
println(arrayList.javaClass) //ArrayList
//分别 获取 key 和 value 的最大值/最小值
println(map.maxBy({ it.key }))//kotlin=99
println(map.maxBy({ it.value }))//go=100
println(map.minBy({ it.key }))//C++=100
println(map.minBy({ it.value }))//java=86
var map2 = mapOf<String, Int>("kotlin" to 99, "go" to 100, "Python" to 120)
println(map + map2)// 两个集合并集
println(map - map2)// 两个集合交集
}
4.5.3 遍历Map
四种遍历Map方式
- 使用entries
- 先遍历keys,通过key获取value
- for-in遍历, Entry(键值对)
- 使用Lambda表达式进行遍历
fun main(args: Array<String>) {
var map = mapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
//遍历方式1 - entries
for (en in map.entries) {
println("${en.key} ==== ${en.value}")
}
//遍历方式2 - map.keys
for (key in map.keys) {
println("${key} ==== ${map[key]}")
}
//遍历方式3 - (key,value)
for ((key, value) in map) {
println("${key} ==== ${value}")
}
//遍历方式4 - Lambda表达式
map.forEach({ println("${it.key} ==== ${it.value}") })
}
4.5.4 可变的Map
除了mapOf()是不可变之外,其他的四个都是可变的
- mutableMapOf()
- hashMapOf()
- linkedMapOf()
- sortedMapOf()
可变Map的方法
方法名 | 作用 |
---|---|
clear() | 清空Map集合 |
put(key,value) | 添加键值对 |
putAll(Map集合) | 添加整个集合 |
remove(key) | 删除键值对 |
fun main(args: Array<String>) {
var map = mutableMapOf<String, Int>("java" to 86, "kotlin" to 99, "go" to 100, "C++" to 100)
//添加元素
map.put("python", 300)
//添加所有元素
map.putAll(mapOf("English" to 150, "shuxue" to 150))
//删除元素
map.remove("java")
//清空元素
map.clear()
}
第5章 函数和lambda表达式
lambda表达式可以让程序更简洁
5.1 函数入门
5.1.1 定义和调用函数
语法
fun 函数名(形参列表):返回值类型{
//由0条多多条可执行语句组成
}
- fun关键字 声明函数使用的
- 函数名
- 返回值类型
- 如果没有返回值可以省略返回值类型 或者 返回 :Unit (Unit代表没有返回值)
- 形参列表
- 参数名:参数类型
- 多个参数用逗号 ,隔开
返回一个变量类型
fun max(x: Int, y: Int): Int {
var z = if (x > y) x else y
return z
}
返回一个表达式
fun max(x: Int, y: Int): Int {
return if (x > y) x else y
}
5.1.2 函数返回值和Unit
如果希望函数没有返回值,有两种方式:
- 直接省略:参数
- 使用":Unit"声明
fun fun1() {
println("方法1")
}
fun fun2(): Unit {
println("方法1")
}
5.1.3 递归函数
fun fn(n: Int): Int {
if (n == 0) {
return 1
} else if (n == 1) {
return 4
} else {
return 2 * fn(n - 1) + fn(n - 2)
}
}
5.1.4 单表达式函数
函数知识返回单个表达式,此时可以省略花括号并在等号(=)后制定函数体即可.
fun area(x: Double, y: Double): Double = x * y
5.2 函数的形参
5.2.1 命名参数
四种方式
-
使用传统调用函数的方式,根据位置传入参数
-
根据参数名来传入参数
-
使用命名参数时可交换位置
-
部分使用命名参数,部分使用位置参数
fun girth(width: Double, height: Double): Double {
println("width:${width}")
println("height:${height}")
return 2 * (width * height)
}
fun main(args: Array<String>) {
//使用传统调用函数的方式,根据位置传入参数
girth(3.0, 5.0)
//根据参数名来传入参数
girth(width = 3.0, height = 5.0)
//使用命名参数时可交换位置
girth(height = 5.0, width = 3.0)
//部分使用命名参数,部分使用位置参数
girth(3.0, height = 5.0)
}
5.2.2 形参默认值
为形参指定默认值的语法格式: 必须在形参类型后面加=值
形参名: 形参类型 = 默认值
命名参数实例:
fun girth(width: Double = 100.0, height: Double = 200.0): Double {
println("width:${width}")
println("height:${height}")
return 2 * (width * height)
}
fun main(args: Array<String>) {
//使用默认参数
girth()
}
5.2.3 尾递归函数 tailrec
当函数将调用自身作为它执行的最后一行代码,且递归调用后没有更多代码时,可使用尾递归语法.
- 尾递归需要使用tailrec修饰
- 不能再异常处理的try/catch/finally块中使用
//普通递归函数
fun fact(n: Int): Int {
if (n == 1) {
return 1
} else {
return n * fact(n - 1)
}
}
//尾递归函数
tailrec fun fact(n: Int, total: Int = 1): Int = if (n == 1) total else fact(n - 1, total * n)
5.2.4 个数可变的形参 vararg
vararg修饰形参,表示该形参可以接受多个参数值,多个参数值被当成数组传入.
定义方式:
fun test(vararg books: String, num: Int) {
for (book in books){
println(book)
}
}
5.3 函数重载
Kotlin允许定义多个同名函数,只要形参列表或返回值类型不同就行.
//无参函数
fun test() {
println("无参数的test()函数")
}
//有参函数
fun test(msg: String) {
println("无参数的test()函数")
}
//有返回值函数
fun test(msg: Int): String {
println("无参数的test()函数")
return msg.toString()
}
5.4 局部函数
局部函数就是函数里的函数
定义局部函数方式:
fun getMathFunc(type: String, nn: Int): Int {
fun square(n: Int): Int {
return n * n
}
fun cube(n: Int): Int {
return n * n * n
}
fun factorial(n: Int): Int {
var result = 1
for (index in 2..n) {
result *= index
}
return result
}
//调用局部函数
when (type) {
"square" -> return square(nn)
"cube" -> return cube(nn)
else -> return factorial(nn)
}
}
使用局部函数
fun main(args: Array<String>) {
//使用局部函数方式
println(getMathFunc("square", 3)) //输出9
println(getMathFunc("cube ", 3)) //输出27
println(getMathFunc("", 3)) //输出6
}
5.5 高阶函数
Kotlin不是单纯的面向对象语言,函数也具有自己的类型,函数类型就像数据类型一样,既可用于定义变量,也可用作函数的形参类型,还可以作为返回值
5.5.1 使用函数类型 ::函数名
定义一个函数类型的变量
fun main(args: Array<String>) {
//定义一个变量 类型为(Int, Int) -> Int
var muFun: (Int, Int) -> Int
//定义一个变量 类型为Sting
var test: (String)
//定义一个乘方的函数
fun pow(base: Int, exponet: Int): Int {
var result = 1
for (i in 1..exponet) {
result *= base
}
return result
}
//给函数赋值类型
muFun = ::pow
println(muFun(3, 4))
}
5.5.2 使用函数类型作为形参类型
fun map(data: Array<Int>, fn: (Int) -> Int): Array<Int> {
var result = Array<Int>(data.size, { 0 })
for (i in data.indices) {
result[i] = fn(data[i])
}
return result
}
fun cube(n: Int): Int {
return n * n * n
}
var data = arrayOf(3, 4, 5)
fun main(args: Array<String>) {
println(map(data, ::cube).contentToString())
}
5.5.3 使用函数类型作为返回值类型
fun getMathFunc(type: String): ((Int) -> Int) {
//定义一个计算平方的局部函数
fun square(n: Int): Int {
return n * n
}
//定义一个计算立方的局部函数
fun cube(n: Int): Int {
return n * n * n
}
//定义一个计算阶乘的局部函数
fun factorial(n: Int): Int {
var result = 1
for (index in 2..n) {
result *= index
}
return result
}
when (type) {
"square" -> return ::square
"cube" -> return ::cube
else -> return ::factorial
}
}
fun main(args: Array<String>) {
var mathFunc = getMathFunc("square")
println(mathFunc(5))
mathFunc = getMathFunc("cube")
println(mathFunc(5))
mathFunc = getMathFunc("factorial")
println(mathFunc(5))
}
5.6 局部函数与Lambda表达式
Lambda表达式是现代编程语言正向引入的一种语法,函数是命名的,方便复用的代码块,Lambda则是功能更灵活的代码块.
5.6.1 回顾局部函数
5.6.2 使用Lambda表达式代替局部函数
Lambda表达式与局部函数的区别
- Lambda表达式总是被大括号括着 { }
- 定义Lambda表达式不需要fun关键字,也无需指定函数名
- 形参列表(如果有的话),在 -> 之前定义,参数类型可以省略
- 函数体 放在-> 之后
- 函数的最后一个表达式自动被作为Lambda表达式的返回值,无需使用return
fun getMathFunc(type: String): ((Int) -> Int) {
when (type) {
"square" -> return { n: Int -> n * n }
"cube" -> return { n: Int -> n * n * n }
else -> return { n: Int ->
var result = 1
for (index in 2..n) {
result *= index
}
result
}
}
}
fun main(args: Array<String>) {
var mathFunc = getMathFunc("square")
println(mathFunc(5))
mathFunc = getMathFunc("cube")
println(mathFunc(5))
mathFunc = getMathFunc("factorial")
println(mathFunc(5))
}
5.6.3 Lambda表达式的脱离
作为参数的Lambda表达式,可以脱离函数独立使用.
//定义一个ArrayList类型的变量,值为空
var lambdalist = ArrayList<(Int) -> Int>()
//定义一个函数,形参类型为 函数
fun collectFn(fn: (Int) -> Int) {
//将传入的函数类型的参数 添加到 ArrayList中
lambdalist.add(fn) //添加数据
}
fun main(args: Array<String>) {
//每调用一次,lambdaList就添加一个数据
collectFn({ it * it })
collectFn({ it * it * it })
println(lambdalist.size)
for (i in lambdalist.indices) {
println(lambdalist[i](i + 10))
}
}
5.7 Lambda表达式
Lambda表达式标准语法:
{(形参列表) ->
//0条到多条可执行语句
}
5.7.1 调用Lambda表达式
- 将Lambda表达式赋值给变量,然后使用变量调用 (可重复调用)
- 在定义Lambda表达式时候,在结尾处用小括号传值进去,(直接调用) (不可重复调用)
fun main(args: Array<String>) {
var square = { n: Int ->
n * n
}
//1.使用square调用Lambda表达式
println(square(5))
println(square(6))
var result = { base: Int, exponent: Int ->
var result = 1
for (i in 1..exponent) {
result *= base
}
result
}(4, 3) //2.直接调用方式
println(result)
}
5.7.2 利用上下文推断类型
完整的Lambda表达式需要定义形参,如果Kotlin可以根据Lambda表达式上下文推断出形参类型,就可以省略形参
var square: (Int) -> Int = { n -> n * n }
fun main(args: Array<String>) {
var square: (Int) -> Int = { n -> n * n }
//使用square调用Lambda表达式
println(square(5))
println(square(6))
var list = listOf<String>("java", "kotlin", "go", "php", "object-c")
var dropWhile = list.dropWhile({ e -> e.length > 3 })
println(dropWhile) //go, php, object-c]
}
5.7.3 省略形参名
如果形参只有一个参数,允许省略表达式的形参名,没有了形参 -> 符号也可以不要
var square: (Int) -> Int = { it * it }
//使用square调用Lambda表达式
println(square(5))
println(square(6
var list = listOf<String>("java", "kotlin", "go", "php", "object-c")
var dropWhile = list.dropWhile({ it.length > 3 })
println(dropWhile) //go, php, object-c]
5.7.4 调用Lambda表达式的约定
函数的最后一个参数是函数类型,并且打算传入一个Lambda表达式作为这个参数,那么就允许在与在元括号之外指定Lambda表达式(尾随包)
var list = listOf<String>("java", "kotlin", "go", "php", "object-c")
var dropWhile = list.dropWhile({ it.length > 3 })
如果Lambda表达式是函数调用的唯一参数,那么可以省略()
var list = listOf<String>("java", "kotlin", "go", "php", "object-c")
//省略的两种情况
var dropWhile = list.dropWhile({ it.length > 3 })
var reduceList = list.reduce { acc, e -> acc + e }
println(dropWhile) //go, php, object-c]
println(reduceList) //javakotlingophpobject-c
5.7.5 个数可变的参数和Lambda参数
fun <T> test(vararg names: String, transform: (String) -> T): List<T> {
var mutablelist: MutableList<T> = mutableListOf()
for (name in names) {
mutablelist.add(transform(name))
}
return mutablelist.toList()
}
fun main(args: Array<String>) {
//这个声明类型: List<Int>可以省略
var list1: List<Int> = test("java", "kotlin", "go", "php", "object-c") { it.length }
println(list1) //[4, 6, 2, 3, 8]
//这个声明类型: List<String>可以省略
var list2: List<String> = test("java", "kotlin", "go", "php", "object-c") { "我喜欢${it}" }
println(list2) //[我喜欢java, 我喜欢kotlin, 我喜欢go, 我喜欢php, 我喜欢object-c]
}
5.8 匿名函数
匿名函数简洁,方面
但是匿名函数不能指定返回值类型
5.8.1 匿名函数的用法
定义方式
- 将普通函数的函数名去掉就变成了匿名函数
- 如果系统可以推算出匿名函数的形参类型,就可以省略匿名函数形参类型
var fun1 = fun(参数:参数类型):返回值类型{
//函数体
return xxx
}
匿名函数赋值给变量使用场景
fun main(args: Array<String>) {
//定义匿名函数,赋值给test变量
var test = fun(x: Int, y: Int): Int {
return x + y
}
//通过test调用匿名函数
println(test(2,4))
}
匿名函数在方法内部使用场景
//初始化List赋值
var list1 = listOf<String>("西红柿", "洋葱", "牛肉", "咖喱料").filter(
fun(el): Boolean {
return el.length > 2
})
匿名函数体是但表达式情况,省略返回值类型
var wawa = fun(x: Int, y: Int) = x + y
println(wawa(2,4))
//初始化List赋值
var list1 = listOf<String>("西红柿", "洋葱", "牛肉", "咖喱料").filter(
fun(el) = el.length>2
)
5.8.2 匿名函数和Lambda表达式的return
匿名函数本质还是函数
- 匿名函数的return用于返回该函数本身
- Lambda表达式的return用于返回它他在的函数
匿名函数的return用于返回该函数本身
fun main(args: Array<String>) {
//初始化List赋值
var list = listOf<String>("西红柿", "洋葱", "牛肉", "咖喱料")
list.forEach(
fun(n) {
println("元素为${n}")
return //返回函数本身
}
)
}
Lambda表达式的return用于返回它他在的函数
var list = listOf<String>("西红柿", "洋葱", "牛肉", "咖喱料")
list.forEach({
println("元素为${n}")
return //
})
Lambda表达式中使用return(使用限定语法)
var list = listOf<String>("西红柿", "洋葱", "牛肉", "咖喱料")
list.forEach({ n ->
println("元素为${n}")
return@forEach //仅会结束forEach循环,不会退出main方法
})
5.9 获取上下文中的变量和常量
Lambda表达式或匿名函数可以*访问或修改*其所在上下文(俗称"闭包")中的变量和常量
//定义一个函数,返回值类型为 () ->List<String>
fun makeList(ele: String): () -> List<String> { //外部函数
//创建空List
var list: MutableList<String> = mutableListOf()
fun addElement(): List<String> { //局部函数
//添加一个元素到list中
list.add(ele)
return list
}
return ::addElement //把局部函数返回给某个变量
}
fun main(args: Array<String>) {
var add1 = makeList("孙悟空")
println(add1()) //孙悟空
println(add1()) //孙悟空,孙悟空
var add2 = makeList("沙和尚")
println(add2()) //沙和尚
}
5.10 内联函数 inline
5.10.1 内联函数的使用
//定义函数类型的形参,fn是(Int) -> Int类型的形参
inline fun map(data: Array<Int>, fn: (Int) -> Int): Array<Int> {
//通过数组构造器创建一个数组
var result = Array<Int>(data.size, { 0 })
//遍历data每个元素,并用fn函数对data[i]进行计算
for (i in data.indices) {
result[i] = fn(data[i])
}
return result
}
fun main(args: Array<String>) {
var arr = arrayOf(2,3,4,5,6,7,8,9)
var mapResult = map(arr, { it + 10 }) //使用内联函数
println(mapResult.contentToString)
}
inline修饰的是一个内联函数,该函数包含一个函数类型的形参
编译后只生成一个InlineTestKt,class文件,不会生成其他额外的内部类class文件
内联函数的缺点:
内联函数本质是将被调用的Lambda表达式或函数的代码赋值,粘贴到原来的执行函数中
-
如果Lambda表达式或函数的代码量非常大,且被多次调用,就不适合用内联函数
-
如果Lambda表达式或函数的代码量小,就应该用内联函数
5.10.2 部分禁止内联 noinline
inline修饰函数之后,所有传入该函数的Lmabda表达式或函数都会被内联化.
如果希望某个或者某几个函数类型的形参不会被内联化,就使用oninline修饰他们.
inline fun test(fn1: (Int) -> Int, noinline fn2: (String) -> String) {
println(fn1(20))
println(fn2("kotlin"))
}
fun main(args: Array<String>) {
test({ it * it }, { "疯狂${it}" })
}
被noinline修饰过的形参,再次编译该文件,将会生成两个文件,意味着Kotlin取消了调用test函数传入的第二个Lambda表达式的内联化,因此编译时只需要为传入test函数的第一个Lambda表达式额外生成一个函数对象.
5.10.3 非局部返回
在Lmabda表达式中不允许直接使用return,因为会直接返回所在的函数
由于内联的Lambd表达式会被直接复制,粘贴到调用它的函数中,所以可以再内联的Lambda表达式中使用return
该内联的Lmabda表达式中的return可用于返回它所在的函数,这种返回被称为非局部返回
inline修饰的函数内部有Lambda表达式时候,允许使用return,没有则不允许使用return.
非内联的Lambda表达式中不能使用return
inline fun each(data: Array<Int>, fn: (Int) -> Unit) {
for (el in data) {
fn(el)
}
}
fun main(args: Array<String>) {
var arr = arrayOf(20, 3, 45, 23, 69)
each(arr, {
println(it)
return //如果each没有inline修饰,此处编译错误
//如果each有inline修饰,此处的return将返回main函数
})
}
上面结果就是只除数20就结束了,因为return了
如果内联函数不是直接从函数体中调用的Lambda表达式,而是从其他的执行上下文(局部对象或几句函数)中来获取Lmabda表达式.此时应该用crossinline来修饰参数
inline fun f(crossinline body: () -> Unit) {
val f = object : Runnable {
override fun run() = body()
}
}
第6章 面向对象
Kotlin支持面向对象编程,提供了定义类,属性,方法等最基本的功能
面向对象的三大特征:封装,继承,多态
访问控制符:private protected internel public
6.1类和对象
类是对象的体现,对象是类的示例.
独立的才叫做函数
在类中定义的叫方法
类的作用
- 定义变量
- 创建对象
- 派生子类
6.1.1 定义类
[修饰符] class 类名 [construction 主构造器]{
零到多个次构造器
零到多个属性
零到多个方法
}
定义一个空类,没有类体的,可以省略花括号
class Empty
主构造器
定义构造器的语法格式
[修饰符] constructor(形参列表){
//0到多条可执行语句组成的构造器执行体
}
class User constructor(firstName:String){
}
如果主构造器没有任何注解或修饰符,则可以省略constructor关键字
class User (firstName:String){
}
如果程序没有为非抽象类定义任何主次构造器,系统默认提供无参主构造器,使用public修饰,一旦提供了构造器,系统将不会提供该类的构造器.
定义属性的语法格式
[修饰符] var|val 属性名:类型 [=默认值]
- 修饰符 public/protected/internal/private/final/open/abstruct
- public|protected|peivate 只能出现其中之一
- final|open|abstract也只能出现其中之一
- 如果private修饰属性,该属性将作为幕后属性使用
- var|val
- var读写属性
- val只读属性
- 属性名
- 类型
- 可以使kotlin允许的任意数据类型
- 默认值
- 可以指定一个可选的默认值,要么在形参处指定,要么在构造器中指定
- getter|setter
- kotlin会为读写属性默认提供getter / setter方法
- 为只读属性提供默认的getter方法
创建一个类
class Person {
//定义两个属性
var name: String = ""
var age: Int = 0
//定义主构造方法
public constructor(name: String, age: Int) {
this.name = name
this.age = age
}
//定义一个say方法
fun say(content: String) {
println(content)
}
}
6.1.2 对象的产生和作用
使用上面那个Person类
fun main(args: Array<String>) {
var p: Person //定义类的类型
p = Person("李四", 24) //给类的类型赋值
//定义和赋值一起
var p2: Person = Person("zhangsan", 23)
//取值
println(p2.name) //zhangsan
println(p2.age) //23
//赋值
p2.name = "lisi"
println(p2.name) //lisi
}
6.1.3 对象的this引用
Kotlin提供了this关键字,他hi是关键字总是指向调用该方法的对象,根据this出现位置的不同,this作为对象的默认引用有三种情况.
- 在构造器中引用正在初始化的对象
- 在方法中引用调用该方法的对象
- 在扩展函数或带接受者的匿名函数中,代表点号左边传递的接收者
构造方法中调用类中的属性
class Person {
//定义两个属性
var name: String = ""
var age: Int = 0
//定义主构造方法
public constructor(name: String, age: Int) {
this.name = name
this.age = age
}
//定义一个say方法
fun say(content: String) {
println(content)
}
}
方法中调用该类其他方法
class Dog{
fun jump(){
println("狗狗跳")
}
fun run(){
this.jump()
println("狗狗跑")
}
}
大部分时候,一个方法访问该类中定义的其他方法,属性时,加不加this前缀,效果完全一样的
this作为对象默认引用使用时
可以像普通变量一样访问this引用,甚至可以把this当成普通方法的返回值
class ReturnThis {
var age = 0
fun grow(): ReturnThis {
age++
return this
}
}
fun main(args: Array<String>) {
val rt = ReturnThis()
rt.grow().grow().grow().grow().grow()
println(rt.age) //5
}
6.2 方法详解
Kotlin的方法不仅仅是单纯的方法,与函数也有极大的关系
6.2.1 方法与函数的关系
Kotlin的方法与函数其实是统一的,语法相同,定义在类中的方法依然可以独立出来,及时我们将方法定义在类里面,这个方法也依然可以转换为函数.
class Dog {
fun jump() {
println("狗狗跳")
}
fun run() {
this.jump()
println("狗狗跑")
}
fun eat(food: String) {
println("正在吃:${food}")
}
}
fun main(args: Array<String>) {
var rn: (Dog) -> Unit = Dog::run
var dog = Dog()
rn(dog) //狗狗跳 狗狗跑
var et = Dog::eat
et(dog, "肉肉") //正在吃:肉肉
}
:: 定义函数类型的变量
6.2.2 中缀表示法 infix
方法使用infix修饰,这样该方法就可通过中缀表示法调用,就像这些方法是双目运算符一样.
infix方法只能有一个参数
class ApplePack(weight: Double) {
var weight = weight
override fun toString(): String {
return "ApplePack [weight=${this.weight}]"
}
}
class Apple(weight: Double) {
var weight = weight
override fun toString(): String {
return "Apple (weight-$(this.weight ]"
}
//定义中缀方法,使用infix修饰
infix fun add(other: Apple): ApplePack {
//这里返回的是ApplePack里的toString方法
return ApplePack(this.weight + other.weight)
}
//定义中缀方法,使用infix修饰
infix fun drop(other: Apple): Apple {
this.weight -= other.weight
return this
}
}
fun main(args: Array<String>) {
var apple = Apple(3.4)
val ap = apple add Apple(2.4)
println(ap) //ApplePack [weight=5.8]
apple drop Apple(1.4)
println(apple) //Apple (weight-$(this.weight]
}
6.2.3 componentN方法与解构
Kotlin允许将一个对象的N个属性"解构给多个变量"
var (name, age) = user
这行代码相当于把uset对象的两个属性分别赋值给name,pass两个变量,类型会根据user对象的属性类型来推断
上面这行代码会自动转换为下面这两行
var name = user.component1()
var age = user.component2()
如果希望将对象解构给多个变量,那么必须为该对象的类定义componentN()方法
希望将对象解构给几个变量,就需要定义几个componentN()方法
class User(name: String, age: Int, pass: String) {
var name = name
var age = age
var pass = pass
operator fun component1(): String {
return this.name
}
operator fun component2(): Int {
return this.age
}
operator fun component3(): String {
return this.pass
}
}
fun main(args: Array<String>) {
var user = User("张三", 23, "123456789")
var (name,age,pass) = user
println(name) //张三
println(age) //23
println(pass) //123456789
}
某些时候只想要后面几个属性,这是用到 ( _ )来占位
class User(name: String, age: Int, pass: String) {
var name = name
var age = age
var pass = pass
operator fun component1(): String {
return this.name
}
operator fun component2(): Int {
return this.age
}
operator fun component3(): String {
return this.pass
}
}
fun main(args: Array<String>) {
var user = User("张三", 23, "123456789")
var (_,age,pass) = user
println(age) //23
println(pass) //123456789
}
map集合的遍历形式
var map = mutableMapOf<String, String>("张三" to "23", "李四" to "24", "王五" to "25")
for ((key, value) in map) {
println(key + value)
}
6.2.4 数据类型和返回多个值的函数
Kotlin提供了一种特殊的类:数据类,数据类型专门用于封装数据.
- 数据类型使用data修饰
- 主构造器至少有一个参数
- 所有参数需要用val或var声明
- 不能用abstract/open/sealed修饰,也不能定义成内部类
- 自动为数据类型生成equals()/hashCode()方法
- 自动重写toString()方法
- 为每个属性自动生成operator修饰的componentN()方法
- 生成copy()方法,用于完成对象的复制
定义格式
data class DataDemo(val result: Int, val status: String)
使用data类型
fun factorial(n: Int): DataDemo {
if (n == 1) {
return DataDemo(1, "成功")
} else if (n > 1) {
return DataDemo(factorial(n - 1).result * n, "成功")
} else {
return DataDemo(-1, "参数必须大于0")
}
}
fun main(args: Array<String>) {
var (rt, status) = factorial(6)
println(rt) //720
println(status) //成功
}
使用方法解构
var (result, statuss) = DataDemo1(100, "成功")
println(result) //100
println(statuss) //成功
使用copy方法
var myResult = DataDemo1(100, "成功")
val oldRt = myResult.copy()
println(oldRt) //DataDemo1(result=100, status=成功)
6.2.5 在Lambda表达式中解构
map集合的解构形式
var map = mutableMapOf<String, Int>("张三" to 23, "李四" to 24, "王五" to 25, "赵六" to 26)
map.mapValues { entry -> "${entry.value}" }
println(map) //{张三=23, 李四=24, 王五=25, 赵六=26}
map.mapValues { (key, value) -> "$value" }
println(map) //{张三=23, 李四=24, 王五=25, 赵六=26}
lambda解构形式
{a -> ...} //一个参数
{a,b -> ...}//两个参数
{(a,b) -> ...}//一个解构对
{(a,b),c -> ...}//一个解构对和一个参数
如果解构的参数中的一个未使用,可以将其替换为下划线
map.mapValues { (_, value) -> "$value" }
6.3 属性和字段
kotlin中的属性相当于Java中的字段(field)
6.3.1 读写属性和只读属性
- val 只读属性
- 只读属性生成getter方法
- var读写属性
- 读写属性生成getter方法和setter方法
6.3.2 自定义getter和setter
自定义getter方法
class User(first: String, last: String) {
var first: String = first
var last: String = last
val fullName: String
//为fullName重写getter方法
get() {
println("执行fullName的getter方法")
return "${first}.${last}"
}
}
fun main(args: Array<String>) {
var user = User("三丰", "张")
println(user.fullName) //三丰.张
}
自定义setter方法
class User(first: String, last: String) {
var first: String = first
var last: String = last
var fullName: String
//为fullName重写getter方法
get() {
println("执行fullName的getter方法")
return "${first}.${last}"
}
set(value) {
println("执行fullNamede的setter方法")
//设置 value中不包含.
if ("." !in value || value.indexOf(".") != value.lastIndexOf(".")) {
println("输入的不合法")
} else {
var tokens = value.split(".")
first = tokens[0]
last = tokens[1]
}
}
}
6.3.3 幕后字段 field
Kotlin中定义一个普通属性时,Kotlin会为该属性生成一个field(字段),
如果Kotlin类的属性有幕后字段,则Kotlin要求该属性显示指定初始值
- 要么在定义时指定
- 在构造器中指定
- 如果没有幕后字段,则不允许为该属性指定初始值
class Person(name: String, age: Int) {
//使用private修饰属性,将这些属性隐藏起来.
var name = name
set(newName) {
if (newName.length > 6 || newName.length < 2) {
println("输入的不符合要求")
} else {
field = newName
}
}
var age = age
set(newAge) {
//执行合理性校验,要求用户年龄必须在0~100之间
if (newAge > 100 || newAge < 0) {
println("您设置的年龄不合法")
} else {
field = newAge
}
}
}
fun main(args: Array<String>) {
var p = Person("张三", 23)
p.age =200 //您设置的年龄不合法
println(p.age) //23
}
6.3.4 幕后属性 field
被private修饰的属性是幕后属性,不会生成getter/setter方法,因此程序无法直接访问这个属性.
class BackingProperty(name: String) {
//使用private修饰属性,该属性是幕后属性
private var _name: String = name
var name
get() = _name
set(newName) {
if (newName.length > 6 || newName.length < 2) {
println("输入的不符合要求")
} else {
_name = newName
}
}
}
fun main(args: Array<String>) {
var p = BackingProperty("张三")
println(p.name)
p.name = "孙" //输入的不符合要求
println(p.name) //张三
}
6.3.5 延迟初始化属性 lateinit
Kotlin提供了lateinit修饰符来延迟属性初始化,可以在属性定义时或构造器都不指定初始值
lateinit修饰符的使用限制:
- 只能修饰在类体中声明的可变属性
- 不能有自定义的getter/setter方法
- 属性必须是非空类型
- 属性不能是原生类型(Java的8种基本类型)
class LateinitUser {
var a: Int = 0
lateinit var name: String
lateinit var birthDay: Date
}
fun main(args: Array<String>) {
var user = LateinitUser()
user.name = "张三"
println(user.name) //张三
}
6.3.6 内联属性 inline
inline修饰符可以修饰没有幕后字段的属性的getter或setter方法
也可以修饰属性本身
class Name(name: String, desc: String) {
var name = name
var desc = desc
}
class Product {
var productor: String? = null
val proName: Name
inline get() = Name("张三", "23") //程序读取该属性时会变成内联化
var author: Name
get() = Name("李刚", "无")
inline set(value) {//程序设置该属性时会变成内联化
this.productor = value.name
}
inline var pubHouse: Name //程序读取和设置该属性时会变成内联化
get() = Name("电子工业出版社", "无")
set(value) {
this.productor = value.name
}
}
6.4 隐藏和封装
封装是面向对象的三大特征之一
-
隐藏类的实现细节
-
让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对属性的不合理访问。
-
可进行数据检查,=
-
便于修改,提高代码的可维护性。
-
将对象的属性和实现细节隐藏起来,不允许外部直接访问。
-
把方法暴露出来,让方法来控制对这些属性进行安全的访问和操作。
6.4.1 包和导包 Package
inport 包名
6.4.2 Kotlin的默认导入
Kotlin定义的List/Map,都会默认导入包
6.4.3 使用访问控制符
访问控制符 | 作用 |
---|---|
private | 只能在该类的内部或文件的内部被访问 |
internal | 只能在该类的内部或同一个模块内部被访问 |
protected | 只能在该类的内部或文件的内部或子类中被访问 |
public | 任意地方被访问 |
位于包内的顶层成员(顶层类,接口,函数,属性)
只能使用private internal public其中之一
- 使用private,这些顶层成员只能在当前文件中被访问
- 使用internal,这些顶层成员只能在当前文件或当前模块中被访问
- 使用public 或 不加修饰符,可以在任一地方被访问
private fun foo() {} //该函数尽在example.kt内可访问
internal fun test() {} //该函数可以在相同模块内被访问
fun info() {} //在任何地方可被访问
public var bar: Int = 5 //该属性可在任意地方被访问
private set //setter尽在example.kt内被访问
internal val baz = 6 //该属性仅在相同模块被访问
private set //仅在example.kt被访问
位于类/接口之内的成员
只能使用private internal protected public其中之一
- 使用private修饰,这些成员只能在该类中被访问
- 使用internal修饰,这些成员能在该类或当前模块中被访问
- 使用protected修饰,这些成员能在该类或子类中被访问
- 使用public或不加修饰符,可以在任意地方被访问
open class Outher {
private val a = 1 //只能在本类中访问
protected open val b = 2 //只能在本类 和 子类访问
internal val c = 3 //只能在本来 或 当前模块中被访问
val d = 4 //可以在任意地方被访问
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outher() {
//a 不可访问
//b/c/d 可访问
//Nested 和 e 可访问
override val b = 5 //b被重写 依然是 protected 访问权限
}
class Other(o: Outher) {
//o.a o.b都不可访问
//o.c 可访问(与Other类在同一个模块中)
//o.d 可访问
//Outher.Nested不可访问
//Nested的::e也不可访问
}
6.5 深入构造器
构造器用于在创建实例时执行初始化
构造器是创建对象的重要途径
6.5.1 主构造器和初始化块
- Kotlin的类可以定义0~1个主构造器,主构造器没有执行体
- 如果主构造器没有任何注解或可见性修饰符,则可以省略constructor关键字
- Kotiln的类可以定义0~N个次构造器,次构造器可以有执行体
主构造器作用:
- 初始化代码块使用主构造器形参
- 声明属性时可以使用主构造器形参
初始化块init的语法格式
init{
//初始化块中的可执行代码
//可以使用主构造器定义的参数
}
定义一个Person类,包含主构造器,包含初始化块
class MyPerson(name: String, age: Int) {
var name: String
var age: Int
init {
this.name = name + "三三"
this.age = age + 100
println("name:${name},age:${age}")
println("第1个初始化代码块")
}
init {
println("第2个初始化代码块")
}
}
fun main(args: Array<String>) {
var p = MyPerson("张三", 23)
//name:张三,age23
//第1个初始化代码块
//第2个初始化代码块
println(p.name) //张三三三
println(p.age) //123
}
6.5.2 次构造器和构造器重载 constructor
Kotlin允许使用constructor关键字定义N个次构造器,次构造器类似于Java的传统构造器
所有的次构造器都要先执行一次init初始化块
class ConstructorOverload {
var name: String?
var count: Int
init {
println("初始化块!")
}
constructor() {
name = null
count = 0
}
constructor(name: String, count: Int) {
this.name = name
this.count = count
}
}
fun main(args: Array<String>) {
var oc1 = ConstructorOverload()
println("${oc1.name}${oc1.count}") //初始化代码块 null0
var oc2 = ConstructorOverload("张三", 28)
println("${oc2.name}${oc2.count}") //初始化代码块 张三28
}
创建主构造器,次构造器,初始化代码块同时的情况
//主构造器
class MyUser(name: String) {
var name: String
var age: Int
var info: String? = null
//初始化代码块
init {
println("User的初始化块")
this.name = name
this.age = 0
}
//次构造器委托给主构造器 使用this
constructor(name: String, age: Int) : this(name) {
this.age = age
}
//次构造器委托给另一个次构造器
constructor(name: String, age: Int, info: String) : this(name, age) {
this.info = info
}
}
fun main(args: Array<String>) {
var user1 = MyUser("张三")
println("${user1.name}${user1.age}${user1.info}") //User的初始化块 张三0null
var user2 = MyUser("张三", 23)
println("${user2.name}${user2.age}${user2.info}") //User的初始化块 张三23null
var user3 = MyUser("张三", 23, "山东")
println("${user3.name}${user3.age}${user3.info}") //User的初始化块 张三23山东
}
6.5.3 主构造器声明属性 和 属性默认值
Kotlin允许在主构造器上使用var | val声明属性. 和给属性设置默认设置
class Itme(val code: String, var price: Double = 0.0) {
}
6.6 类的继承
继承是面向对象的三大特征之一
6.6.1 继承的语法
父类派生(derive)出了子类
子类扩展(extend)了父类
修饰符 class 子类名 : 父类名{
//类定义部分
}
Any类是所有类的父类
Any类只有三个方法:
- equals()
- hashCode()
- toString()
open关键字
Kotlin的类默认就有final修饰符,默认是不能被派生
为了能派生子类,需要时用open修饰父类
open class BaseClass{
}
class SubClass: BaseClass{
}
委托父类构造器
子类构造器总要调用父类构造器一次
- 子类构造器执行体的第一行使用super显示调用父类构造器,根据super调用中传入的实参列表传入参数
- 子类构造器执行体的第一行代码使用this显示调用本类中重载的构造器,系统将根据this调用中传入的实参列表,调用本类中另一个构造器,调用本类中的另一个构造器最终还是要调用父类构造器
- 子类构造器执行体中既没有super也没有this,系统会在执行子类构造器之前,隐式调用父类无参的构造器
子类的主构造器
如果子类定义了主构造器,由于主构造器属于类头部分,为了让主构造器能调用父类构造器,主构造器必须在继承父类的同时委托调用父类构造器
open class BaseClass{
var name:String
constructor(name:String){
this.name = name
}
}
//子类没有显示主构造器
//声明时委托调用父类构造器
class SubClass1: BaseClass("foo"){
}
//子类显示声明主构造器
//声明时委托调用父类构造器
class SubClass2(name: String): BaseClass(name){
}
子类的次构造器
次构造器同样需要委托调用父类构造器
- 显示使用this(参数)显示调用本类中重载的构造器,根据this(参数)调用中传入的实参列表调用另一个本类的构造器,最终还是要调用父类构造器
- 显示使用super(参数)委托调用父类构造器,系统根据super(参数)调用父类对应的构造器
- 子类构造器既没有super 也没有 this ,系统将根据在执行子类构造器之前,隐式调用父类无参构造
open class Base {
constructor() {
println("父类的无参构造")
}
constructor(name: String) {
println("父类的有参构造${name}")
}
}
class Sub : Base {
//构造器没有显示委托
//因此次构造器将会隐式委托调用父类无参的构造器
constructor() {
println("子类无参构造")
}
constructor(name: String) : super(name) {
println("子类的一个参数构造器")
}
constructor(name: String, age: Int) : this(name) {
println("子类的两个参数构造器")
}
}
fun main(args: Array<String>) {
Sub() //父类的无参构造 子类无参构造
Sub("张三")//父类的有参构造张三 子类的一个参数构造器
Sub("李四",24) //父类的有参构造李四 子类的一个参数构造器 子类的两个参数构造器
}
6.6.2 重写父类的方法 override
override修饰
open class Bird {
open fun fly() {
println("我在天空自有翱翔")
}
}
class Os : Bird() {
override fun fly() {
println("我只能在地上肆意奔跑")
}
}
fun main(args: Array<String>) {
var os = Os()
os.fly()//我只能在地上肆意奔跑
}
6.6.3 重写父类属性 override
open class Father {
open protected var name: String = "张三"
open val money: Int = 9999999
open var validDays: Int = 0
}
class Son : Father() {
override public var name: String = "小张张"
override val money: Int = 999
override var validDays: Int = 100
}
fun main(args: Array<String>) {
var son = Son()
println(son.name)//小张张
println(son.money)//999
println(son.validDays)//100
}
6.6.4 super限定
想要在子类方法中调用父类中被覆盖的方法或属性,可以使用super限定
open class Father {
open protected var name: String = "张三"
open val money: Int = 9999999
open var validDays: Int = 0
}
class Son : Father() {
fun printInfo(){
println(super.name)
println(super.money)
println(super.validDays)
}
}
fun main(args: Array<String>) {
var son = Son()
son.printInfo()
// 张三
// 9999999
// 0
}
6.6.5 强制重写
//父类
open class Foo{
open fun test(){
println("Foo的test")
}
fun foo(){
println("Foo的foo")
}
}
//接口
interface Bar{
fun test(){
println("Bar的test")
}
fun bar(){
println("Bar的bar")
}
}
//子类
class Wow:Foo(),Bar{
override fun test() {
super<Foo>.test() //Foo的test
super<Bar>.test() //Bar的test
}
}
fun main(args: Array<String>) {
var w = Wow()
w.test() //Foo的test Bar的test
w.foo()//Foo的foo
w.bar()//Bar的bar
}
6.7 多态 (polymorphism)
Kotlin的变量也有两个类型: 一个是 编译时类型,一个是 运行时类型
- 编译时类型由声明时使用的类型决定
- 运行时类型由实际赋给变量的对象决定
如果 编译时类型和 运行时类型不一致时,就可能出现多态
6.7.1 多态性
open class BaseClass {
open var book = 6
fun base() {
println("父类的base普通方法")
}
open fun test() {
println("父类允许被覆盖的test方法")
}
}
class SubClass : BaseClass() {
override var book: Int = 600
override fun test() {
println("子类重写的test方法")
}
fun sub(){
println("Sub子类的普通方法")
}
}
fun main(args: Array<String>) {
var b = BaseClass()
println(b.book)//6
b.base()//父类的base普通方法
b.test()//父类允许被覆盖的test方法
var s = SubClass()
println(s.book)//600
s.base()//父类的base普通方法
s.test()//子类重写的test方法
s.sub()//Sub子类的普通方法
var bb:BaseClass = SubClass()
println(bb.book)//600
bb.test()//子类重写的test方法
// bb.sub() //报错,因为是BaseClass类型,所以 没有 这个方法
}
bb编译时类型是BaseClass,运行时类型为SubClass
调用bb.test方法运行的是子类的方法,这就是多态
6.7.2 使用is检查类型
- is判断是某种类型
- !is判断不是某种类型
6.7.3 使用as运算符转型
- as 不安全的强制转型运算符,转型失败会ClassCastException异常
- as? 安全的强制转型运算符,转型失败程序不会引发异常,而是返回null
6.8 扩展
Kotlin支持扩展方法和扩展属性
6.8.1 扩展方法
语法: 定义一个函数,在函数名前面增加被扩展的类(或接口)名和点号(.)
fun 类名.扩展方法名(){
println("扩展的info方法")
}
扩展过后,本类的实例和子类的实例都可以使用这个方法
open class Raw {
fun test() {
println("test方法")
}
}
class Sub : Raw() {
}
fun main(args: Array<String>) {
var raw = Raw()
fun Raw.info() {
println("父类扩展的方法")
}
raw.info()//父类扩展的方法
var sub = Sub()
sub.info()//父类扩展的方法
}
为List集合扩展方法
//为List<Int>添加shuffle方法
fun List<Int>.shuffle(): List<Int> {
val size = this.size
//创建用于保存list集合的索引的随机排列
var indexArr = Array<Int>(size, { 0 })
var result: MutableList<Int> = mutableListOf()
//创建随机对象
val rand = Random()
var i = 0
outer@ while (i < size) {
//生成随机数
var r = rand.nextInt(size)
for (j in 0 until i) {
if (r == indexArr[j]) {
continue@outer
}
}
indexArr[i] = r
result.add(this[r])
i++
}
return result.toList()
}
fun main(args: Array<String>) {
var nums = listOf<Int>(20, 30, 40, 60, 70, 80, 10)
println(nums.shuffle())
println(nums.shuffle())
}
为List扩展方法
//为List<Int>添加shuffle方法
fun <T> List<T>.shuffle(): List<T> {
val size = this.size
//创建用于保存list集合的索引的随机排列
var indexArr = Array<Int>(size, { 0 })
var result: MutableList<T> = mutableListOf()
//创建随机对象
val rand = Random()
var i = 0
outer@ while (i < size) {
//生成随机数
var r = rand.nextInt(size)
for (j in 0 until i) {
if (r == indexArr[j]) {
continue@outer
}
}
indexArr[i] = r
result.add(this[r])
i++
}
return result.toList()
}
fun main(args: Array<String>) {
var nums = listOf<String>("zhangsan", "lisi", "wangwu", "zhaoliu", "maizqi")
println(nums.shuffle())
println(nums.shuffle())
}
6.8.2 扩展的实现机制
子父类中同时有相同名字的扩展方法
open class Base
class Son : Base()
fun Base.foo() = println("Base的foo方法")
fun Son.foo() = println("Son的foo方法")
fun invokFoo(b: Base) {
b.foo()
}
fun main(args: Array<String>) {
invokFoo(Base()) //Base的foo方法
invokFoo(Son()) //Base的foo方法
val bb: Base = Son()
bb.foo()//Base的foo方法
}
编译阶段Kotlin只知道invokeFoo()方法的形参是Base类型,因此调用Base的foo()方法
就算是创建出对象传入,也是一样调用Base的foo()方法
成员方法优先级高于扩展方法
class ExtensionAndMember {
fun foo() = println("我是成员方法")
}
fun ExtensionAndMember.foo() = println("我是扩展出来的")
fun main(args: Array<String>) {
var ea = ExtensionAndMember()
ea.foo()//我是成员方法
}
Java中调用Kotlin的扩展方法
public class InvokeExtension{
public static void main(Stringd[] args){
//创建对象
Raw t = new Raw();
//调用成员方法
t.test()
//调用扩展方法,需要自己解析成调用扩展函数
Raw_infoKt.info(t)
//创建RawSub对象
RawSub rs = new RawSub();
//调用RawSub对象的成员方法
rs.test()
rs.sub();
//调用扩展方法,需要自己解析成调用扩展函数
Raw_infoKt.info(rs);
}
}
6.8.3 为可空类型扩展方法
Kotlin允许可空类型(带?后缀的类型)扩展方法
fun Any?.equals(other: Any?): Boolean {
if (this == null) {
return if (other == null) true else false
}
return this.equals(other)
}
fun main(args: Array<String>) {
var a = null
println(a.equals(null))
println(a.equals("Kotlin"))
}
为可空类型扩展了equals方法,null值也可以调用equals()方法与其他对象进行比较了.
6.8.4 扩展属性
Kotlin允许扩展属性,并没有修改原始类,只是通过getter setter方法添加进去的
扩展属性的限制:
- 扩展属性不能有初始值
- 不能用field关键字显示访问幕后字段
- 扩展只读属性必须提供getter方法,读写属性必须提供getter / setter方法
class User(var firstName: String, var lastName: String) {}
var User.fullName: String
get() = "${firstName}${lastName}"
set(value) {
println("扩展属性的set方法")
if ("." !in value || value.indexOf(".") != value.lastIndexOf(".")) {
println("输入的fullName不合法")
} else {
var split = value.split(".")
firstName = split[0]
lastName = split[1]
}
}
fun main(args: Array<String>) {
var u = User("悟空", "孙")
println(u.fullName) //悟空孙
u.fullName = "八戒.猪"
println(u.fullName)//八戒猪
println(u.firstName)//八戒
println(u.lastName)//猪
}
6.8.5 以类成员方式定义扩展
以类成员方式定义的扩展,一方面属于被扩展的类,另一方可以直接调用所在类的成员
class A {
fun bar() = println("A的bar方法")
}
class B {
fun baz() = println("B的baz方法")
fun A.foo() {
bar()//A的bar方法
baz()//B的baz方法
}
fun test(a: A) {
a.bar()//A的bar方法
a.foo()
}
}
fun main(args: Array<String>) {
var b = B()
b.test(A())//A的bar方法 A的bar方法 B的baz方法
}
通过this@调用本类的同名方法
class Tiger {
fun foo() = println("Tiger的foo方法") //Tiger的foo方法
}
class Bear {
fun foo() = println("Bear的foo方法")
fun Tiger.test() {
foo()
this@Bear.foo()//Bear的foo方法
}
fun info(tiger: Tiger) {
tiger.test()
}
}
fun main(args: Array<String>) {
var b = Bear()
b.info(Tiger())
}
6.8.6 带接受者的匿名函数
Kotiln支持为类扩展匿名函数称为"带接收者的匿名函数"
该扩展函数所属的类也是该函数的接收者.
val factorial = fun Int.(): Int {
if (this < 0) {
return -1
} else if (this == 0) {
return 0
} else {
return 1
}
}
fun main(args: Array<String>) {
println(6.factorial()) //1
}
作为参数使用
class HTML {
fun body() {
println("HTML的body")
}
fun head() {
println("HTML的head")
}
}
fun html(init: HTML.() -> Unit) {
println("<html>")
val html = HTML()
html.init()
println("</html>")
}
fun main(args: Array<String>) {
html {
head()
body()
}
/*
<html>
HTML的head
HTML的body
</html>
* */
}
6.8.7 何时使用扩展
扩展的作用:
- 扩展可动态地为已有的类添加方法或属性
- 扩展能以更好地形式组织一些工具方法
6.9 final和open修饰符
final修饰类/属性/方法,表示不可变,但是final不能修饰局部变量
Kotlin会为非抽象类自动添加final修饰符,
取消自动添加的final,需要使用open修饰符
6.9.1 可执行"宏替换"的常量 const
因为Kotlin不允许使用final修饰局部变量,也不允许直接在Kotlin类中定义变量.
因此Kotlin不可能用final定义"宏变量"
Kotlin提供了const来修饰可执行"宏替换"的变量,这种常量也被称为编译时常量,因为他在编译阶段就会被替换掉.
"宏替换"的常量除了使用const修饰之外,还必须满足以下三点
- 位于顶层或者是对象表达式的成员
- 初始值为基本类型值
- 没有自定义的getter方法
常量
直接赋予,并且在程序执行阶段会被替换为里面的具体数据
const val MAX_AGE = 100
fun main(args: Array<String>) {
println(MAX_AGE)
}
宏变量
宏变量是const修饰的val,被赋值为算术表达式或者字符串连接运算
const val a = 5 + 2
const val b: Double = 5.0 / 2
const val c: String = "我喜欢" + "这本书"
const val d: String = "我喜欢" + 9999999999
fun main(args: Array<String>) {
println(a)//7
println(b)//2.5
println(c)//我喜欢这本书
println(d)//我喜欢9999999999
}
6.9.2 final属性
final修饰属性不能被重写,子类可以创建同名的新属性
如果对属性不使用任何修饰符 Kotiln会自动为该属性添加final修饰
open class FinalData {
var test: String = "测试属性" //默认被final修饰
}
class Sub : FinalData() {
//override var test: String = "子类属性" //因为test没有open修饰符,所以不能被重写
}
6.9.3 final方法
final修饰方法不可被重写,但是可以被重载,子类也可以创建同名的新方法
如果对方法不使用任何修饰符**** Kotiln会自动为该方法添加final修饰**
open class Father{
fun test(){}
}
class Son:Father(){
//override fun test(){} //方法不可被重写
}
6.9.4 final类
final修饰的类不可以有子类,无法被继承,需要使用open修饰才可被继承
class FinalClass{}
class Sub:FinalClass(){} //报错
6.9.5 不可变类
不可变类的意思是创建该类的实例后,实例的属性值是不可变的
不可变类的规则:
- 提供带参数的构造器,根据传入的参数来初始化类中的属性
- 定义使用final修饰的只读属性,避免程序通过setter方法改变该属性值
如果有必要,要从写 hashCode()和equals()方法,将关键是性作为两个对象是否相等的标准
class Address(val data: String, val age: Int) {
override operator fun equals(other: Any?): Boolean {
if (this == other) {
return true
}
if (other == null) {
return false
}
if (other.javaClass == Address::class) {
var ad = other as Address
return this.data.equals(ad.data) && this.age == ad.age
}
return false
}
override fun hashCode(): Int {
return data.hashCode() + age.hashCode() * 31
}
}
当不可变类 的 参数为类对象,但是类对象可变时候,不可变类变为可变的
class Name(var firstName: String = "", var lashName: String = "") {}
class Person(val name: Name) {}
fun main(args: Array<String>) {
val name = Name("悟空", "孙")
var person = Person(name)
println(person.name.firstName)//悟空
println(person.name.lashName)//孙
//因为Name是可变的,所以可以更改名字
name.firstName = "八戒"
println(person.name.firstName)//八戒
}
如果设计一个不可变类,要注意其引用类型,如果属性的类型本身是可变的,就采取必要措施来保护该属性所引用的对象不会被修改,这样才能创建真正的不可变类.
class Name(var firstName: String = "", var lashName: String = "") {}
class Person {
val name: Name
get() = Name(field.firstName, field.lashName)
constructor(name: Name) {
//这里把传入的对象重新创建一份,就不会被外面修改了
this.name = Name(name.firstName, name.lashName)
}
}
fun main(args: Array<String>) {
val name = Name("悟空", "孙")
var person = Person(name)
println(person.name.firstName)//悟空
println(person.name.lashName)//孙
//因为Name是可变的,所以可以更改名字
name.firstName = "八戒" //这里修改的的是 Name创建出来的,并不会影响 Person里面重新创建的那个
println(person.name.firstName)//悟空
}
6.10 抽象类
6.10.1 抽象成员和抽象类
抽象成员/抽象方法/抽象属性/抽象类 必须使用abstract修饰符来定义
有抽象成员的类一定是抽象类,抽象类里不一定有抽象成员.
抽象方法和抽象类的5项规则:
- 抽象类和抽象成员(方法和属性)必须用abstract来修饰
- 抽象方法不能有方法体
- 抽象类不能被实例化,无法使用抽象类的构造器创建对象.
- 抽象类可以包含属性和方法(普通方法和抽象方法都可以),构造器,初始化块,嵌套类(接口/枚举)5种成员
- 含有抽象成员的类,只能被定义成抽象类
抽象方法定义方式:
方法前增加abstract修饰符,并且去掉方法体.
抽象类定义方式:
在普通类上加上abstract修饰符即可
abstract class Shape {
//初始化代码块
init {
println("抽象类Shape的初始化代码块")
}
//抽象属性
var color = ""
//无参构造
constructor() {}
//有参构造
constructor(color: String) {
println("执行抽象类Shape的有参构造")
this.color = color
}
//抽象方法
abstract fun getPerimeter(): Double
}
实现抽象类
class Rect(var height: Double, var width: Double, color: String) : Shape(color) {
override fun getPerimeter(): Double {
return height * width
}
}
fun main(args: Array<String>) {
var r = Rect(12.0,22.0,"蓝色")
println(r.getPerimeter())
println(r.color)
// 抽象类Shape的初始化代码块
// 执行抽象类Shape的有参构造
// 264.0
// 蓝色
}
抽象类继承非抽象类
抽象类作为子类可以不重写非抽象类的方法
open class Base {
open fun foo() {}
}
abstract class Sub : Base() {
//可以不重写方法
}
抽象类继承抽象类
抽象类作为子类可以不重写非抽象类的方法
abstract class Base {
abstract fun foo()
}
abstract class Sub : Base() {
//可以不重写方法
}
6.10.2 抽象类的作用
作为一种模板的设计
抽象类可以之定义需要使用的某些放阿飞,把不能实现的部分抽象成抽象方法,留给子类去实现
6.10.3 密封类
密封类是一种很特殊的抽象类
密封类作用
派生子类
密封类和普通类的区别
密封类的子类是固定的,子类必须与密封类同在一个文件中,在其他文件中不能为密封类派生子类
sealed class Apple {
abstract fun taste()
}
open class RedFuji : Apple() {
override fun taste() {
println("红富士很好吃")
}
}
//创建一个Data类,类似于Bean类
data class Gala(var weight: Double) : Apple() {
override fun taste() {
println("Gala很好吃${weight}")
}
}
fun main(args: Array<String>) {
var ap1: Apple = RedFuji()
var ap2: Apple = Gala(2.5)
ap1.taste()//红富士很好吃
ap2.taste()//Gala很好吃2.5
}
6.11 接口 interface
6.11.1 接口的定义
使用 interface关键字修饰
定义格式
[修饰符] interface 接口名: 父接口1, 父接口2 ...{
0-多个属性
0-多个方法
0-多个嵌套类/嵌套接口/嵌套枚举定义
}
- 修饰符
- public | internal | private 中的任意一个
- 可以省略修饰符,省略了,默认采用public
- 命名规则
- 与类名采取相同的命名规则
- 继承
- 可以有多个直接父接口
- 只能继承接口
- 不能继承类
和Java接口的相似处:
都可以包含抽象方法和非抽象方法
接口中的属性没有幕后字段
接口中的属性要么声明为抽象属性,要么提供setter/getter
接口中的方法
可以是抽象也可以是非抽象
如果方法没有方法体,默认添加abstract修饰符
接口中的属性
如果一个只读属性没有定义getter方法,接口默认添加abstract关键字
如果一个读写属性没有setter/getter方法,接口默认添加abstract关键字
interface Outputable {
//只读属性定义了getter方法,非抽象属性
val name: String
get() = "输出的名字"
//只读属性没有定义getter方法,抽象属性
val brand: String
//读写属性没有定义getter /setter方法 ,抽象属性
var category: String
//抽象方法
fun out()
fun getData(msg: String)
//非抽象方法,可以用private修饰
fun print(vararg msgs: String) {
for (msg in msgs) {
println(msg)
}
}
//接口中定义的非抽象方法,可以用peivate修饰
fun test() {
println("接口中的非抽象方法")
}
}
6.11.2 接口的继承
接口支持多继承
interface InterfaceA {
//定义一个非抽象属性
val nameA: String
get() = "张三"
//定义一个抽象方法
fun testA()
}
interface InterfaceB {
//定义一个非抽象属性
val nameB: String
get() = "李四"
//定义一个抽象方法
fun testB()
}
interface InterfaceC : InterfaceA, InterfaceB {
//定义一个非抽象属性
val nameC: String
get() = "王五"
//定义一个抽象方法
fun testC()
}
6.11.3 使用接口
接口不能用于创建实例,但是可以用于声明变量.
接口的作用:
- 定义变量,可用于进行强制类型转换
- 被其他类实现
一个类可以实现多个接口
[修饰符] class 类名: 父类, 接口1, 接口2... {
类体部分
}
6.11.4 接口和抽象类
接口和抽象类的相似之处:
- 接口和抽象类都不能被实例化
- 接口和抽象类都可以包含抽象成员
接口和抽象类的差别:
- 设计目的不同
- 用法上不同
- 接口中不包含构造器,但抽象类中可以包含构造器
- 抽象类的构造器不能创建对象,而是用来完成抽象类的初始化
- 接口中不能有初始化块,抽象类可以有初始化块
- 一个类只可以有一个直接父类,但是可以实现多个接口
6.12 嵌套类和内部类
嵌套类:定义在其它类内部的类
外部类:包含嵌套类的类
6.12.1 内部类 inner
使用inner修饰的嵌套类
相当于java中无static修饰的非静态内部类
可以直接访问外部类的私有数据,但是外部类不能访问内部类的实现细节.
定义内部类(非静态内部类)
class Cow(var weight: Double = 0.0) {
private inner class CowLeg(var length: Double = 0.0, var color: String = "") {
fun info() {
println("当前牛腿的颜色是:${color},高:${length}")
//访问外部方法
foo();
}
}
fun test() {
val c1 = CowLeg(1.12, "黑白相间")
c1.info()
}
private fun foo() {
println("Cow的foo方法")
}
}
fun main(args: Array<String>) {
val cow = Cow(378.9)
cow.test()
//当前牛腿的颜色是:黑白相间,高:1.12
//Cow的foo方法
}
内部类访问外部类属性,内部类属性,局部属性的方式
class Discern {
private val prop = "外部类的属性"
inner class InClass {
private val prop = "内部类的属性"
fun info() {
val prop = "局部属性"
println("外部类属性${this@Discern.prop}")
println("内部类属性${this.prop}")
println("局部类属性${prop}")
}
}
fun test() {
val ic = InClass()
ic.info()
}
}
fun main(args: Array<String>) {
Discern().test()
//外部类属性外部类的属性
//内部类属性内部类的属性
//局部类属性局部属性
}
内部类中this的处理规则
class A {
inner class B {
fun Int.foo() {
val a = this@A
val b = this@B
val c = this
val c1 = this@foo
println(a)
println(b)
println(c)
println(c1)
}
}
}
6.12.2 嵌套类
相当于Java中的静态内部类
将一个类放在另一个类中定义
把嵌套类隐藏在外部类之内,不允许同一个包中的其他类访问该类
嵌套类不能访问外部类的成员,只能访问另一个嵌套类.
class Father {
var prop = 5
fun test(){
println("输出")
}
class A
class Son {
fun print() {
val a = A()//可以访问其他嵌套类
// println(prop) //不能访问外部类的属性和方法
// test()//不能访问外部类的属性和方法
}
}
}
通过创建外部类对象来调用属性和访问
class Father {
var prop = 5
fun test(){
println("输出")
}
class A
class Son {
fun print() {
val a = A()//可以访问其他嵌套类
println(Father().prop) //通过创建外部类对象来调用属性
Father().test()//通过创建外部类对象来调用方法
}
}
}
6.12.3 在外部类以外使用内部类
外部类以外地方定义内部类变量的语法格式:
var | val varName: OutherClass.InnerClass
外部类以外地方创建内部类实例的语法格式:
OutherInstance.InnerConstructor()
class Out {
inner class In(msg: String) {
init {
println(msg)
}
}
}
fun main(args: Array<String>) {
//创建内部类对象
var oi: Out.In = Out().In("测试信息") //测试信息
//以上代码可以改为下面三行
var oii: Out.In
val ot = Out()
oii = ot.In("测试信息")
}
6.12.4 在外部类以外使用嵌套类
外部类以外的地方创建嵌套类实例的语法格式:
class OutClass {
open class InClass {
init {
println("嵌套类的构造器")
}
}
}
fun main(args: Array<String>) {
val nested: OutClass.InClass = OutClass.InClass()
//上面这行代码可以转化为下面这两句
val nestedd: OutClass.InClass
nestedd = OutClass.InClass()
}
嵌套类系需要使用外部类即可调用构造器,内部类要使用外部类对象
6.12.5 局部嵌套类
把嵌套类放在方法或函数中定义,就是局部嵌套类.
仅在该方法中有效.
不能使用访问控制修饰符.
class LocalNestedClass {
fun info() {
//定义局部嵌套类
open class NestedBase(var a: Int = 0) {
}
//定义局部嵌套类的子类
class NestedSub(var b: Int = 0) : NestedBase() {
}
//创建局部嵌套类的对象
val ns = NestedSub()
ns.a = 5
ns.b = 8
println("NestedSub对象的a和b属性是:${ns.a},${ns.b}")
}
}
fun main(args: Array<String>) {
LocalNestedClass().info() //NestedSub对象的a和b属性是:5,8
}
6.12.6 匿名内部类
在Kotlin中被抛弃匿名内部类,转用对象表达式
如果对象是函数式接口(只有一个抽象方法的接口)的实例,可使用带接口类型前缀的lambda表达式创建它.
fun main(args: Array<String>) {
var t = Runnable {
for (i in 0..100) {
println("${Thread.currentThread().name},i:${i}")
}
}
//启动新线程
Thread(t).start()
//主线程的循环
for (i in 0..100) {
println("${Thread.currentThread().name},i:${i}")
}
}
6.13 对象表达式和对象声明
Kotlin提供了额比匿名内部类更强大的语法:对象表达式
匿名内部类和对象表达式的区别:
- 匿名内部类只能指定一个父类型(接口或父类)
- 对象表达式可指定0-N个父类型(接口或父类)
6.13.1 对象表达式
对象表达式本质
就是增强版的匿名内部类
对象表达式定义格式:
object[: 0-N个父类型]{
//对象表达式类体部分
}
对象表达式规则:
- 不能是抽象类
- 不能定义构造器
- 可以包含内部类(inner修饰的)
- 不能包含嵌套
使用对象表达式:
只使用一个接口父类型模式
interface Outputable {
fun output(msg: String)
}
abstract class Product(var price: Double) {
abstract val name: String
abstract fun printInfo()
}
fun main(args: Array<String>) {
var ob1 = object : Outputable{
override fun output(msg: String) {
println("${msg}")
}
}
ob1.output("你好世界")
}
不使用任何父类型模式
var ob2 = object {
//初始化块
init {
println("初始化块")
}
//属性
var name = "Kotlin"
//方法
fun test() {
println("test方法")
}
//只能包含inner内部类,不能包含嵌套类
inner class too
}
使用两个父类型模式
var ob3 = object : Outputable, Product(5.5) {
override fun output(msg: String) {
println("${msg}")
}
override val name: String
get() = "张三"
override fun printInfo() {
println("${name}")
}
}
Kotlin的对象表达式分为两种情形:
- 对象表达式在方法或函数的局部范围内,或使用private修饰的对象表达式,Kotlin编译器可识别该对象表达式的真实类型
- 非private修饰的对象表达式与Java的匿名内部类相似,编译器只会把对象表达式当成它所继承的父类或实现的接口处理,如果没有父类型,默认是Any类型
- 编译器只能识别private修饰对象的真实类型
class ObjectExprType {
//被private修饰可识别真实类型
private val ob1 = object {
val name: String = "你好"
}
//被internal修饰,不可识别真实类型
internal val ob2 = object {
val name: String = "internal你好"
}
//被private修饰可识别真实类型
private fun privateBar() = object {
val name: String = "privateBar"
}
//默认被public修饰,不可识别真实类型
fun publicBar() = object {
val name: String = "publicBar"
}
fun test() {
println(ob1.name)
//internal修饰不能访问
// println(ob2.name)
println(privateBar().name)
//public是非private函数,编译器将它返回的对象表达式当做Any类型
// println(publicBar().name)
}
}
从上面代码可以看出,编译器只可以识别private对象表达式的真实类型
Kotlin的对象表达式可以访问或修改其作用域内的局部变量
fun main(args: Array<String>) {
var a = 20
var obj = object {
fun change() {
println("Change方法修改变量a的值")
a++
}
}
obj.change()
println(a) //21
}
6.13.2 对象声明和单利模式
对象声明语法格式:
object ObjectName[: 0-N个父类型] {
}
对象声明和对象表达式的区别:
- 就是对象表达式在object关键字后面没有名字,而对象声明有名字
- 对象表达式是一个表达式,可以赋值给变量,对象声明不是表达式,不能用于赋值
- 对象表达式可以包含inner内部类,但不能包含嵌套类,对象声明可以包含嵌套类,不能包含inner内部类
- 对象表达式可嵌套在其他对象声明或非内部类里面,对象声明不能定义在函数和方法内
interface Outputable {
fun output(msg: String)
}
abstract class Product(var price: Double) {
abstract val name: String
abstract fun printInfo()
}
fun main(args: Array<String>) {
var ob1 = object : Outputable {
override fun output(msg: String) {
println("${msg}")
}
}
ob1.output("你好世界")
var ob2 = object {
//初始化块
init {
println("初始化块")
}
//属性
var name = "Kotlin"
//方法
fun test() {
println("test方法")
}
//只能包含inner内部类,不能包含嵌套类
inner class too
}
var ob3 = object : Outputable, Product(5.5) {
override fun output(msg: String) {
println("${msg}")
}
override val name: String
get() = "张三"
override fun printInfo() {
println("${name}")
}
}
// 张三
// 初始化块
// test方法
// 张三真不错
}
6.13.3 伴生对象和静态成员
伴生对象:
在类定义的对象声明,使用companion修饰,这样该对象就变成了伴生对象
伴生对象作用:
为伴生对象所在的类模拟静态成员
interface Outputable {
fun output(msg: String)
}
class MyClass {
//省略名字的伴生对象
companion object : Outputable {
override fun output(msg: String) {
for (i in 1..9) {
println("${i}${msg}")
}
}
}
}
fun main(args: Array<String>) {
//使用半生对象所在的类 调用伴生对象
MyClass.output("张三")
//使用Companion访问伴生对象
println(MyClass.Companion)//com.lianghao.companion.MyClass$Companion@eed1f14
}
6.13.4 伴生对象的扩展
为伴生对象扩展方法和属性,就相当于为伴生对象所在的外部类扩展了静态成员.
可以通过外部类的类名访问扩展成员
interface Outputable {
fun output(msg: String)
}
class MyClass {
//省略名字的伴生对象
companion object : Outputable {
val name = "name属性值"
override fun output(msg: String) {
for (i in 1..9) {
println("${i}${msg}")
}
}
}
}
//伴生对象扩展的test方法
fun MyClass.Companion.test() {
println("伴生对象扩展的test方法")
}
//伴生对象扩展的foo属性
val MyClass.Companion.foo
get() = "伴生对象扩展的foo属性"
fun main(args: Array<String>) {
//使用半生对象所在的类 调用伴生对象
MyClass.output("张三")
//使用Companion访问伴生对象
println(MyClass.Companion)//com.lianghao.companion.MyClass$Companion@eed1f14
//使用伴生对象的扩展属性和方法
MyClass.test()
println(MyClass.foo)
}
6.14 枚举类 enum
6.14.1 枚举类入门
使用enum class定义枚举类
枚举类可以有属性 / 方法,可以实现一个或多个接口,可以定义自己的构造器
枚举类和普通类的区别:
- 枚举类可以实现一个或多个接口
- 使用enum定义的非抽象的枚举类不能使用open修饰,因此枚举类不能派生子类
- 枚举类的构造器只能使用private访问控制符,可省略private
- 枚举类的所有实例必须在枚举类的第一行显示列出,否则不能产生实例,最好以分号结尾
枚举类默认的方法:
- valueOf(value:String) 根据枚举的字符串名获取实际的枚举值
- Values() 获取该枚举的所有枚举值组成的数组
- compareTo(E e),与指定的枚举对象比较顺序
- toString() 输出值
枚举类默认的属性
- name属性 返回枚举实例的名称
- ordinal属性:返回枚举值在枚举类中的索引值
定义枚举的格式
enum class Week {
星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期日
}
使用枚举类
enum class Week {
星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期日
}
fun main(args: Array<String>) {
//直接访问枚举
println(Week.星期一) //星期一
println(Week.星期二) //星期二
println(Week.星期三.ordinal) //2 ordinal 顺序
//遍历整个枚举
for (w in Week.values()) {
println(w)
}
//根据枚举的字符串名获取实际的枚举值
var valueOf = Week.valueOf("星期一")
println(valueOf) //星期一
println(Week.星期一.toString())
println(Week.星期五.ordinal) //返回此值的索引值
}
6.14.2 枚举类的属性/方法/构造器
枚举类可以定义属性/方法/构造器
推荐为枚举类定义val只读属性
enum class Gender(val cnName: String) {
//相当于创建两个枚举值对象
MALE("男"), FEMALE("女");
//定义方法
fun info() {
when (this) {
MALE -> println("男人")
FEMALE -> println("女人")
}
}
}
相当于
MALE = new Gender("男")
FEMALE = new Gender("女")
使用
enum class Gender(val cnName: String) {
MALE("男"), FEMALE("女");
//定义方法
fun info() {
when (this) {
MALE -> println("男人")
FEMALE -> println("女人")
}
}
}
fun main(args: Array<String>) {
var g = Gender.valueOf("FEMALE")
println(g)//FEMALE
println("${g}代表${g.cnName}")//FEMALE代表女
g.info()//女人
}
6.14.3 实现接口的枚举类
枚举类也可以实现一个或多个接口,与普通类实现一个或多个接口完全一样
需要实现接口所包含的方法
interface GenderDesc {
fun info()
}
enum class Gender(val cnName: String) : GenderDesc {
MALE("男"), FEMALE("女");
//定义方法
override fun info() {
when (this) {
MALE -> println("男人")
FEMALE -> println("女人")
}
}
}
6.14.4 包含抽象方法的抽象枚举类
enum class Operation {
PlUS {
override fun eval(x: Double, y: Double) = x + y
},
MINUS {
override fun eval(x: Double, y: Double) = x - y
},
TIMES {
override fun eval(x: Double, y: Double) = x * y
},
DIVIDE {
override fun eval(x: Double, y: Double) = x / y
};
//设置抽象方法
abstract fun eval(x: Double, y: Double): Double
}
fun main(args: Array<String>) {
println(Operation.PlUS.eval(1.0, 2.0))
println(Operation.MINUS.eval(1.0, 2.0))
println(Operation.TIMES.eval(1.0, 2.0))
println(Operation.DIVIDE.eval(1.0, 2.0))
// 3.0
// -1.0
// 2.0
// 0.5
}
6.15 类委托和属性委托 by
Kotlin的委托分为
- 类委托
- 属性委托
6.15.1 类委托 by
类委托是代理模式的应用,代理模式可作为继承的替代.
类委托本质就是将本类需要实现的部分方法委托给其他对象.(借用其他对象的方法作为自己的实现)
定义一个类,实现实现一个接口,该类并不实现该接口中的抽象方法,而是借用其他对象中的方法来实现.
被借用的对象被称为委托对象
//定义一个接口
interface Outputable {
fun output(msg: String)
var type: String
}
//定义一个类,实现接口
class DefaultClass : Outputable {
override fun output(msg: String) {
for (i in 1..6) {
println("<h${i}>msg</h${i}>")
}
}
override var type: String = "输出准备"
}
//定义Printer类,指定构造参数b作为委托对象
class Printer(b: DefaultClass) : Outputable by b
//定义Projector类,指定新建的对象作为委托对象
class Projector() : Outputable by DefaultClass() {
override fun output(msg: String) {
javax.swing.JOptionPane.showMessageDialog(null, msg)
}
}
fun main(args: Array<String>) {
val output = DefaultClass()
//Printer的委托对象是DefaultClass
var printer = Printer(output)
//调用委托对象的output方法
printer.output("调用了output方法")
//调用了type方法
println(printer.type) //输出准备
//Projector的委托对象也是output
var projector = Projector()
projector.output("projector") //会弹出一个窗口
println(projector.type)//输出准备
}
上面程序定义了Printer和Projector两个类,这两个类都实现了Ouputable接口,因此需要实现该接口中的抽象方法和抽象属性。程序通过by关键字为这两个类指定了委托对象,这意味着这两个类可直接“借用”被委托对象所实现的方法和属性。
上面程序中Printer类虽然没有实现output()方法和type属性,但它的实例一样可以在main)函数中调用output)方法和type属性。Projector类虽然指定了DefaultOutput对象作为委托,但由于该类本身也实现了output()方法,因此当Projector对象调用output()方法时,它不再需要调用委托对象的output)方法,而是直接使用自己实现的方法。
当一个类重写了委托对象所包含的方法之后,优先使用该类自己实现的方法.
6.15.2 属性委托 by
属性委托可以将多个类的类似属性统一交给委托对象集中实现,可以避免每个类都需要单独实现属性.
对象指定为属性的委托对象后,就会全面接管该属性的getter和setter,属性的委托对象无须实现任何接口
var一定提供getter setter方法
var(读写属性)的委托对象只需要提供使用operator修饰的setValue()方法,该方法参数和返回值要求如下:
方法 | 作用 |
---|---|
thisRef | 代表属性所属的对象,因此该参数的类型必须是属性所属对象的类型或其超类 |
property | 代表目标属性,类型必须是KProperty<*>或其超类型 |
newValue | 代表目标属性新设置的属性值,类型必须具有和目标属性相同的类型或其超类型 |
val提供 getter方法,
val (只读属性)的委托对象只需要提供使用operator修饰的getValue()方法,该方法参数和返回值要求如下:
方法 | 作用 |
---|---|
thisRef | 代表属性所属的对象,因此该参数的类型必须是属性所属对象的类型或其超类 |
property | 代表目标属性,类型必须是KProperty<*>或其超类型 |
import kotlin.reflect.KProperty
class ProertyDelegation {
//该属性的委托对象是MyDelegation
var name: String by MyDelegation()
}
//被委托类
class MyDelegation {
private var _backValue = "默认值"
operator fun getValue(thisRef: ProertyDelegation, property: KProperty<*>): String {
println("${thisRef}的${property.name}属性执行getter方法")
return _backValue
}
operator fun setValue(thisRef: ProertyDelegation, property: KProperty<*>, newValue: String) {
println("${thisRef}的${property.name}属性执行setter方法,传入值为${newValue}")
_backValue = newValue
}
}
fun main(args: Array<String>) {
val pd = ProertyDelegation()
println(pd.name)
// com.lianghao.entrust.ProertyDelegation@1a93a7ca的name属性执行getter方法
// 默认值
pd.name = "张三"
println(pd.name)
// com.lianghao.entrust.ProertyDelegation@1a93a7ca的name属性执行setter方法,传入值为张三
// com.lianghao.entrust.ProertyDelegation@1a93a7ca的name属性执行getter方法
// 张三
}
6.15.3 延迟属性 lazy()
Kotlin提供了一个lazy()函数,参数时Lambda表达式,返回一个Lazy对象
返回的Lazy对象包含了一个只读属性委托要求的getValue()方法,
Lazy对象只能作为只读属性的委托对象
Lazy函数的使用
val laztProp: String by lazy {
println("第一次访问时执行代码块")
"HelloWorld"
}
fun main(args: Array<String>) {
println(laztProp)
println(laztProp)
// 第一次访问时执行代码块
// HelloWorld
// HelloWorld
}
Lazy设置线程是否安全的方式
- fun lazy(initializer: ()-> T): LazysT>
- fun lazy(mode: LazyThreadSafetyMode, initializer: ()-> T): LazysT>
上面是Lazy函数重载的两个方法,
第一个函数相当于第二个函数参数指定为LazyThreadSafetyMode.SYNCHRONIZED.
Lazy函数的设置线程安全有两种模式:
- LazyThreadSafetyMode.SYNCHRONIZED. 线程安全
- LazyThreadSafetyMode.NONE 线程不安全
//创建线程不安全的延迟属性lazy
val laztProp: String by lazy(LazyThreadSafetyMode.NONE) {
println("第一次访问时执行代码块")
"HelloWorld"
}
fun main(args: Array<String>) {
println(laztProp)
println(laztProp)
// 第一次访问时执行代码块
// HelloWorld
// HelloWorld
}
6.15.4 属性监听 Delegates
observable方法
import kotlin.properties.Delegates
//创建属性监听
var observableProp: String by Delegates.observable("默认值") { prop, old, new ->
println("${prop}的原始值${old},被改为了${new}")
}
fun main(args: Array<String>) {
println(observableProp)//默认值
observableProp = "HelloWorld"
println(observableProp)
//var observableProp: kotlin.String的原始值默认值,被改为了HelloWorld
//HelloWorld
}
vetoable方法
Lambda函数里有一个返回值,只有返回true时候才能修改成功
import kotlin.properties.Delegates
//创建属性监听
var observableProp: Int by Delegates.vetoable(20) {
prop, old, new ->
println("${prop}的原始值${old},被改为了${new}")
new > old //只有 新值比 旧值大的时候才返回true
}
fun main(args: Array<String>) {
println(observableProp)//20
observableProp = 30
println(observableProp)
//var observableProp: kotlin.Int的原始值20,被改为了30
//30
}
6.15.5 使用Map存储属性值
- Map的 getVlaue方法
- MutableMap的 getValue 和 setValue方法
MutableMap的两个方法符合读写属性的委托对象要求,可作为读写对象的委托
程序可将类只读属性委托给Map对象管理,对象本身并不负责存储对象状态,而是将状态保存在Map集合中.
**好处是:**当程序需要与 外部接口(如json)通讯时,程序并不需要将该对象直接暴露出来,只要将该对象属性所委托的Map暴露出来即可.
使用Map存储对象的只读属性
class Item(val map: Map<String, Any?>) {
val barCode: String by map
val name: String by map
val price: Double by map
}
fun main(args: Array<String>) {
val item = Item(
mapOf(
"barCode" to "13590",
"name" to "小鸭子找妈妈",
"price" to 13.14
)
)
var map = item.map
println(map["barCode"])//13590
println(map["name"])//小鸭子找妈妈
println(map["price"])//13.14
}
使用MutableMap存储对象的只读属性
class MutableItem(val map: MutableMap<String, Any?>) {
var barCode: String by map
var name: String by map
var price: Double by map
}
fun main(args: Array<String>) {
val item = MutableItem(mutableMapOf())
//设置属性,其实属性会委托给MutableMap处理
item.barCode = "1333333"
item.name = "活着 - 余华"
item.price = 999.0
var map = item.map
println(map["barCode"]) //1333333
println(map["name"]) //活着 - 余华
println(map["price"]) //999.0
}
MutableItem的读写属性委托给MutableMap对象
设置MutableItem的属性值的时候,实际上是交给MutableMap处理了.相当于为MutableMap设置key-value对,
为被委托MutableMap设置key-value时,就是修改了Item对象的状态
6.15.6 局部属性委托 operator
Kotlin1.1开始,支持为局部变量指定委托对象,被称为 局部委托属性
其实还是局部变量,只是对该变量的读取,赋值 会交给委托对象去实现
- 对于只读属性val而言,getValue方法的第一个参数,是Nothing?类型,因为局部变量不属于任何对象
- 对于读写属性var而言,需要实现getValue和setValue两个方法,setValue方法的第一个属性也是Nothing?类型
局部属性委托的使用
import kotlin.reflect.KProperty
class MyDelegationObject {
private var _backValue = "默认值"
operator fun getValue(thisRef: Nothing?, property: KProperty<*>): String {
println("${thisRef}的${property.name}属性执行getter方法")
return _backValue
}
operator fun setValue(thisRef: Nothing?, property: KProperty<*>, newValue: String) {
println("${thisRef}的${property.name}属性执行setter方法" + ",传入参数值为: ${newValue}")
_backValue = newValue
}
}
fun main(args: Array<String>) {
var name: String by MyDelegationObject()
println(name)
// null的name属性执行getter方法
// 默认值
name = "HelloWorld"
println(name)
// null的name属性执行setter方法,传入参数值为: HelloWorld
// null的name属性执行getter方法
// HelloWorld
}
从上面代码可看出局部变量不属于任何对象
lazy()函数对局部变量延迟初始化
fun main(args: Array<String>) {
val name: String by lazy {
println("name局部变量,我只会在第一次调用时候输出一次")
"我是name的值"
}
println(name)
//name局部变量,我只会在第一次调用时候输出一次
//我是name的值
println(name)
//我是name的值
}
6.15.7 委托工厂
Kotlin1.1开始,提供一种"委托工厂" 的对象也作为委托对象.
委托工厂需要提供如下方法:
provideDelegate(thisRef:Any?, prop:KProperty<*>)
参数 | 作用 |
---|---|
thisRef | 代表属性所属的对象,因此该参数的类型必须是属性所属对象的类型或其超类 |
prop | 代表目标属性,类型必须是KProperty<*>或其超类型 |
使用provideDelegate()生成委托的好处
Kotlin会保证对象在初始化时调用该方法来生成委托
我们可以在该方法中加入自定义代码,完成自定义逻辑
委托工厂的用法
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class MyTarget {
var name: String by ProperteChecker()
}
class ProperteChecker() {
operator fun provideDelegate(thisRef: MyTarget, prop: KProperty<*>): ReadWriteProperty<MyTarget, String> {
checkProperty(thisRef, prop.name)
return MyDelegation()
}
private fun checkProperty(thisRef: MyTarget, name: String) {
println("----检查属性----")
}
}
class MyDelegation : ReadWriteProperty<MyTarget, String> {
private var _backValud = "默认值"
override fun getValue(thisRef: MyTarget, property: KProperty<*>): String {
println("${thisRef}的${property.name}属性执行getter方法")
return _backValud
}
override fun setValue(thisRef: MyTarget, property: KProperty<*>, value: String) {
println("${thisRef}的${property.name}属性执行setter方法" + ",传入参数值为: ${value}")
_backValud = value
}
}
fun main(args: Array<String>) {
val pd = MyTarget()
println(pd.name)
//----检查属性----
//com.lianghao.entrust.MyTarget@17d10166的name属性执行getter方法
//默认值
pd.name = "HelloWorld"
println(pd.name)
//com.lianghao.entrust.MyTarget@17d10166的name属性执行setter方法,传入参数值为: HelloWorld
//com.lianghao.entrust.MyTarget@17d10166的name属性执行getter方法
//HelloWorld
}
第7章 异常
7.1 异常处理机制
7.1.1 使用try…catch捕获异常
try{
//业务代码
} catch (e:Exception) {
//异常处理代码
} finally {
//可选的finally块
}
try 出现一个
catch 可以出现0 - N个
finally可以出现0 - 1个
import java.io.FileInputStream
import java.io.IOException
fun main(args: Array<String>) {
var fis: FileInputStream? = null
try {
fis = FileInputStream("a.txt")
} catch (ioe: IOException) {
println("我是catch里的语句")
} finally {
println("我是finally里的语句")
fis?.close()
}
//我是catch里的语句
//我是finally里的语句
}
不要在finally块中使用return或throw等导致方法终止的语句
一旦在finally块中使用return或throw语句,会导致try块catch块中的return/throw语句失效
7.1.2 异常类的继承体系
import java.lang.ArithmeticException
import java.lang.Exception
import java.lang.IndexOutOfBoundsException
import java.lang.NumberFormatException
fun main(args: Array<String>) {
try {
var a = Integer.parseInt(args[0])
var b = Integer.parseInt(args[1])
val c = a / b
} catch (ie: IndexOutOfBoundsException) {
println("数组越界异常")
} catch (ne: NumberFormatException) {
println("数字格式异常")
} catch (ae: ArithmeticException) {
println("算术异常")
} catch (e: Exception) {
println("未知异常")
}
}
7.1.3 访问异常信息
异常对象都包含了如下几个常用属性和方法
属性和方法名 | 作用 |
---|---|
message | 返回该异常的详细描述字符串 |
stackTrace | 返回该异常的跟踪栈信息 |
printStackTrace() | 将该异常的跟踪栈信息输出到标准错误输出 |
printStackTrace(PrintStream s) | 将该异常的跟踪栈信息输出到指定输出流 |
fun main(args: Array<String>) {
var fis: FileInputStream? = null
try {
fis = FileInputStream("a.txt")
} catch (ioe: IOException) {
println(ioe.message)
ioe.printStackTrace()
}
}
7.1.4 异常处理嵌套
异常处理流程代码可以放在任何能放可执行代码的地方,因此完整的异常处理流程既可放在try块中,也可放在catch块虫,还可放在finally块,这样就会形成异常嵌套。
异常处理嵌套的深度没有很明确的限制,但通常没有必要使用超过两层的嵌套异常处理,因为嵌套层次太深的话会导致程序可读降低。
7.1.5 try语句是表达式
fun main(args: Array<String>) {
val input = readLine()
val a: Int? = try {
Integer.parseInt(input)
} catch (e: NumberFormatException) {
null
}
println(a)
}
7.2 使用throw抛出异常
和Java类似,Kotlin允许程序自行抛出异常,使用throw语句来完成
7.2.1 抛出异常
throw抛出的不是异常类,而是异常实例
语法格式如下:
throw ExceptionInstance
import java.lang.Exception
fun main(args: Array<String>) {
throwChecked(-5)
throwRuntime(5)
}
fun throwChecked(a: Int) {
if (a > 0) {
throw Exception("a的值大于0异常")
}
}
fun throwRuntime(a: Int) {
if (a > 0) {
throw Exception("a的值大于0,Runtime异常")
}
}
7.2.2 自定义异常类
选择抛出异常时,应该选择合适的异常类
- 继承Exception
- 实现两个构造器,无参和有参数
import java.lang.Exception
//定义自定义异常
class MyException : Exception {
constructor()
constructor(msg: String) : super(msg) {}
}
fun main(args: Array<String>) {
try {
throw MyException("出错了")
} catch (e: MyException) {
println(e.message)
}
}
7.2.3 catch和throw同时使用
import java.lang.Exception
//创建自定义异常
//定义自定义异常
class AuctionException : Exception {
constructor()
constructor(msg: String) : super(msg) {}
}
class AuctionTest {
var initPrice: Double = 30.0
fun bid(bidPrice: String) {
var d: Double
try {
d = bidPrice.toDouble()
} catch (e: Exception) {
e.printStackTrace()
//再次抛出异常
throw AuctionException("输入的必须是Double类型的字符串")
}
if (initPrice > d) {
throw AuctionException("价格比起拍价格底,不允许竞拍")
}
}
}
fun main(args: Array<String>) {
var a = AuctionTest()
try {
a.bid("13.14")
//再次抛出的异常的接受catch
} catch (e: AuctionException) {
println(e.message)
}
}
7.2.4 异常链
7.2.5 throw语句是表达式
与try语句一样,throw语句也是表达式,是Nothing类型
可以在Elvis表达式中使用throw表达式
import java.lang.NullPointerException
class User(var name: String? = null, var pass: String? = null)
fun main(args: Array<String>) {
val user = User()
//throw作为表达式使用
val th: String = user.name ?: throw NullPointerException("目标对象不能为空")
}
包含throw的方法作为表达式时候
返回值一定是Nothing类型
import java.lang.IllegalArgumentException
class User(var name: String? = null, var pass: String? = null)
//返回值类型必须是Nothing,否则会出错
fun fail(msg: String): Nothing {
throw IllegalArgumentException(msg)
}
fun main(args: Array<String>) {
val user = User()
//throw作为表达式使用
val th: String = user.name ?: fail("目标对象不能为空")
}
7.3 异常的跟踪栈
printStackTrace()方法用于打印异常的跟踪栈信息.
第8章 泛型
与Java类似,Kotlin的泛型也允许在定义类/接口/方法/函数 时候使用泛型形参
8.1 泛型入门
8.1.1 定义泛型接口/类
open class Apple<T> {
open var info: T?
constructor() {
info = null
}
constructor(info: T) {
this.info = info
}
}
fun main(args: Array<String>) {
var a1: Apple<String> = Apple<String>("苹果")
var a2: Apple<Int> = Apple<Int>(3)
}
8.1.2 从泛型类派生子类
创建了带有泛型声明的接口/父类之后,可以为该接口创建实现类,或者为该父类派生子类.
实现泛型接口 , 派生子类时候,泛型形参要实现.不能使用T作为泛型形参
open class Fruit<T> {
open var info: T?
constructor() {
info = null
}
constructor(info: T) {
this.info = info
}
}
class A : Fruit<String>() {
}
8.2 型变
Java是不支持型变的,Java使用通配符
8.2.1 泛型型变的需要
- 通配符上限(泛型协变)意味着从中取出(out)对象是安全的,但传入对象(in)则不可靠。
- 通配符下限(泛型逆变)意味着向其中传入(in)对象是安全的,但取出对象(out)则不可靠。
8.2.2 声明处型变
型变分为:
协变和逆变
Kotlin处理泛型型变的规则:
- 如果泛型只需要出现在方法的返回值声明中(不出现在形参声明中),那么该方法就只是取出泛型对象,因此该方法就支持泛型协变(通配符上限),如果一个类的所有方法都支持泛型协变,那么该类的泛型参数可用out修饰
- 如果泛型只需要出现在方法的形参声明中(不需要出现在返回值中),那么该方法就只是传入泛型对象,因此该方法就支持泛型逆变(通配符下限),如果一个类的所有方法都支持泛型逆变,那么该类的泛型参数可使用in修饰
支持协变的类定义方式
类型低 传到类型高
class User<out T> {
//此处不能用var,用var就会有setter方法,就会让T出现在方法形参中
val info: T
constructor(info: T) {
this.info = info
}
fun test(): T {
return info
}
}
fun main(args: Array<String>) {
var user = User<String>("kotlin")
println(user.test())
//对于u2而言,他的类型是User<Any>,此时的T类型是Any
var u2: User<Any> = user
}
支持逆变的类定义方式
类型高传到类型低
类似于 下限 <? super Int>
class Item<in T> {
fun foo(t: T) {
println(t)
}
}
fun main(args: Array<String>) {
//此时T是Any类型
var item = Item<Any>()
item.foo(200)
//im2被赋值后 实际类型是Any,因此,参数只要是Any即可
var im2: Item<String> = item
im2.foo("kotlin")
}
8.2.3 使用处型变: 类型投影
如果一个类里面既有方法使用泛型声明返回值,也有方法使用泛型声明形参类型.
8.2.4 星号投影
星号投影为了处理java 的原始类型
//Java中
ArrayList list = new ArrayList();
//Kotlin中
var list:ArrayList<*> = arrayListOf(1,"kotlin")
- 对于Foo<*, String>,其实相当于Foo<in Nothing, String>。
- 对于Foo<Int, *>,其实相当于Foo<Int, out Any?>。
- 对于Foo<*,*>,其实相当于Foo<in Nothing, out Any?>。
8.3 泛型函数
在定义类/接口时候没使用泛型,但是在定义方法时候,想使用泛型形参.
8.3.1 泛型函数的使用
在声明函数时允许定义一个或多个泛型形参
泛型形参要用尖括号括起来,整体放在fun与函数名之间
fun <T , S> 函数名(形参列表) : 返回值类型{
//函数体
}
fun <T> copy(from: List<T>, to: MutableList<in T>) {
for (item in from) {
to.add(item)
}
}
fun main(args: Array<String>) {
var list1 = listOf("hello", "world")
var objectList: MutableList<Any> = mutableListOf(2.4, 5, 6, "kotlin", 234, 364)
//执行泛型函数的类型为String
copy<String>(list1, objectList)
println(objectList) //[2.4, 5, 6, kotlin, 234, 364, hello, world]
var intList = listOf(7, 8, 9)
copy(intList, objectList)
println(objectList)//[2.4, 5, 6, kotlin, 234, 364, hello, world, 7, 8, 9]
}
8.3.2 具体化类型参数 reified
Kotlin允许在内联函数(inline)中使用reified修饰泛型形参,这样就将泛型形参变成一个具体化的类型参数.
泛型形参变成具体化类型参数时在函数中就可以像普通类型一样使用该类型参数,包括使用 is / as 等运算符
val db = listOf("java", java.util.Date(), 103, 13.14, 'C')
inline fun <reified T> findData(): T? {
for (ele in db) {
if (ele is T) {
@Suppress("UNCHECKED_CAST")
return ele
}
}
return null
}
8.4 设定类型形参的上限
Kotlin允许使用通配符形参时设定上限,也可以在定义类型形参时设定上限,
传给该类型形参的实际类型要么是该类型的上限,要么是上限类型的子类
类使用泛型上限
class MyClass<T : Number> {
var col: T
constructor(col: T) {
this.col = col
}
}
fun main(args: Array<String>) {
var ai = MyClass<Int>(2)
println(ai.col)
}
方法使用泛型上限
fun <T : Number> sum(vararg params: T): Double {
var sum = 0.0
for (p in params) {
sum += p.toDouble()
}
return sum
}
类和方法使用多个泛型上限 where
由于尖括号内只能指定一个上限,指定多个需要使用where
在类名或者方法名后添加where
//为类中的T指定多个上限
class MyClassDemo<T> where T : Comparable<T>, T : Cloneable {}
//为方法中的T指定多个上限
fun <T> cloneFun(list: List<T>, threshold: T)
where T : Comparable<T>, T : Cloneable {
}
第9章 注解
Kotlin的注解与Java的注解完全相同,是代码里的特殊标记,这些标记可以在编译类加载/运行时被读取,并执行相应的处理.
作用:
可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息.比如代码分析工具,开发工具,部署工具
9.1 注解入门
9.1.1 定义注解
创建自定义注解的条件
- 使用annotation class关键字来定义注解
- 定义注解不允许有花括号
创建简单的自定义注解
//定义一个简单的注解
annotation class Test
使用自定义注解修饰类/属性/方法
//使用自定义注解来修饰类,属性,方法
@Test
class MyClass {
//使用自定义注解来修饰属性
@Test
var name: String = ""
//使用自定义注解来修饰方法
@Test
fun info() {
}
}
使用自定义注解修饰主构造器
//注解修饰主构造器
class User @Test constructor(var name: String, var age: Int) {
}
9.1.2 注解的属性和构造器
注解的属性规则
- 注解可以有属性,但是没有注解体
- 注解的属性只能在注解声明部分指定
- 注解的属性只能在使用时指定
- 注解的属性值只能定义为只读val
- 注解的属性值不能为可空类型(类型后面不能加?)
- 一旦为注解的属性指定了属性值,以后就绝不会改变其属性值
- 注解的属性可以有初始值
注解的属性支持的类型
- Kotlin对应Java的基本类型
- 字符串
- 类
- 枚举
- 其他注解
- 上面各种类型的数组
定义带属性的注解
annotation class MyTag(val name: String, val age: Int)
使用带属性的注解
annotation class MyTag(val name: String, val age: Int)
class MyDear {
@MyTag(name = "张三", age = 13)
fun info() {
}
}
定义带属性初始值的注解
annotation class MyTag(val name: String = "zhangsan", val age: Int = 13)
使用带初始值的注解
class MyDear {
@MyTag
fun info() {
}
}
接收多个属性的注解
annotation class MyTag(val name: String, vararg val age: Int)
class MyDear {
@MyTag(name = "张三", 13, 14, 15, 16, 17, 18)
fun info() {
}
}
注解属性是注解
annotation class Tag
annotation class MyTag(val name: String, val tag:Tag)
class MyDear {
@MyTag(name = "张三", tag = Tag())
fun info() {
}
}
根据注解是否可以包含属性,可以把注解分为两类:
注解类型 | 作用 |
---|---|
标记注解 | 没有定义属性的注解,利用自身的存在与否来提供信息 |
元数据注解 | 包含属性的注解,可接受配置信息,以属性值的方式进行设置 |
9.2 元注解 4个
9.2.1 使用@Retention
只能修饰注解定义
指定被修饰的注解可以保留多长时间
有一个属性值
值 | 作用 |
---|---|
AnnotationRetention.SOURCE | 只保留在源码阶段 |
AnnotationRetention.BINARY | 保留到class字节码阶段 |
AnnotationRetention.RUNTIME | 保留到JVM阶段 |
@Retention(AnnotationRetention.BINARY) //保留到JVM阶段
annotation class Tag
9.2.2 使用@Target
只能修饰注解定义
指定被修饰的注解能修饰哪些程序单元
有一个属性值
值 | 作用 |
---|---|
AnnotationTarget.CLASS | 指定只能修饰类 |
AnnotationTarget.ANNOTATION CLASS | 指定只能修饰注解 |
AnnotationTarget.TYPE PARAMETER | 指定只能修饰泛型形参(目前还不能使用) |
AnnotationTarget.PROPERTY | 指定只能修饰属性 |
AnnotationTarget.FIELD | 指定只能修字段(包括属性的幕后字段) |
AnnotationTarget.LOCAL_VARIABLE | 指定只能修饰局部变量 |
AnnotationTarget.VALUE_PARAMETER | 指定只能修饰**(函数或构造器)的形参** |
AnnotationTarget.CONSTRUCTOR | 指定只能修饰构造器 |
AnnotationTarget.FUNCTION | 指定只能修饰函数和方法 |
AnnotationTarget.PROPERTY GETTER | 指定只能修饰属性的getter方法 |
AnnotationTarget.PROPERTY SETTER | 指定只能修饰属性的getter方法 |
AnnotationTarget.TYPE | 指定只能修饰类型 |
AnnotationTarget.EXPRESSION | 指定只能修饰各种表达式 |
AnnotationTarget.FILE | 指定只能修饰文件 |
AnnotationTarget.TYPEALIAS | 指定只能修饰类型别名 |
定义注解只能修饰方法
@Target(AnnotationTarget.FUNCTION)
annotation class MyTag(val name: String, val tag: Int)
9.2.3 使用@MustBeDocumented
只能修饰注解定义
使用@MustBeDocumented元注解修饰的注解将被文档工具提取到API文档中
@MustBeDocumented
annotation class MyTag(val name: String, val tag: Int)
9.2.4 使用@Repeatable标记可重复注解
使用多个相同的注解来修饰同一个程序单元,这种注解称为可重复注解。
由于Java8之前JVM不支持可重复注解,所以Kotlin的@Repeatable策略只能指定为@Retention(AnnotationRetention.SOURCE)
@Retention(AnnotationRetention.SOURCE)
@Repeatable
annotation class MyTag(val name: String = "李四", val tag: Int = 24)
class MyDear {
@MyTag(name = "张三", tag = 13)
@MyTag(name = "王五", tag = 25)
fun info() {
}
}
9.3 使用注解
Kotlin提供了一些支持注解的API
9.3.1 提取注解信息
使用注解修饰类/方法/属性等成员后,注解不会自己失效,必须由开发者提供相应的工具来提取并处理注解信息
Kotlin使用Kotlin.Annotaion接口来代表程序元素前面的注解,该接口是所有注解的父接口
Kotlin在kotin.reflect包下新增了KAnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有如下几个实现类。
实现类 | 作用 |
---|---|
KCallable | 代表可执行的程序实体,如函数和属性。 |
KClass | 代表Kotlin的类、接口等类型。 |
KParameter | 代表函数和属性的参数。 |
kotin.reflect包下主要包含一些反射功能的工具类,可以读取运行时注解的信息(只有注解被修饰为AnnotationRetention.RUNTIME时候才可)
程序通过三个实现类获取KAnnotatedElement对象之后,可以通过以下两个方法获取信息
方法名 | 作用 |
---|---|
List | 该属性返回该程序单元上所有的注解。 |
findAnnotation() | 根据注解类型返回该程序单元上特定类型的注解,如果注解不存在返回null |
获取方法的所有注解
@Retention(AnnotationRetention.SOURCE)
@Repeatable
annotation class MyTag(val name: String = "李四", val age: Int)
class MyDear {
@MyTag(age = 2)
@MyTag(name = "王五", age = 9)
fun info() {
}
}
val aArray = MyDear::info.annotations
fun main(args: Array<String>) {
for (an in aArray){
//如果是MyTag类型的注解
if (an is MyTag){
println(an)
}
}
}
9.3.2 使用注解的示例
使用标记注解,通过反射获取方法,再判断是否有注解,有注解的就运行,没有就不运行
package com.lianghao.annotation
import java.lang.Exception
import java.lang.IllegalArgumentException
import java.lang.RuntimeException
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.functions
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
//定义一个标记注解,不包含任何属性
annotation class Testable
class Test {
@Testable
fun m1() {
}
fun m2() {}
@Testable
fun m3() {
throw IllegalArgumentException("m3参数出错了")
}
fun m4() {}
@Testable
fun m5() {
}
fun m6() {}
@Testable
fun m7() {
throw RuntimeException("m7运行时出错了")
}
fun m8() {}
}
inline fun <reified T : Any> processTestable() {
var padded = 0
var failed = 0
//只需要传入泛型,根据泛型的类型,反射创建出对象
val target = T::class.createInstance<T>()
//遍历T对应的类里的所有方法
for (m in T::class.functions) {
if (m.findAnnotation<Testable>() != null) {
try {
//用m调用方法
m.call(target)
//运行成功一次加1
println("方法${m}运行成功")
padded++
} catch (e: Exception) {
println("方法${m}运行失败,异常${e.cause}")
//运行失败一次加1
failed++
}
}
}
println("一共运行了${padded + failed}个方法,成功${padded}个,失败${failed}个")
}
fun main(args: Array<String>) {
//调用方测试
processTestable<Test>()
//方法fun com.lianghao.annotation.Test.m1(): kotlin.Unit运行成功
//方法fun com.lianghao.annotation.Test.m3(): kotlin.Unit运行失败,异常java.lang.IllegalArgumentException: m3参数出错了
//方法fun com.lianghao.annotation.Test.m5(): kotlin.Unit运行成功
//方法fun com.lianghao.annotation.Test.m7(): kotlin.Unit运行失败,异常java.lang.RuntimeException: m7运行时出错了
//一共运行了4个方法,成功2个,失败2个
}
通过注解来简化事件编程
传统事件总是需要通过addActionListener()来绑定事件监听器
下面通过自定义注解 @ActionListenerFor来给按钮绑定事件监听器
package com.lianghao.annotation
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import javax.swing.*
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
//创建自定义注解
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class ActionListenerFor(val listener: KClass<out ActionListener>)
class AnnotationTest {
val mainWin = JFrame("使用注解给按钮绑定事件监听器")
@ActionListenerFor(listener = OkListener::class)
val ok = JButton("确定")
@ActionListenerFor(listener = CancelListener::class)
val cancel = JButton("取消")
fun init() {
//初始化界面的方法
val jp = JPanel()
jp.add(ok)
jp.add(cancel)
mainWin.add(jp)
processAnnotations(this)
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)//设置朝向
mainWin.pack()//包装
mainWin.isVisible()//设置显示
}
}
//定义的两个监听器
class OkListener : ActionListener {
override fun actionPerformed(e: ActionEvent?) {
JOptionPane.showMessageDialog(null, "单击了确认按钮")
}
}
class CancelListener : ActionListener {
override fun actionPerformed(e: ActionEvent?) {
JOptionPane.showMessageDialog(null, "单击了取消按钮")
}
}
//
fun processAnnotations(obj: Any) {
//获取obj对象类
var cl = obj::class
//获取指定obj对象的所有成员,遍历每个成员
for (prop in cl.memberProperties) {
//获取该成员上ActionListenerFor类型的注解
var a = prop.findAnnotation<ActionListenerFor>()
//获取属性prop的值
val fObj = prop.call(obj)
//如果fObj是AbstractButton的实例,且a不为null
if (a != null && fObj != null && fObj is AbstractButton) {
//获取a注解的listener属性值(一个监听类)
var listenerClazz = a.listener
//使用反射来创建listener类的对象
var al = listenerClazz.createInstance()
//为fObj按钮事件添加事件监听器
fObj.addActionListener(al)
}
}
}
fun main(args: Array<String>) {
AnnotationTest().init()
}
9.4 Java注解与Kotlin的兼容性
Java注解和Kotlin的注解完全兼容,只是使用时略加注意即可
9.4.1 指定注解的作用目标
语法格式: @符号和注解之间添加目标和符号
一个目标上指定一个注解情况
@目标:注解(注解属性值)
一个目标上指定多个注解情况
@目标:[注解1(注解属性值) 注解2(注解属性值) ...]
目标包含以下几个
目标 | 作用 |
---|---|
file | 指定注解对文件本身起作用. |
property | 指定注解对整个属性起作用 |
field | 指定注解对属性的幕后字段起作用。 |
get | 指定注解对属性的getter方法起作用。 |
set | 指定注解对属性的setter方法起作用。 |
receiver | 指定注解对扩展方法或扩展属性的接收者起作用。 |
param | 指定注解对构造器的参数起作用。 |
setparam | 指定注解对setter方法的参数起作用。 |
delegate | 指定注解对委托属性存储其委托实例的字段起作用。 |
对属性的getter方法起作用的实例
package com.lianghao.annotation
annotation class MoTag
annotation class FkTag(val info: String)
class Item {
@get:[MoTag FkTag(info = "补充信息")]
var name: String = "fkjava"
}
fun main(args: Array<String>) {
//获取Item类对应的Java类(Class对象)
var clazz = Item::class.java
//遍历clazz类所包含的所有方法
for (mth in clazz.getDeclaredMethods()) {
println("--方法${mth}上的注解如下--")
for (an in mth.getDeclaredAnnotations()) {
println(an)
}
}
//遍历clazz类包含的所有成员变量
for (f in clazz.getDeclaredFields()) {
println("--成员变量${f}上的注解如下--")
for (an in f.getDeclaredAnnotations()) {
println(an)
}
}
//--方法public final java.lang.String com.lianghao.annotation.Item.getName()上的注解如下--
//@com.lianghao.annotation.MoTag()
//@com.lianghao.annotation.FkTag(info="补充信息")
//--方法public final void com.lianghao.annotation.Item.setName(java.lang.String)上的注解如下--
//--成员变量private java.lang.String com.lianghao.annotation.Item.name上的注解如下--
}
9.4.2 使用Java注解
Kotlin完全兼容Java注解,因此可以直接在Kotlin程序中使用Java注解
Java注解的成员变量相当于Kotiln注解的属性,因此只能通过属性名来设置属性值(kotlin还可已通过位置来设置属性值)
定义一个java注解
public @interface JavaTag {
public String name();
public int age();
}
定义一个kotlin注解
annotation class KotlinTag(val name: String, val age: Int)
给两个注解赋值的不同方式
- Kotlin的注解支持位置和属性名两种赋值方式
- Java只支持属性名赋值
//使用位置设置注解属性值
@KotlinTag("学习Kotlin", 13)
class Book {
//使用属性名设置注解属性值
@KotlinTag(name = "Kotlin", age = 13)
//java只能通过使用属性名
@JavaTag(name = "java", age = 28)
// @JavaTag "java", 28) //报错
fun test() {
}
}
Java注解属性值是数组类型的情况,属性值是value的时候
定义一个java注解,属性值是数组,属性名是value时候
public @interface JavaArrayTag {
public String[] vlaue();
}
在Kotlin中使用这个注解数组,属性名是value时候可以省略属性名传入,
@JavaArrayTag("张三", "李四", "王五")
class Stock
Java注解属性值是数组类型的情况,属性名不是value的时候
定义一个属性名不是value的java注解
public @interface JavaArrayTag {
public String[] names();
}
在Kotlin中需要使用属性名传入值
@JavaArrayTag(names = arrayOf("张三", "李四", "王五"))
class Stock
第10章 Kotlin和Java互相调用
Kotlin最初设计就是兼容JVM平台的,因此Java和Kotlin可以相互调用, 会有细微差异需要注意.
10.1 Kotlin调用Java
10.1.1 属性
Kotlin调用属性实际上就是访问getter, setter方法,因此Java类只要提供了getter方法,Kotlin就可将其当成只读属性;如果Java类提供了getter. setter方法, Kotlin就可将其当成读写属性.
如果get方法以is开头,方法名是isMarried,并且返回Boolean类型,那么Kotlin将这个方法作为只读属性,属性名为isMarried
其实Java是否包含成员变量不重要,关键是getter. setter方法:如果只有getter方法,那么就是只读属性;如果有getter, setter方法,那么就是读写属性。
java类
import java.util.Random;
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setMarried(boolean married) {
System.out.printf("调用setMarried方法:参数为" + married);
System.out.println(); //仅仅用来换行
}
public boolean isMarried() {
return true;
}
public int getAge() {
return new Random().nextInt(100);
}
}
Kotlin类调用Java中的属性
fun main(args: Array<String>) {
val user = User()
//给java对象中的user中的name属性设置值
user.name = "张三"
println(user.name)
//操作isMarried读写属性
user.isMarried = false
println(user.isMarried)
//读取只读属性age
println(user.age)
//张三
//调用setMarried方法:参数为false
//true
//74 随机的
}
10.1.2 void和调用名为关键字的成员
如果Java方法的返回值是void,在Kotin中则对应于Unit返回值类型。
如果Java的类名、接口名、方法名等是Kotin的关键字。此时就需要使用反引号(就是键盘上数字1左边的那个键)对关键字进行转义。
Java类中有in方法, in和Kotlin关键字重名
public class InMethod {
public void in(){
System.out.println("java中的in方法执行了");
}
}
Kotin的调用方式(使用反引号转义重名的方法名)
fun main() {
val im = InMethod()
im.`in`() //使用反引号进行转义
}
10.1.3 Kotlin的已映射类型
- Kotin并未完整地提供整套类库
- Kotlin为部分Java类提供了特殊处理,这部分Java类被映射到Kotlin类
- 这种映射只在编译阶段发生,在运行阶段依然使用Java类型。
Java基本类型与Kotlin类之间的映射关系
Java基本类型 | Kotlin类 |
---|---|
byte | kotlin.Byte |
short | kotlin.Short |
int | kotlin.Int |
long | kotlin.Long |
char | kotlin.Char |
float | kotlin.Flost |
double | kotlin.Double |
boolean | kotlin.Boolean |
Java基本类型的包装类与Kotlin类之间的映射关系
Java基本类型包装类 | Kotlin类 |
---|---|
java.lang.Byte | kotlin.Byte? |
java.lang.Short | kotlin.Short? |
java.lang.Integer | kotlin.Int? |
java.lang.Long | kotlin.Long? |
java.lang.Character | kotlin.Char? |
java.lang.Float | kotlin.Flost? |
java.lang.Double | kotlin.Double? |
java.lang.Boolean | kotlin.Boolean? |
Java的常用类与Kotin类之间的映射关系
Java常用的类 | Kotlin类 |
---|---|
java.lang.Object | kotlin.Any! |
java.lang.Cloneable | kotlin.Cloneable |
java.lang.Comparable | kotlin.Comparable! |
java.lang.Enum | kotlin.Enum! |
java.lang.Annotation | kotlin.Annotation! |
java.lang.Deprected | kotlin.Deprected! |
java.lang.CharSequence | kotlin.CharSequence! |
java.lang.String | kotlin.String! |
java.lang.Number | kotlin.Number! |
java.lang.Throwable | kotlin.Throwable! |
10.1.4 Kotlin对Java类型的转换
Kotlin不支持Java的泛型通配符语法,但Kotin提供了使用处型变来代替泛型通配符。
- Java的通配符将会转换为Kotin的使用处型变(类型投影)
- Java的原始类型则转换为Kotin的星号投影。
Java泛型 | Kotlin语法 |
---|---|
Foo<? extend Bar> | Foo<out Bar!>! |
Foo<? super Bar> | Foo<in Bar!>! |
Foo<*> | Foo<out Any?>! |
Foo | Foo<*> |
Kotiln和Java一样都无法在运行时保存泛型信息
Kotlin的is运算符只能检测型号投影(Foo<*> 相当于Java的原始类型),不能检测泛型信息
fun main(args: Array<String>) {
val list = listOf(1, 2, 3, 4, 5)
//使用is检测是否为泛型是错误的
// println(list is List<String>)
//只能检测星号投影
println(list is List<*>)
}
10.1.5 对Java数组的处理
- Kotlin数组有一个重大的改变: Kotlin数组是不型变的。Array不能赋值给Array变量。
- Java数组是型变的,String[]]可以直接赋值给Object[]变量。
Java数组可型变容易出现异常
public class Demo4 {
public static void main(String[] args) {
String[] strArr = new String[10];
Object[] objArr = strArr;
objArr[0] = 999;
//Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
}
}
Java基本类型数组和Kotlin数组对象对应
Java提供了int[] long[] 等基本类型的数组,可以避免拆装箱带来的性能开销
kotlin不支持这种基本类型数组,所以Kotlin提供了ByteArray / IntArray等数组来代替Java中的基本类型数组
Kotlin访问数组元素时编译器会对数组访问进行优化,采用和Java类似的方式来访问数组元素,根据索引计算数组元素的内存地址
Java基本类型数组 | Kotlin数组对象 | 创建方法 |
---|---|---|
byte[] | ByteArray | byteArrayOf() |
short[] | ShortArray | shortArrayOf() |
int[] | IntArray | intArrayOf() |
long[] | LongArray | longArrayOf() |
char[] | CharArray | charArrayOf() |
float[] | FloatArray | floatArrayOf() |
double[] | DoubleArray | doubleArrayOf() |
boolean[] | BooleanArray | booleanArrayOf() |
Java定义一个方法sum,参数是int[]
public class MyArrayDemo {
public int sum(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
}
Kotlin需要创建IntArray数组传入参数
fun main(args: Array<String>) {
val iarr = MyArrayDemo()
//iarr的sum方法需要的是int[]类型,所以此处需要使用IntArray对象
val intArray = intArrayOf(2, 5, 8, 10)
println(iarr.sum(intArray))
}
10.1.6 调用参数个数可变的方法
Java可以直接传入一个数组
Kotlin要求只能传入多个参数值, 可通过使用“*”解开数组的方式来传入多个数组元素作为参数值。
Java方式
public class JavaVarargs {
public void test(int... nums){
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
}
}
Kotiln方式
fun main(args: Array<String>) {
val jv = JavaVarargs()
//Java可直接传入一个数组
jv.test(1, 2, 3, 4, 5, 6, 7, 8, 9)
//Kotlin数组的话
val intArr = intArrayOf(9, 8, 7, 6, 5, 4, 3, 2, 1)
jv.test(*intArr) //使用*解开输出元素
}
10.1.7 checked异常
Kotiln没有checked异常
fun main(args: Array<String>) {
//下面代码可能引发IOException异常(checked检查异常)
//但是Kotlin并不强制处理该异常
val fis = FileInputStream("aa.text")
print(fis.read())
fis.close()
}
10.1.8 Object的处理
Java的java.long.Object对应Kotlin中的Any,但是只声明了toString() / hashCode() / equals(),因此需要解决使用其他方法的问题
wait() / notify() / notifyAll()
Kotin中调用的方式
将Any转型为java.long.Object
(foo as java.lang.Object).wait()
getClass()
Kotlin有两种方式来获取对象的Java类
//方式1
obj::class.java
//方式2
obj.javaclass
clone()
如果要让对象重写clone()方法,需要让类实现kotlin.Cloneable接口
class Example:Cloneable{
override fun clone(): Any {
return null
}
}
finalize()
主要完成一些资源清理的工作
kotlin只要重写方法就行,不需要使用override关键字
class Example{
protected fun finalize(){
//实现资源清理逻辑
}
}
10.1.9 访问静态成员
Kotlin本身没提供static关键字,但是Kotlin提供了伴生对象来实现静态成员,因此Java类中的静态成员都可通过伴生对象的语法来调用.
fun main(args: Array<String>) {
//调用Runtime的静态方法,就像调用伴生对象一样
val rt = Runtime.getRuntime()
println(rt)
}
10.1.10 SAM变换
Java8支持Lambda表达式来作为函数式接口的实例(被称为SAM准换),Kotlin同样支持
Predicate的Lambda表达式
fun main(args: Array<String>) {
//创建过滤规则
val pred = Predicate<Int> { t -> t > 5 }
//定义一个数组
val list = arrayListOf(2, 4, 6, 8, 10)
//进行过滤
list.removeIf(pred)
//输出显示
println(list) //[2, 4]
}
Runnable的Lambda表达式
fun main() {
//使用Lambda表达式来创建函数式接口Runnable的对象
val rn = Runnable {
for (i in 1..10) {
println(i)
}
}
//使用Thread启动线程
Thread(rn).start()
}
ThreadPoolExecutor的Lambda表达式
fun main(args: Array<String>) {
val executor = ThreadPoolExecutor()
//程序可自动将符合该接口规范的Lambda表达式创建成Runnable对象
executor.excute { println("这是线程池里面的语句") }
//也可以显示指定Lmabda表达式,创建Runnable接口
executor.excute { Runnable { println("这是线程池里面的语句") } }
}
10.1.11 在Kotlin中使用JNI
Java中使用JNI需要使用native修饰该方法,表示交给平台本地的C或C++代码来实现
Kotlin同样支持使用external关键字修饰方法
external fun foo(x: Int): Double
使用external修饰了,就不能有函数体,函数会交给C或C++来实现
10.2 Java调用Kotlin
10.2.1 属性
Kotlin属性可以编译成下面三个成员
- private修饰的实际变量
- getter方法
- setter方法
例如:
Kotiln的属性
var name: String
Java的成员
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
如果属性名以is开头,类型无所谓,哪么该属性的对应的生成规则略有差异,get方法和属性名同名,set方法省略is
kotlin的is开头的属性
var isMarried: Boolean
Java的成员
private boolean isMarried;
public boolean isMarried() {
return isMarried;
}
public void setMarried(boolean isMarried) {
this.isMarried = isMarried;
}
10.2.2 包级函数
包级函数指的是Kotlin中直接定义的顶级函数(不在任何类中定义)
Kotlin编译器会为整个Kotlin源文件中生成一个类(只要该源文件中包含了顶级函数,顶级变量),
类名后面加kt
这些顶级函数,顶级变量都会变成该类的静态方法,静态变量
Kotlin代码,Demo13
var naem: String = "张三"
fun test() {
println("顶级函数中的输出函数")
}
java代码,TestDemo13
public class TestDemo13 {
public static void main(String[] args) {
System.out.println(Demo13Kt.getNaem());//张三
Demo13Kt.test();//顶级函数中的输出函数
}
}
默认情况下Kotlin编译器为顶级成员的源文件所生成的类名格式为: 文件名+Kt的后缀,但是Kotlin也允许使用**@JvmName注解**(用来修饰文件本身,是一个AnnotationRetention.SOURCE的注解),
用来改变Kotlin编译生成的类名
@file:JvmName(name = "KtUtils")
package com.lianghao.end
fun testPrint() {
println("顶级方法里的输出")
}
java中调用方式
KtUtils.testPrint();
Kotlin和Java包名和类名都相同的情况,可以指定Kotlin源文件统一生成一个Java类,该Java类将会包含不同源文件中的顶级成员.为了实现这种效果,需要使用**@JvmMultifileClass注解**
两个Kotlin类
@file:JvmName("CiUtils")
@file:JvmMultifileClass
package com.lianghao.end
fun foo(){
println("foo")
}
@file:JvmName("CiUtils")
@file:JvmMultifileClass
package com.lianghao.end
fun bar(){
println("bar")
}
Java中使用
CiUtils.foo();//foo
CiUtils.bar();//bar
10.2.3 实例变量
Kotin允许将属性暴露成实例变量,只要在程序中使用**@JvmField修饰该属性**即可,暴露出来的属性将会和Kotin属性具有相同的访问权限。
使用@JvmField将属性暴露成实例变量的要求如下:
- 该属性具有幕后字段。
- 该属性必须是非private访问控制的。
- 该属性不能使用open, override, const修饰。
- 该属性不能是委托属性。
Kotiln定义
class InstanceField(name: String) {
@JvmField val myName = name
}
Java中使用
InstanceField instanceField = new InstanceField("kotlin");
System.out.println(instanceField.myName);
10.2.4 类变量
在命名对象(对象声明)或伴生对象中声明的属性会在该命名对象或包含伴生对象的类中具有静态幕后字段(类变量)。但这些类变量通常是private访问权限,程序可通过如下三种方式之一将它们暴露出来。
- 使用@JvmField注解修饰。
- 使用lateinit修饰。
- 使用const修饰。
@JvmField修饰情况
class MyClass {
companion object MyObject {
@JvmField
val name = "张三"
}
}
java调用
public class MyClassTest {
public static void main(String[] args) {
System.out.println(MyClass.name); //张三
}
}
lateinit修饰的情况
object MuObject {
lateinit var name: Stirng
}
java调用
public class MyClassTest {
public static void main(String[] args) {
MuObject.name = "李四";
System.out.println(MuObject.name);//李四
}
}
const修饰
kotlin文件 名字是 Demo18
const val MAX = 999
object Obj {
const val NAME = "张三"
}
class Css {
companion object {
const val SIFT = "www.zhangsan.com"
}
}
java调用
System.out.println(Demo18Kt.MAX);
System.out.println(Obj.NAME);
System.out.println(Css.SIFT);
10.2.5 类方法
Kotlin的顶级函数会被转换成类方法
Kotin还可以将命名对象或伴生对象中定义的方法转换成类方法
如果这些方法使用@JvmStatic修饰的话。一旦使用该注解,编译器就既会在相应对象的类中生成类方法,也会在对象自身中生成实例方法。
object MyObject {
fun test() {
println("test方法")
}
@JvmStatic
fun foo() {
println("被@JvmStatic修饰的foo方法")
}
}
class MyClazz {
companion object {
fun test() {
println("test方法")
}
@JvmStatic
fun foo() {
println("被@JvmStatic修饰的foo方法")
}
}
}
Java代码调用
public class MyClassTest {
public static void main(String[] args) {
MuObject.name = "李四";
System.out.println(MuObject.name);//李四
System.out.println(Demo18Kt.MAX);
System.out.println(Obj.NAME);
System.out.println(Css.SIFT);
//通过单例形式调用
MyObject.INSTANCE.foo();
MyObject.INSTANCE.test();
//通过类方法形式调用
MyObject.foo();
// MyObject.test(); //错误,因为不是类方法
//通过伴生对象调用
MyClazz.Companion.test();
MyClazz.Companion.foo();
//通过类方法调用
MyClazz.foo();
// MyClazz.test(); //错误,因为不是类方法
}
}
10.2.6 访问控制符的对应关系
Kotlin的访问控制符与Java的对应关系
访问控制符 | 对应Java |
---|---|
private | private |
protected | protected |
internal | public |
public | public |
10.2.7 获取KClass
Java获取Kotlin的KClass对象.
通过调用Class.kotlin扩展属性的等价形式来实现
JvmClassMappingKt.getKotlinClass(clazz);
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
public class KClassTest {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println(clazz);
//获取class对象的kclass对象
KClass kotlinClazz = JvmClassMappingKt.getKotlinClass(clazz);
System.out.println(kotlinClazz);
}
}
10.2.8 使用@JvmName解决签名冲突
Kotlin中要定义两个同名函数,但是JVM平台无法区分这两个函数.(类型擦除引起的)
可以使用@JvmName来解决两个函数同名
kotlin文件,名字是NameConfilctKt
//定义普通扩展方法
fun List<String>.filterValid(): List<String> {
var result = mutableListOf<String>("张三", "李四", "王五")
for (s in this) {
if (s.length > 5) {
result.add(s)
}
}
return result.toList()
}
@JvmName(filterValidInt)
fun List<Int>.filterValid(): List<Int> {
var result = mutableListOf<Int>()
for (i in this) {
if (i < 20) {
result.add(i)
}
}
return result.toList()
}
java中使用
System.out.println(NameConfilctKt.filterValid(list));
System.out.println(NameConfilctKt.filterValidInt(list));
10.2.9 生成重载
Kotlin为方法或函数 提供了参数默认值,但对于有参数默认值的方法或函数,编译器默认只生成一个方法:默认带所有参数的方法
为了让编译器能为带参数默认值的方法生成多个重载的方法或函数,可以使用@JvmOverloads注解
kotlin代码,文件名是Demo21
@JvmOverloads
fun test(name: String, len: Int = 30, price: Double = 2.2){
}
java中使用
Demo21Kt.test("张三");
Demo21Kt.test("张三",23);
Demo21Kt.test("张三",23,88.99);
10.2.10 checked异常
Kotlin没有checked检查异常,因此也无须抛出throws
例如
fun foo() {
val fis = FileInputStream("a.txt")
}
如果路径不对或者没这个文件会出现IOException
可以使用@Throws注解修饰该函数,
@Throws(IOException::class)
fun foo() {
val fis = FileInputStream("a.txt")
}
被@Throws注解修饰后相当于声明了 throws IOException,因此java程序在调用该函数是编译器会检查checked异常,要么捕获异常,要么显示抛出异常.
public static void main(String[] args) {
try {
new Demo22().foo(); //显示抛出异常
} catch (IOException e) {
e.printStackTrace();
}
}
10.2.11 泛型的型变
Kotlin和Java型变的区别:
- Kotlin的泛型支持声明处型变
- Java的泛型只支持使用处型变(通过通配符,支持协变和逆变两种形式)
Kotlin的声明处型变转化成Java的使用处型变,转换规则是:
-
对于协变类型的泛型类 Bar ,当它作为参数出现时,Kotlin 会自动将 Bar 类型的参数替换成 Bar <? extends Base>。
-
对于逆变类型的泛型类 Foo ,当它作为参数出现时,Kotlin 会自动将 Foo类型的参数替换成 Foo<? super Sub>。
-
不管是协变类型的泛型类 Bar ,还是逆变类型的泛型类 Foo ,当它作为返回值出现时,Kotlin 不会生成通配符。
Kotlin代码
open class Base
class Sub : Base()
class Box<out T>(val value: T)
fun boxSub(value: Sub): Box<Sub> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
//上面编译会生成下面两个函数
//public static final Box <Sub> boxsub (Sub)
//public static final Base unboxbase (Box <? extends Base>)
上面程序中定义了一个支持声明处协变的 Box 类,接下来程序定义了两个函数,分别使
用 Box <Base》作为参数类型,使用 Box <Sub》作为返回值类型。编译上面程序,可以看到编译器生成的两个函数的签名.
z注解 | 用途 |
---|---|
@JvmWidcard | 可指定在编译器默认不生成通配符的地方强制生成通配符 |
@JvmSuppressWildcards | 可指定在编译器默认生成通配符的地方强制不生成通配符 |
@JvmWidcard 用法
open class Base
class Sub : Base()
class Box<out T>(val value: T)
fun boxSub(value: Sub): Box<@JvmWildcard Sub> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
//上面编译会生成下面两个函数
//public static final Box <? extends Sub> boxsub (Sub)
//public static final Base unboxbase (Box <? extends Base>)
@JvmSuppressWildcards 用法
open class Base
class Sub : Base()
class Box<out T>(val value: T)
fun boxSub(value: Sub): Box<Sub> = Box(value)
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
//上面编译会生成下面两个函数
//public static final Box <Sub> boxsub (Sub)
//public static final Base unboxbase (Box <Base>)
10.3 Kotlin反射
Kotlin也支持反射,把函数和属性当成一等公民,可以直接通过反射获取函数/属性的引用
10.3.1 类引用
Kotlin的类引用 使用 KClass代表
获取已知的Kotlin类的KClass对象:
//使用类获取
val c = MyClass::class
//使用对象获取
val c1 = myObj::class
Kotlin 的类引用是 KClass 对象,Java 的类引用是java.lang.Class 对象
通过 KClass 获取对应的 java.lang.Class 对象,可调用 Class 对象的Java属性
10.3.2 从KClass获取类信息
获取KClass对象之后可通过 KClass 提供的大量方法或属性来获取该 Class 对象所对应类的详细信息
Kotlin类
//定义一个注解
annotation class Anno
//使用3个解修饰该类
@Deprecated("该类已经不能使用")
@Anno
@Suppress("UNCHECKED_CAST")
class ClassTest(age: Int) {
var name: String = "Kotlin"
//定义一个私有构造
private constructor() : this(20) {
}
//定义一个有参构造器
constructor(name: String) : this(15) {
println("有参构造执行了,name${name}")
}
//定义一个无参数的info方法
fun info() {
println("info方法执行了")
}
//定义一个有参的info方法
fun info(str: String) {
println("info有参方法执行了,str${str}")
}
//定义一个测试用的嵌套类
class Inner
}
//为ClassTest类添加扩展方法
fun ClassTest.bar() {
println("ClassTest类的扩展方法bar")
}
//为ClassTest类定义扩展属性
val ClassTest.foo: Double
get() = 2.5
Kotlin通过反射获取信息
//main方法===========================
fun main(args: Array<String>) {
//获取ClassTest对应的KClass
val clazz = ClassTest::class
//通过constructors属性获取KClass对象所对应类的全部构造器
var ctors = clazz.constructors
println("所有的构造器")
ctors.forEach { println(it) }
println("主构造器")
println(clazz.primaryConstructor)
//通过function属性获取该KClass对象所对应类的全部方法
var functions = clazz.functions
println("所有的方法如下")
functions.forEach { println(it) }
//通过declaredFunctions属性全部方法法(不包括继承的方法)
var declaredFunctions = clazz.declaredFunctions
println("本身声明的所有方法如下")
declaredFunctions.forEach { println(it) }
//通过declaredMemberFunctions属性获取全部成员方法(不包括继承的方法)
var declaredMemberFunctions = clazz.declaredMemberFunctions
println("本身声明的所有成员方法如下")
declaredMemberFunctions.forEach { println(it) }
//通过memberExtensionFunctions获取所有扩展方法(不包括继承的)
var memberExtensionFunctions = clazz.memberExtensionFunctions
println("本身声明的所有扩展方法如下")
memberExtensionFunctions.forEach { println(it) }
//通过declaredMemberProperties本身声明的所有成员属性
var declaredMemberProperties = clazz.declaredMemberProperties
println("本身声明的所有成员属性如下")
declaredMemberProperties.forEach { println(it) }
//通过memberExtensionProperties本身声明的所有扩展属性
var memberExtensionProperties = clazz.memberExtensionProperties
println("本身声明的所有扩展属性如下")
memberExtensionProperties.forEach { println(it) }
//通过获取所有注解
var annotations = clazz.annotations
println("全部注解")
annotations.forEach { println(it) }
println("该元素的@Anno注解为${clazz.findAnnotation<Anno>()}")
//通过nestedClasses获取全部嵌套类 和 内部类
var inner = clazz.nestedClasses
println("全部内部类")
inner.forEach { println(it) }
//通过supertypes获取该类的所有父类型(包括父类和接口)
println("ClassTest的父类型为:${clazz.supertypes}")
//所有的构造器
//fun <init>(): com.lianghao.end.ClassTest
//fun <init>(kotlin.String): com.lianghao.end.ClassTest
//fun <init>(kotlin.Int): com.lianghao.end.ClassTest
//主构造器
//fun <init>(kotlin.Int): com.lianghao.end.ClassTest
//所有的方法如下
//fun com.lianghao.end.ClassTest.info(): kotlin.Unit
//fun com.lianghao.end.ClassTest.info(kotlin.String): kotlin.Unit
//fun com.lianghao.end.ClassTest.equals(kotlin.Any?): kotlin.Boolean
//fun com.lianghao.end.ClassTest.hashCode(): kotlin.Int
//fun com.lianghao.end.ClassTest.toString(): kotlin.String
//本身声明的所有方法如下
//fun com.lianghao.end.ClassTest.info(): kotlin.Unit
//fun com.lianghao.end.ClassTest.info(kotlin.String): kotlin.Unit
//本身声明的所有成员方法如下
//fun com.lianghao.end.ClassTest.info(): kotlin.Unit
//fun com.lianghao.end.ClassTest.info(kotlin.String): kotlin.Unit
//本身声明的所有扩展方法如下
//本身声明的所有成员属性如下
//var com.lianghao.end.ClassTest.name: kotlin.String
//本身声明的所有扩展属性如下
//全部注解
//@kotlin.Deprecated(level=WARNING, replaceWith=@kotlin.ReplaceWith(imports={}, expression=""), message="该类已经不能使用")
//@com.lianghao.end.Anno()
//该元素的@Anno注解为@com.lianghao.end.Anno()
//全部内部类
//class com.lianghao.end.ClassTest$Inner
//ClassTest的父类型为:[kotlin.Any]
//
//Process finished with exit code 0
}
10.3.3 创建对象
获取KClass对象后,调用 该对象的createInstance()方法即可创建该类的的无参数构造器来创建实例
调用 该对象的constructors()方法即可创建有参构造.
class ItemTest(var name: String) {
var price = 0.0
constructor() : this("未知商品") {
this.price = 0.0
}
constructor(name: String, price: Double) : this(name) {
this.name = name
this.price = price
}
}
fun main(args: Array<String>) {
val clazz = ItemTest::class
//使用createInstance创建五参数构造器创建的实例
var inst1 = clazz.createInstance()
println(inst1.name)
println(inst1.price)
//获取所有构造器
var constructors = clazz.constructors
constructors.forEach {
if (it.parameters.size == 2) {
var inst2 = it.call("张三", 23)
println(inst2.name)
println(inst2.price)
}
}
//未知商品
//0.0
//张三
//23.0
}
10.3.4 构造器引用 ::
构造器的本质是一个返回值为当前类实例的函数。因此程序可将构造器引用当成函数使用。
Kotlin 允许通过使用 :: 操作符并添加类名来引用该类的主构造器
class Foo(var name: String = "未知") {
}
//参数类型是 Foo主构造器类型
fun test(factory: (String) -> Foo) {
val x: Foo = factory("Kotlin")
println(x.name)
}
fun main(args: Array<String>) {
//::Foo引用Foo类的主构造器
test(::Foo)//传入的是Foo类的主构造器
}
10.3.5 调用方法
Kotlin所有的构造器和方法都属于KFunction实例,都通过call()方法来调用.
程序要调用指定类的方法,只要先获取方法的 Kfunction 实例,然后调用call()方法即可
fun main(args: Array<String>) {
var clazz = Foo::class
//创建实例对象
var ins = clazz.createInstance()
//获取全部成员方法
var funs = clazz.declaredMemberFunctions
for (f in funs) {
//根据参数数量判断
if (f.parameters.size == 3) {
f.call(ins, "Kotlin", 99.9)
}
if (f.parameters.size == 2) {
f.call(ins, "java")
}
}
}
10.3.6 函数引用 ::
Kotlin 的函数也是一等公民,函数也有其自身的类型。Kotlin 程序可以获取函数的引用,把函数当成参数传入另一个函数中
Kotlin 也通过 “::” 符号加函数名的形式来获取特定函数的引用。当存在多个重载函数时 Kotlin 可通过上下文推断出实际引用的是哪个函数;如果 Kotlin 无法通过上下文准确推断出引用哪个函数,编译器就会报错。
fun isSmall(i: Int) = i < 50
fun isSmall(s: String) = s.length < 5
fun main(args: Array<String>) {
val list = listOf(20, 30, 40, 50, 60, 70, 80, 90, 100)
var resultFilter = list.filter(::isSmall)
println(resultFilter) //[20, 30, 40]
var strList = listOf("java", "kotlin", "go", "object-c", "swift")
var strFilter = strList.filter(::isSmall)
println(strFilter) //[java, go]
}
如果需要引用类的成员方法或扩展方法,那么需要进行限定.
fun abs(d: Double): Double = if (d < 0) -d else d //求绝对值
fun sqrt(d: Double): Double = java.lang.Math.sqrt(d) //求根号
//定义一个comp()函数,将这两个函数组合起来
fun comp(fun1: (Double) -> Double, fun2: (Double) -> Double): (Double) -> Double = { x -> fun2(fun1(x)) }
fun main(args: Array<String>) {
println(abs(-3.2))//3.2
//使用将两个方法连接起来的放啊comp
val f = comp(::abs, ::sqrt)
println(f(-9.0)) //3.0
}
如果要获取 Kotlin 函数引用对应的 Java 方法对象(Method),则可通过调用 Kfunction 的扩展属性 javamethod 来实现
::abs.javaMethod
10.3.7 访问属性值
获取 KClass 对象之后,也可通过 KClass 对象来获取该类所包含的属性。Kotlin 为属性提供了众多的 API。
API名 | 作用 |
---|---|
KProperty | 代表通用的属性。它是 Callable 的子接口。 |
KMutableProperty | 代表通用的读写属性。它是 Kproperty 的子接口。 |
KProperty0 | 代表无需调用者的属性(静态属性)。它是 KProperty 的子接口。 |
KMutableProperty0 | 代表无需调用者的读写属性(静态读写属性)。它是 KPropert的子接口。 |
KProperty1 | 代表需要 1 个调用者的属性(成员属性)。它是 Kproperty 的子接口。 |
KMutableProperty1 | 代表需要 1 个调用者的读写属性(成员读写属性)。它是 Kproperty1的子接口 |
KProperty2 | 代表需要 2 个调用者的属性(扩展属性)。它是 KProperty 的子接口 |
KMutableProperty2 | 代表需要 2 个调用者的读写属性(扩展读写属性)。它是 KProperty2 的子接口。 |
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberProperties
class MyItem {
var name: String = "Kotlin"
var price: Double = 99.9
}
fun main(args: Array<String>) {
val clazz = MyItem::class
var ins = clazz.createInstance()
var props = clazz.declaredMemberProperties
props.forEach {
when (it.name) {
"name" -> {
@Suppress("UNCHECKED_CAST")
//将属性转换为读写属性
val mp = it as KMutableProperty1<MyItem, Any>
//修改属性值
mp.set(ins, "我喜欢Kotlin")
println(it.get(ins))
}
"price" -> {
//只读属性,只能通过get()方法读取值
println(it.get(ins))
}
}
}
}
程序要设置name属性时,由于name属性是一个成员读写属性,因此程序将该属性转型为 Kmutableproperty1 对象
转换之后 可以调用 set()方法设置属性的值.
10.3.8 属性引用 ::
Kotlin 同样提供了 “::” 符号加属性名的形式来获取属性引用,获取属性引用也属于前面介绍的 KProperty 及其子接口的实例
class Itemm {
var name: String = "Kotlin"
val price: Double = 99.9
}
var fooo = "测试属性"
fun main(args: Array<String>) {
//获取foo属性,属于KMutablePropertyt0的实例
val topProp: KMutableProperty0<String> = ::fooo
topProp.set("Kotlin")
println(topProp.get()) //Kotlin
val im = Itemm()
//获取Itemm的price属性,属于KMutableProperty1
val mp: KMutableProperty1<Itemm, String> = Itemm::name
mp.set(im, "我喜欢Kotlin")
println(mp.get(im)) //我喜欢Kotlin
//获取Item的price属性,属于KProperty1的实例
val prop: KProperty1<Itemm, Double> = Itemm::price
println(prop.get(im)) //99.9
}
Java 反射互操作的扩展属性。由于 Kotlin 属性会对应于 Java 的 3 种成员,因此 Property 包含如下 3 个扩展属性。
属性 | 作用 |
---|---|
javaField | 获取该属性的幕后字段(如果该属性有幕后字段s)返回ava.lang. Reflect Field 对象。 |
javaGetter | 获取该属性的 getter方法。该属性返回 java.lang,. reflect Method 对象。 |
javaSetter | **获取该属性的 setter 方法(**如果该属性是读写属性)返回java.lang reflect. Method 对象。 |
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.javaSetter
class ItemBea {
var name: String = "Kotlin"
val price: Double = 99.9
}
var sss = "测试属性"
fun main(args: Array<String>) {
//获取 sss 属性,属于 Kmutableproperty0 的实例
val topProp = ::sss
println(topProp.javaField) //获取幕后字啊单
println(topProp.javaGetter) //获取getter方法
println(topProp.javaSetter) //获取setter方法
//private static java.lang.String com.lianghao.end.Demo31Kt.sss
//public static final java.lang.String com.lianghao.end.Demo31Kt.getSss()
//public static final void com.lianghao.end.Demo31Kt.setSss(java.lang.String)
//获取 ItemBea的name 属性,属于 KMutableProperty1 的实例
val mp = ItemBea::name
println(mp.javaField) //获取幕后字啊单
println(mp.javaGetter) //获取getter方法
println(mp.javaSetter) //获取setter方法
//private java.lang.String com.lianghao.end.ItemBea.name
//public final java.lang.String com.lianghao.end.ItemBea.getName()
//public final void com.lianghao.end.ItemBea.setName(java.lang.String)
//获取 ItemBea的price 属性,属于 KProperty1 的实例
val prop = ItemBea::price
println(prop.javaField) //获取幕后字啊单
println(prop.javaGetter) //获取getter方法
// println(prop.javaSetter) //只读属性没有setter方法
//private final double com.lianghao.end.ItemBea.price
//public final double com.lianghao.end.ItemBea.getPrice()
}
10.3.9 绑定的方法与属性引用
前面介绍的都是通过 KClass(类本身)来获取方法或属性的引用的,当函数或属性不在任何类中定义时,程序直接使用 “::” 加函数名(或属性名)的形式来获取函数或属性的引用,这些函数或属性都没有绑定任何对象,因此调用函数或属性时第一个参数必须传入调用者。
从 Kotlin1.1 开始,Kotlin 支持一种“绑定的方法或属性引用”,这种方法或属性引用不是通过类获取的,而是通过对象获取的,这意味着该方法或属性已经绑定了调用者,因此程序执行方法或属性时无需传入调用者.
fun main() {
val str = "kotlin"
//获取对象绑定方法
val f: (CharSequence, Boolean) -> Boolean = str::endsWith
//调用绑定的方法时无需传入调用者
println(f("lin", true)) //true
//获取对象绑定的属性
val prop = str::length
println(prop.get()) //6
var list = listOf<String>("java", "kotlin", "c", "object-c", "swift", "dart", "flutter")
//获取对象绑定的方法
val fn = list::subList //截取字符串的方法
//调用绑定的方法时无需传入调用者
println(fn(1, 3))//[kotlin, c]
//获取对象绑定的属性
val prp = list::indices // //获取遍历区间范围
//调用绑定的属性时无需传入调用者
println(prp.get())//0..6
}
上面程序先定义了一个简单的字符串,其中第一行粗体字代码使用对象来获取绑定的方法, 这样当程序调用方法时只要传入该方法的两个参数即可,无须传入调用者;第二行粗体字代码使用对象来获取绑定的属性,这样程序直接使用即可,无需传入调用者.
Kotlin图片处理
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
fun main(args: Array<String>) {
//创建一个宽高为100的图片
var image = BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB)
var w = 0..99
var h = 0..99
// image.setRGB(0,0,0xff0000)
image.apply {
for (i in w){
for (j in h){
setRGB(i,j,0xff0000)
}
}
}
//写出图片
ImageIO.write(image, "bmp", File("a.bmp"))
}