《Kotlin实战》学习笔记

96
newtrek
2017.11.13 21:21* 字数 2676

第一章 定义和目的

kotlin的主要特征

  • 目标平台:服务器端,Android及任何Java运行的地方
  • 静态类型
  • 函数式和面向对象
  • 免费且开源

小结

  • fun关键字用来声明函数,val关键字和var关键字分别用来声明只读变量和可变变量
  • 字符串模板帮助你避免繁琐的字符串连接。在变量名称前加上$前缀或者用${}包围一个表达式,来把值注入到字符串中
  • 值对象类在Kotlin中以简洁的方式表示
  • 熟悉的if现在是带返回值的表达式
  • when表达式类似于Java中的switch但功能更强大
  • 在检查过变量具有某种类型之后不必显示地转换它的类型:编译器使用智能转换自动帮你完成
  • for,while,和do-while循环与Java类似,但是for循环现在更加方便,特别是当你需要迭代map的时候,又或是迭代集合需要下标的时候。
  • 简洁的语法1..5会创建一个区间,区间和 数列允许Kotlin在for循环中使用统一的语法和同一套抽象机制,并且还可以使用in运算符和!in运算符来检查值是否属于某一个区间
  • Kotlin中的异常处理和Java非常相似,除了Kotlin不要求你声明函数可以抛出的异常

第三章 函数的定义和调用

创建集合

val set = hashSetOf(1,2,3,4,5,6)
val list = arrayListOf(1,2,3,4,5)
val map = mapOf(1 to "one",2 to "two",3 to "tree")

kotlin没有自己的集合类,而是采用的标准的Java集合类,kotlin可以更容易地与Java代码交互,可以调用变量的JavaClass查看变量的类型。

让函数更好的调用

/**
 * 集合按规定格式转换为字符串
 */
fun <T> joinToString(collection: Collection<T>,
                     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()
}

调用println(joinToString(set,",","[","]"))
结果:[1,2,3,4,5,6]
joinToString是kotlin集合类默认的方法,可以直接用

命名参数

命名参数

可以显示地表明一些参数的名称,如果在调用一个参数时,指明了一个参数的名称,为了避免混淆,那它以后的所有参数都需要标明名称。

默认参数值

当使用常规的调用语法时,必须按照函数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数。如果使用命名参数,可以省略中间的一些参数,也可以以你想要的任意顺序只给定你需要的参数。

fun main(args: Array<String>) {
    println(joinToString(set,",","[","]"))
    println(joinToString(set,",","["))
    println(joinToString(set,","))
    println(joinToString(set,postfix = "}",prefix = "{"))
}

/**
 * 集合按规定格式转换为字符串
 */
fun <T> joinToString(collection: Collection<T>,
                     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()
}
默认参数值

消除静态工具类:顶层函数和属性

在Kotlin中,不需要去创建一个类作为静态函数的容器,相反,可以把这些函数直接放在代码文件的顶层,不用从属于任何的类。这些放在 文件顶层的函数依然的包内的成员,如果你需要从包外访问它,则需要import,但不需要额外包一层。Kotlin编译生成的类的名称,对应于包含函数的文件的名称,这个文件中的所有顶层函数编译为这个类的静态函数,因为Java要调用这个函数的时候和调用静态函数一样
改变包含Kotlin顶层函数的生成的类的名称,需要为这个文件添加@JvmName的注解,将其放到这个文件的开头,位于包名的前面。

顶层属性

和函数一样,顶层属性和顶层函数一样可以放在文件的顶层,和其它属性一样,val只有一个getter,var对应一对getter和setter,如果想要把一个常量public static final的属性暴露给Java,可以用const来修饰它

给别人的类添加方法:扩展函数和属性

fun String.lastChar(): Char = this.get(this.length - 1)

扩展函数就是一个类的成员函数,不过定义在类的外面,所要做的就是把要扩展的类或者接口的名称,放到即将添加的函数前面,这个类的名称称为接收者类型;用来调用这个扩展函数的那个对象,叫做接收对象。

import string.lastChar

fun main(args: Array<String>) {
    println("Hello".lastChar())
}

导入函数还可以用as改名

import string.lastChar as last
fun main(args: Array<String>) {
    println("Hello".last())
}

Java中调用kotlin的函数就是调用kotlin生成的类文件的静态方法,比如刚才那个方法如果声明在StringUtil.kt的文件中,java中

char c = StringUtilkt.lastChar("Java")

扩展函数无非就是静态函数的一个高效的语法糖,可以使用具体的类型作为接收者类型,而不是一个类。

不可重写扩展函数

扩展函数并不是类的一部分,它是声明在类之外的。
如果一个类的成员函数和扩展函数有相同的签名,成员函数往往会被优先调用。

扩展属性

扩展属性提供了一种方法,用来扩展类的API,可以用来访问属性,用的是属性语法而不是函数的语法。

val String.lastChar: Char
    get()=get(length-1)

调用

fun main(args: Array<String>) {
    println("Hello".lastChar)
}

可变属性

var StringBuilder.builderLastChar: Char
    get()=get(length-1)
    set(value :Char) = this.setCharAt(length-1,value)

就是一个set和一个get

处理集合:可变参数,中缀调用和库的支持

可变参数:让函数支持任意数量的参数

vararg 修饰可变参数,数组变量前加*表示展开这个集合

fun zhankaiArgs(vararg args: Int){
    for ( e in args){
    print("$e  ")
    }
}

fun main(args: Array<String>) {
 val list = intArrayOf(1,3,4,5,6)
    zhankaiArgs(1,*list)
}

键值对的处理:中缀调用和解构声明

1.to("one) //一般to函数的调用
1 to "one"  //使用中缀符号调用to函数

中缀调用可以与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。要允许使用中缀符号调用函数,需要使用infix修饰符来标记它。

infix fun Any.to(other:Any) = Pair(this,other)
//使用
 val pair = 2 to "two"
    print(pair)
//mapOf的声明
public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> 

结果:(2,two),一个Pair对象就是一个键值对

字符串和正则表达式的处理

//分割表达式
val s="12.345-6.A".split("\\.|-".toRegex())//用正则表达式
val s2="12.345-6.A".split(".","-")//指定多个分隔符
//print结果都为[12, 345, 6, A]

使用String的扩展函数 解析文件路径string

fun parsePath(path:String){
    val directory = path.substringBeforeLast("/")
    val fullName = path.substringAfterLast("/")

    val fileName = fullName.substringBeforeLast(".")
    val extension = fullName.substringAfterLast(".")
    print("directory: $directory  fullName: $fullName fileName: $fileName extention: $extension")
}

结果:

directory: H:/Users/yorl/kotlin-book fullName: chater.adoc fileName: chater extention: adoc

使用正则表达式解析

fun regexParsePath(path:String){
    val regex="""(.+)/(.+)\.(.+)""".toRegex()
    val matchResult = regex.matchEntire(path)
    if (matchResult!=null){
        val (directory,fileName,extention) = matchResult.destructured
        println("directory: $directory  fileName: $fileName extention: $extention")
    }
}

在三重引号字符串中,不需要对任何字符串进行转义,包括反斜线

让代码更整洁:局部函数和扩展

如果一个函数内有一些重复操作,可以使用局部函数,局部函数可以访问所在函数中的所有参数和变量。

fun saveUser(user:User){
    fun validate(value:String, fieldName:String){
        if (value.isEmpty()){
            throw IllegalArgumentException(
                    "Can't save user ${user.id}: empty $fieldName"
            )
        }
    }
    validate(user.name,“Name”)
    validate(user.name,“Address”)
    //...
}

进一步改进

fun saveUser(user:User){
   user.validateBeforeSave()
    //...
}

fun User.validateBeforeSave(){
    fun validate(value :String, fieldName :String){
        if (value.isEmpty()){
            throw IllegalArgumentException(
                    "Can't save user ${id} : empty $fieldName"
            )
        }
    }
    validate(name,"Name")
    validate(adress,"Address")
}

可以看到扩展函数也可以用局部函数

小结

  • kotlin没有定义自己的集合类,而是在Java集合类的基础上提供了更丰富的API。
  • Kotlin可以给函数参数定义默认值,这样大大降低了重载函数的必要性,而且命名参数让多参数函数的调用更加易读
  • Kotlin可以用扩展函数和属性来扩展任何类的API,包括在外部库中定义的类,而不需要修改其源代码,也没有运行时开销
  • 中缀调用提供了单个参数的,类似调用运算符方法的简明语法
  • Kotlin为普通字符串和正则表达式都提供了大量的方便字符串处理的函数。
  • 三重引号的字符串提供了一种简洁的方式,解决了原本在java中需要啰嗦的的转义和字符串连接的问题
  • 局部函数帮助保持代码整洁的同时,避免重复

第四章 类,对象和接口

Kotlin的类和接口与Java的类和接口有一点区别,例如,接口可以包含属性声明,与Java不同,Kotlin的声明默认是final和public的,此外,嵌套的类默认并不是内部类,它们并没有包含对其外部类的隐式引用。

4.1 定义类继承结构

kotlin的接口

  • 使用interface关键字声明接口.
  • Kotlin在类名后面使用冒号代替了Java中的extends和implements关键字,和Java一样,一个类可以实现任意多个接口,但是只能继承一个类。override修饰符用来标注被重写的父类或者接口中的方法和属性,并且是kotlin中的override修饰符是强制要求的。
  • 接口的方法可以有一个默认实现

interface Clickable{
    fun click()
    fun showOff()=println(" I'm clickable!")
}

interface  Focusable{
    fun setFocus(b: Boolean){
        println("I ${if(b) "got" else "lost"} focus.")
    }
    fun showOff() = println("I'm focusable!")
}
class Button:Clickable,Focusable{
    override fun click() {
        println("I was Clicked!")
    }

    /**
     * 两个接口有同样的函数下,必须自己显示实现
     */
    override fun showOff() {
        super<Clickable>.showOff()//可调用接口的默认实现
        super<Focusable>.showOff()
    }
    //或者
//    override fun showOff() = super<Clickable>.showOff()
}

open,final和abstract修饰符:默认为final

java的类和方法默认是open的,而kotlin默认是final的,如果想允许创建一个类的子类,需要使用open修饰符来标示这个类,此外,需要给每一个可以被重写的属性或方法添加open修饰符


open class RichButton:Clickable{//这个类是open的,其它类可以继承它
    fun desable(){}//这个函数是final的,不能在子类重写它
    open fun animate(){}//这个函数是open的,可以在子类重写它
    override fun click() {//这个函数重写了一个open函数并它本身同样是open的
        println("I'm clicked!")
    }
    final override fun showOff() =super.showOff()//声明这个函数在子类不可重写
}

注意:如果重写了一个基类或者接口的成员,重写了的成员同样默认是open的,如果想改变这一行为,阻止你的类的子类重写你的子类,可以显示地标注为final

abstract 声明抽象类,这种了不能实例化,一个抽象类通常包含一些没有实现并且必须在子类重写的抽象成员,抽象成员始终是open的,所有不需要显示的使用open修饰符

abstract class Animated{//类是抽象的,不能被实例化,所有是open的
    abstract fun animate()//函数的抽象的,必须被子类实现
    open fun stopAnimating(){}//抽象类的非抽象函数并不是默认open的,但是可以标注为open的
    fun animateTwice(){}
}
修饰符 相关成员 评注
final 不能被重写 类中成员默认使用
open 可以被重写 需要明确的表明
abstract 必须被重写 只能在抽象类中使用;抽象成员不能有实现
override 重写父类或接口中的成员 如果没有使用final,重写的成员默认是开放的

可见性修饰符:默认为public

Javaz中的默认可见性--包私有,在Kotlin中并没有使用,Kotlin只把包作为在命名空间里组织代码的一种方式使用,并没有将其用作可见性控制。Kotlin提供了一个新的修饰符,internal,表示只在模块内部可见,一个模块就是一组一起编译的kotlin文件

修饰符 类成员 顶层声明
public(默认) 所有地方可见 所有地方可见
internal 模块中可见 模块中可见
protected 子类中可见 -
private 类中可见 文件中可见

内部类和嵌套类:默认是嵌套类

类A在另一个类B中声明 在Java中 在Kotlin中
嵌套类(不存储外部类的引用) static class A class A
内部类(存贮外部类的引用) class A inner Class A
/**
 * 内部类
 */
class Outer{
    inner class Inner{
        fun getOuterReference(): Outer = this@Outer //获取外部类的引用
    }
}

密封类:定义受限的类继承结构

**
 * 密封类,Expr2包括了所有的子类
 */
sealed class Expr2{
    class Num(val value: Int): Expr2()
    class Sum(val left: Expr2,val right: Expr2):Expr2()
}

fun eval2(e: Expr2):Int =
        when(e){//when 表达式涵盖了所有可能的情况,所以不在需要else分支
            is Expr2.Num -> e.value
            is Expr2.Sum -> eval2(e.left)+ eval2(e.right)
        }

4.2 声明一个带非默认构造方法或属性的类

初始化类:主构造函数和初始化语句块


//class User3(val nickName:String) //"val"意味着相应的属性会用构造方法的参数来初始化

class User3 constructor(_nickName: String){//带一个参数的主构造方法
    val nickName: String
    init {
        nickName = _nickName //初始化语句块
    }
}

如上图是两种声明类的方式,方式二用到了关键字constructor和init,constructor用来开始一个主构造方法或从构造方法的声明,init用来引入一个初始化语句块,这种语句快包含了在类被创建时执行的代码,并会与主构造方法一起使用。

当然再简化可以这样:

class User5 constructor(_nickName: String){//带一个参数的主构造方法
    val nickName =_nickName
}

构造方法:用不同的方式来初始化父类

//用不同的方法来初始化父类
class Context
class Attribute

open class View {
    protected var name: String="View"
    val context: Context
    val attr: Attribute?

    constructor(ctx: Context)
            :this(ctx,null)

    constructor(ctx: Context,art: Attribute?){
        context=ctx
        attr=art
    }

    override fun toString(): String {
        return "View(name='$name')"
    }

}

class MyButton: View{
    constructor(ctx: Context)
    :this(ctx,null){
        name="MyButton"
    }
    constructor(ctx: Context,art: Attribute?)
    :super(ctx=ctx,art = art)

    override fun toString(): String{
        return "Mybutton(name=${name})"
    }
}

实现在接口中声明的属性

// 实现在接口中声明的属性

interface  UserInterface{
    val nickName: String //声明属性
}
//只填写了昵称的用户
class PrivateUser(override val nickName: String):UserInterface //主构造方法属性
//提供了email进行注册的用户
class SubscribingUser(val mail: String):UserInterface{
    override val nickName: String
        get() = mail.substringBefore("@")//自定义属性及getter
}
//共享了Facebook账户的用户
class FacebookUser(val accountId: Int):UserInterface{
    override val nickName = getFacebookName(accountId)//属性初始化
    /**
     * 由id获取用户名
     */
    fun getFacebookName(accountId: Int): String{
        return "facebook:"+accountId
    }
}

通过getter和setter访问支持字段


/**
 *通过getter或setter访问支持字段
 */
class User2(val name: String){
    var address: String ="unspecified"
    set(value) {
        println("""
            Address was changed for $name:
            "$field" -> "$value".""".trimIndent())
        field=value
    }
}

修改访问器的可见性

/**
 * 修改访问器的可见性
 */
class LengthCounter{
    var counter: Int =0
        private set //不能在类外部修改这个属性
    fun addWord(word: String){
        counter +=word.length
    }
}
kotlin
Web note ad 1