kotlin诞生
2011年,JetBrains宣布开发Kotlin编程语言,这门新语言可以在Java虚拟机上运行的代码,kotlin语法简介,具备现代高级语言特性,并与Java遗留代码无缝互操作
kotlin与jvm
Java Kotlin Scala Groovy 被编译成字节码 运行在 JVM 通过指令 在操作操作系统上 windows linux macOS
kotlin是编译型语言
kotlin的跨平台特性
Kotlin不仅支持编写代码在虚拟机上运行,而且还是一门跨平台的通用型语言,我们可以用Kotlin开发各种类型的原生应用,如Android macOS Windows Javascript应用
Kotlin能脱离虚拟机层,直接编译成可以在windows linux macos平台上运行的原生二进制代
码、
声明变量和内置数据类型
var max : Int = 5
var : 变量定义关键字
max 变量名
Int 类型定义
= 赋值运算符
5 赋值
fun main{
var str : String = "Hello World"
println(str)
}
kotlin内置数据类型
类型 | 描述 | 示例 |
---|---|---|
String | 字符串 | “Hello World” |
Char | 单字符 | 'A' |
Boolean | true / false | true false |
Double | 小数 | 3.14 |
List | 元素集合 | 1,8,10 "a","b","c","a" |
Set | 无重复元素集合 | "a","b","c" |
Map | 键值对集合 | "small" to 5 , "medium " to 8 , "large" to 9 |
Int | 整数 | 5 |
只读变量
声明可修改变量 使用var关键字
声明只读变量 使用val关键字
fun main(args : Array<String>){
val name : String = "jack"
var age : Int = 10
age += 1
println(name)
}
类型推断
类型推断:对于已声明并赋值的变量 ,它允许你省略类型定义
Remove explicit type specification
编译时常量
只读变量并非绝对只读。
编译时常量只能在函数之外定义,因为编译时常量必须在编译时赋值,而函数都是在运行时才调用,函数内的变量也是在运行时赋值,编译时常量要在这些变量赋值前就已存在。
编译时常量只能是常见的基本数据类型:String、Int、Double、Float、Long、Short、Byte、Char、Boolean
查看kotlin字节码
查看kotlin编译之后的字节码
两种方式:
1.Shift 键两次,输入Show kotlin
2.Tools -> Kotlin -> Show Kotlin Bytecode
kotlin的引用类型于基本数据类型
Java有两种数据类型:引用类型与基础数据类型
Kotlin只提供引用类型这一种数据类型,但出于更高性能的需要,Kotlin编译器会在Java字节码中改为基本数据类型
表达式
if / else 表达式
range表达式
in A..B,in 关键字用来检查某个值是否在指定范围之内
when表达式
允许你编写条件式 ,在某个条件满足时,执行对应的代码
只要代码包含else if分支,都建议改为when表达式
fun main() {
val age = 3
if(age in 0..3){
println("婴幼儿")
}else if(age in 3..12){
println("少儿")
}else{
println("未知")
}
if(age !in 1..3){
}
val school = "0小学"
val level = when(school){
"学前班" -> "幼儿"
"小学" -> "少儿"
"中学" -> "青少年"
else -> {
println("未知")
}
}
println(level)
}
String模板
模板支持在字符串的引号内放入变量值
还支持字符串里计算表达式的值并插入结果,添加在${}中的任何表达式,都会作为字符串的一部分求值
fun main() {
val origin = "Jack"
val dest = "Rose"
println("$origin love $dest")
val flag = false
println("Answer is: ${if(flag) "我可以" else "对不起"}")
}
函数头
private fun doSomething(age : Int , flag : Boolean) : String
private 可见性修饰符
fun 函数声明关键字
doSomething 函数名
flag 函数参数
String 返回类型
fun main() {
println(fix(10,"Rose"))
}
private fun doSomething(age:Int = 2, flag:Boolean):String{
return "result"
}
fun fix(name:String, age:Int = 2){
println(name + age)
}
函数参数默认值参数
默认值参
如果不打算传入值参 ,可以预先给参数指定默认值
具名函数参数
如果使用命名值参,就可以不用管值参的顺序
fun main() {
//使用具名函数参数 也就是指定参数指定名字 键值对形式
println(fix(age=10,name="Rose"))
}
//默认值
private fun doSomething(age:Int = 2, flag:Boolean):String{
return "result"
}
fun fix(name:String, age:Int = 2){
println(name + age)
}
Unit函数
Kotlin没有返回值的函数叫Unit函数 返回类型是Unit 有点像Java的void
fun main() {
println(fix(age=10,name="Rose"))
}
fun fix(name:String, age:Int = 2){
println(name + age)
}
输出 Rose10
kotlin.Unit 没有返回参数类型
Nothing函数
也是没有返回值类型
TODO函数的任务就是抛出异常,就是永远别指望它运行成功,返回Nothing类型
fun main() {
//使用具名函数参数 也就是指定参数指定名字 键值对形式
println(fix(age=10,name="Rose"))
TODO("nothing")
println("after nothing.")
}
//默认值
private fun doSomething(age:Int = 2, flag:Boolean):String{
return "result"
}
fun fix(name:String, age:Int = 2){
println(name + age)
}
反引号中的函数名
Kotlin可以使用空格和特殊字符对函数命名,不过函数名要用一对反引号括起来。
为了支持Kotlin和Java互操作,而Kotlin和Java各自却有着不同的保留字关键字,不能作为函数名,使用反引号括住函数名就能避免冲突。
fun main() {
MyJava.`is`()
}
fun `**~special function with weird name~**`(){
}
public class MyJava {
public static void is(){
System.out.println("is invoked");
}
}
匿名函数
定义时不取名字的函数,我们称匿名函数,匿名函数通常整体传递给其他函数,或者从其他函数返回。
匿名函数对Kotlin来说很重要,有了它,我们能够根据需要定制特殊规则,轻松定制标准库里的内置函数
fun main() {
val total = "Mississippi".count()
val totalS = "Mississippi".count{it == 's'}
println(total)
println(totalS)
//变量的类型是一个匿名函数
/*val blessingFunction:()->String = {
val holiday = "New Year."
"Happy $holiday"
}*/
/*val blessingFunction:(String) -> String = { name ->
val holiday = "New Year."
"$name, Happy $holiday"
}*/
/*val blessingFunction:(String) -> String = {
val holiday = "New Year."
"$it, Happy $holiday"
}*/
/*val blessingFunction = {
val holiday = "New Year."
"Happy $holiday"
}*/
/*val blessingFunction:(String,Int) -> String = {name, year ->
val holiday = "New Year."
"$name, Happy $holiday $year"
}*/
val blessingFunction = {name:String, year:Int ->
val holiday = "New Year."
"$name, Happy $holiday $year"
}
println(blessingFunction("Jack",2027))
}
函数类型与隐式返回
匿名函数也有类型,匿名函数可以当作变量赋值给函数类型变量,就像其他变量一样,匿名函数就可以在diamagnetic里传递了。变量由类型,变量可以等于函数,函数也会有类型,函数的类型,由传入的参数和返回值类型决定。
和具名函数不一样,除了极少数情况外,匿名函数不需要return关键字来返回数据,匿名函数会隐式或自动返回函数体最后一行语句的结果。
val blessingFunction:(String) -> String = {
val holiday = "New Year."
"$it, Happy $holiday"
}
val blessingFunction = {
val holiday = "New Year."
"Happy $holiday"
}
val blessingFunction:(String,Int) -> String = {name, year ->
val holiday = "New Year."
"$name, Happy $holiday $year"
}
函数参数
和具名函数一样,匿名函数可以不带参数,也可以带一个或多个任何类型的参数,需要带参数时,参数的类型放在匿名函数类型定义中,参数名则放在函数定义中
val blessingFunction:(String) - > String = {name ->
val holiday = "New Year"
"$name , Happy $holiday"
}
println(blessingFunction("Jack"))
it关键字
定义只要一个参数的匿名函数时,可以使用it关键字来表示参数名。当你需要传入两个值参,it关键字就不能用了
val blessingFunction:(String) -> String = {
val holiday = "New Year"
"$it , Happy $holiday"
}
println(blessingFunction("Jack"))
匿名函数的类型推断
定义一个变量时,如果已把匿名函数作为变量赋值给它,就不需要显示指明变量类型了
val blessing function {
val holiday = "New Year"
"Happy $holiday"
}
类型推断也支持带参数的匿名函数,但为了帮助编译器更准确地推断变量类型,匿名函数的参数名和参数类型必须有
val blessingFunction = {name :Stirng, year: Int ->
val holiday = "New Year"
"$name , Happy $holiday $year"
}
lambda
我们将匿名函数称为lambda ,将它的定义称为lambda表达式,它返回的数据称为lambda
定义参数式函数的函数
函数的参数是另外一个函数
fun main() {
val getDiscountWords = {goodsName:String, hour:Int ->
val currentYear = 2027
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
showOnBoard("卫生纸",getDiscountWords)
}
//具名函数
private fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
val hour = (1..24).shuffled().last()
println(getDiscountWords(goodsName,hour))
}
const val MAX = 500
简略写法
如果一个函数的lambda参数排在最后,或者是唯一的参数,那么括住lamdba值参的一堆圆括号就可以省略
val totalS = "Mississipi".count({it = 's'})
val totalS = "Mississipi".count{it = 's'}
函数内联 inline
在JVM上,你定义的lambda会以对象实例的形式存在,JVM会为所有同lambda打交道的变量分配内存,就会产生内存开销。kotlin有一种优化机制叫内联,有了内联,JVM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体复制粘贴到哪里
使用lambda的递归函数无法内联,因为会导致复制粘贴无线循环,编译会发出警告
fun main() {
val getDiscountWords = {goodsName:String, hour:Int ->
val currentYear = 2027
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
showOnBoard("卫生纸",getDiscountWords)
}
//具名函数
private inline fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
val hour = (1..24).shuffled().last()
println(getDiscountWords(goodsName,hour))
}
函数引用
要把函数作为参数传给其他函数使用,除了传lambda表达式,kotlin还提供了其他方法,传递函数引用,函数引用可以把一个具名函数转换成一个值参,使用lambda表达式的地方,都可以使用函数引用
fun main() {
showOnBoard("牙膏",::getDiscountWords)
}
private fun getDiscountWords(goodsName: String,hour: Int):String{
val currentYear = 2027
return "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
private fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
val hour = (1..24).shuffled().last()
println(getDiscountWords(goodsName,hour))
}
函数类型作为返回类型
函数类型也是有效的返回类型,也就是说可以定义一个能返回函数的函数
fun main() {
val getDiscountWords = configDiscountWords()
println(getDiscountWords("沐浴露"))
}
fun configDiscountWords(): (String) -> String{
val currentYear = 2027
val hour = (1..24).shuffled().last()
return {goodsName: String ->
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
}
闭包
在Kotlin中,匿名函数能修改并应用定义在自己的作用域之外的变量,匿名函数引用者定义自身的函数里的变量,Kotlin中的lambda就是闭包
能接收函数或者返回函数的函数又叫做高级函数,高级函数广泛应用于函数式编程当中
简单理解:在同一个包里两个不同类 定义两个相同名字的参数 就会报错
lambda与匿名函数内部类
函数类型能让开发者少写模式化代码,写出更灵活的代码。Java8支持面向对象编程和lambda表达式,但不支持将函数作为参数传给另一个函数或变量,不过Java的替代方案式匿名内部类
public class JavaAnonymousClass {
public static void main(String[] args) {
showOnBoard("牙膏", new DiscountWords() {
@Override
public String getDiscountWords(String goodsName, int hour) {
int currentYear = 2017;
return String.format(String.format("%d年,双11%s促销倒计时:%d 小时",currentYear,goodsName,hour));
}
});
}
public interface DiscountWords{
String getDiscountWords(String goodsName,int hour);
}
public static void showOnBoard(String goodsName,DiscountWords discountWords){
int hour = new Random().nextInt(24);
System.out.println(discountWords.getDiscountWords(goodsName,hour));
}
}
Kotlin的可空性
对于null值问题,Kotlin反其道而行之,除非另有规定,变量不可为null值,这样一来,运行时崩溃从根源上得到解决
fun main(){
val str = "butterfly"
str = null //这里报红
pringln(str)
}
为了避免NullPointerException,Kotlin的做法时不让我们给非空类型变量赋null值,但null在Kotlin中依然存在 加个问号就是可空类型
fun main() {
var str:String? = ""
str = null
println("input:$str")
}
安全调用操作符 null安全
Kotlin区分可空类型和非可空类型,所有,你要一个可空类型变量运行,而它又可以不存在,对于这种潜在危险,编译器时刻警惕着,为了应对这种风险,Kotlin不循序你在可空类型值上面调用函数,除非你主动接手安全管理
1.安全调用操作符
这次kotlin不报错了,编译器看到有安全调用操作符,所以它知道如何检查null值,如果遇到null值,他就跳过函数调用,而不是返回null ?.就是安全调用符
fun main() {
var str:String? = "butterfly"
str = str?.capitalize()?.plus(" is great.")
}
使用带let的安全调用
安全调用允许在可空类型上调用函数,但是如果还是想做点额外的事 ,比如创新新值,或者判断不为null就调用其他函数,怎么办?可以使用带let函数的安全调用操作符,你可以在任何类型上调用let函数,它的主要作用是让你在指定的作用域内定义一个或多个变量
fun main() {
var str:String? = "butterfly"
str = null
str = str?.let {
//非空白的字符串
if(it.isNotBlank()){
it.capitalize()
}else{
"butterfly"
}
}
println(str)
}
非空断言操作符
2.使用非空断言操作符
!!.又称感叹号操作符,当变量为null时,会抛出KotlinNullPointerException
fun main() {
var str:String? = "butterfly"
str = null
println(str!!.capitalize())
}
对比使用if判断null值情况
3.我们也可以使用if判断,但是相比之下安全调用操作符用起来更灵活,代码也更简介
我们可以使用安全操作符进行多个函数的链式调用
fun main() {
var str:String? = "butterfly"
str = null
if(str != null){
str = str.capitalize()
}else{
println("为null.")
}
str = str?.capitalize().plus(" is great.") 链式 前面为空 后面都不执行
println(str)
}
空合并操作符
?: 操作符的意思是 如果左边的求值结果为null 就使用右边的结果值
val strWithSafe:String = str ?: "butterfly"
空合并操作符也可以和let函数一起使用来代替if/else语句
fun main(){
var str : String? = readLine()
str = str?.let{it.capitalize() }?:"butterfly"
println(str)
}
异常处理与自定义异常
fun main() {
var number: Int? = null
try {
checkOperation(number)
number!!.plus(1)
}catch (e: Exception){
println(e)
}
}
fun checkOperation(number: Int?){
//number ?: throw UnskilledException()
checkNotNull(number, {"Something is not good."})
}
//自定义异常
class UnskilledException() : IllegalArgumentException("操作不当")
先决条件函数
Kotlin标准库提供了一些便利函数,使用这些内置函数,你可以抛出带自定义信息的异常,这些遍历函数叫做先决条件函数。您可以用它定义先决条件,条件必须满足,目标代码才能执行
函数 | 描述 |
chechNotNull | 如果参数为null,则抛出IIIegalStateException异常,否则返回非null值 |
require | 如果参数为false,则抛出IIIegaIArgumentException异常 |
requireNotNull | 如果参数为null,则抛出IIIegaIStateException异常,否则返回非null值 |
error | 如果参数为null ,则抛出IIIegaIStateException异常并输出错误信息,否则返回非null值 |
assert | 如果参数为false,则抛出AssertError异常,并打上断言编译器标记 |
fun checkOperation(number: Int?){
//number ?: throw UnskilledException()
checkNotNull(number, {"Something is not good."})
}
substring
字符串截取,substring 函数支持IntRange函数(表示一个整型范围的类型)的参数,until创建的范围不包括上限值
const val NAME = "Jimmy's friend"
fun main() {
val index = NAME.indexOf('\'')
//val str = NAME.substring(0, index)
var str = NAME.substring(0 until index)
println(str)
}
split
split函数返回的是List集合数据,List集合又支持解构语法特性,它允许你在一个表达式里多个变量赋值,解构常量来简化变量的赋值
const val NAMES = "jack,jacky,jason"
fun main() {
val data = NAMES.split(",")
//data[0]
val (origin,dest,proxy) = NAMES.split(",")
println("$origin $dest $proxy")
}
replace
字符串替换
fun main() {
//加密替换一个字符串
val str1 = "The people's Republic of China."
//第一个参数式正则表达式,用来决定要替换哪些字符
//第二个参数式匿名函数,用来确定该如何替换正则表达式搜索到的字符
val str2 = str1.replace(Regex("[aeiou]")){
when(it.value){
"a" -> "8"
"e" -> "6"
"i" -> "9"
"o" -> "1"
"u" -> "3"
else -> it.value
}
}
println(str1)
println(str2)
}
== 与 ===比较
在Kotlin中,用==检查两个字符串中的字符是否匹配,用===检查两个变量是否指向内存堆上同一对象,而在Java中==做引用比较,做结构比较时用equals方法
fun main() {
val str1 = "Jason"
val str2 = "jason".capitalize()
println(str1 == str2)
println(str1 === str2) //true 1 false 2
}
字符串遍历
"The people's Republic of China.".forEach {
print("$it *")
}
数字类型的安全转换函数
Kotlin提供了toDoubleOrNull和toIntOrNull这样的安全转换函数,如果数值不能正确转换,与其触发异常不如干脆返回null值
fun main() {
//val number1: Int = "8.98".toInt()
val number1: Int? = "8.98".toIntOrNull()
println(number1)
}
Double转Int与类型格式化
精度损失与四舍五入
fun main() {
println(8.956756.toInt())
println(8.956756.roundToInt())
val s = "%.2f".format(8.956756)
println(s)
}
apply
apply函数可看作一个配置函数,你可以传入一个接收者,然后调用一些列函数来配置以便使用,如果提供lambda给apply函数去执行,它会返回配置好的接收者
fun main() {
val file1 = File("E://i have a dream_copy.txt")
file1.setReadable(true)
file1.setWritable(true)
file1.setExecutable(false)
val file2 = File("E://i have a dream_copy.txt").apply {
setReadable(true)
setWritable(true)
setExecutable(false)
}
}
可以看到,调用一个个函数类配置接收者时没变量名就省掉了,这是因为,在lambda表达式里,apply能让每个配置函数都作用于接收者,这种行为有时又叫做相关作用域,因为lambda表达式里的所有函数调用都针对接收者,或者说,他们是针对接收者的隐式调用
let
let函数能使某个变量作用于其lambda表达式里,让it关键字能引用它,let与apply比较,let会把接收者传给lambda,而apply什么都不传,匿名函数执行完,apply会返回当前接收者,而let会返回lambda的最后一行
fun main() {
val result = listOf(3, 2, 1).first().let {
it * it
}
println(result)
//val firstElement = listOf(3, 2, 1).first()
//val result = firstElement * firstElement
println(formatGreeting("Jack"))
}
fun formatGreeting(guestName: String?): String {
return guestName?.let {
"Welcome, $it."
} ?: "What's your name?"
}
fun formatGreeting2(guestName: String?): String {
return if(guestName != null){
"Welcome, $guestName."
}else{
"What's your name?"
}
}
run
光看作用域行为,run和apply差不多,但与apply不同,run函数不返回接收者,run返回的是lambda结果,也就是true或者false
fun main() {
var file = File("E://i have a dream_copy.txt")
val result = file.run {
readText().contains("great")
}
println(result)
val result2 = "The people's Republic of China.".run(::isLong)
println(result2)
"The people's Republic of China."
.run (::isLong)
.run (::showMessage)
.run (::println)
}
fun isLong(name: String) = name.length >= 10
fun showMessage(isLong:Boolean):String{
return if(isLong){
"Name is too long."
}else{
"Please rename."
}
}
with
with函数是run的变体,他们的功能行为是一样的,但with的调用方式不同,调用with时需要值参作为其第一个参数传入
fun main() {
val result1 = "The people's Republic of China.".run {
length >= 10
}
val result2 = with("The people's Republic of China.") {
length >= 10
}
}
also
also 函 数 和 let 函 数 功 能 相 似 , 和 le t 一 样 , also 也 是 把 接 收 者 作 为 值 参 传给 lambda , 但 有 一 点 不 同 : also 返 回 接 收 者 对 象 , 而 let 返 回 lambda 结 果 。 因 为 这 个 差 异 , also 尤 其 适 合 针 对 同 一 原 始 对 象 , 利 用 副 作 用 做 事 , 既 然 also 返 回 的 是 接 收 者 对 象 , 你 就 可 以 基 于 原 始 接 收 者 对 象 执 行 额 外 的 链 式 调 用 。
fun main() {
var fileContents:List<String>
val file = File("E://i have a dream_copy.txt")
.also {
println(it.name)
}.also {
fileContents = it.readLines()
}
println(fileContents)
}
takeIf
和 其 他 标 准 函 数 有 点 不 一 样 takeIf 函 数 需 要 判 断 lambda 中 提 供 的 条 件 表 达 式 , 给 true 或 false 结 , 如 果 判 断 结 果 是 true , 从 takeIf 函 数 返 回 接 收 者 对 象 , 如 果 是 false , 则 返 回 null. 如 果 需 要 判 断 某 个 条 件 是 否 满 足 , 再 决 定 是 否 可 以 賦 值 变 量 或 执 行 某 项 任 务 , takeIf 就 非 常 有 用 , 概 念 上 讲 , takeIf 函 数 类 似 于 if 语 句 , 但 它 的 优 势 是 可 以 直 接 在 对 象 实 例 上 调 用 , 避 免 了 临 时 变 量 賦 值 的 麻 烦 。
fun main() {
val result = File("D://i have a dream_copy.txt")
.takeIf { it.exists() && it.canRead() }
?.readText()
println(result)
}
takeUnless
takeIf辅助函数takeUnless,只要判断你给定的条件结果是false时,takeUnless才会返回原始接收者对象
fun main(){
val result = File("E://i have a dream_copy.txt")
.takeUnless { it.isHidden }
?.readText()
println(result)
}
List创建与元素获取
集合可以方便你处理一组数据,也可以作为值参给函数,和我们学过的其他变量类型一样,list set map 类型的变量也分为两类,只读和可变
getOrElse是一个安全索引取值函数,它需要两个参数,第一个是索引值,第二个是能提供默认的lambda表达式,如果索引值不存在的话,可以来代替异常
getOrNull是kotlin提供的另一个安全索引取值函数,它返回null结果,而不抛出异常
fun main(){
val list:List<String> = listOf("a","b","c")
printlin(list.getOrElse(4){"Unknown"})
printlin(list.getOrNull(4))
printlin(list.getOrNull(4) ?: {"Unknown"})
}
可变List集合
在kotlin中支持内容修改的列表叫可变列表,要创建可变列表,可以使用mutableListOf函数。List还支持使用toList和toMutableList函数动态实现只读列表和可变列表的互相转换
fun main(){
val mutableList:MutableList<String> = mutableListOf("a","b","c")
mutableList.add("d")
mutableList.remove("b")
println(mutableList)
listOf("a","b","c").toMutableList()
mutableListOf("a","b","c").toList()
}
mutator函数
能修改可变列表的函数有一个统一的名字:mutator函数
添加元素运算符与删除元素运算符
基于lambda表达式指定的条件删除元素
fun main(){
val mutableList:MutableList<String> = mutableListOf("a","b","c")
mutableList += "d"
println(mutableList)
mutableList -= "d"
println(mutableList)
mutableList.removeIf{it.contains("a")}
println(mutableList)
}
List集合遍历
for in 遍历
forEach 遍历
forEachIndexed 遍历时要获取索引
fun main(){
val list:List<String> = listOf("a","b","c")
for(s:String in list){
println(s)
}
list.forEach{it:String
println(it)
}
list.forEachIndexed{index, item ->
println("$index,$item")
}
}
解构语法过滤元素
通过_符号过滤不想要的元素
fun main(){
val list:List<String> = listOf("a","b","c")
val (origin:String,_:String,proxy:String) = list
}
Set创建与元素获取
通过setOf创建set集合,使用elementAt函数读取集合中的元素
fun main(){
val set : Set<String> = setOf("kotlin","java","scala")
//没有这样写法
//set[3]
set.elementAt(2)
}
可变Set集合
通过mutableSetOf创建可变的set集合
fun main(){
val mutableSet : MutableSet<String> = mutableSetOf("kotlin","java",scala)
mutableSet += "Groovy"
}
集合转换与快捷函数
把list转换成set ,去掉重复元素
fun main(){
val list:List<String> = listOf("java","jack","jacky","jacky")
.toSet()
.toList()
println(list)
println(listOf("jason","jack","jacky","jacky").distinct())
}
数组类型
kotlin提供各种array. 虽然是引用类型,但可以编译成java基础数据类型
数组类型 | 创建函数 |
---|---|
InArray | inArrayOf |
DoubleArray | doubleArrayOf |
LongArray | longArrayOf |
ShortArray | shortArrayOf |
ByteArray | byteArrayOf |
FloatArray | floatArrayOf |
BooleanArray | booleanArrayOf |
Array | arrayOf |
Map的创建
to看上去像关键字,但事实上,它是个省略了点号和参数的特殊函数,to函数将它左边和右边的值转化成一对Pair
fun main(){
val map : Map<String,Int> = mapOf("java" to 20,"kotlin" to 80)
println(map)
mapOf(Pair("Jack",20),Pair("Jason",18))
}
读取Map的值
[]取值运算符,读取键对应的值,如果键不存在就返回null
getValue,读取件对应的值,如果键不存在就抛出异常
getOrElse,读取键对应的值,或者使用匿名函数返回默认值
getOrDefalut,读取键对应的值。或者返回默认值
fun main(){
val map : Map<String,Int> = mapOf("java" to 20,"kotlin" to 80)
println(map["java"])
println(map.getValue["java"])
println(map.getOrElse["java"])
println(map.getOrDefault["java"])
}
遍历Map
forEach遍历Map
fun main(){
val map : Map<String,Int> = mapOf("java" to 20,"kotlin" to 20)
map.forEach{it:Map.Entry<String,Int>
println("${it.ket},${it.value}")
}
map.forEach{(key : String,value : Int) ->
println("${it.ket},${it.value}")
}
}
可变Map结合
通过mutableMapOf创建可变的Map
getOrPut键值不存在,就添加并返回结果,否则就返回已有键对应的值
fun main(){
val mutableMap : MutableMap<String,Int> = mutableMapOf("java" to 20,"kotlin" to 20)
mutableMap += "Jimmy" to 30
mutableMap.put("Jimmy",31)
println(mutableMap.getOrPut("Jimmy"){18})
mutableMap.getOrPut("Rose"){18}
println(mutableMap)
}
定义类的fieId关键字
针对你定义的每一个属性,kotlin都会产生一个field,一个getter,以及一个setter,field用了存储属性数据,你不能直接定义field,kotlin会封装field,保护里面的数据,只暴露getter和setter使用,属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只能可变属性才会有setter方法,尽管kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,你也可以自定义他们
定义一个player类
class Player{
var name = "abc"
get() = field.capitalize()
set(value){
field = value.trim()
}
var age = 10
get() = field.absoluteValue
private set(value){
field = value.absoluteValue
}
}
计算属性与防范竞态条件
计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了
class Player{
val rolledValue
get() = (1..6).shuffled)().first()
}
如果一个类属性既可空又可变,那么引用它之前你必须保证它非空,一个办法是用also标准函数
class Player{
var words : String? = "hello"
fun saySomething(){
words?.also{it:String
println("Hello ${it.toUpperCase()}")
}
}
}
主构造函数
我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始化,在kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名
class Player(
_name : String,
_age :Int,
_isNormal : Boolean
){
var name = _name
get() = field.capitalize()
private set(value){
field = value.trim()
}
var age = _age
var isNormal = _isNormal
}
fun main(){
var player = Player("Jack",20,true)
}
在主构造函数里定义属性
kotlin允许你不使用临时变量赋值,而是直接用一个定义同时制定参数和类属性,通常,我们更喜欢用这种方式定义类属性,因为他会减少重复代码
calss Player2(
_name : String,
var age : Int,
val isNormal : Boolean
){
var name = _name
get() = field.capitalize()
private set(value){
field = value.trim()
}
}
次构造函数
有主就有次,对应主构造函数的是此构造函数,我们可以定义多个次构造函数来配置不同的参数组合
使用次构造函数,定义初始化代码逻辑
constructor(name : String) : this(name,
age = 100,
isNormal = false){
this.name = name.toUpperCase()
}
默认参数
定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值
class Player3(
_name : String,
var age : Int = 20,
private val isNormal : Boolean
){
var name = _name
get() = field.capitalize()
private set(value){
field = value.trim()
}
}
初始化块
初始化块可以设置变量或值,以及执行有效性能检查,如检查传给某构造函数的值是否有效,初始化代码会在构造类实例时执行
init {
require(age > 0){"age must be positive"}
require(name.isNotBlank()){"player must have a name"}
}
初始化顺序
主构造函数里声明的属性
类级别的属性赋值
init初始化里的属性赋值和函数调用
次构造函数里的属性赋值和函数调用‘
延时初始化lateinit
使用lateinit关键字相当于做了一个约定,在用它之前负责初始化
只要无法确认lateinit 变量是否完成初始化。可以执行isInitialized检查
calss Player4{
lateinit var equipmen : String
fun ready(){
equipmen = "sharp knife"
}
fun battle(){
if (::equipment.isInitialized) println(equipment)
}
}
惰性初始化by lazy
延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化
class Player5(_name : String){
var name = _name
val config by lazy{loadCofig()}
private fun loadCofig():String{
println("loading...")
return "xxx"
}
}
继承与重载的open关键字
继承:类默认都是封闭的,要让某个类开放继承,必须使用open关键字修饰它
函数重载:父类的函数也要以open关键字修饰,子类才能覆盖它
open class Product(val name:String){
fun description() = "Product: $name"
open fun load() = "Nothing..."
}
class Luxuryproduct : Product("Luxury"){
override fun load() = "LuxuryProduct loading..."
}
fun main(){
val p : Product = LuxuryProduct()
println(p.load())
}
类型转换
Kotlin的is运算符是个不错的工具,可以用来检查某个对象的类型
fun main(){
val p = LuxuryProduct()
println(p is LuxuryProduct)
println(p is Product)
}
智能类型转换
kotlin编译器很聪明,只有能确认any is 父类条件检查属实,它就会将any当做子类型对待,因此,编译器允许你不经过类型转换直接使用
fun sale(p : Product){
println(p.load())
}
fun main(){
val p = LuxuryProduct()
//sale(p as Product)
sale(p)
}
Any超类
无需在代码里显示指定,每一个类都会继承一个共同的叫作Any的超类
对象声明
object 关键字
使用object关键字,你可以定义一个只能产生一个实例的类,单例
使用object关键字有三种
对象声明
对象表达式
伴生对象
对象声明有利于组织代码和管理状态,尤其是管理整个应用运行生命周期内的某些一致性状态
object ApplicationConfig{
init{
println("loading config....")
}
fun setSomething(){
println(setSomething)
}
}
fun main(){
ApplicationConfig.setSomething()
println(ApplicationConfig)
println(ApplicationConfig). 打印同一对象地址
}
对象表达式
有时候你不一定非要定义一个新的命名类不可,也许你需要某个现有类的一种变体实例,但只需用一次就行了,事实上,对于这种用完就丢的类实例,连命名都可以省略,这个对象表达式是XX的子类,这个匿名类依然遵循object关键字的一个规则,即一旦实例化,该匿名类只能有唯一一个实例
open class Player{
open fun load = "loading nothing"
}
fun main(){
val p = object : Player(){
override fun load() = "anonymous class load ...."
}
println(p.load())
}
伴生对象
如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用co mpanion修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象
open class ConfigMap{
//只有初始化ConfigMap类或调用load函数时,伴生对象的内容才会载入
//而且无论实例化ConfigMap类多少次,这个伴生类对象只有一个实例存在
companion object{
private const val PATH = "xxx"
fun load() = File(PATH).readBytes()
}
}
嵌套类
“ 如 果 一 个 类 只 对 另 一 个 类 有 , 那 么 将 其 嵌 入 到 该 类 中 并 使 这 两 个 类 保 持 在 一 起 是 合 乎 逻 辑 的 , 可 以 使 用 嵌 套 类 。
class Player2 {
class Equipment(var name: String) {
fun show() = println("equipment:$name")
}
fun battle(){
}
}
fun main() {
Player2.Equipment("sharp knife").show()
}
数据类
数 据 类 , 是 专 门 设 计 用 来 存 储 数 据 的 类 声 数 据 类 提 供 了 toString 的 个 性 化 实 现 二 二 符 号 默 认 情 况 下 , 比 较 对 象 計 是 比 较 它 们 的 引 用 值 , 数 据 类 提 供 了 equals 和 hash Code 的 个 性 化 实 现
data class Coordinate(var x: Int, var y: Int) {
val isInBounds = x > 0 && y > 0
}
fun main() {
println(Coordinate(10, 20))
// == 比较的是内容,equals,Any 默认实现===,比较引用
// === 比较的是引用
println(Coordinate(10, 20) == Coordinate(10, 20))
val (x, y) = Coordinate(10, 20)
println("$x, $y")
}
copy函数
除 了 重 写 Any 类 的 部 分 函 数 , 提 供 更 好 用 的 默 认 实 现 外 , 数 据 类 还 提 供 了 一 个 函 数 , 它 可 以 用 来 方 便 地 复 制 一 个 对 象 : 假 设 你 想 创 建 一 个 Student 实 例 , 了 namex 性 , 它 拥 有 和 另 一 个 现 有 Stude nt 实 例 完 全 一 样 的 属 性 值 , 如 果 Student 是 个 数 据 类 , 那 么 复 制 现 有 Student 实 例 就 很 简 单 了 , 只 要 调 用 ( opy 函 数 , 给 想 修 改 的 属 性 传 入 值 参 就 可 以 了 ·
data class Student(var name: String, val age: Int) {
private val hobby = "music"
val subject: String
//var score = 0
init {
println("initializing student")
subject = "math"
}
constructor(_name: String) : this(_name, 10){
//score = 10
}
override fun toString(): String {
return "Student(name='$name', age=$age, hobby='$hobby', subject='$subject')"
}
}
fun main() {
val s = Student("Jack")
val copy = s.copy("Rose")
println(s)
println(copy)
}
解析声明
解 构 声 明 的 后 台 实 现 就 是 声 明 componentl 、 component2 等 若 干 个 组 件 函 数 , 计 每 个 函 数 负 责 管 理 你 想 返 回 的 。 个 属 性 数 据 , 如 果 你 定 义 一 个 数 据 类 , 它 会 自 动 为 所 有 定 义 在 主 构 造 函 数 的 属 性 添 加 对 应 的 组 件 函 数 。
class PlayerScore(val experience: Int, val level: Int) {
operator fun component1() = experience
operator fun component2() = level
}
fun main() {
val (x, y) = PlayerScore(10, 20)
println("$x, $y")
}
运算符重载
如 果 要 将 内 置 运 算 符 应 用 在 自 定 义 类 身 上 , 你 必 须 重 写 运 算 符 函 数 , 告 诉 编 译 器 该 如 何 挨 作 自 定 义 类 。
data class Coordinate2(var x: Int, var y: Int) {
val isInBounds = x > 0 && y > 0
operator fun plus(other: Coordinate2) = Coordinate2(x + other.x, y + other.y)
}
fun main() {
val c1 = Coordinate2(10, 20)
val c2 = Coordinate2(10, 20)
println(c1 + c2)
}
枚举类
枚举类,用来定义常量集合的一种特殊类
enum class Direction{
EAST,
WEST,
SOUTH,
NORTH
}
fun main() {
println(Direction.EAST)
println(Direction.EAST is Direction)
}
枚举类定义函数
枚举类也可以定义函数
enum class Direction2(private val coordinate: Coordinate){
EAST(Coordinate(1,0)),
WEST(Coordinate(-1,0)),
SOUTH(Coordinate(-1,0)),
NORTH(Coordinate(1,0));
fun updateCoordinate(playerCoordinate: Coordinate) =
Coordinate(playerCoordinate.x + coordinate.x,
playerCoordinate.y + coordinate.y)
}
fun main() {
println(Direction2.EAST.updateCoordinate(Coordinate(10,20)))
}
代数数据类型
可以用来表示一组子类型的闭集 枚举类就是一种简单的ADT
enum class LicenseStatus {
UNQUALIFIED,
LEARNING,
QUALIFIED;
var licenseId: String? = null
}
class Driver(var status: LicenseStatus) {
fun checkLicense(): String {
return when(status){
LicenseStatus.UNQUALIFIED -> "没资格"
LicenseStatus.LEARNING -> "在学"
LicenseStatus.QUALIFIED -> "有资格"
}
}
}
fun main() {
println(Driver(LicenseStatus.QUALIFIED).checkLicense())
}
密封类
对 于 更 复 杂 的 ADT , 你 可 以 使 用 Kotlin 的 密 封 类 (sealed class) 来 实 现 更 复 杂 的 定 义 , 密 封 类 可 以 用 来 定 义 一 个 类 似 于 枚 举 类 的 ADT , 但 你 可 以 更 灵 活 地 控 制 某 个 子 类 型 。 , 密 封 类 可 以 有 若 干 个 子 类 , 要 继 承 密 封 类 , 这 些 子 类 必 须 和 它 定 义 在 同 一 个 文 件 里 。
//密封
sealed class LicenseStatus2 {
object UnQualified : LicenseStatus2()
object Learning : LicenseStatus2()
class Qualified(val licenseId: String) : LicenseStatus2()
}
class Driver2(var status: LicenseStatus2) {
fun checkLicense(): String {
return when(status){
is LicenseStatus2.UnQualified -> "没资格"
is LicenseStatus2.Learning -> "在学"
is LicenseStatus2.Qualified -> "有资格,驾驶证编号:${(this.status as LicenseStatus2.Qualified).licenseId}"
}
}
}
fun main() {
val status = LicenseStatus2.Qualified("238239329")
val driver = Driver2(status)
println(driver.checkLicense())
}
数据类使用条件
数 据 类 , 是 专 门 设 计 用 来 存 储 数 据 的 类 ,
数 据 类 供 了 toString 的 个 性 化 实 现
==符 号 默 认 情 况 下 , 比 较 对 象 就 是 比 较 它 们 的 用 值 , 数 据 类 提 供 了 equals 和 hash Code 的 个 性 化 实 现
正 是 因 为 上 述 这 些 特 性 , 你 才 傾 向 于 用 数 据 类 来 表 示 存 储 数 据 的 简 单 对 象 , 对 于 那 些 经 常 需 要 比 较 , 复 制 或 打 印 自 身 内 容 的 类 , 数 据 类 尤 其 适 合 它 们 。 然 而 , 一 个 类 要 成 为 数 据 类 , 也 要 符 合 一 定 条 件 。 总 结 下 来 , 主 要 有 三 个 方 面 ·
数 据 类 必 须 有 至 少 带 一 个 参 数 的 主 构 造 函 数 ·
数 据 类 主 构 造 函 数 的 参 数 必 须 是val var
数 据 类 不 能 使 用 abstract 、 open 、 sealed 和 inner 修 饰 符
接口定义
Kotlin 规 定 所 有 的 接 口 属 性 和 函 数 实 觋 都 要 使 用 override 关 键 字 , 接 囗 中 定 义 的 函 数 并不 需 要 open 关 键 字 修 饰 , 他 们 默 认 就 是 open 的 。
interface Movable {
var maxSpeed: Int
var wheels: Int
fun move(movable: Movable): String
}
class Car(_name: String, override var wheels: Int = 4) : Movable {
override var maxSpeed: Int
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
set(value) {}
override fun move(movable: Movable): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
接口的默认实现
只要你愿意,你可以在接口里提供默认属性的getter方法和函数实现
interface Movable {
var maxSpeed: Int
get() = (1..500).shuffled().last()
set(value) {}
var wheels: Int
fun move(movable: Movable): String
}
class Car(_name: String, override var wheels: Int = 4) : Movable {
override var maxSpeed: Int
get() = super.maxSpeed
set(value) {}
override fun move(movable: Movable): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
抽象类
要 定 义 一 个 抽 象 类 , 你 需 要 在 定 义 之 前 加 上 abstract 关 键 字 , 除 了 具 体 的 函 数 实 现 , 抽 象 类 也 可 以 包 含抽 象 函 数 一 只 有 定 义 , 没 有 函 数 实 觋 。
abstract class Gun(val range: Int) {
protected fun doSomething(){
println("doSomething")
}
abstract fun pullTrigger(): String
}
//多重继承
class AK47(val price: Int) : Gun(range = 500){
override fun pullTrigger(): String {
TODO("not implemented")
}
}
定义泛型类
泛 型 类 的 构 造 函 数 可 以 接 受 任 何 类 型
MagicBox类 指 定 的 泛 型 参 数 由 放 在 一 对 < > 里 的 字 母 T 表 示 , T 是 个 代 表 item 类 型 的 占 位 符 。MagicBox 类 接 受 任 何 类 型 的 item 作 为 主 构 造 函 数 值 (item: T) , 并 将 item 值 赋 给 同 样 是 T 类 型 的 subject 私 有 属 性 .
class MagicBox<T>(item: T) {
private var subject: T = item
}
class Boy(val name: String, val age: Int)
class Dog(val weight: Int)
fun main() {
val box1:MagicBox<Boy> = MagicBox(Boy("Jack",20))
val box2:MagicBox<Dog> = MagicBox(Dog(20))
}
泛型函数
泛 型 参 数 也 可 以 用 于 函 数 。 定 义 一 个 函 数 甲 于 获 取 元 素 , 当 且 仅 当 MagicBox 可 用 时 , 才 能 获 取 元 素 。
class MagicBox<T>(item: T) {
var available = false
private var subject: T = item
fun fetch():T?{
return subject.takeIf { available }
}
}
class Boy(val name: String, val age: Int)
class Dog(val weight: Int)
fun main() {
val box1:MagicBox<Boy> = MagicBox(Boy("Jack",20))
val box2:MagicBox<Dog> = MagicBox(Dog(20))
box1.available = true
box1.fetch()?.run {
println("you find $name")
}
}
多泛型参数
泛型函数或泛型类也可以有多个泛型参数
class MagicBox<T>(item: T) {
var available = false
private var subject: T = item
fun fetch(): T? {
return subject.takeIf { available }
}
//业务,把元素进行修改
//魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
//return -> R
fun <R> fetch(subjectModFunction: (T) -> R): R? {
return subjectModFunction(subject).takeIf { available }
}
}
class Boy(val name: String, val age: Int)
class Man(val name: String, val age: Int)
class Dog(val weight: Int)
fun main() {
val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 15))
val box2: MagicBox<Dog> = MagicBox(Dog(20))
box1.available = true
box1.fetch()?.run {
println("you find $name")
}
val man = box1.fetch {
Man(it.name, it.age.plus(15))
}
}
泛型类型约束
如果要确保MagicBox里面只能装指定类型的物品,如Human类型,怎么办?
class MagicBox<T : Human>(item: T) {
var available = false
private var subject: T = item
fun fetch(): T? {
return subject.takeIf { available }
}
//业务,把元素进行修改
//魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
//return -> R
fun <R> fetch(subjectModFunction: (T) -> R): R? {
return subjectModFunction(subject).takeIf { available }
}
}
open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)
class Dog(val weight: Int)
fun main() {
val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 15))
//val box2: MagicBox<Dog> = MagicBox(Dog(20))
box1.available = true
box1.fetch()?.run {
println("you find $name")
}
val man = box1.fetch {
Man(it.name, it.age.plus(15))
}
}
vararg关键字与get函数
MagicBox 能存放任何类型的Human实例,但一次只能放一个,如果需要放入多个实例呢?
class MagicBox<T : Human>(vararg item: T) {
var available = false
private var subject: Array<out T> = item
fun fetch(index: Int): T? {
return subject[index].takeIf { available }
}
//业务,把元素进行修改
//魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
//return -> R
fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R? {
return subjectModFunction(subject[index]).takeIf { available }
}
}
open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)
class Dog(val weight: Int)
fun main() {
val box1: MagicBox<Boy> = MagicBox(
Boy("Jack", 15),
Boy("Jacky", 16),
Boy("John", 26)
)
box1.available = true
box1.fetch(1)?.run {
println("you find $name")
}
val man = box1.fetch(2) {
Man(it.name, it.age.plus(15))
}
}
[]操作符
class MagicBox<T : Human>(vararg item: T) {
var available = false
private var subject: Array<out T> = item
fun fetch(index: Int): T? {
return subject[index].takeIf { available }
}
//业务,把元素进行修改
//魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
//return -> R
fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R? {
return subjectModFunction(subject[index]).takeIf { available }
}
operator fun get(index: Int): T? = subject[index]?.takeIf { available }
}
open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)
class Dog(val weight: Int)
fun main() {
val box1: MagicBox<Boy> = MagicBox(
Boy("Jack", 15),
Boy("Jacky", 16),
Boy("John", 26)
)
box1.available = true
box1.fetch(1)?.run {
println("you find $name")
}
val man = box1.fetch(2) {
Man(it.name, it.age.plus(15))
}
box1[0]
}
out协变 in逆变
out(协变) 如果泛型类只将泛型类型作为函数返回(输出)那么使用out,可以称之为生产类/接口,因为它主要是用来生成(produce)指定的泛型对象
in ( 逆 变 ) , 如 果 泛 型 类 只 将 泛 型 类 型 作 为 函 数 的 入 参 ( 输 入 ) , 那 么 使 用 in , 可 以 你 之 为 消 费 者 类 / 接 囗 , 因 为 它 主 要 是 用 来 消 费 (consume) 指 定 的 泛 型 对 象 。
//out
interface Production<out T> {
fun product(): T
}
//in
interface Consumer<in T> {
fun consume(item: T)
}
//不变
interface ProductionConsumer<T> {
fun product(): T
fun consume(item: T)
}
open class Food
open class FastFood : Food()
class Burger : FastFood()
//生产者
//食品商店
class FoodStore : Production<Food>{
override fun product(): Food {
println("Produce food.")
return Food()
}
}
//快餐商店
class FastFoodStore : Production<FastFood>{
override fun product(): FastFood {
println("Produce FastFood.")
return FastFood()
}
}
//汉堡商店
class BurgerStore : Production<Burger>{
override fun product(): Burger {
println("Produce Burger.")
return Burger()
}
}
//消费者
class Everybody : Consumer<Food>{
override fun consume(item: Food) {
println("Eat food.")
}
}
class ModernPeople : Consumer<FastFood>{
override fun consume(item: FastFood) {
println("Eat fastFood.")
}
}
class American : Consumer<Burger>{
override fun consume(item: Burger) {
println("Eat burger.")
}
}
fun main() {
//赋值
//子类泛型对象可以赋值给父类泛型对象,用 out。
val production1: Production<Food> = FoodStore()
val production2: Production<Food> = FastFoodStore()
val production3: Production<Food> = BurgerStore()
//父类泛型对象可以赋值给子类泛型对象,用 in。
val consumer1: Consumer<Burger> = Everybody()
val consumer2: Consumer<Burger> = ModernPeople()
val consumer3: Consumer<Burger> = American()
}
reified关键字
有 时 候 , 你 可 能 想 知 道 某 个 泛 型 参 数 貝 体 是 什 么 类 型 , 阉 d 关 謎 字 能 帮 你 检 查 泛 型 参 数 类 型 。 Kotlin 不 允 许 对 泛 型 参 融 T 做 类 型 检 查 , 因 为 泛 型 参 融 类 型 会 被 类 型 藻 除 , 也 就 是 说 , T 的 类 型 信 息 在 运 行 时 是 不 可 知 的 , Java 也 有 这 样 的 规 则 。
class MagicBox<T : Human>() {
//随机产生一个对象,如果不是指定类型的对象,就通过backup函数生成一个指定类型的对象
/*fun <T> randomOrBackup(backup: () -> T): T {
val items = listOf(
Boy("Jack", 20),
Man("John", 35)
)
val random = items.shuffled().first()
return if(random is T){
random
}else{
backup()
}
}*/
inline fun <reified T> randomOrBackup(backup: () -> T): T {
val items = listOf(
Boy("Jack", 20),
Man("John", 35)
)
val random = items.shuffled().first()
println(random)
return if(random is T){
random
}else{
backup()
}
}
}
open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age){
override fun toString(): String {
return "Boy(name='$name',age='$age')"
}
}
class Man(val name: String, age: Int) : Human(age){
override fun toString(): String {
return "Man(name='$name',age='$age')"
}
}
fun main() {
val box1:MagicBox<Boy> = MagicBox()
//又backup函数,推断出来T的类型
val subject = box1.randomOrBackup {
Boy("Jimmy", 38)
}
println(subject)
}
超类上定义扩展函数
扩 展 可 以 在 不 接 修 改 类 定 义 的 情 况 下 增 加 类 扩 展 可 以 用 于 戔 定 义 类 , 也 可 以 用 于 比 如 List. String, 以 及 Kotlin 标 准 库 里 的 其 他 类 力 和 继 承 相 似 , 扩 展 也 能 共 享 类 行 为 , 在 你 无 法 接 触 某 个 类 定 义 , 或 者 某 个 类 没 有 使 用 open 修 饰 符 , 导 致 你 无 法 继 承 它 时 , 扩 展 就 是 增 加 类 功 能 的 最 好 选 择 。
//给字符串追加若干个感叹号
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)
fun Any.easyPrint() = println(this)
fun main() {
println("abc".addExt(2))
"abc".easyPrint()
15.easyPrint()
}
infix关键字
infix 关 键 字 适 用 于 有 单 个 参 数 的 扩 展 和 类 函 数 , 可 以 让 你 以 更 简 洁 的 语 法 调 用 函 数 , 如 果 一 个 函 数 定 义 使 用 了 infix 关 键 字 , 那 么 调 用 它 时 , 接 收 者 和 函 数 之 间 的 点 操 作 以 及 参 数 的 一 对 括 号 都 可 以 不 要 。
infix fun String?.printWithDefault(default: String) = print(this ?: default)
fun main() {
val nullableString: String? = null
nullableString printWithDefault "abc"
//"jack".to(18)
//mapOf("jack" to 18)
}
定义扩展文件
扩 展 函 数 需 要 在 多 个 文 件 里 面 使 用 , 可 以 将 它 定 义 在 单 独 的 文 件 , 然 后 im porto
package com.jason.kotlin.extension
fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()
重命名扩展
有 时 候 , 你 想 使 用 一 个 扩 展 或 一 个 类 , 但 它 的 名 字 不 和 你 的 意 。
import com.jason.kotlin.extension.randomTake as randomizer
fun main() {
val list = listOf("Jason", "Jack", "Tom")
val set = setOf("Jason", "Jack", "Tom")
list.randomizer()
}
apply函数详解
Kotlin 标 准 提 供 的 很 多 功 都 是 过 扩 展 函 数 和 扩 展 属 性 来 实 现 的 , 包 含 类 扩 展 的 标 准 库 文 件 通 帛 都 是 以 类 名 加 s后 缀 来 命 名 的 , 例 如 Sequences.kt , Ranges.kt, Maps.kt
apply函 数 是 如 何 做 到 支 持 接 收 者 对 象 的 隐式 调 用 的 。
import java.io.File
//扩展函数
fun String.addExt() = "!".repeat(count())
//泛型的扩展函数
fun <T> T.easyPrint(): Unit = println(this)
//为什么要传入扩展函数(泛型),而不是一个普通的匿名函数?
//T.() -> Unit
//扩展函数里自带了接收者对象的this隐式调用
//为什么是泛型的扩展函数?
//匿名函数,也可以是扩展函数
//普通的匿名函数
//() -> Unit
//匿名函数内部this指向一个File对象,隐式调用
//File.() -> Unit
/*public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}*/
public inline fun File.apply(block: File.() -> Unit): File {
block()
return this
}
fun main() {
val file = File("xx").apply {
setReadable(true)
}
//这里分解一下
//1.定义扩展函数
fun File.ext(): Unit {
setReadable(true)
}
//2.给block变量赋值
val block = File::ext
//3.传入apply函数
File("xx").apply { block }
}
DSL
使 用 这 样 的 编 程 范 式 , 就 可 以 写 出 业 界 知 名 的 “ 领 域 特 定 语 言 " ( DSL) , 一 种 API 编 程 范 式 , 接 收 者 的 函 数 和 特 , 以 便 于 使 用 你 定 义 的 mbda 表 达 式 来 读 取 和 配 置 它 们 。
import java.io.File
//扩展函数
fun String.addExt() = "!".repeat(count())
//泛型的扩展函数
fun <T> T.easyPrint(): Unit = println(this)
//为什么要传入扩展函数(泛型),而不是一个普通的匿名函数?
//T.() -> Unit
//扩展函数里自带了接收者对象的this隐式调用
//为什么是泛型的扩展函数?
//匿名函数,也可以是扩展函数
//普通的匿名函数
//() -> Unit
//匿名函数内部this指向一个File对象,隐式调用
//File.() -> Unit
/*public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}*/
public inline fun File.apply(block: File.() -> Unit): File {
block()
return this
}
fun main() {
val file = File("xx").apply {
setReadable(true)
}
//这里分解一下
//1.定义扩展函数
fun File.ext(): Unit {
setReadable(true)
}
//2.给block变量赋值
val block = File::ext
//3.传入apply函数
File("xx").apply { block }
}
函数类别
一 个 函 数 式 应 用 通 常 由 三 大 类 函 数 构 成 : 变 换transform、 过 滤 filter, 合 并 ( ombine. 每 类 函 数 都 针 对 集 合 数 据 类 型 设 计 , 目 标 是 产 生 一 个 最 终 结 果 。 函 数 式 编 程 用 到 的 函 数 生 来 都 是 可 组 合 的 , 也 就 是 说 , 你 可 以 组 合 多 个 简 单 函 数 来 构 建 复 杂 的 计 算 行 为 。
变换函数map
map 变 换 函 数 会 遍 历 接 收 者 集 合 , 变 换 器 函 数 作 用 于 集 合 里 的 各 个 元 素 , 返 回 结 果 是 包 含 已 修 改 元 素 的 集 合 , 会 作 为 链 上 下 一 个 函 数 的 输 入 。
fun main() {
val animals = listOf("zebra", "giraffe", "elephant", "rat")
val babies = animals
.map { animal -> "A baby $animal"}
.map { baby -> "$baby,with the cutest little tail ever!" }
println(animals)
println(babies)
val animalsLength = animals.map { it.length }
println(animalsLength)
//List<String> List<List<String>>
}
变换函数flatMap
flatMap 函 数 操 作 一 个 集 合 的 集 合 , 将 其 中 多 个 集 合 中 的 元 素 合 并 后 返 回 一 个 包 含 所 有 元 素 的 单 一 集 合 。
fun main() {
val result = listOf(listOf(1,2,3), listOf(4,5,6)).flatMap { it }
println(result)
}
过滤函数filter
过 滤 是 函 数 式 编 程 的 第 二 大 类 函 数 , 过 滤 函 数 接 受 一 个 predi te 函 数 , 用 它 按 给 定 条 件 检 查 接 收 者 集 合 里 的 元 素 并 纟 合 出 true 或 false 的 判 定 。 如果 predicate 函 数 返 回 true, 受 检 元 素 就 会 添 加 到 过 滤 函 数 返 回 的 新 集 合 里 。 如 果 predicate 函 数 返 回 false, 那 么 受 检 元 素 就 被 移 出 新 集 合 。
filter 过 滤 函 数 接 受一 个 predicate 函 数 , 在 flattMap遍 历 它 的 输 入 集 合 中 的 所 有 元 素 时 , filter 函 数 会 让 predicate 函 数按 过 滤 条 件 , 将 符 合 条 件 的 元 素 都 放 入 它 返 回 的 新 集 合 里 。 最 后 , 到 atMa 会 把 变 换 器 函 数 返 回 的 子 集 合 合 并 在 一 个 集 合里
fun main() {
val result = listOf("Jack","Jimmy","Rose","Tom")
.filter { it.contains("J") }
println(result)
val items = listOf(
listOf("red apple", "green apple", "blue apple"),
listOf("red fish", "blue fish"),
listOf("yellow banana", "teal banana")
)
val redItems = items.flatMap { it.filter { it.contains("red") } }
println(redItems)
}
组合使用filter和map找素数
找 素 数 , 除 了 1 和 它 本 身 , 不 能 被 任 何 数 整 除 的 数 。 仅 使 用 了 几 个 简 单 函 数 , 我 们 就 解 决 了 找 素 数 这 个 比 较 复 杂 的 问 题 , 这 就 是 函 数 式 编 程 的 独 特 魅 力 : 每 个 函 数 做 一 点 , 组 合 起 来 就 能 干 大 事 。
fun main() {
//除了1和它本身,不能被任何数整除的数
//取模等于0,说明能够整除,如果没有一个是等于0的,说明是素数
val numbers = listOf(7, 4, 8, 4, 3, 22, 18, 11)
val primes = numbers.filter { number ->
(2 until number).map { number % it }
.none { it == 0 }
}
println(primes)
}
合并函数zip
合并函数folder
为什么要使用函数式编程
序列 使用序列查找素数
互操作性与可空性
、