一、kotlin基础
1. 函数和变量
变量
var :可变引用。
val :不可变引用。(Java中的final)
常见容器类型:
Byte 整数 -128~127
Short 整数 -32768~32767
Int 整数 -2147483648~2147483647
Long 整数 -9223375036854775807~9223375036854775807
Float 小数 小数点后6位
Double 小数 小数点后15-16位
String 字符串 用""包含
Any/Any? 根类型(相当于java中的Object类)
Any 是非空类型的根类型,Any?是所有类型的根类型
Unit 无返回类型
interface Processor<T>{
fun process():T
}
class NoResultProcessor : Processor<Unit>{
override fun process(){
//do stuff
}
}
Nothing 永远没有返回(非正常返回)
kotlin 中没有基础数据类型的概念从而 == 运算符可以默认是值的比较(其实本质上就是equals 是通过operator关键字进行运算符重载)
避免了 java中 == 和 equals 的区分
字符串模板:
$ 表示一个变量名或者变量值
-
$varName 表示变量值
-
${varName.fun()} 表示变量的方法返回值:
val s2 = "${s1.replace("is", "was")}, but now is $a"
运算符
"""运算符
可以自动转义不需要用转义符号
""" c:\Users\pyh\kotlin-book """
解决了原本在Java中需要进行大量啰嗦的转义和字符串链接的问题
is运算符和 !is 运算符(类似于java中的instanceof关键字用法)
println(mAccount is String)
as运算符和as?运算符(父类不可转换成子类,相当于java中的向上转型和向下转型,kotlin不允许向下转型,即强转)
as运算符用于执行引用类型的显式类型转换
as?如果转换不成功的话返回null
open class Fruit
open class Apple(name: String) : Fruit()
val mFruit = Fruit()
val mApple = Apple("苹果")
println(mFruit as? Apple)
Int和Long类 位操作
shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向
下标约定:
x[a,b]--->x.get(a,b)
operator fun XXX.get(a:Any,b:Any)
x[a,b]=12:c--->x.set(a,b,12:c)
operator fun XXX.set(a:Any,b:Any,c:Any)
"in"约定:
a in c ---> c.contains(a);
operator fun XXX.contains(a:Any)
"rangTo"约定
start..end ---> start.tangeTo(end)
operator fun <T:Comparable<T>> T.rangeTo(that:T):ClosedRange<T>
"for"中iterator的约定
for(x in list) ---> xxx.iterator()
operator fun XXX.iterator() :XXXIterator{
override fun hasNext()
override fun next()
}
示例:
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
object :Iterator<LocalDate>{
var current = start
override fun hasNext() = current <= endInclusive
override fun next() = current.apply{
current = plusDays(1)
}
}
解构声明:
展开单个复合值,并使用它来初始化多个单独的变量。
说明:
自己声明的类:可以通过定义commponentX() 函数来返回解构的值 加上
数据类(data class)可以自动声明/定义componentN()函数
集合:标准库只允许使用该语法来返回一个对象的前5个元素
val (a,b)=p ---> val a = p.commponent1() val b = p.commponent2()
(_,a) :解构声明时如果不需要某个变量可以用下划线取代
示例:
fun splitFileName(fullName:String):List<String>{
return fullName.split('.')
}
val (a,b) = splitFileName("aa.text")
数据类(data class)
data class User(val name: String, val id: Int)
fun main(args: Array<String>) {
val u = User("lioil.win", 1)
// 传统用法
println("${u.name}, ${u.id}")// 输出: lioil.win, 1
// 解构声明
val (n, i) = u
println("$n, $i")// 输出: lioil.win, 1
// 直接调用componentN函数
println("${u.component1()}, ${u.component2()}")
}
类型转换
toXXX():基础类型的之间的转换 比如 String.toInt()
as,as? : 父类型转换成子类型
null检测机制
如果类型后面没有加?:表示该变量的值不可为空
//类型后面加?表示可为空
var age: String? = "23"
//age为null时,抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1
// as? 元素类型判断 如果类型不匹配则返回null
var flag = flag as? Int
==(equals)于 ===(地址)
==:本质上是equals函数(operator 做的运算符重载)可以通过重写equals方法修改比较逻辑
===:值相等,对象地址相等
fun main(args: Array<String>) {
val a: Int = 10000
println(a === a) // true,值相等,对象地址相等
//经过了装箱,创建了两个不同的对象
val boxedA: Int? = a
val anotherBoxedA: Int? = a
//虽然经过了装箱,但是值是相等的,都是10000
println(boxedA === anotherBoxedA) // false,值相等,对象地址不一样
println(boxedA == anotherBoxedA) // true,值相等
}
集合和数组:
参考文档:https://zhuanlan.zhihu.com/p/109380993
数组:
1. 用arr[index]的获取元素。
2. 用arr.component1() ... arr.component5()获取数组的前5个元素。同样适用与集合。
3. 用arr.reverse()反转元素。
//创建数组
val arr = arrayOf("1",2,3,4)
//arrayOfNulls<数据类型>(长度),默认值都是null 相当于int[] intArray = new int[6]
val fixedSizeArr = arrayOfNulls<Int>(6)
//闭包进行初始化
val arr = Array(3,{it -> it.inc()})//参数1:数组大小,参数2:一个函数参数的工厂函数
二维数组:
//基本类型的二维数组
val arr = Array(3){IntArray(3)}//三个长度为3的Int数组的二维数组
print(arr[1][1])
for (one in arr){
println()
for (two in one){
print(two)
}
}
集合:
只读集合:(并不是线程安全的)
继承至Collection<out E>接口
只实现isEmpty()、size属性、get()、contains()等方法
List、Set、Map
可变集合:
继承至MutableCollection<out E>接口(该接口继承Collection<out E>,所以有可读方法)
实现add()、remove()、clear()等方法
MutableList<E>、MutableSet<E>、MutableMap<K,V>
常用api:
分组:
appleList.also { println("Kotlin Stream:") }.groupBy(Apple::id, Apple::getMe)
List转Map:
appleList.also { println("Kotlin Stream:") }.associateBy { it.id }
过滤Filter
appleList.also { println("Kotlin Stream:") }.filter { it.name != "香蕉" }
求和sumByXXX
appleList.also { println("Kotlin Stream:") }.sumByDouble {it.getMoney()}
去重distinctBy
appleList.also { println("去重") }.distinctBy { it.id }
最大值和最小值
println("最大值:" + appleList.maxWith(kotlin.Comparator { o1, o2 -> if (o1.num!! > o2.num!!) 1 else -1 }))
println("最小值:" + appleList.minWith(kotlin.Comparator { o1, o2 -> if (o1.num!! > o2.num!!) 1 else -1 }))
集合的流式操作(有性能缺失慎用):
参考:https://blog.csdn.net/qq_40803115/article/details/109645409
集合创建函数
集合类型 | 只读 | 可变 |
---|---|---|
List | listOf | mutableListOf、arrayListOf |
Set | setOf | mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf |
Map | mapOf | mutableMapOf、hashMapOf、linkedMapOf、sortMapOf |
元组
Pair:二元组
Triple:三元组
Pair可以通过to函数快速创建
Pair(1,"a")---->1 to "a"
1.解构访问元组中的属性
val (i, j, k) = Triple(1, "a", 2.0)
val (a, b) = Pair(4, "5")
2.元组引用索引访问
val v = Triple(1, 2, 3)
println("${v.first},${v.second},${v.third}")
注意:二元组Pair可以用来创建Map集合
//Pair的类型可以不固定
val map = mapOf(Pair(1, "A"), Pair("2", "B"))
//注意:遍历时entry的类型是Entry
for (entry in map) {
println("${entry.key},${entry.value}")
println("${entry.key.javaClass},${entry.value.javaClass}")
}
//添加的Pair需要固定类型
val typeMap = mapOf<Int, String>(Pair(3, "c"), Pair(4, "d"))
编译器自动推断与智能转换
非空性
在代码前已经变量做非空判断后,后面的代码会自动将该类型转换成非空类型
示例:
var value: String? = null
value = "zhangsan"
if (value != null) {
println(value.length)
类型
用is判断了变量的类型后,后面的代码会自动推断变量的类型并进行转换。
示例:
var zhangsan: Zhangsan = Person("zhangsan")
if (zhangsan is Person) {
println(zhangsan.name)
}
流程可达性
比如if else都已经返回了,返回后面的流程将都不会执行。
控制语句
if
fun a():String?
if(true){
return "a"
}
表达式:
fun a(flag:Boolean):String?=if(flag) "a" else "b"
when
fun mix(c1: Color, c2: Color): Color? {
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> return ORANGE
setOf(YELLOW, BLUE) -> return GREEN
setOf(BLUE, VIOLET) -> return RED
else -> throw Exception("Dirty color")
}
}
表达式:
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
不带参数的when
when{
布尔表达式 -> {执行体}
布尔表达式 -> {执行体}
else -> {执行体}
}
for 和 while
for(a in list){
}
while 和java中一样
try,catch,finally
普通类型和java中一样
表达式:
fun readNumber(reader:BufferedReader) = try {
Integer.parseInt(reader.readLine())
}catch (e:NumberFormatException){
null
}
函数
(kotlin 默认的可见修饰符是public)
可见性修饰符 fun 函数名(参数名 :类型,...) : 返回值{}
fun basis(){
...
}
()圆括号必须存在,即使是没有参数的情况下。
{}大括号必须存在,即使是没有函数体的时候,不过在Kotlin中有一个特例就是,函数具备返回值的时候,如果只用一个表达式就可以完成这个函数,则可以使用单表达式函数。
在函数没有返回值时可以省略其返回值
默认参数
//代码块函数体
fun max(a:Int,b:Int):Int{
return a+b
}
//表达式函数体
去掉了括号和return语句
fun max(a:Int,b:Int):Int = if(a>b) a else b
fun whenFlag(flag: Int) =
when (flag) {
1 -> 10086
2 -> "bb"
3 -> fun(a: Int, b: Int) = { if (a > b) a else b }
else -> "aaaaa"
}
默认参数
在调用函数时不给对应的参数赋值时,会使用默认参数
fun defArgs(numA : Int = 1, numB : Float = 2f, numC : Boolean = false){
println("numA = $numA \t numB = $numB \t numC = $numC")
}
可变长参数
当一个函数中的参数数量不定且是同一类型,则可使用vararg修饰符去修饰这个变量,被vararg修饰的参数相当于一个固定类型的数组。
相比于java中可变长参数只能放参数的末尾,Kotlin中可变长参数可以放在任何位置
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
命名参数
即使用函数时,使用参数名 = 参数值这种方式传递参数 (可以提高代码的可读性,但是会降低代码的编写速度)
callFun("str",isTrue = true,numA = 3, numB = 3.0f, numC = 3)
顶级函数
将函数放在文件顶层,这个函数不属于任何类,如果需要从包外调用它,只需要导入这个包即可。(公共函数 相当于Java中的untils静态方法)
package hello.aa.cc.dd.KotlinLearn.chapter_1.hello
fun sayMessage(message:String){
println("hello ${message}")
}
import hello.aa.cc.dd.KotlinLearn.chapter_1.hello.sayMessage
fun main(args: Array<String>) {
sayMessage("hello world");
}
扩展函数
Kotlin的扩展函数是可以让你作为一个类成员进行调用的函数,但是又定义在这个类的外部。这样可以很方便的扩展一个已经存在的类,为它添加额外的方法。(扩展函数不会被子类重写,但是会被继承)
(个人认为这样写虽然方便了编码,但是会使代码的归类变的混乱了,自定义的类建议少用)
fun receiverType.functionName(params){
body
}
receiverType:表示函数的接收者,也就是函数扩展的对象
functionName:扩展函数的名称
params:扩展函数的参数,可以为NULL
局部函数
函数体内部定义函数
局部函数对外部是隐蔽的,局部函数只能在其封闭函数内有效,其封闭函数也可以返回局部函数,以便程序在其他作用域中使用局部函数
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)
}
}
中缀函数与中缀表达式
如果一个函数只有一个参数,且用infix修饰,那么这个函数就是中缀函数
infix fun <T> List<T>.n(other: List<T>): List<T> {
val result = ArrayList<T>()
forEach {
if (other.contains(it)) {
result.add(it)
}
}
return result
}
var result = list1.toList() n (list2.toList())
必须是成员函数或者扩展函数
必须只有一个参数
参数不能是可变参数或默认函数
库函数
apply
可以像构建者风格的Api创建和初始化任何对象
示例:
User("aaa",12).apply { message() }
with
允许你调用同一个对象的多个方法,而不需要写出这个对象的引用
val result = with(user) {
println("my name is $name, I am $age years old, my phone number is $phoneNum")
1000
}
let
类
class 类名 [可见性修饰符] [注解] [constructor] (Params){
...
}
示例:
class Person private @Inject constructor(name: String) { …… }
主构造函数
class Cat constructor(name: String){
...
}
//没有任何注解或者可见性修饰符,可以省略这个constructor关键字。
class Cat(name: String){
...
}
//主构造函数 是没有有函数体的 初始化可以用init
//init代码块可以有多个,执行顺序是按 从上至下
class Cat(name: String){
//初始化代码块
init {
// 在这里面做一些需要在主构造函数中做的初始化操作
println("第一个初始化代码块,name:$name")
}
}
次构造函数
- 可以有一个主构造函数和多个次构造函数
- 可以只有主构造函数或者只有次构造函数
- 主、次构造函数同时存在的时候,次构造函数必须直接或者间接地委托到主构造函数
- 没有声明主构造函数或者次构造函数时,会有一个默认的无参数主构造函数,方便创建对象,这与Java一样
- 如果不希望类有公有构造函数,那么请私有化一个无参数主构造函数
//初始化块中的代码实际上会成为主构造函数的一部分。所以init代码块会在次构造函数前执行
//如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
//次构造函数可以有多个
class Person(name: String){
var name: String = name
var gender: Int = 0
init {
println("这是初始化代码块...")
}
constructor(name: String, gender: Int) : this(name) {
println("这是次构造函数...")
this.name = name
this.gender = gender
}
}
数据类(data class)
数据类需满足以下要求:
- 主构造函数需要至少有一个参数;
- 主构造函数的所有参数需要标记为 val 或 var;
- 数据类不能是抽象、开放、密封或者内部的;
data class User(var name:String,var age:Int,var gender: Int,var avatar: String)
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
equals()/hashCode() ;
toString() 格式是 "User(name=John, age=42)";
componentN() 函数 按声明顺序对应于所有属性;
copy() 函数(深拷贝)
注意:数据类也可以在类体中声明属性,但是在类体中声明的属性不会出现在那些自动生成的函数中
枚举类
enum class Season(var enumName: String,var range: String){
Spring("春季","1-3"),
Summer("夏季","4-6"),
Fall("秋季","7-9"),
Winter("冬季","10-12");
fun printSeason(){
print("name:$enumName,range:$range")
}
}
ordinal 属性:枚举常量的顺序,从0开始
name属性: 枚举常量的名字
// 遍历
Direction.values().forEach {
println("value:${it.ordinal}")
}
// 获取"EAST"对应枚举常量,如果枚举类中没这个常量会抛异常
val direction = Direction.valueOf("EAST")
密封类(印章类)
密闭类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密闭类的一个子类可以有可包含状态的多个实例。
1、一个密闭类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员
2、密闭类不允许有非-private 构造函数(其构造函数默认为 private)
3、扩展密闭类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。
子类在内部
sealed class Expr{
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
或
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
示例:
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
fun main(args: Array<String>) {
val const = eval(Expr.Const(12.0))
val sum = eval(Expr.Sum(Expr.Const(10.0),Expr.Const(12.0)))
}
嵌套类
一个类可以嵌套在另一个类里面
嵌套类不能访问外部类的属性,它其实就相当于Java 中的静态内部类
class Outer{
val attr = 0
// 嵌套类
class Nested{
fun inMethod(){
// 不能访问外部类的属性
println("内部类")
}
}
}
内部类
内部类用inner关键字,声明,跟Java 一样,内部类持有一个外部类的对象引用,可以访问外部类的属性和方法。
class Outer{
private val attr = 10
inner class Inner{
fun method(){
println("内部类可以访问外部类属性:$attr")
}
}
}
fun main(args: Array<String>) {
// 调用内部类方法,看出区别了吗
Outer().Inner().method()
}
//嵌套类通过类直接调用(Java静态方法方式),内部类通过对象调用。
object
对象申明是定义单例的一种方式。
- object定义后即刻实例化。
- 一个对象申明可以包含属性、方法、初始化语句块。
- object不允许有构造函数。
- 定义在类内部的object并不能访问类的成员。
object OutObject {
val outParameter = "out"
fun outFunction(parameter:String) {
println("Out object function result: $parameter.")
}
}
class MyClass {
val classParameter = "class"
object InnerObject {
//val innerParameter = this.classParameter //error: 1,this refers to InnerObject 2,classParameter cannot be reached
val innerParameter = "inner"
fun innerFunction(parameter:String) {
println("Out object function result: $parameter.")
}
}
}
//匿名对象替代java中匿名内部类
window.addMouseListener(
object : MouseAdapter(){
override fun mouseClicked(e:MouseEvent){
//...
}
}
)
伴生对象(companion object)
在类中定义的对象之一可以使用一个特殊的关键字来标记:companion,这样做就获得了直接通过容器类名称来访问这个对象的方法和属性的能力(用来代替java中static)
class A{
//每个类中只有一个伴生对象,可以有名字也可以没有
companion object{
fun bar(){
println("Companion object called")
}
}
}
//可以通过类名的方式调用方法,类似于java中的静态方法和静态变量
A.bar()