在Kotlin中创建集合
//set支持数字
val set = hashSetOf(1,7,53)
println(set.javaClass) // class java.util.HashSet
// 创建集合
val list = arrayListOf(1,7,53)
println(list.javaClass) //
//获取集合最后个元素
println(list.last()) // 53
//获取集合里最大值
println(list.max()) // 53
//获取集合里第一个值
println(list.first()) // 1
//toSting
println(list)// [1, 7, 53]
//创建map
val map = hashMapOf(1 to "one",7 to "seven",53 to "fifty-three")
println(map.javaClass) // class java.util.HashMap
//直接setof
val numbers = setOf(1,14,2)
println(numbers.max())// 14
函数
//kotlin的toString的函数基本写法
fun joinToString(
collection: Collection,//集合
separator: String,//元素间的分隔符
prefix: String,//第一个元素分割符
postfix: String): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator) //不用在第一个元素前添加分隔符
result.append(element)
}
result.append(postfix) //最后个元素分隔符
return result.toString()
}
//调用
fun main(args: Array) {
val list = arrayListOf(1, 7, 53)
println(joinToString(list, ";", "(", ")"))// (1;7;53)
}
命名参数
函数关注的第一个首要问题是其可读性,上面函数的基本实现如果不查看源码的函数声明很难知道对应参数,虽然可以借助IDEA或者studio3.0可以实现,但是仍然很隐晦,kotlin可以改善并做的更优雅
//调用
fun main(args: Array) {
val list = arrayListOf(1, 7, 53)
println(joinToString(collection = list, separator = ";", prefix = "(", postfix = ")"))// (1;7;53)
}
当调用一个kotlin定义函数时,可以显示地注明一些参数的名称,如果指定了一个参数名称,为避免混淆,那它所有的参数都该被注明(注:显示注明参数时候可以不用在意参数的顺序,调用java函数时候,不能采用命令参数)
默认参数值
java的重载函数太多,kotlin在声明函数时候,可以指定参数的默认值,这样可以指定参数默认值,避免创建重载参数
//默认参数名
fun joinToString(
collection: Collection,//集合
separator: String = ",",//元素间的分隔符
prefix: String = "",//第一个元素分割符
postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator) //不用在第一个元素前添加分隔符
result.append(element)
}
result.append(postfix) //最后个元素分隔符
return result.toString()
}
//调用
fun main(args: Array) {
val list = arrayListOf(1, 7, 53)
//常规写法(必须按照参数顺序)
println(joinToString(list,",","","")) //1,7,53
//默认命名参数(省略参数)
println(joinToString(list)) //1,7,53
//改写默认命名(可以省略排在末尾的参数)
println(joinToString(list,";")) //1;7;53
}
常规语法必须按照参数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数,如果使用命名参数,如上所说,可以省略中间参数,也可以不按照函数声明中的参数顺序,可以自定义顺序。
注:参数的默认值是被编码到被调用的函数中,而不是调用的地方。如果改变了参数的默认值,并重新编译函数,没有给参数重新赋值的调用者,将使用新的默认值。
如果使用java调用kotlin的命名参数和默认参数函数,请添加注解@JvmOverloads让编译器生成重载函数。
消除静态工具类:顶层函数和属性
同级包目录下新建KotlinTest.kt文件,将JoinToString移到KotlinTest,在
package src.main
fun joinToString(
collection: Collection,//集合
separator: String = ",",//元素间的分隔符
prefix: String = "",//第一个元素分割符
postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator) //不用在第一个元素前添加分隔符
result.append(element)
}
result.append(postfix) //最后个元素分隔符
return result.toString()
}
在MainTest.kt(与KotlinTest.kt在同级包下)里直接调用
//调用
fun main(args: Array) {
val list = arrayListOf(1, 7, 53)
//常规写法(必须按照参数顺序)
println(joinToString(list,",","","")) //1,7,53
//默认命名参数(省略参数)
println(joinToString(list)) //1,7,53
//改写默认命名(可以省略排在末尾的参数)
println(joinToString(list,";")) //1;7;53
}
不同包下的顶层函数调用需导包,新建Package strings,在strings包下建JoinKt.kt,将joinToString函数移到JoinKt
fun joinToString(
collection: Collection,//集合
separator: String = ",",//元素间的分隔符
prefix: String = "",//第一个元素分割符
postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator) //不用在第一个元素前添加分隔符
result.append(element)
}
result.append(postfix) //最后个元素分隔符
return result.toString()
}
依旧在MainTest里调用
import src.strings.joinToString //不同包目录下调用顶层函数需导包
//调用
fun main(args: Array) {
val list = arrayListOf(1, 7, 53)
//常规写法(必须按照参数顺序)
println(joinToString(list,",","","")) //1,7,53
//默认命名参数(省略参数)
println(joinToString(list)) //1,7,53
//改写默认命名(可以省略排在末尾的参数)
println(joinToString(list,";")) //1;7;53
}
顶层函数编译成class文件后实质上就是java的静态函数。
注:如果要改变包含Kotlin顶层函数的生成的类的名称,需要为这个文件添加**
@field:JvmName("...")**,在包名之上,文件最顶层
顶层属性与顶层函数一样,不同的是属性声明,不细说
val count = 0;
//如果是public staic final修饰,kotlin用const修饰符替代
const val COUNT = 0;
扩展函数和属性(重点,重中之重)
扩展函数是kotlin一大特色,可以与现有代码平滑的集成。也可以基于java库(JDK,Android及其它第三方框架)构建kotlin。
//初始写法(this可以省略)
/**
* String.lastChar(): Char 接收者类型,由扩展函数自己定义
* this.get(this.length - 1) 接收者对象,是该类型的一个实例
* */
fun String.lastChar(): Char = this.get(this.length - 1)
//idea智能转换
fun String.lastChar(): Char = this[this.length - 1]
import src.strings.lastChar
//调用
fun main(args: Array) {
println("Kotlin".lastChar()) // n
}
上述代码中,String是接收者类型,"Kotlin"就是接收者对象。
从某种意义来说,你已经为Sting类添加了自己的方法。即使字符串不是代码一部分,也没有源代码,也是可以根据需要去写扩展方法。
只要是JVM类型语言,只要会被编译成Java类,你就可以为这个类添加自己的扩展方法
注:扩展函数并不允许打破它的封装性,和在类内部定义的方法不同,扩展函数不能访问私有的或者是受保护的成员。 换个说法即:扩展函数内部,可以在接收者上调用该类的任意方法,无论成员函数还是扩展函数,都可以调用。在调用一方,扩展函数与成员函数没有区别。
导入和扩展函数
定义的扩展函数不会自动的在整个项目范围内生效(使用需要导入,避免偶然的命命名冲突),导入扩展函数语法与导入类语法相同
//导入扩展函数
import src.strings.lastChar
//也可以用*导入
import src.strings.*
//用关键字as可以修改导入的类或者函数名称
import src.strings.lastChar as last
//调用
fun main(args: Array) {
println("Kotlin".lastChar()) //n
//as修改函数名后
println("Kotlin".last()) // n
}
当在不同包中,有重名函数时候,一般函数和类可以选择用全名来指出这个类或者函数,对于扩展函数(扩展函数名需简短),就需要as来重命名解决命名冲突了。
java调用扩展函数
扩展函数实质上也是静态函数,它把调用对象作为了它的第一个参数。调用扩展函数不会去创建适配对象或者运行时候的额外消耗。java调用扩展函数即调用这个静态函数,然后把接收者对象作为第一个参数传进去(与顶层函数相同,包含Java类的名称是由这个函数声明的文件名决定)。
char c = JoinKt.lastChar("Java");
作为扩展函数的工具函数
joinToString函数的终极版本
/**
* 为Collection声明一个扩展函数
* */
fun Collection.joinToString(
separator: String = ",",//元素间的分隔符
prefix: String = "",//第一个元素分割符
postfix: String = ""): String {
val result = StringBuilder(prefix)
//this指向接收者对象T的集合
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator) //不用在第一个元素前添加分隔符
result.append(element)
}
result.append(postfix) //最后个元素分隔符
return result.toString()
}
//调用
fun main(args: Array) {
val list = arrayListOf(1,2,3)
//显示命名调用
println(list.joinToString(separator = ";",prefix = "(",postfix = ")")) //(1,2,3)
//默认命名调用
println(list.joinToString(" ")) //1 2 3
}
扩展函数是静态函数的一个语法糖,可以使用具体类型作为接收者类型,而不是一个类。如果你想要一个joinToString函数只能由字符串类型触发。
fun Collection.join(
separator: String=",",
prefix: String="",
postfix: String=""
)=joinToString(separator,prefix,postfix)
//调用
fun main(args: Array) {
val list = listOf("one","two","three")
//显示命名调用
println(list.join(" ")) //one two three
}
如果是其它类型的对象调用,抛出异常。
/**
* Error:(13, 13) Kotlin: Type mismatch: inferred type is List
* but Collection was expected
*/
println(listOf(1, 2, 3).join(" "))
不可重写的扩展函数
在kotlin中,重写成员函数是很平常的,但是扩展函数的静态性质也决定了扩展函数不能被子类重写。
/**
* 可继承的View,声明一个click函数
*/
open class View{
open fun click()= println("View clicked")
}
/**
* 继承View
*/
class Button:View(){
override fun click() = println("Button clicked")
}
fun main(args: Array) {
val view: View = Button()
println(view.click()) // Button clicked
}
但对于扩展函数来说,并非如此。扩展函数并不是类的一部分,它是声明在类之外的。虽然可以给基类和子类都能分别定义一个同名扩展函数,但是当此函数调用时,是由变量静态类型决定,而不是这个变量运行时类型决定。
/**
* View增加扩展函数showoff()
*/
fun View.showOff()= println(" this is View")
/**
* Button增加扩展函数showOff
*/
fun Button.showOff()= println(" this is Button")
//调用
fun main(args: Array) {
val view:View = Button()
view.showOff() // this is View
}
尽管view现在声明的是Button对象,但是类型仍然是View。再次强调,扩展函数实质上是java的静态函数,而在上述中也有提到,扩展函数会把接受者作为第一个参数。
所以,Kotlin的扩展函数不存在重写一说,Kotlin会把扩展当作静态函数对待。
需要注意的是,如果一个类的成员函数和扩展函数名相同,成员函数会被优先使用。所以在扩展API时候,如果遇到此情况,那么对应类定义的消费者将重新编译代码,这将改变它的意义并开始指向新的成员函数(这句话暂时没明白啥意思,记住加黑的要点就行)。
扩展属性
扩展属性和扩展函数一样,扩展属性也像接收者的一个普通成员属性一样。
/**
* 因为这里没有支持字段,没有默认getter()实现,所以必须定义getter函数,
* 初始化也不行,因为没有地方储存初始值
*/
val String.lastChar:Char
get() = get(length-1)
//调用
fun main(args: Array) {
//可以像访问成员属性一样访问
println("kotlin".lastChar) //n
}
在StringBuilder上定义相同属性可以设置为var,因为StringBuilder是可变的
var StringBuilder.lastChar: Char
get() = get(length-1) //getter()
set(value) = this.setCharAt(length-1,value) //setter()
//调用
fun main(args: Array) {
//可以像访问成员属性一样访问
val sb = StringBuilder("kotlin?")
sb.lastChar='!'
println(sb) // kotlin!
}
注:当从java访问扩展属性的时候,应该显示的调用它的getter函数。
处理集合:可变参数,中缀调用和库的支持
kotlin语言的相关特性:
可变参数的关键字vararg,可以用来声明一个函数将可能有任意数量的参数
一个中缀表示法,当你在调用一些只有一个参数的函数时候,使用它会让代码更简练
解构声明,用来把一个单独的组合值展开到多个变量中
可变参数:让函数支持任意数量的参数
//创建集合,可以传递任意个数参数
val list = listOf(2, 3, 5, 7, 11)
/**
* 源码,参数声明是vararg
*/
public fun listOf(vararg elements: T): kotlin.collections.List { /* compiled code */ }
与Java不同(java声明是在类型后...)。kotlin传递的参数已经包装在数组中时候,调用该函数的语法。java可以原样传递数组,kotlin要求你显示的解包数组,方便每个数组元素在函数中作为单独参数调用(展开运算符),使用时候,在对应参数前加"*"。
//调用
fun main(args: Array) {
//通过展开运算符,可单个调用中组合来自数组的值和某些固定值
val list = listOf("args:", *args)
println(list) //[args:]
}
键值对处理:中缀调用和解构声明
//to并非是内置结构,而是特殊函数调用,即中缀调用
//中缀调用中,没有添加额外分隔符,函数名称是直接放在目标对象名称和参数之间的
//1 to "one"等价于 1.to("one")
val map = mapOf(1 to "one",2 to "two",3 to "three")
中缀调用可以与只有一个参数的函数一起使用,无论是普通函数还是扩展函数,都需要关键字infix修饰符标记。
/**
* to函数
* 返回Pair类型对象,用来表示一对元素(函数声明皆为泛型,此处省略泛型)
* */
infix fun Any.to(other:Any)= Pair(this,other)
可以用Pair直接初始化两个变量
val (number,name)=1 to "one"
解构还可以使用map的key和value来初始化两个变量(参考joinToString函数)
to是扩展函数,可以创建任意一对元素
1 to "one"
"one" to 1
val list = listOf(1,2,3)
println(list to list.size) //([1, 2, 3], 3)
println("$list,${list.size}")//[1, 2, 3],3
字符串和正则表达式
分割字符串
//kotlin的split是一个扩展函数
//split传入显示的正则表达式
//kotlin的toRegex()也可以将字符串转为正则表达式
println("12.345-6.A".split("\\.|-".toRegex())) //[12, 345, 6, A]
//split重载支持文本分隔符
println("12.345-6.A".split(".","-")) //[12, 345, 6, A]
java的split是实现不了此效果的,它会返回一个空数组,这是因为java的split()是一个正则表达式,而(.)表示任何字符的正则表达式。
正则表达式和三重引号的字符串
使用扩展函数解析完整路径名到对应组件
示例路径:"/Users/yole/kotlin-book/chapter.adoc"
/**
* 使用String的扩展函数来解析文件路径
*/
fun parsePath(path:String){
//substringBeforeLast表示给定分隔符之前(第一次或最后次),/之前为目录
val directory = path.substringBeforeLast("/")
//substringAfterLast表示给定分割符之后(第一次或最后次) ,/之后文件名称
val fullName = path.substringAfterLast("/")
//同上,.之前文件名
val fileName=fullName.substringBeforeLast(".")
//同上,.之后扩展名
val extension = fullName.substringAfterLast(".")
println("Dir:$directory,name:$fileName,ext:$extension")
}
//调用
fun main(args: Array) {
//Dir:/Users/yole/kotlin-book,name:chapter,ext:adoc
parsePath("/Users/yole/kotlin-book/chapter.adoc")
}
使用正则表达式实现(正则表达式不熟悉不推荐)
fun parsePath(path:String) {
val regex="""(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if(matchResult!=null){
val (directory,fileName,extension)=matchResult.destructured
println("Dir:$directory,name:$fileName,ext:$extension")
}
//调用
fun main(args: Array) {
//Dir:/Users/yole/kotlin-book,name:chapter,ext:adoc
parsePath("/Users/yole/kotlin-book/chapter.adoc")
}
创建一个正则表达式,输入路径匹配格式,符合(不为null)就destructured属性赋值给相应变量(与pair初始化变量效果相同)。不懂正则,略。只知道**kotlin中"""三重引号不需要对任何字符串转义
**
多行的三重引号字符串不仅可以避免转义字符,而且可以包含任何字符,甚至换行符。
//调用
fun main(args: Array) {
val kotlinLogo = """|//
.|//
.|/\"""
//trimMargin()函数可以用来删除每行前缀和空格
println(kotlinLogo.trimMargin("."))
}
/**
* |//
* |\\
* |/\
*/
多行字符串中使用字符模板
//调用
fun main(args: Array) {
//字符串中使用美元符号(与字符串模板冲突),必须使用嵌入表达式
val price = """${'$'} 100"""
println(price) //$ 100
}
扩展函数强大在于,可以扩展现有API适应新语言,即Pimp My Library 模式。kotlin标准库由标注java类扩展函数而来,Anko库是AndroidAPI的扩展。
让代码简洁:局部函数和扩展
kotlin可以在函数中嵌套提取的函数,这样可以便捷的获得所需结构,也无需额外的语法开销。
class User(val id:Int,val name:String,val address:String) {
//带重复代码的函数
fun saveUser(user: User){
//检查字段
if(user.name.isEmpty()){
println("user.name is null")
}
//重复检查不同字段
if(user.address.isEmpty()){
println("user.address is null")
}
}
}
//调用
fun main(args: Array) {
val user: User = User(1, "", "")
// user.name is null
// user.address is null
println(user.saveUser(user))
}
将重复代码放入到局部函数
class User(val id:Int,val name:String,val address:String) {
fun saveUser(user: User){
// 局部函数
fun validate(user: User,
value:String,
fieldName:String){
if(value.isEmpty()){
println("$value is null")
}
}
validate(user,user.name,"Name")
}
}
//调用
fun main(args: Array) {
val user: User = User(1, "", "")
println(user.saveUser(user))//is null
}
这样可以不用重复验证,也可以像User添加其它字段验证。
同时,上层楼,不用把User传给验证函数
class User(val id: Int, val name: String, val address: String) {
fun saveUser(user: User) {
//验证函数取消传入User
fun validate(
value: String,
fieldName: String) {
if (value.isEmpty()) {
//局部函数可以直接访问外部函数
println("${user.id}")
}
}
validate( user.name, "Name")
}
}
//调用
fun main(args: Array) {
val user: User = User(1, "", "")
println(user.saveUser(user))// 1
}
更上层楼,验证逻辑放到User扩展函数里
//扩展函数
fun User.validateBeforeSave(){
//局部函数
fun validate(
value: String,
fieldName: String) {
if (value.isEmpty()) {
println("is null")
}
}
validate( name, "Name")
}
class User(val id: Int, val name: String, val address: String){
fun saveUser(user: User){
//调用扩展函数
user.validateBeforeSave()
}
}
//调用
fun main(args: Array) {
val user: User = User(1, "", "")
println(user.saveUser(user))// is null
}
提取到扩展函数中相当有效。即使User属于当前代码库而不是类库一部分,同时扩展函数validateBeforeSave()与用到User的地方并没有关系,不应该放到User的方法里。我们应该遵循类的API只包含必须的方法。
扩展函数也能使用局部函数,但是涉及到局部深度嵌套问题,不建议多层嵌套。