Kotlin是一门与Swift类似的静态类型JVM语言,由软件公司JetBrains设计开发并开源
为何说Kotlin非常适合于Android?
1.体积小,其大小相当于support-v4库
2.同一家公司的IDE和语言,所以IDE对该语言提供了非常棒的支持
3.互操作性是非常智能,Kotlin代码中使用Android SDK而不会遇到任何问题
特性:
1.Null安全
2.数据类
3.互操作
4.Lambda表达式
补充:
1.POJO:简单的Java对象(Plain Old Java Object)
Kotlin学习笔记资料:
kotlin函数定义:
1.在Java中,所有的控制结构都是语句,而在Kotlin中,除了for、do和do/while以外大多数控制结构都是表达式。
2.表达式和语句区别:表达式有值,并且能作为另一个表达式的一部分使用。而语句无值,并总是包含着它的代码块中的顶层元素。
3.函数体写在花括号中,则这个函数有代码块体
4.直接返回了一个表达式的,则它就有表达式体
5.对于表达式体函数,可以省略返回类型,因为编译器会分析作为函数体的表达式,并把它的类型作为函数的返回类型,这种分析称为 类型推导。但是对于有返回值的 代码块体函数,必须显示地写出返回类型和return语句。
fun main(args: Array<String>) {
println("值:"+max(1, 5))//值:5
}
fun max(a: Int, b: Int): Int{
return if(a > b) a else b
}
变量声明:使用关键字val或val声明
val:不可变引用
var:可变引用
字符串模板处理:
fun main(args: Array<String>) {
//直接引用局部变量
val name = if(args.size > 0) args[0] else "Kotlin"
println("嘿嘿,$name")//嘿嘿,Kotlin
//在字符串中使用$,需要对它进行转义
val money = 50;
println("有钱,\$$money")//有钱,$50
//引用更加复杂的表达式,需要把表达式用花括号扩起来
println("a is ${if(5 > 6) "bigger" else "smaller"} than b")
}
类定义:
1.在Kotlin中,属性是头等的语言特性,完全替代了字段和访问器方法
fun main(args: Array<String>) {
personFunc()
}
class Person(val name: String, var isMarried: Boolean)
fun personFunc(){
val person = Person("zemao", false)
println("${person.name} isMarried = ${person.isMarried}")//zemao isMarried = false
person.isMarried = true
println("${person.name} isMarried = ${person.isMarried}")//zemao isMarried = true
}
类自定义访问器:
fun main(args: Array<String>) {
val rectangle = Rectangle(41, 43)
println(rectangle.isSquare)//false
rectangle.height = 43
println(rectangle.isSquare)//true
}
class Rectangle(var height: Int, var width: Int){
val isSquare:Boolean
get() = height == width
}
枚举:
1.声明枚举类时,enum是一个软关键字,只有当它出现在class前面时才有特殊的意义,在其他地方可以当做普通名称使用,而class仍然是一个关键字。
2.声明一个带属性的枚举类时需要注意:
1)当声明每个枚举常量的时候,必须提供该常量的属性值。
2)如果要在枚举类中定义任何方法,就要使用分号把枚举常量列表和方法分开。
fun main(args: Array<String>) {
println(Color.BLUE.rgb())//255
}
//不带属性的枚举类
// enum class Color{
// RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
// }
//带属性的枚举类
enum class Color(var r: Int, val g: Int, val b: Int){
RED(255, 0, 0),
ORANGE(255, 155, 0),
YELLOW(255, 255, 0),
GREEN(0, 255, 0),
BLUE(0, 0, 255),
INDIGO(70, 0, 135),
VIOLET(238, 130, 220);
fun rgb() = (r * 256 + g) * 256 + b
}
使用 "when” 处理枚举类:
1.when是一个有返回值的表达式,因此,作为表达式函数体,它可以去掉花括号和return语句,并省略返回类型的声明。
2.when和Java中的switch语句类似,根据when中Color的值走到对应的分支,除此之外,我们可以把多个值用逗号间隔,合并到同一个分支
3.在 “when”结构中使用任意对象
4.对于不属于的分支,我们需要提供额外的else操作符
fun main(args: Array<String>) {
println(getWarmth(Color.ORANGE))//warm
println(getWarmth(Color.GREEN))//neutral
println(mix(Color.BLUE, Color.YELLOW))//GREEN
}
enum class Color{
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
//when color匹配如下的一组
fun getWarmth(color: Color) = when(color){
Color.RED, Color.ORANGE, Color.YELLOW ->"warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET ->"cold"
}
//在 “when”结构中使用任意对象
fun mix(c1: Color, c2: Color) =
when(setOf(c1, c2)){
setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
setOf(Color.BLUE, Color.VIOLET) -> Color.INDIGO
//对于不属于的分支,我们需要提供额外的else操作符
else -> throw Exception("Dirty color")
}
类型智能转换:
1.在kotlin中,判断一个变量是否是某种类型需要使用is关键字,类似Java当中的instanceOf
2.在Java中,在检查完后,使用时还需要显式地加上类型转换。
3.在kotlin中,如果你检查过一个变量是某种类型,后面在使用的过程中,就不需要再转换它,可以把它当做你检查过的类型来使用。
4.智能转换,只在变量经过is检查且之后不再发生变化的情况下有效,当你对一个类的属性进行智能转换的时候,这个属性必须是一个val属性,而且不能有自定义的访问器,否则,每次对属性的访问是否都能返回同样的值将无从验证。
5.when,try catch代码块相关,代码块中的最后一个表达式就是结果
fun main(args: Array<String>) {
println(cal(Sum( Sum(Num(2), Num(3)), Num(4) )))//9
}
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun cal(expr: Expr): Int =
when(expr){
is Num -> expr.value//value值是否是Num类型,是的话下面直接用
is Sum -> cal(expr.left) + cal(expr.right)//代码块中,最后一个表达式,即结果
else -> 0
}
在Kotlin中,使用了 "区间" 替代常见的循环用法:
用法:两个值之间的间隔,这两个值通常是数字,一个起始值,一个结束值,使用..运算符来表示区间,而结束值始终是区间的一部分。
fun main(args: Array<String>) {
println(forFunc(1, 5))//Num=1 Num=2 Num=3 Num=4 Num=5
println(forFunc2(1, 10))//Num=10 Num=8 Num=6 Num=4 Num=2
println(forFunc3(1, 5))//Num=1 Num=2 Num=3 Num=4
}
//遍历区间的每一个元素,默认步长是1
fun forFunc(startValue: Int, endValue: Int){
for(i in startValue..endValue){
println("Num=$i")
}
}
//downTo用于递减到指定的值
fun forFunc2(startValue: Int, endValue: Int){
for(i in endValue downTo startValue step 2){
println("Num=$i")
}
}
//使用until则可以使迭代不包含指定的结束值
fun forFunc3(startValue: Int, endValue: Int){
for(i in startValue until endValue){
println("Num=$i")
}
}
迭代map使用:(Beta)
fun main(args: Array<String>) {
val binaryReps = TreeMap<Char, String>()
for(c in 'A'..'F'){
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary//下标变成了key值
}
for((letter, binary) in binaryReps){
//允许展开迭代中的集合的元素,把展开的结果存储到了两个独立的变量中:letter是键、binary是值
println("$letter = $binary")
}
}
使用 in 运算符判断值是否在区间中,反之逆运算 !in:
fun main(args: Array<String>) {
println(isLetter('q'))//true
println(isNotDigit('x'))//true
}
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
kotlin 中的异常:(Beta)
1.kotlin不区分受检异常和未受检异常,不必指定函数抛出的异常,而且可以处理也可以不处理异常。
2.kotlin的throws子句没有出现在代码中
3.kotlin的throw结构是一个表达式时,能作为另一个表达式的一部分使用。
fun main(args: Array<String>) {
val reader = BufferedReader(StringReader("234"))
println(readNumber(reader))
}
fun readNumber(reader: BufferedReader): Int?{
try{
val line = reader.readLine()
return Integer.parseInt(line)
}catch (e: NumberFormatException){
return null
}finally{
reader.close()
}
}
扩展函数:
1.Kotlin会把扩展函数当做静态函数来对待,因此扩展函数不存在重写。
2.改变字符串内的字符,使用单引号' ',因为外层已经用了双引号。
fun main(args: Array<String>) {
println("Hello, world!".lastChar())//!
println("Kotlin".lastChar)//n
val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb)//Kotlin!
}
//拓展方法
fun String.lastChar(): Char = this.get(this.length -1)
//拓展属性
val String.lastChar: Char
get() = get(length - 1)
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char){
this.setCharAt(length - 1, value)
}
可变参数的相关处理:
1.可变参数,使用关键字 vararg
2.参数打包成一个数组,在传入参时需要通过 * 操作符进行解包
fun main(args: Array<String>) {
varargFunc(1, 2, 3, 4, 5, 6)//多个单独参数
val intArray = intArrayOf(1, 2, 3, 4, 5)
varargFunc(*intArray)//参数装成数组,需要*解包
}
fun varargFunc(vararg ints:Int){
println(ints.javaClass)//class [I
for(i in ints){
println("var=$i")//var=1 var=2 var=3 var=4 var=5 var=6
}
}
中缀调用:
1.无论是普通的函数还是扩展函数,要允许使用中缀符号调用函数,需要使用infix修饰符来标记它
2.中缀调用 不是特殊的内置结构,而是一种特殊的函数调用。在中缀调用中,没有添加额外的分隔符,函数名称是直接放在目标对象名称和参数之间。
3.Any是Kotlin中所有类的父类,和Java中的Object相同
fun main(args: Array<String>) {
val married: Person = "haha" create true
println("Name=${married.name}, isMarried=${married.isMarried}")//Name=haha, isMarried=true
}
class Person(val name: Any, val isMarried: Any)
infix fun Any.create(isMarried: Any) = Person(this, isMarried)
字符串进行分割:
1.在kotlin中可以使用Regex类型的重载函数对字符串进行分割
2.如下\\.转义,而|是或的意思
例子1:
fun main(args: Array<String>) {
println("haha.hehe.AA|BB-CC-DD".split("\\.|-".toRegex()))//[haha, hehe, AA|BB, CC, DD]
}
例子2:
需求:通过这个字符串获取到chapter.adoc的目录、文件名和扩展名1
fun main(args: Array<String>) {
parsePath("/Users/yole/kotlin-book/chapter.adoc")
}
//使用扩展函数实现
// fun parsePath(path: String){
// val directory = path.substringBeforeLast("/")
// val fullName = path.substringAfterLast("/")
// val fileName = fullName.substringBeforeLast(".")
// val extension = fullName.substringAfterLast(".")
// //Dir:/Users/yole/kotlin-book,name:chapter,ext:adoc
// println("Dir:$directory,name:$fileName,ext:$extension")
// }
//使用正则表达式
fun parsePath(path: String){
//"/Users/yole/kotlin-book/chapter.adoc"
// (.+)/(.+) .(.+)
//这个正则表达式将一个路径分为三个由斜线和点分隔的组
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if(matchResult != null){
val (directory, fileName, extension) = matchResult.destructured
//Dir:/Users/yole/kotlin-book,name:chapter,ext:adoc
println("Dir:$directory,name:$fileName,ext:$extension")
}
}
局部函数使用:
fun main(args: Array<String>) {
saveUser(User(1, "", ""))
}
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.address, "Address")
}
class User(val id: Int, val name: String, val address: String)
异常打印:
Exception in thread "main" java.lang.IllegalArgumentException: Can't save user 1:empty Name
at ch02.ex1_1_HelloWorld._1_HelloWorldKt$saveUser$1.invoke(1_HelloWorld.kt:11)
at ch02.ex1_1_HelloWorld._1_HelloWorldKt.saveUser(1_HelloWorld.kt:14)
at ch02.ex1_1_HelloWorld._1_HelloWorldKt.main(1_HelloWorld.kt:5)
kotlin第二部分:
接口相关:
1.一个简单的Kotlin接口使用 interface 关键字来声明,所有实现这个接口的非抽象类都需要实现接口中定义的抽象方法。
2.Kotlin在类名后面使用 冒号 代替了Java中的extends和implements关键字,一个类可以实现多个接口,但是只能继承一个类。
3.override修饰符用来标注被重写的父类或者接口的方法和属性,并且是 强制要求的。
fun main(args: Array<String>) {
Button().click()//Button()对象实例
Button().showOff()//I'm clickable!
}
interface Clickable{
fun click()
//默认的实现方法
fun showOff() = println("I'm clickable!")
}
class Button : Clickable{
override fun click() = println("I was clicked")
}
4.如果一个类实现了两个接口,而这两个接口定义了相同的方法,并且都提供了该方法的默认实现,那么该类必须显示实现该方法,否则会在编译时报错;
另外,调用父类或接口的方法,可以使用与Java相同的关键字 super,并在后面的尖括号中指明父类的名字,最后是调用的方法名。
fun main(args: Array<String>) {
Button().click()//Button()对象实例
Button().showOff()//I'm focusable!
}
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()
}
}
访问性修饰符:open、final、abstract
1.在Kotlin中,类和方法默认都是final的,如果想允许创建一个类的子类,需要使用open修饰符来标示这个类,此外还需要给每一个允许被重写的属性或方法添加open修饰符
2.抽象类中的抽象函数:没有函数体就默认是abstract的,不一定要加上关键字,其访问性始终是open的。
3.抽象类中的非抽象函数:默认是final的,如果需要重写,那么需要加上open修饰符。
4.open、final和abstract这三个访问修饰符都 只适用于类,不能用在接口当中。
5.被重写方法必须加上override修饰符。
可见性修饰符:
public 所有地方可见;internal 模块中可见;protected 子类中可见;private 类中可见
1.Java和Kotlin在可见性上的区别包括以下几点:
在Java中默认的可见性是包私有的,而在Kotlin中,默认的可见性是public的。Kotlin用internal作为包可见的替代方案,它表示“只在模块内部可见”。
2.Kotlin允许在顶层声明中使用private可见性,包括类、函数和属性,这些声明就只在声明它们的文件中可见,这是隐藏子系统实现细节的非常有用的方式。
3.类的扩展函数不能访问它的private和protected成员。
4.Kotlin中,一个外部类不能看到其内部类中的private成员。
内部类和嵌套类:
1.kotlin中是嵌套类,是一个静态内部类,不持有外部类引用,类似java中的内部类
interface State: Serializable
interface View{
fun getCurrentState(): State
fun restoreState(state: State){}
}
class Button: View{
override fun getCurrentState()
override fun restoreState(state: State){}
//kotlin中默认是嵌套类,不持有外部类引用,类似java中的内部类
class ButtonState : State{}
}
2.把嵌套类变成内部类需要使用inner修饰符,并且访问外部类时需要this@{外部类名}
class Outer{
//把嵌套类变成内部类需要使用inner修饰符,并且访问外部类时需要this@{外部类名}
inner class Inner{
fun getOuterReference(): Outer = this@Outer
}
}
密封类:定义受限的类继承结构,使用sealed修饰符,对可能创建的子类做出严格的限制,所有的直接子类必须嵌套在父类中。
fun main(args: Array<String>) {
println(eval(Expr.Sum( Expr.Sum(Expr.Num(2), Expr.Num(3)), Expr.Num(4) )))//9
}
sealed class Expr{
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int =
when(e){
is Expr.Num -> e.value//value值是否是Num类型,是的话下面直接用
is Expr.Sum -> eval(e.left) + eval(e.right)//代码块中,最后一个表达式,即结果
}
这时候,我们在when表达式中已经处理了所有Expr的子类,就不再需要提供默认的分支(else分支),假如这时候我们给Expr添加一个新的子类Multi,但是不修改when中的逻辑,那么就会导致编译失败:
解决:1.when中加上该分支 2.when加上默认的else分支
fun main(args: Array<String>) {
//println(eval(Expr.Sum( Expr.Sum(Expr.Num(2), Expr.Num(3)), Expr.Num(4) )))//9
println(eval(Expr.Multi( Expr.Num(2), Expr.Num(3) )))//6
}
sealed class Expr{
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
class Multi(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int =
when(e){
is Expr.Num -> e.value//value值是否是Num类型,是的话下面直接用
is Expr.Sum -> eval(e.left) + eval(e.right)//代码块中,最后一个表达式,即结果
is Expr.Multi -> eval(e.left) * eval(e.right)//代码块中,最后一个表达式,即结果
}
构造方法:
kotlin将构造方法分为两类:
(java中则可以在类中声明一个或多个构造方法)
1.主构造方法:主要而简洁的初始化类的方法,并且在类体外部声明。
2.从构造方法:在 类体内部声明。
3.红色部分是主构造方法,简化写法:class User(val nickname: String)
(主构造方法:定义参数及参数类型属性)
完整写法:
class User constructor(_nickname: String){
val nickname: String
init{
nickname = _nickname
}
}
4.子类继承父类,并传递参数
fun main(args: Array<String>) {
val tUser = TwitterUser("haha")
println("Name=${tUser.nickname}")//Name=haha
}
open class User(val nickname: String)
class TwitterUser(nickname: String) : User(nickname)
5.kotlin中假如一个类没有声明任何的构造方法,将会生成一个不做任何事的默认构造方法,如果有子类继承了它,那么必须显示地调用父类的构造方法
6.定义从构造方法:constructor
fun main(args: Array<String>) {
val view = View()
println("Name=${view.ctx.name}, attr=${view.attr.name}")//Name=I am default, attr=I am default
val view2 = View(Context("hahahehe"))
println("Name=${view2.ctx.name}, attr=${view2.attr.name}")//Name=hahahehe, attr=I am default
}
class Context(val name: String)
class Attribute(val name: String)
open class View{
var ctx = Context("I am default")
var attr = Attribute("I am default")
constructor(){
}
constructor(ctx: Context){
this.ctx = ctx
}
constructor(ctx: Context, attr: Attribute){
this.ctx = ctx
this.attr = attr
}
}
7.子类调用父类的从构造方法:super
8.子类调用自己的另一个构造方法:this
实现在接口中声明的属性:
fun getFacebookName(accountId: Int) = "fb:$accountId"
interface User{
val nickname: String
}
//直接在主构造方法中声明了这个属性,这个属性实现了来自于User的抽象属性,所以要标记为override。
class PrivateUser(override val nickname: String) : User
//通过一个自定义的getter实现,这个属性没有一个支持字段来存储它的值,它只有一个getter在每次调用时从email中得到昵称。
class SubscribingUser(val email: Strng) : User{
override val nickname: String
get() = email.substringBefore('@')
}
//在初始化时,将nickname属性与值关联。
class FacebookUser(val accountId: Int) : User{
override val nickname = getFacebookName(accountId)
}
第三部分:
data关键字:
1.data关键字自动生成,equals、hashCode、toString方法
2.equals方法会检测所有的属性的值是否相等
3.hashCode方法会返回一个根据所有属性生成的哈希值
4.使用copy方法,副本不会影响到原始实例内容
(在设计数据类时,应当尽量只使用只读的属性,让数据类的实例不可变,因为如果不这样,被用作键的对象在加入HashMap或者类似容器后被修改了,容器会进入一种无效的状态。)
fun main(args: Array<String>) {
val bob = Client("Bob", 973293)
//使用自动生成的toString
println(bob)//Client(name=Bob, postalCode=973293)
val bob2 = Client("Bob", 973293)
//使用自动生成的equals
println(bob == bob2)//true
val hashSet = hashSetOf<Client>()
hashSet.add(bob)
//使用自动生成的hashCode
println(hashSet.contains(bob2))//true
//使用copy方法,副本不会影响到原始实例内容
println(bob.copy(postalCode = 382555))//Client(name=Bob, postalCode=382555)
}
data class Client(val name: String, val postalCode: Int)
by关键字:将接口的实现委托到另一个对象
fun main(args: Array<String>) {
val cset = CountingSet<Int>()
cset.addAll(listOf(1, 2, 3))
println("${cset.objectsAdded} objects were added, ${cset.size} remain")//3 objects were added, 3 remain
}
class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet{
var objectsAdded = 0
override fun add(element: T): Boolean{
objectsAdded++
return innerSet.add(element)
}
override fun addAll(c: Collection<T>): Boolean{
objectsAdded += c.size
println("$objectsAdded,${c.size}")//3,3
return innerSet.addAll(c)
}
}
object关键字:定义一个类并同时创建一个实例
1.对象声明:是定义单例的一种方式
1)一个对象声明可以包含属性、方法、初始化语句块等的声明,但是不允许声明构造方法,这是因为对象在定义的时候就已经创建了,不需要在其他地方调用构造方法。
2)对象声明允许使用对象名 . 字符的方式来调用方法和访问属性。
3)可以在类中使用对象声明
fun main(args: Array<String>) {
println("Name=${Person.name}, Age=${Person.age}")//Name=Default Name, Age=15
}
//单例
object Person{
var name : String = "Default Name"
var age : Int = 15
}
2.伴生对象:可以持有工厂方法和其它与这个类相关,但在调用时并不依赖类实例的方法,它们的成员可以通过类名来访问。
fun main(args: Array<String>) {
Outer.innerMethod()//类名直接调用
}
class Outer{
//companion object关键字,不需要指定类名,其他地方可以直接类名调用
companion object{
fun innerMethod(){
println("innerMethod is called")
}
}
}
3.对象表达式:用来替代Java的匿名内部类。(beta)
fun main(args: Array<String>) {
val button = Button()
//object代替匿名内部类
button.setOnClickListener(
object : OnClickListener){
override fun onClick() = println("Button is clicked")
}
button.click()
}
interface OnClickListener{
fun onClick()
}
class Button{
var listener : OnClickListener? = null
fun setOnClickListener(listener : OnClickListener){
this.listener = listener
}
fun click(){
listener?.onClick()
}
}
在Java中使用Kotlin对象:
如果要在Java中使用Kotlin中的声明对象,可以通过访问静态的INSTANCE字段:
Kotlin中的对象声明:
Java中调用方式: