目录
标准库函数
also
also函数和let函数功能相似,和let一样,also也是把接收者作为值参传给lambda,但是有一点不同,also返回接收者对象,而let返回lambda结果,所以also更适合针对同一原始对象,利用副作用做事,可以基于原始接收者对象执行额外的链式调用
fun main() {
var fileContest:List<String>
File("D:/workspace/studyproject/untitled/src/main/resources/test.txt")
.also {
println(it.name)
}.also {
fileContest = it.readLines()//一行一行读取,返回list集合
}
println(fileContest)
}
takeif
和其他标准函数有一点不一样,takeif函数需要判断lambda中提供的条件表达式,给出true或false结果,如果为true,则返回接收者对象,反之返回null,如果需要判断某个条件是否满足,再决定是否可以赋值变量或执行某项任务,takeif就非常有用,它类似于if语句,优势是可以直接在对象实例上调用,避免了临时变量赋值的麻烦
fun main() {
val result = File("D:/workspace/studyproject/
untitled/src/main/resources/test.txt")
.takeIf {
it.exists()&&it.canRead()
}?.readText()//读整个文件,返回字符串
println(result)
}
takeUnless
takeUnless与takeif完全相反,只有给定条件结果为false时才会返回原始接收对象
集合
和变量类型的var和val一样,kotlin中集合List,Set和Map的变量也分为两类,只读和可变
List
List的创建是通过listof()创建只读集合,mutableList()创建可变集合,可以增,删,改。只读与可变能够相互转化,通过.toList()和.toMutableList()
get()根据索引取值
getOrElse是一个安全索引取值函数,它需要两个参数,一个是索引值,第二个是能提供默认值的Lambda表达式,如果索引值不存在的话,返回Lambda表达式结果,可用来代替异常
getOrNull是kotlin提供的另一个安全索引函数,它返回null结果,而不是抛出异常
//list取值方式
fun main() {
var listOf = listOf("Jason", "Jack", "Rose")//不可变集合
var mutableListOf = mutableListOf("a", "b", "c")//可变集合
//增删改
mutableListOf.set(0,"c")
mutableListOf.add("d")
mutableListOf.remove("b")
//取值方式
println(mutableListOf[0])
println(listOf[1])
println(listOf.get(1))
println(listOf.getOrElse(3,{"抛出异常,索引不存在"}))
println(listOf.getOrNull(3))
//可变与不可变的相互转化
listOf.toMutableList()
mutableListOf.toList()
}
添加元素运算符,删除元素运算符(类似C++中的运算符重载)
mutableListOf += "王五"
println(mutableListOf)
mutableListOf -= "c" //只删去一个c
println(mutableListOf)
//基于lambda表达式指定的条件删除元素
mutableListOf.removeIf { it.contains("c") }
List集合的遍历
//集合的遍历
var nameList = mutableListOf("孙伟", "张帅", "李磊",
"赵瑜", "刘静", "夏天", "元宝")
//for-in
for (name in nameList) {
println(name)
}
//forEach
nameList.forEach{
println(it)
}
//forEachIndexed 遍历时要获取索引
nameList.forEachIndexed { index, name ->
println("$index : $name")
}
解构语法过滤元素
list集合可以一次性为多个元素赋值,如果想只为特定位置的元素赋值的话,需要把不需要赋值的元素用下划线 '_' 替换,例
// _ 解构语法过滤元素
var(a,b,_,d,e)=nameList
println("$a $b $d $e")
java源码对照:可以看到将2号下标跳过赋值
Set
与Java一样不允许有重复元素,并且无序,通过setof创建不可变,mutableSetOf创建可变,elementAt获取,安全获取与List相似。
var set = setOf("a", "a", "C") |
List也可以通过toSet()转换成Set,做去重操作。kotlin中还提供distinct()函数做去重
println(listOf("java","java","python").distinct()) |
Map
Map集合创建时会用到一个 to,像个关键字,事实上它是个省略了点号和参数的特殊函数,to函数将他左边和右边的值转化成一对 Pair,也可使用Pair(Key , Value)的方式创建
//kotlin中Map集合的创建
var map1 = mapOf(1 to "李磊", 2 to "赵刚", 3 to "彭于晏")
var map2 = mapOf(Pair(1, "Java"), Pair(1, "VUE"),Pair(2,"Python"),Pair(3,"C++"))
读取Map的值
println(map1[2])//[]取值运算符,读取对应键值对的值,如果键不存在就返回null
println(map1.get(5))//读取对应键值对的值,如果键不存在就返回null
println(map1.getValue(1))//读取对应键值对的值,如果键不存在就抛出异常
println(map1.getOrElse(5,{ 1>=6 }))//不存在就返回匿名函数结果
println(map1.getOrDefault(5,"默认值"))//没有就返回默认值
遍历Map
for (i in map2){
println(i)
}
map2.forEach {
println("${it.key}=${it.value}")
}
map2.forEach { (t, u) ->
println("$t=$u")
}
可变Map
fun main() {
//mutableMapOf 创建可变Map集合
var mutableMapOf = mutableMapOf(1 to "李磊", 2 to "赵刚", 3 to "彭于晏")
//添加
mutableMapOf += 4 to "潘安"
mutableMapOf[5] = "武松"
mutableMapOf.put(6,"西门")
println(mutableMapOf)
}
Field
针对类中定义的每一个属性,Kotlin都会产生一个field,一个getter和一个setter,field用来储存属性数据,你不能直接定义field,kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用
尽管kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,还是应手动自定义他们
var aaa = "aaa"
get() = field.uppercase()//将属性值转大写返回
set(value) {
field =value.trim()//将参数值格式化后赋给属性
}
注意:kotlin中临时变量名用下划线开头 如: class Player( _name :String ){ var name = _name } |
初始化顺序
- 主构造函数里声明的属性
- 类级别的属性赋值和init初始化块里的属性赋值/函数调用是同级别的,按从上之下执行
- 次构造函数里的属性赋值和函数调用
验证
class InitOrder(_name:String,val age:Int) {
//类级属性,age属于主构造函数里声明的属性
var name = _name
var score:Int = 10
private val hobby = "music"
val subject : String
init {
println("==========初始化块执行了========")
subject = "math"
}
constructor(name:String):this(name,10){
score = 20
}
override fun toString(): String {
return "InitOrder(age=$age, name='$name', score=$score, hobby='$hobby', subject='$subject')"
}
}
Java源码对照:可以看到init块是构造对象时运行,并且在次级构造之前,主构造和类级属性赋值之后执行
延迟初始化
指属性在用到时才被初始化化,kotlin中使用lateinit关键字相当于做了一个约定;在用它之前负责初始化,如果lateinit修饰的变量在未被初始化前使用,会抛出异常
class LateInitTest {
lateinit var equipment:String
fun ready(){
equipment = "AK-47"
}
fun battle(){
println(equipment)
}
}
fun main(){
var lateProperty = LateInitTest()
//下面代码打开就不会抛异常,而会输出AK-47
//lateProperty.ready()
lateProperty.battle()
}
只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查,如下buttle()中代码替换
fun battle(){
if (::equipment.isInitialized){
println(equipment)
}
}
惰性初始化
使用lateinit定义的属性在调用之前还是需要手动初始化(如:调用ready()),而惰性初始化是在声明变量时通过 by lazy 定义好初始化函数,只要用到这个属性就调用这个函数,不需要手动调用函数进行初始化,但是此属性变量只能设置val
class ByLazyTest(_name:String) {
var name = _name
val config by lazy {
println("loading...........")
}
}
fun main() {
var p = ByLazyTest("王五")
p.config
}
类型检测和转换
kotlin中is运算符可以用来检测某个对象的类型,通过as关键字进行类型转换
var 元宝 :Animal = Dog("旺旺","狗","元宝","公")
if (元宝 is Dog){//判断元宝是不是Dog类型
元宝 as Dog //如果是就进行转换
}
const关键字
在kotlin语法中,修饰符var用来修饰可变变量,val修饰只读变量
但是Kotlin同时又提供了一个const修饰符。kotlin中const只能用在顶级属性,以及object对象的属性中(伴随对象也是obejct)。它只能修饰val,在开发过程中,如果我们在伴生对象中定义了一个val类型的变量,那么Android Studio会智能的提示开发者需要使用const来修饰该变量。
const和val区别
- const val 可见性为public final static,可以直接访问。val 可见性为private final static,并且val 会生成方法getNormalObject(),通过方法调用访问。
- 当定义常量时,出于效率考虑,我们应该使用const val方式,避免频繁函数调用。
Object关键字
使用Object关键字,你可以定义一个只能产生一个实例的类——单例,类名又是对象名,可以直接调用方法
object SingletonTest {
init {
println("ApplicationConfig Loading......")
}
fun doSomeThing(){
println("Object Test")
}
}
fun main() {
SingletonTest.doSomeThing()//SingletonTest即是类名又是对象名,可以直接调用方法
}
对象表达式
有时候你不一定非要定义一个新的命名类不可,也许只需要某个现有类的一种变体实例,但只需要用一次就行了,事实上,对于这种用完就丢的类实例,命名也可以省略,这个对象表达式是XX的子类,这个匿名类依然遵循object关键字的一个规则,即一旦实例化,该匿名类只能有唯一一个实例存在
open class father{
open fun load() = "loading someThing"
}
fun main() {
var p = object : father(){
override fun load(): String {
return "子类重写load()"
}
}
println(p.load())
}
伴生对象
如果想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用companion修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象,类似静态static
class SingletonTest03 {
companion object{
private const val PATH = "xxxxxx"
fun load() = File(PATH).readBytes()
}
}
fun main(){
//伴生对象函数可以通过类名直接调用
SingletonTest03.load()
}
嵌套类
如果一个类只对另一个类有用,那么将其嵌入到该类中并使这两个类保持在一起
class InnerClassTest {
class Equipment(var name:String){
fun show(){
println(name)
}
}
}
fun main() {
//kotlin中可以直接通过类名.内部类名调用,JAVA中实现这种操作的前提是内部类为静态
InnerClassTest.Equipment("knife").show()
}
数据类
数据类提供了个性化的toString()并且重写了Hashcode()和equals()方法
kotlin中 == 比较内容默认通过equals()比较,===比较引用,但是Any超累的equals()默认实现是用===比较,所以一个类不重写equals时使用==默认还是===比较 |
Copy函数
格式:对象名.copy(构造参数)
如果有属性在次构造函数中赋值,那么copy后的对象这个属性就不会赋值,因为copy时不执行次构造函数,即使用次构造函数copy |
解构声明
解构声明的后台实现就是声明component1,component2等若干个组件函数,让每个函数负责管理你想返回的一个属性数据,如果你定义一个数据类,它会自动为所有定义在主构造函数的属性添加对应的组件函数
class Student(val name:String,val sex:String,val age:Int) {
//从1开始计数
operator fun component1()= name
operator fun component2()= sex
operator fun component3()= age
}
fun main() {
val (x,y,_) = Student("张三","男",1)
}
运算符重载
如果要将内置运算符应用在自定义类身上,必须重写运算符函数,该函数使用 operator 修饰符,告诉编译器该如何操作自定义类
class OperatorTest (var num : Int){
//重写plus加法运算符
operator fun plus(other: OperatorTest): Int {
return this.num + other.num
}
}
fun main() {
var one1 = OperatorTest(1)
var one2 = OperatorTest(1)
println(one1+one2)//调用重写后的plus()
}
函数汇总:
枚举类
在kotlin中定义枚举需要在class前加enum关键字修饰,kotlin中枚举类可以加构造函数和普通函数
enum class EnumTest (private val coordinate:Coordinate){
//每一个方向都是实例对象
EAST(Coordinate(2,0)),
WEST(Coordinate(4,0)),
NORTH(Coordinate(0,8)),
SOUTH(Coordinate(0,10));
//枚举类定义函数 传参坐标与实例坐标相加
fun updataCoordinate(playerCorrdinate:Coordinate)=
Coordinate((playerCorrdinate.x+coordinate.x),(playerCorrdinate.y+coordinate.y))
}
fun main() {
println(EnumTest.EAST is EnumTest) //判断是不是实例
println(EnumTest.NORTH.updataCoordinate(Coordinate(1,1)))
}
代数数据类型(ADT)
指可以用来表示一组子类型的闭集,枚举类就是一种简单的ADT
enum class EnumTest02 {
UNQUALIFIED,
LEARNING,
QUALIFIED
}
class Driver(var status:EnumTest02){
fun checkLicense():String{
return when(status){
EnumTest02.UNQUALIFIED -> "没资格"
EnumTest02.LEARNING -> "在学"
EnumTest02.QUALIFIED -> "有资格"
}
}
}
fun main() {
println(Driver(EnumTest02.QUALIFIED).checkLicense())
}
密封类
对于更复杂的ADT,可以使用Kotlin的密封类(sealed class),来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活的控制某个子类型。密封类可以有若干个子类,要继承密封类,这些子类必须和他定义在同一个文件里
sealed class SealClass {
//下面两个状态类无属性,设置为单例
object UnQualified : SealClass()
object Learning : SealClass()
class Qualified(val id : String) : SealClass()
}
class Driver2(var status:SealClass){
fun checkLicense():String{
return when(status){
is SealClass.UnQualified -> "没资格"
is SealClass.Learning -> "在学"
is SealClass.Qualified-> "有资格,id编号为:
${(this.status as SealClass.Qualified).id}"
}
}
}
fun main() {
var driver2 = Driver2(SealClass.Qualified("123456"))
println(driver2.checkLicense())
}
接口
kotlin中规定所有重写的接口属性和函数实现都要使用override关键字,接口中定义的函数并不需要open关键字修饰,他们默认就是open
interface InterfaceTest {
var maxSpeed:Int
var brand :String
fun move():String
}
class Car(override var maxSpeed: Int, override var brand: String):InterfaceTest {
override fun move(): String {
return "这台${brand}的速度是:${maxSpeed}公里/h"
}
}
fun main() {
println(Car(20,"奔驰").move())
}
kotlin中的接口属性或者方法可以有默认实现
interface InterfaceTest {
//kotlin可以在接口里提供默认属性的getter方法和函数实现
val maxSpeed
get() = (0..500).shuffled().last()
var brand :String
fun move():String{
return "接口默认实现"
}
}
class Car():InterfaceTest {
override var brand: String
get() = TODO("Not yet implemented")
set(value) {}
}
fun main() {
var car :InterfaceTest = Car()
println("${car.maxSpeed}>>>>${car.move()}")
}
抽象类
kotlin中抽象类概念和Java中没有区别,只是定义时在class前加abstract修饰,重写抽象函数要在前加override
abstract class AbstractTest(var range:Int) {
abstract fun trigger():String
}
class AK47(_price:Int):AbstractTest(100){
var price= _price
//重写抽象类方法
override fun trigger(): String {
return "${price}"
}
}
- 在Kotlin中的抽象类在顶层定义的时候只能使用public可见性修饰符修饰抽象类不能直接被实例化
- 抽象类中可以定义内部抽象类
- 抽象类可以继承自一个继承类,即抽象类可以作为子类。不过,抽象类建议不用open修饰符修饰,因为可以覆写抽象类的父类的函数
- 若要实现抽象类的实例化,需要依靠子类采用向上转型的方式处理
泛型
定义泛型类
泛型类的构造函数可以接受任何类型,泛型参数常用字母T表示(代表英文type)
class Animal<T>(item:T) {
var subject:T =item
override fun toString(): String {
return "${subject.toString()}"
}
}
class Dog(var name:String,var age:Int){
override fun toString(): String {
return "Dog(name='$name', age=$age)"
}
}
class Cat(var weight:Int){
override fun toString(): String {
return "Cat(weight=$weight)"
}
}
fun main() {
println(Animal<Dog>(Dog("旺财",12)))
println(Animal<Cat>(Cat(12)))
}
泛型函数
class MagicBox<T>(item:T) {
var available = false
var subject = item
fun fatch():T?{//返回可空的T类型
return subject.takeIf { available } //takeif标准函数true返回接收对象
}
}
class Boy(var name:String,var age:Int)
fun main{
var magicBox = MagicBox<Boy>(Boy("小米", 15))
magicBox.available = true
println(magicBox.fatch() ?.run {
"you find ${name}"
})
}
多泛型参数
class MagicBox<T>(item:T) {
var subject = item
fun <R> fatch(subjectFun:(T)-> R):R?{
return subjectFun(subject) //调用匿名函数并将subject传入
}
}
class Boy(var name:String,var age:Int)
class Man(var name: String,var age: Int){
override fun toString(): String {
return "Man(name='$name', age=$age)"
}
}
fun main() {
var man =MagicBox<Boy>(Boy("李磊",15)).fatch {
Man(it.name,it.age+15)
}
println(man)
}
泛型类型约束
用 T:父类 来约束,表示泛型T只能传入指定父类的子类
//约束泛型类 MagicBox
class MagicBox<T:Human>(item:T) {
}
//指定父类 Human
open class Human(val age: Int)
//可传入的子类 Boy Man
class Boy(var name:String,age:Int):Human(age)
class Man(var name: String,age:Int):Human(age){
override fun toString(): String {
return "Man(name='$name', age=$age)"
}
}
关键字补充(vararg,infix,inline)
Vararg
修饰形参,表明该参数是个数可变的形参。类似Java中 ...
//可变长参数函数
//函数的变长参数可以用 vararg 关键字进行标识:
fun vars(vararg v:Int){
var arr = v //v为Array数组类型
for(vt in v){
print(vt)
}
}
Infix
标有infix关键字的函数可以使用中缀表示法(忽略该调用的点与圆括号)调用,中缀函数必须满足以下要求:
- 它们必须是成员函数和扩展函数
- 它们必须只有一个参数
- 其参数不得接受可变数量的参数,而且不能有默认值
infix fun Int.plus(x: Int): Int =
this.plus(x)
// 可以中缀表示法调用函数
1 plus 2 //until 和 to就是中缀表示法的一种
// 等同于这样
1.plus(2)
inline
当调用一个inline function的时候,编译器不会产生一次函数调用,而是会在每次调用时,将inline function中的代码直接嵌入到调用处。(正常的是利用引用去寻找我们要调用的函数)
inline 在一般的方法是标注,是不会起到很大作用的,inline 能带来的性能提升,往往是在参数是 lambda 的函数上。
fun meth(){
println("aaaa")
meth1()
}
inline fun meth1(){
println("bbbb")
}
//编译之后会变成
fun meth(){
println("aaaa")
println("bbbb")
}