《Kotlin实战》学习笔记
第一章 定义和目的
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
}
}