1.增强的类型推导。(类型推导是Kotlin在Java基础上增强的语言特性之一)
fun main(args: Array<String>) {
val str = "Kotlin"
val numberInt = 123456
val numberLong = 123456L
val numberFloat = 12.45f
val numberDouble = 12.34
val numberDouble2 = 10.1e6
println(str.javaClass.name)
println(numberInt.javaClass.name)
println(numberLong.javaClass.name)
println(numberFloat.javaClass.name)
println(numberDouble.javaClass.name)
println(numberDouble2.javaClass.name)
}
java.lang.String
int
long
float
double
double
2.函数会默认被当成返回Unit类型。所以必须显示声明返回值类型。
fun sum(x: Int, y: Int): Int {
return x + y
}
fun sum2(x: Int, y: Int) {
return 1
}
//报错
The integer literal does not conform to the expected type Unit
3.Kotlin支持单行表达式与等号的语法来定义函数,叫作表达式函数体。
这种情况下可以不声明返回值类型。
fun sum3(x: Int, y: Int) = x + y
但是:当前编译器并不能针对递归函数的情况进行推导类型。
//错误
fun foo(n: Int) = if (n == 0) 1 else n * foo(n - 1)
Type checking has run into a recursive problem. Easiest workaround: specify types of your declarations explicitly
修改如下就可以了:
fun foo(n: Int): Int = if (n == 0) 1 else n * foo(n - 1)
判断是否需要显示声明类型:
如果它是一个函数的参数,必须使用。
如果它是一个非表达式定义的函数,除了返回Unit,其他情况必须使用。
如果是一个递归函数,必须使用。
从代码可读性上建议显示声明类型。
val 和 var的使用规则
val引用不可变,val声明的变量是只读变量,它的引用不可更改,但并不代表其引用对象也不可变。
fun main(args: Array<String>) {
val payBook = PayBook("Kotlin")
payBook.name = "Pay Kolin"
payBook.printName()
}
class PayBook(var name: String) {
fun printName() {
println(this.name)
}
}
Pay Kolin
所以尽可能使用val、不可变对象及纯函数来设计程序。
Kotlin支持一开始不定义val变量的取值,随后再进行赋值。因为引用不可变,val声明的变量只能被赋值一次。
//局部变量
fun main(args: Array<String>) {
val count:Int
count = 1
println(count)
}
var, 因为Kotlin兼容java,所以Kotlin必须支持var。用var声明一个局部变量可以让程序的表达更加直接。
高阶函数和Lambda
Ktolin支持部分函数特性
不仅可以像类一样在顶层直接定义一个函数,也可以在一个函数内部定义一个函数,可以直接将函数像普通变量一样传递给另一个函数,或在其他函数内被返回。
fun copy(x: Int) {
fun double(y: Int): Int {
return y * 2
}
println(double(x))
}
高阶函数:以其他函数作为参数或返回值的函数。
函数作为参数的需求
//data class 声明类一个Country数据类
data class PayCountry(val name: String, val continient: String, val population: Int)
class PayCountryApp {
//该方法用于找出欧洲国家
fun filterCountries(countries: List<PayCountry>): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for (c in countries) {
if (c.continient == "EU") {//EU代表欧洲
res.add(c)
}
}
return res
}
//将洲作为参数传入
fun filterCountries(countries: List<PayCountry>, continient: String): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for (c in countries) {
if (c.continient == continient) {
res.add(c)
}
}
return res
}
//将洲和人口作为筛选条件
fun filterCountries(countries: List<PayCountry>, continient: String, population: Int): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for (c in countries) {
if (c.continient == continient && c.population > population) {
res.add(c)
}
}
return res
}
}
从上面的代码可以看出如果需要增加筛选条件,那么方法参数会不断增加。
所以需要对filterCountries方法进行解耦。所以需要把所有的搜索逻辑抽象成一个参数。
思路一
传入一个类对象是一种解决方法,可以根据不同的筛选需求创建不同的子类,它们各自实现了一个校验方法。
思路二
因为Kotlin支持高阶函数,就可以把筛选的逻辑变成一个方法传入。
class CountryTest {
fun isBigEuropeanCountry(country: PayCountry): Boolean {
return country.continient == "EU" && country.population > 1000
}
}
尝试将isBigEuropeanCountry变为一个参数。
Kotlin 中的函数类型
例:
(Int) -> Unit
Kotlin中的函数声明需要遵循以下几点:
1.通过->符号来组织参数类型和返回值类型,左边是参数类型,右边是返回值类型;
2.必须用一个括号来包裹参数类型;
3.返回值类型即使是Unit,也必须显示声明。
如果是一个没有参数的函数类型,参数类型部分就用()来表示。
()-> Unit
多个参数的情况下,需要使用逗号来进行分割。
(Int, String) -> Unit
Kotlin支持为声明参数指定名字
(errCode: Int, errMsg: String) -> Unit
因为函数类型的变量也是可选的
((errCode: Int, errMsg: String?) -> Unit)?
高阶函数还支持返回另一个函数。
例:
传入一个类型为Int的参数,然后返回另一个函数,类型为(Int) ->Unit的函数
(Int) -> ((Int) -> Unit)
后半部分的括号可以省略掉
(Int) -> Int -> Unit
修改为传入一个函数类型的参数,返回一个Unit
((Int) -> Int) -> Unit
重新定义filterCountries方法
//增加一个函数类型的参数
fun filterCountries(countries: List<PayCountry>, test: (PayCountry) -> Boolean): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for(c in countries) {
if(test(c)){ // 直接调用test来进行帅选
res.add(c)
}
}
return res
}
方法引用表达式
方法和成员引用
Kotlin 通过两个冒号来实现对于某个类的方法进行引用。
例:定义一个类的构造方法引用变量。
//getBook的类型为(name:String)->Book
fun main(args: Array<String>) {
val getBook = ::PayBook
println(getBook("Kotlin Book").name)
}
Kotlin Book
如果我们需要引用某一个类中的成员变量,例如PayBook类中name
PayBook::name
//上面创建的是类型为(PayBook)->String
如果name是private属性,就不可以像上面这样写。
val bookNames = listOf(PayBook("6666"), PayBook("7777")).map(PayBook::name)
println(bookNames)
[6666, 7777]
//使用方法引用传递参数
fun main(args: Array<String>) {
val payCountryApp = PayCountryApp()
val countryTest = CountryTest()
val countries = listOf<PayCountry>(PayCountry("A", "EU", 40000), PayCountry("B", "UE", 40000))
val countriesAfterFilter = payCountryApp.filterCountries(countries, countryTest::isBigEuropeanCountry)
println(countriesAfterFilter)
}
[PayCountry(name=A, continient=EU, population=40000)]
匿名函数
Kotlin支持在缺省函数名的情况下,直接定义一个函数。
payCountryApp.filterCountries(countries, fun(country: PayCountry): Boolean {
return country.continient == "EU" && country.population > 10000
})
Lambda表达式可以理解成是简化表达后的匿名函数。用->把参数和返回值连接在一起。
例:
val filterCountries1 = payCountryApp.filterCountries(countries, { payCountry ->
payCountry.continient == "EU" && payCountry.population > 1000
})
Lambda表达式总结
1.一个Lambda表达式必须通过{}来包裹;
val paySum: (Int, Int) -> Int = {x:Int, y:Int -> x + y}
2.如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么 Lambda变量就可以省略函数类型声明。
val paySum = { x: Int, y: Int -> x + y }
3.如果Lambda变量声明了函数类型,那么Lambda 的参数部分的类型就可以省略。
val paySum: (Int, Int) -> Int = { x, y -> x + y }
4.如果Lambda表达式返回的不是Unit,那么默认最后一行表达式的值类型就是返回类型。
例:
val payFool = { x: Int ->
val y = x + 1
y
}
val payFool2 = {
val payCountry = PayCountry("zy", "EU", 666)
payCountry
}
如果使用fun关键字来声明Lambda表达式
例子:
fun payFool3(a: Int) = {
println(a)
}
//结果是打印不出任何东西的
listOf<Int>(1,2,3).forEach{
payFool3(it)
}
// 两者等价
//默认情况下,直接使用it来代表item,而不需要用item->声明
listOf<Int>(1,2,3).forEach{
item->
payFool3(item)
}
1.单个参数的隐式名称
上面的it是Kotlin 简化Lambda表达的一种语法糖,叫作单个参数的隐式名称。代表了这个lambda所接收的单个参数。上面的例子是打印不出任何例子的。
Function 类型
Kotlin 在JVM 层设计了Function 类型,(Function0、Function1......Function22)来兼容 java的lamdba表达式,其后缀数字代表了Lambda参数的数量。
设计Function类型的主要目的之一就是要兼容Java,实现在Kotlin中也能调用java的Lambda。在Java中,实际上不支持把函数作为参数,而是通过函数式接口
来实现这一特性。所以如果我们要把 java的Lambda传给Kotlin,那么它们必须实现Kotlin的Function接口。
invoke方法
例
实际上调用payFool3(it)仅仅是创造了一个Function0对象,所以需要调用Function0的invoke 方法才能执行print方法。
listOf<Int>(1,2,3).forEach{
payFool3(it).invoke()// 增加invoke调用
}
其实也是可以通过括号调用来替代invoke。
listOf<Int>(1,2,3).forEach{
payFool3(it)()
}
函数、 Lambda和闭包
fun可以声明函数、Lambda表达式,如何区分?
1.fun在没有等号、只有花括号的情况下,是我们最常见的代码块函数体,如果返回非Unit值,必须带return。
fun sum(x: Int, y: Int): Int {
return x + y
}
2.fun带有等号,是表达式函数体。该情况下可以省略return
fun sum(x:Int, y:Int) = x + y
3.不管是用val还是fun,如果是等号+花括号的语法,那么构建的就是一个Lambda表达式,Lambda的参数在花括号内部声明。所以如果左侧是fun,
那么就是Lambda表达式函数体,也必须通过()或invoke来调用Lambda。
val payFool4 = { x: Int, y: Int -> x + y }
println(payFool4.invoke(5,6))
println(payFool4(5,6))
fun payFool5(x: Int) = { y: Int -> x + y }
println(payFool5(5).invoke(6))
println(payFool5(5)(6))
在Kotlin中,发现匿名函数体、Lambda(以及局部函数、object表达式)在语法上都存在“{}”,由这对花括号包裹的代码块如果访问了外部环境变量则被称为一个闭包。
一个闭包可以被当作参数传递或者直接使用,它类似于“访问外部变量的函数”。
与java不同的是,Kotlin中的闭包不仅可以访问外部变量,还能够对其进行修改。
var total = 0
listOf<Int>(1,2,3).filter { it > 0 }.forEach {
total +=it
}
println(total)
6
Kotlin支持一种自运行的Lambda语法
val test = { x: Int -> println(x) }(1)
函数式编程 “柯里化”,就是函数作为返回值的典型应用。
一个函数返回另一个函数
fun paySum(x: Int) = { y: Int -> x + y }
//等价,执行完paySum函数后,会返回一个类型为(Int)->Int的函数
fun paySum2(x: Int): (Int) -> Int {
return { y: Int -> x + y }
}
简单来说,柯里化指的是把接收多个参数的函数变成一系列仅接收单一参数函数的过程,在返回最终结果之前,前面的函数依次接收单个参数,然后返回下一个新函数。
fun paySum3(x: Int, y: Int, z: Int) = x + y + z
//按照柯里化的思路重新设计
fun paySum4(x: Int) = { y: Int ->
{ z: Int ->
x + y + z
}
}
println(paySum4(3)(2)(1))
6
在Lambda表达式中,还存在一种特殊的语法,如果一个函数只有一个参数,且该函数为函数类型,那么在调用函数时,外面的括号就可以省略。
例
fun omitParentheses(block: () -> Unit) {
block()
}
omitParentheses {
println("6666")
}
6666
如果参数不止一个,且最后一个函数为函数类型时,就可以采用类似柯里化的调用:
fun currying(content: String, block: (String) -> Unit) {
block(content)
}
currying("currying style") {
content->
println(content)
}
//等价
currying("currying style", {
content->
println(content)
})
currying style
Kotlin的一个特性——扩展函数
Kotlin中的扩展函数允许我们在不修改已有类的前提下,给它增加新方法。
fun View.invisible() {
this.visibility = View.INVISIBLE
}
类型View被称为接受者类型,this对应的是这个类型所创建的接收者对象。this可以被省略。
fun View.invisible() {
visibility = View.INVISIBLE
}
基于扩展函数的语法,我们可以对Array<T>类型增加一个方法。由于Kotlin的特殊语法支持,定义普通多参数函数的形式。
fun <A, B> Array<A>.corresponds(that: Array<B>, p: (A, B) -> Boolean): Boolean {
val i = this.iterator()
val j = that.iterator()
while (i.hasNext() && j.hasNext()) {
if (!p(i.next(), j.next())) {
return false
}
}
return !i.hasNext() && !j.hasNext()
}
val a = arrayOf(1, 2, 3)
val b = arrayOf(2, 3, 4)
println(a.corresponds(b) { x, y ->
x + 1 == y
})
println(a.corresponds(b) { x, y ->
x + 2 == y
})
true
false
面向表达式编程
表达式可以是一个值、常量、变量、操作符、函数,或者它们之间的组合,编程语言对其进行解释和计算,以求产生另一个值。
表达式就是可以返回值的语句。
1//单纯的字面量表达式,值为1
-1//增加前缀操作符,值为-1
1+1//加法操作符,返回2
listOf(1,2,3)//列表表达式
"kotlin".length //值为6
Kotlin中复杂的表达式例子
{x:Int -> x + 1}//Lambda表达式,类型为(Int) -> Int
fun(x: Int) { println(x))}//匿名函数表达式,类型为(Int) -> Unit
if(x > 1) x else 1//if-else表达式,类型为 Int,假设x已赋值
与Java的函数不同,Kotlin中所有的函数调用也都是表达式。
Unit类型:让函数调用皆为表达式
因为Java 中存在void ,所以java中的函数调用不全是表达式。Java中如果声明的函数没有返回值,那么它需要使用void来修饰。因为此时不存在值和类型信息,所以
此时不能算作表达式。
在kotlin中,函数在所有的情况下都具有返回类型,所以引入了Unit代替Java中的void关键字。
void 与 Void
在描述void的时候,需要注意首字母的大小写。Java在语言层设计了一个Void类。 java.lang.Void类似java.lang.Integer,Integer是为了对基本类型int的实例进行装箱操作,
Void对应void,由于void表示没有返回值,所以Void并不能具有实例。它继承自Object。
Unit和int一样,是一种类型,然而它不代表任何信息,用面向对象的术语来描述就是一个单例,它的实例只有一个,可写为()。
Kotlin 引入Unit的原因?
函数式编程侧重于组合,尤其是很多高阶函数,在源码实现的时候都是使用泛型实现的。然而void在涉及泛型的情况时会出现问题。
Java不天然支持函数时头等公民,模拟一种函数类型。
interface Function<Arg, Return> {
Return apply(Arg arg);
}
Function<String, Integer> strLength = new Function<String, Integer>() {
@Override
public Integer apply(String arg) {
return arg.length();
}
};
int result = strLength.apply("hello");
如果只是需要实现一个print 方法,此时Return的类型不能使用void,只能用Return换成Void,由于Void没有实例,则返回一个null。
Function<String, Void> payPrint = new Function<String, Void>() {
@Override
public Void apply(String s) {
return null;
}
};
Java8实际解决方法是通过引入Action<T>这种函数式接口来解决问题。最好解决方法就是引入一个单例类型Unit.
复合表达式,表达式组合
1.try 在Kotlin中也是一个表达式,try/catch/finally语法的返回值类型由try或catch部分决定,finally不会产生影响。
2.在Kotlin中,if-else很大程度上替代了传统三元运算符的做法。
Kotlin中的"?:"
虽然kotlin中没有采用三元运算符,但存在"?:"的语法,注意这里的问号和冒号必须放在一起使用。由于 kotlin可以用“?”来表示一种类型的可空性,
所以可以使用 “?:”来给一种可空类型的变量指定为空情况下的值。
fun main(args: Array<String>) {
val maybeInt: Int? = null
println(maybeInt ?: 1)
}
1
枚举类和when表达式
1.枚举是类
在Kotlin中,枚举是通过一个枚举类实现的。
enum + class + 类名
enum class Day {
MON,
TUE,
WEN,
THU,
FRI,
SAT,
SUN
}
由于是一种类,就可以有构造函数
enum class DayOfWeek(val day: Int) {
MON(1),
TUE(2),
WEN(3),
THU(4),
FRI(5),
SAT(6),
SUN(7); //如果以下有额外的方法或属性定义,则必须强制加上分号
fun getDayNumber(): Int {
return day
}
}
需要注意的是,当在枚举类中存在额外的方法或属性定义,必须强制加上分号。
2.用when来代替if-else,可以使用when来优化if-else
fun schedule(sunny: Boolean, day: Day) = when (day) {
Day.MON-> println(Day.MON)
Day.THU-> println(Day.THU)
else -> when { //when关键字的参数吧可以省略。该情况下,分支->的左侧部分需要返回布尔值。
sunny -> println("sunny")
else -> println("rainy")
}
}
when表达式具体语法
1.一个完整的when 表达式由when关键字开始,用花括号包含多个逻辑分支,每个分支通过->连接,此时不需要break,直到匹配完为止。否则执行else分支(类似switch的
default)。
2.每个逻辑分支都具有返回值,并且可以不同。
fun payFoo(a:Int) = when (a) {
1->1
2->2
else -> 0
}
可以减少when表达式的嵌套。
fun schedule(sunny: Boolean, day: Day) = when {
day == Day.MON -> println(Day.MON)
day == Day.THU -> println(Day.THU)
sunny -> Day.FRI
else -> 1
}
for循环和范围表达式
1.for循环
kotlin中for循环更加简洁
for(i in 1..10) {
println(i)
}
//等价
for(i: Int in 1..10) {
println(i)
}
2.范围表达式
Range表达式通过rangeTo函数实现,通过“..”操作符与某种类型的对象组成,除了整型的基本类型之外,该类型需要实现java.lang.Comparable接口。
String类实现了Comparable接口,字符串之间可以比较大小。
kotlin提供step函数来定义迭代的步长。
for(i in 1..10 step 2) {
println(i)
}
downTo 实现倒序
for(i in 10 downTo 1 step 2) {
println(i)
}
until函数实现一个半开区间
for(i in 1 until 10) {
println(i)
}//不包含10
3.用in 检查成员关系
kotlin用in关键字来对检查一个元素是否是一个区间或集合中的成员。
println("a" in listOf<String>("b", "c")) false
in前面加上!表示相反的结果。
println("a" !in listOf<String>("b", "c"))
in结合范围表达式使用
println("kot" in "abc".."xyz")
//两者等价
println("kot">="abc" && "abc"<="xyz")
任何提供迭代器(iterator)的结构都可以使用 for语句进行迭代
val array = arrayOf(1, 2, 3, 4, 5)
for (i in array) {
println(i)
}
通过调用withIndex方法,提供一个键值元组。
val array = arrayOf(1, 2, 3, 4, 5)
for((index, value) in array.withIndex()) {
println("element at $index is $value")
}
element at 0 is 1
element at 1 is 2
element at 2 is 3
element at 3 is 4
element at 4 is 5
中缀表达式
例如:in、step、downTo、until
kotlin标准库中一个类似的方法to的设计:
infix fun <A, B> A.to(that: B): Pair<A,B>
to这种形式定义的函数称为中缀函数。
A 中缀方法 B
定义一个中缀函数需要满足的条件:
1.该中缀函数必须是某个类型的扩展函数或者成员方法。
2.该中缀函数只能有一个参数。
3.虽然kotlin的函数支持默认值,但中缀函数的参数不能有默认值,否则以上形式的B会缺失,对中缀表达式的语义造成破坏。
4.参数数量始终为1,不能是可变参数。
函数可变参数
kotlin 通过varargs关键字来定义函数中的可变参数,Java中的可变参数必须是最后一个参数,kotlin中没有这个限制,两者都可以在函数体中以数组的方式来使用
可变参数变量。
例:
fun printLetters(vararg letters: String, count: Int) {
println("$count letters are")
for(letter in letters) {
println(letter)
}
}
可以使用*(星号)来传入外部的变量作为可变参数的变量
var letters = arrayOf("a", "b", "c")
printLetters(*letters, count = 3)
自定义中缀表达式
called方法用infix进行修饰,中缀方法也可以使用普通的语法习惯进行调用。
class PayPerson {
infix fun called(name: String) {
println("My name is $name")
}
}
val p = PayPerson()
p called "ZY"
p.called("LC")
My name is ZY
My name is LC
参考Kotlin核心编程