目录
- Kotlin语法进阶
- in 区间
- When表达式
- 函数参数默认值(缺省值)
- 匿名函数 lambda
- 函数参数
- 类型推断
- 参数是匿名函数的函数
- 内联函数
- noinline
- 具体化的类型参数 reified
- 函数引用
- 函数类型作为返回值
- 闭包
- lambda于匿名内部类
- 带let的安全调用
- 异常处理于自定义异常
- replace
- == 与===比较
- double转Int
- apply
- let
- run
- With
- also
- takeIf
- takeUnless
- List创建于元素获取
- 可变List集合
- mutator函数
- List集合遍历
- Set创建于元素获取
- 集合转换于快捷函数
- Map创建
- 遍历Map
- field
- 计算属性
- 主构造函数
- 次构造函数 constructor
- 默认参数
- 初始化代码块
- 初始化顺序
- 对象声明
- 对象表达式
- 伴生对象
- 嵌套类
- 数据类
- Copy
- 解构声明
- 枚举类
- 枚举类定义函数
- 代数数据类型
- 密封类
- 接口
- 接口默认实现
- 泛型
- 多泛型参数
- 可变参数
- [ ]操作符
- in-逆变 out-协变
- reified关键字
- 定义扩展函数
- 泛型扩展函数
- 扩展属性
- 可空类扩展
- infix关键字
- 定义扩展文件
- apply函数详解
- 函数式编程
- 变换
- 变换函数-map
- flatMap
- 过滤-filter
- 合并-zip
- 合并函数-fold
- 序列
- JvmName
- JvmField
- 函数类型操作
Kotlin语法进阶
适用于使用kotlin半年到2年左右的Android程序员 系统学习kotlin
in 区间
区间表达式由具有操作符形式 … 的 rangeTo 函数辅以 in 和 !in 形成。
区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。以下是使用区间的一些示例:
for (i in 1..4) print(i) // 输出“1234”
for (i in 4..1) print(i) // 什么都不输出
if (i in 1..10) { // 等同于 1 <= i && i <= 10
println(i)
}
// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
// 使用 until 函数排除结束元素
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
When表达式
when类似于Java的Switch语法
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x 不是 1 ,也不是 2")
}
}
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
扩展:
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
函数参数默认值(缺省值)
fun main(){
doSomething(true)
doSomething(3,false)
}
private fun doSomething(age:Int = 2,flag:Boolean){
return "age:$age flag:$flag"
}
匿名函数 lambda
- 定义时不取名字的函数,称为匿名函数,匿名函数通常整体传递给其他函数,或者从其他函数返回
- 匿名函数对kotlin很重要,我们能够根据需要制定特殊规则,轻松定制标准库里的内置函数
//将匿名函数赋值给一个变量
val blessingFunction:()->String = {
val holiday = "National Day."
"Happy $holiday"
}
println(blessingFunction())
和具名(有名字的)函数不一样,除了极少数情况下,匿名函数不需要return关键字返回数据,匿名函数会隐式或自动返回函数体最后一行语句的结果
函数参数
和具名参数一样,匿名函数可以不带参数,也可以带一个或者多个任何类型的参数,需要带参数时,参数的类型放在匿名函数的类型定义中,参数名则放在函数定义中
val blessFunction:(String)->String = { name->
val holiday = "New Year."
"$name, Happy $holiday"
}
println(blessFunction("Jack"))
类型推断
fun main(){
//隐式推断匿名函数返回值类型
val blessingFunction:()->String = {
val holiday = "National Day."
"Happy $holiday"
}
val blessingFunction = {
val holiday = "National Day."
"Happy $holiday"
}
//隐式推断匿名函数返回值参数类型
val blessFunction:(String,Int)->String = { name,year->
val holiday = "New Year."
"$name, Happy $holiday $year"
}
val blessFunction = { name:String,year:Int->
val holiday = "New Year."
"$name, Happy $holiday $year"
}
println(blessingFunction())
println(blessFunction("Jack",2027))
}
参数是匿名函数的函数
如果 一个函数的lambda参数排在最后,或者是唯一参数,那么括住lambda值参的一对圆括号就可以省略
fun main(){
val discountWords = {goodsName:String,hour:Int->
val currentYear = 2027
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
showonBoard("卫生纸",discountWords)
//简写 如果 一个函数的lambda参数排在最后,或者是唯一参数,那么括住lambda值参的一对圆括号就可以省略
showonBoard("卫生纸"){goodsName:String,hour:Int->
val currentYear = 2027
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
}
fun showonBoard(goodsName:String,showDiscount:(String,Int)->String){
//1-24区间随机数的最后一个
val hour = (1..24).shuffled().last()
println(showDiscount(goodsName,hour))
}
fun main(){
val total = "Mississippi".count()
//通过匿名函数给标准函数制定规则
val totalS = "Mississippi".count({letter->
letter == "s"
})
//简写 如果 一个函数的lambda参数排在最后,或者是唯一参数,那么括住lambda值参的一对圆括号就可以省略
val totalS = "Mississippi".count{ it == "s"}
}
内联函数
lambda可以让你更灵活编写应用但是灵活也是需要付出代价的
在JVM上,定义的lambda会以对象实例的形式存在,JVM会同所有lambda打交道的 变量分配内存,这就产生了内存开销。更糟的是lambda内存开销会带来严重的性能问题,幸运的是,kotlin有一种优化机制叫做内联,有了内联,JVM不需要lambda对象实例,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体复制粘贴到那里
fun test() {
//多次调用 sum() 方法进行求和运算
println(sum(1, 2, 3))
println(sum(100, 200, 300))
println(sum(12, 34))
//....可能还有若干次
}
/**
* 求和计算
*/
fun sum(vararg ints: Int): Int {
var sum = 0
for (i in ints) {
sum += i
}
return sum
}
以上我们做了多此sum()方法,为了避免多次调用sum()方法带来的性能损耗,我们期望的代码类似这样子的
fun test() {
var sum = 0
for (i in arrayOf(1, 2, 3)) {
sum += i
}
println(sum)
sum = 0
for (i in arrayOf(100, 200, 300)) {
sum += i
}
println(sum)
sum = 0
for (i in arrayOf(12, 34)) {
sum += i
}
println(sum)
}
3次数据球和操作,都是在test()里面执行的,没有之前的sum()方法调用,最后的结果依然是一样的,但是由于减少了 方法的调用,虽然代码量增加了,但是性能提升了
定义内联函数
inline fun sum(vararg ints: Int): Int {
var sum = 0
for (i in ints) {
sum += i
}
return sum
}
用关键字inline标记函数,该函数就是一个内联函数,还是原来的test(),编译器会在编译时,自动把内联函数sum()方法体内的代码替换到调用该方法的地方,查看编译后的字节码,会发现test()方法里面没有了对sum()调用,凡是原来代码出现sum()方法调用的地方,出现的都是sum()方法体内的字节码了。
noinline
如果一个内联函数的参数包含lambda表达式,也就是函数参数,那么该形参也是inline的
inline fun test(inlined: () -> Unit) {...}
这里有个问题需要注意,如果在内联函数的内部,函数参数被其他非内联函数调用,就会报错
//内联函数
inline fun test(inlined: () -> Unit) {
//这里会报错
otherNoinlineMethod(inlined)
}
//非内联函数
fun otherNoinlineMethod(oninline: () -> Unit) {
}
要解决这个问题,必须为内联函数参数加上noinline修饰,表示禁止内联,保存原有函数特性
inline fun test(noinline inlined: () -> Unit) {
otherNoinlineMethod(inlined)
}
具体化的类型参数 reified
java中不能直接使用泛型的类型的,但是kotlin中确可以
inline fun <reified T: Activity> startActivity() {
startActivity(Intent(this, T::class.java))
}
使用时直接传入泛型即可,代码简洁明了
startActivity<MainActivity>()
函数引用
要把函数中作为参数传给其他函数使用,除了传lambda表达式,kotlin还提供了其他方法,传递函数引用,函数引用可以把一个具名函数转换成一个值参, 使用lambda表达式的地方,都可以使用函数引用
fun main(){
/**val discountWords = {goodsName:String,hour:Int->
val currentYear = 2027
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
**/
//要获得函数引用,使用::操作符 后跟要引用的函数名
showonBoard("卫生纸",::getDiscountWords)
}
//将匿名函数更改为具名函数
fun getDiscountWords(goodsName:String,hour:Int):String{
val currentYear = 2027
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
fun showonBoard(goodsName:String,showDiscount:(String,Int)->String){
//1-24区间随机数的最后一个
val hour = (1..24).shuffled().last()
println(showDiscount(goodsName,hour))
}
函数类型作为返回值
一个函数返回另一个函数类型
函数类型也是有效的返回值类型,也就是说可以定义一个能返回函数的函数。
fun main(){
val getDiscountWords:(String)->String = configDiscountWords()
println(getDiscountWords("沐浴露"))
}
fun configDiscountWords():(String)->String{
val hour = (1..24).shuffled().last()
val currentYear = 2027
return {goodsName:String->
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
}
闭包
kotlin中,匿名函数能修改并引用定义在自己作用域之外的变量,匿名函数引用着定义自身的函数里的变量。kotlin中的lambda就是闭包
lambda于匿名内部类
lambda让开发者少写模块化代码,写出更灵活的代码
Java将函数作为参数传递给另一个函数或变量(传一个函数)
public class JavaAnonymousClass{
public static void main(String[] args){
showOnBoards("牙膏",new MyDiscountWords());
//new一个匿名内部类 有实现类只不过是没有名字(有一个类继承了接口)
showOnBoards("牙膏",new DiscountWords{
@Override
public String getDiscountWords(String goodsName,int hour){
int currentYear = 2017;
return "这是一段得花文本";
}
});
}
//模块化代码
public interface DiscountWords{
String getDiscountWords(String goodsName,int hour);
}
public static void showOnBoards(String goodsName,DiscountWords discountWords){
int hour = new Random().nextInt(24);
System.out.println(discountWords.getDiscountWords(goodsName,hour));
}
static class MyDiscountWords implements DiscountWords{
@Override
public String getDiscountWords(String goodsName,int hour){
int currentYear = 2017;
return "这是一段得花文本";
}
}
}
kotlin写法
fun main(){
showonBoard("卫生纸"){goodsName:String,hour:Int->
val currentYear = 2027
"${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}
}
fun showonBoard(goodsName:String,showDiscount:(String,Int)->String){
//1-24区间随机数的最后一个
val hour = (1..24).shuffled().last()
println(showDiscount(goodsName,hour))
}
带let的安全调用
let 返回的是最后一行的结果
fun mian(){
var str:String? = "butterfly"
str = ""
//let 返回的是最后一行的结果
str = str?.let{it->
//非空字符串
if(it.isNotBlank()){
it.capitalize()
}else{
"butterfly"
}
}
println(str)
}
异常处理于自定义异常
fun mian(){
var number:Int? = null
try{
checkOperation(number)
number!!.plus(1)
}catche(e:Exception){
println(e)
}
fun checkOperation(number:Int?){
number?:throw UnskilledException()
checkNotNull(number,{"Something is not good."})
}
}
//自定义异常
class UnskilledException():IllegalArgumentException("操作不当")
replace
val str1 = "The people's Republic of China."
//Regex正则匹配
val str2 = str1.replace(Regex("[aeiou]")){
when(it.value){
"a"->"7"
"e"->"6"
"i"->"9"
"o"->"1"
"u"->"3"
else-> it.value
}
}
== 与===比较
用来检查两个字符串中字符是否匹配,用=检查两个变量是否指向内存堆上同一对象
fun mian(){
val str = "Jason"
val str2 = "Jason"
println(str==str2)->true
//java字符串有一个字符串常量池 变量值相同指向同一个内存
println(str===str2)->true
}
double转Int
fun mian(){
val number1:Int? = "8.98".toIntOrNull()
println(number1)->null
println(8.56756.toInt())->9
println(8.956756.roundToInt())->8.96
val s = "%.2f".format(8.956756)
println(s)->8.96
}
apply
fun main(){
val file1 = File("E://i have a dream_copy.txt")
file1.setReadable(true)
file1.setWriteable(true)
file1.setExecutable(false)
val file2 = File("E://i have a dearm_copy.txt").apply{this:File
setReadable(true)
setWriteable(true)
setExecutable(false)
}
}
let
let 返回的是最后一行的结果
fun main(){
val firstElement = listof(3,2,1).first()
val result = firstElement * firstElement
val result:Int = listOf(3,2,1).first().let{it:Int
it * it
}
println(result)
}
fun formatGetting2(guestName:String?):String{
return if(guestName !=null){
"Welcome,$it."
} ?: "What's your name?"
}
fun formatGreeting(guestName:String?):String{
return guestName?.let{it:String
"Welcome,$it."
} ?: "What's your name?"
}
run
run和apply差不多,但与apply不同,run函数不返回接收者,返回的是lambda结果,也就是true 或者false。不一定(run返回的是lambda表达式返回的结果/最后一行的结果)
fun main(){
val file = File("E://i have a dream_copy.txt")
val result:Boolean = file.run{this:File
readText().contains("great")
}
println(result)
val result2:Boolean = "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的调用方式不同,调用时需要值参作为其第一个参数传入。
fun main(){
val result:Boolean = "The people's Republic of China".run{this:String
length >= 10
}
val result2:Boolean = with("The people's Republic of China"){this:String
length >= 10
}
}
also
also函数和let函数功能相视,和let一样,also也是把接受者作为值参传递给lambda,但有一点不同:also返回接受者对象,而let返回lambda结果。因为这个差异,also尤其适合 针对同一原始对象
fun main(){
var fileContents:List<String>
File("E://i have a dream_copy.txt")
.also{it:File
println(it.name)
}.also{
fileContents = it.readLines()
}
println(fileContents)
}
takeIf
takeIf函数需要判断lambda中提供的条件表达式,给出true或false结果,如果判断结果是true,从takeIf函数返回接受者对象,如果false,则返回null,使用场景:需要判断某个条件是否满足,再决定是否可以赋值变量或执行某项任务
fun main(){
val result:String? = File("E://i have a dream_copy.txt")
.takeIf{ it.exists() && it.canRead() }->true返回File对象 false返回null
?.readText()
println(result)
}
takeUnless
takeIf辅助函数takeUnless,只有判断你给定的条件 结果是false时takeUnless才会返回原始接受者对象。
fun main(){
val fileContents3:String? = File("E://i have a dream_copy.txt")
.takeUnless{ it.isHidden //隐藏不可见 }
?.readText()
println(result)
}
List创建于元素获取
fun main(){
val list:List<String> = listOf("Jason","Jack","Jacky")
println(list.getOrElse(4){"Unknown"})->找不到索引值就返回后面的
println(list.getOrNull(4))->找不到索引值就返回null
println(list.getOrNull(4) ?: "Unknown")
}
可变List集合
fun main(){
val mutableList:MutableList<String> = mutableListOf("Jack","Jason","Jerry")
mutableList.add("Jimmy")
mutableList.remove("Jack")
//List支持toList和toMutableList函数动态实现只读列表和可变列表的相互转换
listOf("Jason","Jack","Jacky").toMutableList()
mutableListOf("Jason","Jack","Jacky").toList()
}
mutator函数
能够修改可变列表的函数有个统一的名字:mutator函数
fun main(){
val mutableList:MutableList<String> = mutableListOf("Jack","Jason","Jacky")
mutableList += "Jimmy"
mutableList -= "Jason"
mutableList.removeIf{ it.contains("Jack") } //列表中如果包含Jack的全部删除
println(mutableList)->[Jimmy] //Jacky也满足条件 会被删除
}
List集合遍历
fun main(){
val list:List<String> = listOf("Jason","jack","Jacky")
for(s in list){
println(s)
}
list.forEach{it:String
println(it)
}
list.forEachIndexed{ index,item ->
println("$index ,$item")
}
}
Set创建于元素获取
通过setOf创建set集合,使用elementAt函数读取集合中的元素。
set集合和list创建的集合的区别:set创建的集合不允许有重复的
fun main(){
val set:Set<String> = setOf("Jason","Jack","Jacky","Jack")
println(set.elementAt(2))
}
集合转换于快捷函数
fun main(){
val list:List<String> = listOf("Jason","Jack","Jacky","Jack")
.toSet()
.toList()
println(list)
//快捷函数 等同于上面的效果
println(listOf("Jason","Jack","Jacky","Jack").distinct())
}
Map创建
fun main(){
val map:Map<String,Int> = mapOf("Jack" to 20,"Jason" to 18,"Jack" to 30)
map(Pair("Jimmy",20),Pair("Jacky",20))
println(map["Jack"])
println(map.getValue("Jack"))
println(map.getOrElse("Rose"){"Unknow"})
println(map.getOrDefault("Rose",0))
}
遍历Map
fun main(){
val map:Map<String,Int> = mapOf("Jack" to 20,"Jason" to 18,"Jack" to 30)
map.forEach{it:Map.Entry<String,Int>
println("${it.key},${it.value}")
}
map.forEach{(key:String, value:Int)->
println("$key, $value")
}
//可变map
val mutableMap:MutableMap<String,Int> = mutableMapOf("Jack" to 20,"Jason" to 18,"Jack" to 30)
mutableMap += "Jimmy" to 30
mutableMap.put("Jimmy",31)
//没有就创建
mutableMap.getOrPut("Rose"){18}
println(mutableMap)
}
field
针对定义的每一个属性,kotlin都会产生一个field,一个getter以及一个setter,field用来存储属性数据,你不能直接定义field,kotlin会封装field,只暴露给getter和setter使用,尽管kotlin会自动提供默认getter和setter方法,但在需要控制如何读写属性数据时,你也可以定义他们
class Player{
var name:String = "jack"
//这里的field就是name值
get() = field.capitalize()
set(value){
//value传入的值
field = value.trim()
}
}
fun main(){
var p = Player()
println(p.name)
p.name = " rose "
println(p.name)
}
计算属性
计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了。
class Player{
val rolledValue
get() = (1..6).shuffled().first()
}
主构造函数
Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始值,在kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以 下划线开头的名字命名
class Player(
_name:String,
_age:Int,
_isNormal:Boolean
){
var name = _name
get() = field.caplize()
private set(value){//私有化setName
field = value.trim()
}
var age = _age
var isNormal = _isNormal
}
fun main(){
var player = Player("Jack",20,true)
player.name = "Rose"//这段报错
}
次构造函数 constructor
有主就有次,对应主构造函数的是次构造函数,我们可以定义多个次构造函数来配置不同参数组合。
class Player(
_name:String,
var age:Int,
var isNormal:Boolean
){
var name = _name
get() = field.caplize()
set(value){
field = value.trim()
}
//次构造方法也会调用主构造方法
constructor(name:String) : this(name,age=10,isNormal = false)
constructor(name:String,age:Int) : this(name,age,isNormal = false){
this.name = name.toUpperCase()
}
}
fun main(){
var player = Player("Jack",20,true)
var player2 = Player("Rose")
var player3 = Player("Jimmy",30)
}
默认参数
定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值
class Player3(
_name:String,
var age:Int = 20,
var isNormal:Boolean
){
var name = _name
get() = field.caplize()
set(value){
field = value.trim()
}
//次构造方法也会调用主构造方法
constructor(name:String) : this(name,age=10,isNormal = false)
constructor(name:String,age:Int) : this(name,age,isNormal = false){
this.name = name.toUpperCase()
}
}
fun main(){
Player3(isNormal = false,_name = "Jack")
}
初始化代码块
初始化块可以设置变量或值,以及执行有效腥检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行
class Player3(
_name:String,
var age:Int = 20,
var isNormal:Boolean
){
var name = _name
get() = field.caplize()
set(value){
field = value.trim()
}
//次构造方法也会调用主构造方法
constructor(name:String) : this(name,age=10,isNormal = false)
constructor(name:String,age:Int) : this(name,age,isNormal = false){
this.name = name.toUpperCase()
}
init{
//先决条件函数 require()条件不满足(为false)走后边的
require(age>0){"age must be positive"}
require(name.isNotBlank()){"Player must have a name."}
}
}
fun main(){
Player3(isNormal = false)
}
初始化顺序
- 主构造函数里声明的属性
- 类级别的属性赋值
- init初始化块里的属性赋值和函数调用
- 次构造函数里的属性赋值和函数调用
class Student(_name:String,val age:Int){
var name = _name
var score = 10
private val hobby = "music"
val subject:String
init{
println("initializing student")
subject = "math"
}
construtor(name:String):this(name,age=10){
score = 20
}
}
对象声明
object ApplicationConfig{
init{
println("ApplicationConfig loading...")
}
fun doSomething(){
println("doSomething")
}
}
fun main(){
//类名,实例名
ApplicationConfig.doSomething()
//ApplicationConfig loading...
//doSomething
println(ApplicationConfig)->使用的是同一个实例
//ApplicationConfig@7ea987ac
println(ApplicationConfig)
//ApplicationConfig@7ea987ac
}
对象表达式
open class Player{
open fun load() = "loading nothing..."
}
fun main(){
val p = object:Player(){
override fun load() = "anoymous nothing."
}
println(p.load())->anoymous nothing.
}
伴生对象
object class ConfigMap{
companion object{
private const val PATH = "xxx"
fun load() = File(PATH).readBytes()
}
}
fun main(){
//无论ConfigMap实例化多少次,伴生对象(companion object)始终只有一份存在
ConfigMap.load()
}
嵌套类
一个类对另一个类有用,将其嵌套到该类中并使这两个类保持在一起是合乎逻辑的,可以使用嵌套类
class Player2(){
class Equipment(var name:String){
fun show() = println("equipment:$name")
}
fun battle(){
Equipment("sharp knife").show()
}
}
fun main(){
Player2.Equipment("AK47").show()
}
数据类
- 数据类,专门设计用来存储数据的类
- 数据类提供toString的个性化实现
- ==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equals和hashCode的个性实现
class Coordinate(var x:Int,var y:Int){
val isInBounds = x>0 && y>0
}
fun main(){
println(Coordinate(10,20))-> Coordinate@987
// == 比较的是内容->equals,Any 默认实现=== 比较对象引用
// ===比较的是引用
println(Coordinate(10,20)== Coordinate(10,20)) ->false
//使用data 修饰的类称为数据类 == 默认比较的是引用值
}
Copy
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"
}
construtor(name:String):this(name,age=10){
score = 10
}
}
fun main(){
val s = Student("Jack")
val p = s.copy()
//copy的一个弊端 不会走construtor 次构造方法里面的函数
print(s) ->Student(name="Jack",age=10,hobby="music",subject="math",score=10)
print(p)->Student(name="Jack",age=10,hobby="music",subject="math",score=0)
}
解构声明
data 修饰的类默认支持结构语法
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")
}
枚举类
enum class Direction{
EAST,
WEST,
SOUTH,
NORTH
}
fun main(){
println(Direction.EAST)->EAST
println(Direction.EAST is Direction)->true
}
枚举类定义函数
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(10,20))
}
代数数据类型
enum class LicenseStatus{
UNQUALIFIED,
LEARNING,
QUALIFIED;
}
class Driver(var status:LicenseStatus){
fun checkLicense():String{
return when(status){
LicenseStatus.UNQUALIFIED->"没资格"
LicenseStatus.LEARNING->"在学"
LicenseStatus.QUALIFIED->"有资格"
}
}
}
fun main(){
println(Driver(LicenseStatus.UNQUALIFIED).checkLicense())
}
密封类
对于更复杂得ADT,可以使用kotlin的密封类(sealed class) 来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活的控制他们(枚举类满足不了 )
//密封类
sealed class LicenseStatus2{
//object修饰得是单例得
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.UnQualified("2333333")
val driver = Driver2(status)
println(driver.checkLicense())
}
接口
kotlin规定所有接口函数和属性实现都是要使用override关键字,接口中定义的函数并不需要open关键字修饰,他们默认就是open的
interface Movable{
val maxSpeed:Int
var whells:Int
fun move(movable:Movable):String
}
class Car(_name:String,override var wheels:Int = 4):Movable{
override var maxSpeed:Int
get() =
set(value){}
override fun move(movable:Movable):String{
}
}
接口默认实现
interface Movable{
val maxSpeed:Int
get() = (0..4).shuffled().last()
var whells: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 val maxSpeed:Int
get() = super.maxSpeed
// set(value){}
override fun move(movable:Movable):String{
}
}
泛型
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",10))
val box2:MagicBox(Dog) = MagicBox(Dog(20))
}
多泛型参数
class MagicBox<T>(item:T){
var available = false
private var subject :T = item
fun fetch():T?{
//takeif true返回对象 false返回null
return subject.takeIf{available}
}
fun <R> fetch(subjectModeFunction:(T)->R):R?{
return subjectModeFunction(subject).takeIf{available}
}
}
class Boy(val name:String,val age:Int)
class Dog(val weight:Int)
class Man(val name:String,val age:Int)
fun main(){
val box1:MagicBox(Boy) = MagicBox(Boy("Jack",10))
val box2:MagicBox(Dog) = MagicBox(Dog(20))
box1.available = true
box1.featch()?.run{this:Boy
println("you find $name")
}
//传入一个类型返回另外一个类型
box1.fetch{it:Boy
Man(it.name,it.age.plus(15))
}
}
可变参数
class MagicBox<T:Human>(vararg item:T){
var available = false
//泛型为可变参数时,必须写out(不准确) -> 存放Human和human的子类
private var subject :Array<out T> = item
fun fetch(index:Int):T?{
//takeif true返回对象 false返回null
return subject[index].takeIf{available}
}
fun <R> fetch(index:Int,subjectModeFunction:(T)->R):R?{
return subjectModeFunction(subject[index]).takeIf{available}
}
}
open class Human(val age:Int)
class Boy(val name:String, age:Int):Human(age)
class Dog(val weight:Int)
class Man(val name:String, age:Int):Human(age)
fun main(){
val box1:MagicBox(Boy) = MagicBox(
Boy("Jack",10),
Boy("Jacky",16),
Boy("John",16)
)
box1.available = true
box1.featch()?.run{this:Boy
println("you find $name")
}
//传入一个类型返回另外一个类型
box1.fetch{it:Boy
Man(it.name,it.age.plus(15))
}
}
[ ]操作符
运算符重载
class MagicBox<T>(vararg item:T){
var available = false
//泛型为可变参数时,必须写out
private var subject :Array<out T> = item
fun fetch(index:Int):T?{
//takeif true返回对象 false返回null
return subject[index].takeIf{available}
}
fun <R> fetch(index:Int,subjectModeFunction:(T)->R):R?{
return subjectModeFunction(subject[index]).takeIf{available}
}
operator fun get(index:Int):T? = subject[index]?.takeIf{available}
}
open
class Boy(val name:String,val age:Int)
class Dog(val weight:Int)
class Man(val name:String,val age:Int)
fun main(){
val box1:MagicBox(Boy) = MagicBox(Boy("Jack",10),)
box1.available = true
box1.featch()?.run{this:Boy
println("you find $name")
}
//传入一个类型返回另外一个类型
box1.fetch{it:Boy
Man(it.name,it.age.plus(15))
}
box1[0]
}
in-逆变 out-协变
out协变,如果泛型类只将泛型类型作为函数的返回(输出),那么使用out可以称之为生产类/接口,因为它主要是用来生产(production)指定泛型对象。
//协变
interface Production<out T>{
//只将泛型类型作为函数的返回
fun porduct():T
}
in逆变,如果泛型只将泛型类作为函数的入参(输入)那么使用in,可以称之为消费者类/接口,因为它主要是用来消费(cunsume)指定泛型对象。
interface Consumer<in T>{
fun consume(item:T)
}
invariant不变
如果泛型类即将泛型类型作为函数参数,又将泛型类型作为函数输出,那么既不用in也不用out
interface ProductionConsumer<T>{
fun product():T
fun consume(item:T)
}
汇总
//协变
interface Production<out T>{
//只将泛型类型作为函数的返回
fun porduct():T
}
//逆变
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 porduct():Food{
println("Product food.")
return Food()
}
}
//快餐商店
class FastStore:Production<FastFood>{
override fun porduct():FastFood{
println("FastFood food.")
return FastFood()
}
}
//汉堡商店
class BurgerStore:Production<Burger>{
override fun porduct():Burger{
println("Burger food.")
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 不写out关键字直接报错(同java)
val production1:Production<Food> = FoodStore()
val production2:Production<Food> = FastStore()
val production3:Production<Food> = BurgerStore()
//父类泛型对象可以赋值给子类泛型对象,用in
val consumer1:Consumer<Burger> = EveryBody()
val consumer2:Consumer<Burger> = ModernPeople()
consumer2.consume(Burger())->"Eat fastFood."
val consumer3:Consumer<Burger> = American()
}
为什么使用in&out
- 父类泛型对象可以赋值给子类泛型对象,用in
- 子类泛型对象可以赋值给父类泛型对象,用out
reified关键字
有时候你可能想知道某个泛型参数具体是什么类型,reified关键字能帮你检查泛型类型,kotlin不允许对泛型参数T做类型检查,因为泛型参数类型会被类型擦除,也就是说,T的类型信息在运行时是不可知的,java也有这样的规则。
class MagicBox<T:Human>(){
//随机产生一个对象,如果不是指定类型的对象,就通过backup函数生成一个指定类型的对象
//这里会报错
/** fun <T> randomOrBackup(backup:()->T):T{
val items:List<Human> = listOf(
Boy("Jack",20),
Man("John",35)
)
val random:Human = items.shuffled().first()
return if(random is T){//这里会直接报错 因为编译时kotlin不知道T具体是什么类型 (反射机制)
random
}else{
backup()
}
} **/
//inline内联 能够提高效率 reified函数必须和inline一起使用
inline fun <reified T> randomOrBackup(backup:()->T):T{
val items:List<Human> = listOf(
Boy("Jack",20),
Man("John",35)
)
val random:Human = items.shuffled().first()
return if(random is T){//这里类型就保留下来了
random
}else{
backup()
}
}
fun <T> randomOrBackup(backup:()->T):T{
val items:List<Human> = listOf(
Boy("Jack",20),
Man("John",35)
)
val random:Human = items.shuffled().first()
return if(random is T){//这里会直接报错 因为编译时kotlin不知道T具体是什么类型 (反射机制)
random
}else{
backup()
}
}
}
open class Human(val age:Int)
class Boy(val name:String,age:Int):Human(age)
class Man(val name:String,age:Int):Human(age)
fun main(){
//val box1:MagicBox<Human> = MagicBox()
val box1:MagicBox<Man> = MagicBox()
//由backup函数,推断出T的类型
val subject:Man = box1.randomOrBackup{
Man("Jimmy",38)
}
}
定义扩展函数
扩展可以在 不修改类的定义的情况下增加类的功能扩展可以用于自定义类,也可以用于List,String,以及kotlin标准库里的其他类,和继承相似,扩展也能共享类行为,在你无法接触某个定义,或者某个类没有使用open修饰符,导致你无法继承它扩展就是增加类功能的最好选择。
//给字符串增加若干个感叹号
fun String.addExt(amount:Int=1) = this+"!".repeat(amount)
//在超类上定义扩展函数,Any所有子类都能使用该函数了
fun Any.easyPrint() = println(this)
fun main(){
println("abc".addExt(2))->"abc!!"
"test".easyPrint() ->"test"
15.easyPrint()->15
}
泛型扩展函数
新的泛型扩展函数不仅可以支持任何类型的接受者,还保留了接受者的 类型信息,使用泛型类型后,扩展函数能够支持更多类型的接受者,使用范围更广了
//给字符串增加若干个感叹号
fun String.addExt(amount:Int=1) = this+"!".repeat(amount)
/**fun Any.easyPrint():Any{
println(this)
return this
} **/
fun <T> T.easyPrint():T{
println(this)
return this
}
fun main(){
//链式调用 使用注释的直接报错
"abc".easyPrint().addExt(2).easyPrint()
}
扩展属性
除了给类添加功能扩展函数外,可以给类定义扩展属性,给String类添加一个扩展,这个扩展属性可以统计字符串里有多少个元音字母。
val String.numVowels
get() = count{ "aeiou".contains(it) }
fun <T> T.easyPrint():T{
println(this)
return this
}
fun main(){
"The People's Republic of China".numVowels.easyPrint()
}
可空类扩展
定义扩展函数用于可空类型,在可空类型上定义扩展函数,你就可以直接在扩展函数体内解决可能出现的空值问题。
fun String?.printDefault(default:String) = print(this ?: default)
fun main(){
val nullableString:String? = null
nullableString.printDefault("abc")
}
infix关键字
infix关键字适用于由单个参数的扩展类和函数,可以让你以更简洁的语法调用函数,如果一个函数定义使用了infix关键字,那么调用它时, 接收者和函数之间的点操作以及参数的一对括号都可以不要
infix fun String?.printDefault(default:String) = print(this ?: default)
fun main(){
val nullableString:String? = null
//加上infix关键字后这里可这样写
nullableString printDefault "abc"
public infix fun<A,B> A.to(that:B):Pair<A,B> = Pair(this,that)
//mapOf("jack" to 18)
//"jack".to(18)
}
定义扩展文件
扩展函数需要在多个文件里面使用,可以将它定义在单独的文件,然后import
package com.jason.kotlin.extension
//这里list/set都可以用都是它的子类
fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()
import com.jaon.kotlin.extension.randomTake
import com.jaon.kotlin.extension.randomTake as randomzer
fun main(){
val list:List<String> = listOf("Jason","Jack","Tom")
val set:Set<String> = setOf("Jason","Jack","Tom")
list.randomTake()
list.randomzer()
}
apply函数详解
//扩展函数
fun String.addExt(amount:int = 1) = this+"!".repeat(amount)
//泛型扩展函数里自带了接受者对象的this隐式调用
fun String.addExt() = "!".repeat(count())->"!".repeat(this.count())
fun <T> T.easyPrint():Unit = println(this)
//T.()->unit
public inline fun <T> T.apply(block:T.()->unit):T{
block()
return this
}
fun main(){
val file:File = File("XXX").apply{this:File
setReadable(true)
}
}
函数式编程
函数式编程 主要依赖于高阶函数(以函数为参数或返回函数)返回数据 这些高阶函数专用于处理各种集合,可方便地联合多个同类函数构建链式操作以创建复杂的计算行为。
变换
- 变换是函数式编程的第一大类函数,变换函数会 遍历集合内容,用一个值参形式 传入的变换器函数,变换每一个元素然后 返回包含已修改元素的集合给链上的其他函数
- 最常用的两个变换函数是map和flatMap
变换函数-map
map变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素,返回结果是包含已修改元素的集合,让作为链上下一个函数的输入。
fun main(){
val animals:List<String> = listOf("zebra","giraffe","elephant","rat")
val babies:List<String> = animals.map{ animal -> "A baby $animal" }
.map{ baby -> "$baby,with the cutest little tail evver!" }
//map返回的集合中元素个数和输入集合必须一样,不过返回新集合里的元素可以是不同类型的
val length:List<Int> = listOf.map{it.length}
println(animals)->["zebra","giraffe","elephant","rat"]
println(babies)->["A baby zebra","A baby giraffe","A baby elephant","rat"]
}
- 可以看到,原始集合没有被修改,map变换函数和你定义的变换器幻术做完事情后,返回的是一个新的集合,这样变量就不用变来变去了
- 事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递
flatMap
flatMap函数操作一个集合的集合,将其中 多个集合中的元素合并后返回一个包含所有元素的单一集合。
fun main(){
val result:List<Int> = listOf(
listOf(1,2,3),
listOf(4,5,6)
).flatMap{ it }
println(result)->[1,2,3,4,5,6]
}
过滤-filter
过滤函数接受一个predicate函数,用它按给定条件检查接收者集合里的元素并给出true或false的判定。如果predicate函数返回true,受检元素就会添加到过滤函数返回的新集合中,如果predicate函数返回false,那么受检元素就被移出新集合。
//过滤集合中元素含有"J"字母的元素
fun main(){
val result:List<String> = listOf("Jack","Jimmy","Rose","Tom")
.filter{ it.contains("J") }
println(result)->["Jack","Jimmy"]
val items:List<List<String>> = listOf(
listOf("red apple","green apple","blue apple"),
listOf("red fish","blue fish"),
listOf("yellow banana","teal banana")
)
val readItems:List<String> = items.flatMap{ it.filter{ it.contains("red") }
println(readItems) -> ["red apple","red fish"]
}
fun main(){
//如果集合中没有元素,则返回true,否则返回false。
val mList1 = arrayListOf(0, 1, 2, 3, 4)
println(mList1.none())//false
//如果集合中没有符合匹配条件的元素,返回true,否则返回false 。
val mList2 = arrayListOf(0, 1, 2, 3, 4)
println(mList2.none { it == 5 })//true
println(mList2.none { it == 4 })//false
//除了1和它本身,不能被任何数整除的数
//取模等于0,说明能够整除,如果没有一个是等于0的,说明是素数
val numbers:List<Int> = listOf(7,4,8,4,3,22,18,11)
val primes:List<Int> = numbers.filter{number->
(2 until number).map{ number % it }
.none(it==0)
}
println(primes)->[7,3,11]
}
合并-zip
合并是函数式编程的第三大类函数,合并函数能 将不同的集合合并成一个新集合这和接受者是包含集合的集合的flatMap函数不同
zip合并函数来合并两个集合,返回一个包含键值对的新集合
fun main(){
val employees:List<String> = listOf("Jack","Jimmy","Jhon")
val ages:List<Int> = listOf(18,20,30)
val test:List<??> = employees.zip(ages)->[(Jack, 18), (Jimmy, 20), (Jhon, 30)]
val employeeAges:Map<String,Int> = employees.zip(ages).toMap()
println(employeeAges["Jack"])->18
}
合并函数-fold
- 另一个可以用来合并值的合并类函数是fold,这个函数 接受一个初始累加值,随后会根据匿名函数的结果更新
fun main(){
//将每个元素值乘以3以后累加起来
val foldedValue:Int = listOf(1,2,3,4).fold(0){accmulator,number->
println("Accmutor value:$accmulator")->0/3/9/18
accmulator + (number *3)
}
println("Final value:$"foldedValue)->30
}
序列
kotlin有内置惰性集合类型叫序列(Sequence),序列不会索引排序它的内容,也不记录元素数目,事实上,在使用一个序列时, 序列里的值可能有无限多因为某个数据源能产生无限多个元素。
fun Int.isPrime():Boolean{
(2 until this).map{it:Int
if(this % it == 0 ){ //说明能被整除 不是素数
return false
}
}
return true
}
fun main(){
//产生1000个素数
//假定0-500之内,可以找到1000个素数
//take(n)执行n次 执行到n时会直接提出不在执行
val totalList:List<Int> = (1..500).toList().filter{it.isPrime()}.take(1000)
println(totalList.size)->670
//从2开始一直累加
val oneTousandPrimes:Sequence<Int> = generateSequence(2){value->
value+1
}.filter{it.isPrime()}.take(1000)
println(oneTousandPrimes.toList().size)->1000
}
JvmName
给kotlin类取一个别名-指定编译类的名字
Hero.kt
@file:JvmName("Hero")
fun makeProclamation() = "Greetings,beast!"
在java中调用
//没有别名状态下
HeroKt.makeProclamation()
Hero.makeProclamation()
JvmField
在java中不能直接访问spells字段,所以必须调用getSpells,你可以给kotlin属性添加@JvmField注解
暴漏他的支持字段给java调用,从而避免使用getter方法
Spellbook.kt
class Spellbook{
//默认会有get/set方法
//使用JvmField修饰后mjava中就可以像在访问kotlin代码一样
@JvmField
val spells = listOf("Magic Ms.","Dartaa")
}
java中调用
public class Jhava{
Spellbook spellbook = new Spellbook();
spellbook.getSpells();
//使用JvmField修饰后
Spellbook spellbook = new Spellbook();
for(String spell:spellbook.spells){
spell
}
}
函数类型操作
在java里,kotlin函数类型使用FunctionN这样的名字的接口来表示,FunctionN中的N代表值参的数数目(参数个数)最多23个,每个FunctionN都包含一个invoke函数
Hero.kt
//添加一个translator函数类型,接受一个字符串
//将其改为小写字母,再大写第一个字母,最后打印结果
//函数类型
val translator = {utterance:String->
println(utterance.toLowerCase().capitalize())
}
//java中调用
Function1<String,Unit> translator = Hero.getTranslator();
translator.invoke("HELLOW")->Hellow