Kotlin 总结分享
如果用一句话总结kotlin,那么就是:更好的java
类型申明
String a = "I an java";
Val a :String = "I an Kotlin"
为什么采用这种风格? 代码的可读性更好
增强的类型推到
val String = "I an Kotlin"
val int = 12
val long = 12L
val float = 13.14f
val double = 12.12
val double = 1.0e2 // 100
类型推导在很大程度上提高了开发效率,当我们使用kotlin的时候,不再需要写大量的类型
声明函数 返回值类型
fun sum(x:Int,y:Int):Int{
return x+y
}
fun sum(x:Int,y:Int) = x+y
val 和var的使用规则
var代表了 变量,val 具有java中的final 关键字的效果,引用不可变,但是其引用的值是可以更改的
val bool = Book() //用val声明的book对象的引用不可变
book.name = "kt"
优先使用val 声明一个本身不可变的变量是一种防御性编码思维,更加安全可靠,不可变的变量意味着更加容易推理,越是复杂的业务逻辑,优势越大,但是如何保证引用对象的不可变?--- 参考集合
高阶函数与lambda
函数试语言一个典型的特征就在于 函数是头等公民 ---- 我们不仅可以像类一样在顶层直接定义一个函数,还可以在一个函数内部定义一个局部函数
fun foo(x:Int){
fun double(y:Int) = y*2
print(double(x))
}
>>> foo(1)
2
此外,我们还可以直接将函数像普通变量一样传递给另一个函数,或在其他函数内被返回
实例:函数作为参数的需求
现有关于国家的数据集List<Country> 如何筛选出欧洲的国家?
data class Country(
val name:String,
val continent:String,
val population:Int
)
class CountryApp{
fun filterCountries(countries:List<Country>):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(c.continent == "EU"){
res.add(c)
}
}
return res
}
}
需求变化一:不仅想要欧洲,还想要亚洲 如何修改
fun filterCountries(countries:List<Country>,continent: String):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(c.continent == continent){
res.add(c)
}
}
return res
}
需求变化二:不仅需要欧洲,亚洲等,还需要筛选一些 具有人口规模的国家
fun filterCountries(countries:List<Country>,continent: String,population: Int):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(c.continent == continent && c.population>=population){
res.add(c)
}
}
return res
}
如果按照现有的修改,更多的筛选条件会作为方法参数不断的累加,而且业务逻辑高度耦合,解决问题的核心在于对这个方法进行解耦合 java做法:传入一个类对象,根据不同的筛选需求创建不同的子类(貌似就是策略模式),对于 kotlin ,支持高阶函数,我们可以把筛选逻辑变成一个方法传入,这种思路更加简单
为了了解高级特性,所以假如有一个新的测试类
class CountryTest{
fun isBigEuropeanCountry(country: Country):Boolean{
return country.continent == "EU" && country.population>1000000
}
}
调用isBigEuropeanCountry 方法就能够判断一个国家是否是一个人口超过百万的欧洲国家,那么怎么才能把这个方法变成 filterCountries 方法的一个参数呢?要解决以下两个问题
· 方法作为参数传入,必须像其他参数一样具备具体的类型信息 (也就是说 需要一个函数类型)
· 需要把isBigEuropeanCountry 的方法引用当作参数 传递给 filterCountries
函数的类型
格式:(Int)-> Unit
左边是参数类型,右边是返回值类型
(a:Int,b:Int) ->Int
(Int ,String) ->String
(Int)->((Int) ->Unit) // 返回类型是一个函数也是可以的
有了函数类型,那么就可以修改filterCountries 方法了
fun filterCountries(countries:List<Country>,filter:(Country)->Boolean):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(filter(c)){
res.add(c)
}
}
return res
}
fun isBigEuropeanCountry(country: Country):Boolean{
return country.continent == "EU" && country.population>1000000
}
虽然已经改造了筛选方法 但是我现在已经有了一个筛选策略(isBigEuroupeanCountry)如何把这个函数传进去呢? 也许你想这么用
filterCountries(countries, isBigEuropeanCountry) // 很遗憾这里第二个参数会报错 ,原因是类型不匹配
//凌乱了,不是说 函数可以当成参数来传递吗,为啥这里不行
方法和成员引用
kotlin 存在一种特殊的语法,通过两个冒号来实现对于某个类的方法进行引用,假如我们有一个CountryTest类的实例对象 countryTest ,如果要引用他的isBigEuropeanCountry方法,就可以这么写
countryTest::isBigEuropeanCountry
在kotlin中函数是头等公民,那么怎么直接对方法引用呢
::isBigEuropeanCountry
所以上面的使用我们就可以这样写
filterCountries(countries, ::isBigEuropeanCountry) // 这里不会再报错
经过这样的重构,程序显然比之前优雅许多,可以根据任意的筛选需求,调用同一个filterCountries 方法来获取国家数据。
匿名函数
继续思考一下筛选方法,每新增一个需求,就需要写一个新的筛选方法,但是很多都是零食性的,不需要被复用,于是匿名函数就派上用场,kotlin支持在缺省函数名的情况下,直接定义一个函数,所以isBigEuropeanCountry方法我们可以直接定义为
fun (country: Country):Boolean{ // 没有函数名字
return country.continent == "EU" && country.population>100000
}
当我们在编译器里这么写一个匿名函数 它会报错 Function declaration must have a name ,又凌乱了,不是可以定义匿名函数吗,为啥又要有名字,可见 匿名函数不是这么用的
那这个匿名函数有啥用? 其实匿名函数是用来传递的,他的使用方式是这样
filterCountries(countries, fun(country:Country):Boolean{ return country.continent == "EU" && country.population>100000})
知道了匿名函数,我们再来看一下lambda是什么,由匿名函数可以知道,由于要传入的匿名函数 早已经在参数中声明了参数和返回值,那么匿名函数中的参数和返回值是不是可以也不要了
fun filterCountries(countries:List<Country>?,filter:(Country)->Boolean):List<Country>{//这里可以推导出传入的参数和返回的类型
val res = mutableListOf<Country>()
for (c in countries!!){
if(filter(c)){
res.add(c)
}
}
return res
}
fun test(){
filterCountries(countries, fun(country:Country):Boolean{ return country.continent == "EU" && country.population>100000})
filterCountries(null, {country -> country.continent == "EU" && country.population>100000 })
}
当我们只保留需要的内容 ,就形成了lambda表达式 {参数变量 -> 返回值}
lambda 的表达是有自己的规则
- 一个lambda必须通过{} 来包裹
- 如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略函数类型声明
val sum:(Int,Int) ->Int = {x:Int,y:Int ->x+y} // 完整写法
val sum = {x:Int,y:Int -> x+y} // 推导类型写法
val sum:(x:Int,y:Int)->Int = {x,y ->x+y} // 省略参数部分类型
- 如果Lambda 变量声明了函数类型,那么Lambda的参数部分类型就可以省略
- 此外 Lambda表达式返回不是Unit,那么默认最后一行表达式的值类型就是返回值类型
一个例子来看匿名函数,Lambda究竟是什么
fun hello(int: Int) = { // 用Lambda初始化了一个函数
print(int)
}
hello(12) // 调用
上述例子会打印12 吗 不会,因为foo(12)它不是函数,也就是说 Lambda 不是函数,他其实是一个对象,我们通过查看其Java代码
private final Function0 hello(final int a) {
return (Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
this.invoke();
return Unit.INSTANCE;
}
public final void invoke() {
int var1 = a + TT2.INSTANCE.getCc();
boolean var2 = false;
System.out.print(var1);
}
});
}
通过把kotlin编译成Java代码发现 hello这个方法里返回了一个Function0 类的匿名内部类的对象,并且其内部有invoke方法,那么我们想让上述调用打印出12 应该这样写
hello(12).invoke // 这个时候会打印出12
通过这个我们发现 匿名函数(简写 后成Lambda)都不是函数,而是对象,由于他们是对象,所以能在函数中当成参数传递,这也就是高阶函数的本质
Function类型
kotlin 在JVM层设计了Function类型(Function0 Function1 ... Function22)来兼容Java的Lambda表达式,后缀数字代表了Lambda参数的数量,每一个Function类型都有一个invoke方法
中缀表达式
kotlin中的中缀表达式有些什么?
for (i in 1..10) print(i) //[1,10]
for( i in 1..10 step 2) println(i) // 1,3,5,7,9
for (i in 10 downTo 1) print(" $i") // 倒叙
for(i in 1 until 10) print(" $i") // [1,10)
val array:IntArray = IntArray(10)
for ((index,value) in array.withIndex()) // 数组index value 一起遍历
以上这些奇特方法 如 in,step, downTo ,until 它们可以不通过点号,而是通过中缀表达式来被调用,从而让语法变得更加简洁直观。
自定义一个中缀表达式
class Person(val name:String,val age:Int)
infix fun Person.vs(person: Person){
when {
age - person.age >0 -> {
print("$name 比 ${person.name} 年长")
}
age == person.age -> {
print("$name 和 ${person.name} 一样大")
}
else -> {
print("$name 比 ${person.name} 小")
}
}
}
val zhansan = Person("张三",18)
val lisi = Person("李四",19)
val wangwu = Person("王五",18)
zhansan vs lisi //张三 比 李四 小
如果把复杂条件写成中缀的形式,会让代码看起来特别简洁
定义中缀函数的条件
- 中缀函数必须是某个类型的扩展函数或成员方法
- 中缀函数只能有一个参数 (有且只有一个参数)
中缀的形式 A 中缀方法 B(比如 张三 vs 李四)
由于to会返回Pair这种键值对的数据结构,因此我们经常会与map结合一起使用
mapOf(1 to "one",2 to "two")
字符串
定义原生字符串 使用"""
val c = """ \nAAA """ // 这里的\n 不会转义 定义html比较方便
val a ="A\nA" //
val c = "A${}B" // 字符串模版写法 {表达式}
网友评论