之前搞过一小段时间的Swift,发现Kotlin和Swift很像,哦,不,应该是Swift和Kotlin很像,Kotlin早在2010年就发布了。Swift是后来者。
一. 第一篇
- 三元表达式
Kotlin不能使用三元表达式,可以用一行if else来替代,看起来更简单易懂
fun > max(a:T,b:T) : T {
return if (a > b) a else b //相当于Java的(a > b) ? a : b
}
方法体里的if else 和java里的三元表达式是等价的
还等价于:
fun > max(a:T,b:T) : T = if(a > b) a else b
因为方法体里就是直接return了这个表达式,而表达式是有值的
只有表达式才能忽略返回值。对于有一个返回值的块主体的函数来说,你必须制定返回类型并且显式的写上return声明。
- Kotlin和Swift中有几个小区别:
- kotlin方法是用 fun 关键字,Swift用 func
- kotlin方法返回值用 ** : ** (冒号)隔开,Swift用 ** -> ** (箭头)
- kotlin中不可变的用 val ,而 Swift用 ** let **. 可变的两者都用 var
- 和其他现代语言一样,你不需要在每条语句后面写上分号
- 方法函数和其他一样,是一等公民。
- Kotlin中大部分控制结构都是表达式而不是声明,这就意味着有值
在Kotlin中,if 是一个表达式,并不是一个声明。两者的区别在于,表达式有值。它可以用作另一个表达式的一部分。然而,一个声明却总是闭合块中的一个顶层元素,而没有自己的值。在Java中,所有的控制结构都属于声明(statement)。而在Kotlin中,循环以外的大多数控制结构都是表达式。正如你在书中后续将会看到的那样,将控制结构和其他表达式结合起来的能力让你更精简地表达许多常见的模式。 另一方面,赋值在Java中是表达式,但在Kotlin中却是声明。这有助于避免比较和赋值之间的困惑。而这种困惑是错误的常见源头。
- Kotlin中的数组就是普通的类,并不像java那样有特殊的语法(比如中括号)来表示。比如一个String数组 Array
. Swift淡化了Java中List和数组的概念,统一称之为数组,且用Array类来表示。 - Kotlin变量的声明总是以val 或 var 作为开始的。可以忽略类型或者显式的写上类型。
例如:
val answer = 25
var length:Int = 120
- 在Kotlin中public是默认的可见性,因此,你也可以忽略它。
- Kotlin中的enum 后面需要跟上class,即** enum class **,Java里并不需要class关键词。但是这里我觉得kotlin更规范点,因为枚举本身就是一个类。
- Kotlin中的when. 首先要知道 when 和 if 一样是个表达式 是有值的。
- 如果if分支里只有一个表达式,闭合的括号是可选的。如果if分支是一个代码块,最后的一句表达式作为结果返回.例子请见上面的max方法.
- when 的几个例子:
interface Expr
class Num(val number: Int):Expr
class Sum(val left:Expr,val right:Expr):Expr
fun eval3(expr: Expr): Int =
when (expr) {
is Num -> expr.number
is Sum -> eval3(expr.left) + eval3(expr.right)
else -> throw IllegalArgumentException("unknown expr: $expr")
}
kotlin
enum class Color(val r:Int,val g:Int,val b:Int){
RED(255, 0, 0), ORANGE(255, 265, 0), // 当每个常量被创建时指定属性值
YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
INDIGO(75, 0, 130), VIOLET(238, 130, 238); // 分号(;)在这里是必须的.
// 这个示例中展示了Kotlin语法中唯一 一处需要你使用分号的地方:如果你在枚举类中定义了任何方法,(请使用)分号将枚举常量列表从函数定义中分隔开来
fun rgb() = (r * 256 + g) * 256 + b //在enum中定义了一个方法
}
fun getMnmonic(color: Color) : String =
when(color){
Color.ORANGE,Color.YELLOW,Color.RED -> "暖色"
Color.GREEN -> "自然"
Color.BLUE,Color.INDIGO,Color.VIOLET -> "cold"
}
fun mixColor(color1: Color,color2: Color):String =
when (setOf(color1, color2)) {
setOf(Color.RED,Color.YELLOW) -> "橙色"
setOf(Color.BLUE,Color.YELLOW) -> "绿色"
setOf(Color.BLUE,Color.VIOLET) -> "紫蓝色"
else -> throw Exception("Dirty Color")
}
fun mixOptimized(color1: Color,color2: Color) =
when{
(color1==Color.RED && color2 == Color.YELLOW) ||
(color1 == Color.YELLOW && color2 == Color.RED) -> Color.ORANGE
(color1 == Color.BLUE && color2 == Color.YELLOW) ||
(color1 == Color.YELLOW && color2 == Color.BLUE) -> Color.GREEN
(color1 == Color.BLUE && color2 == Color.VIOLET) ||
(color1 == Color.VIOLET && color2 == Color.BLUE) -> Color.INDIGO
else -> throw Exception("unknown mixed of color $color1 $color2")
}
```
- “代码中的最后一个表达式就是结果”这个规则在所有可以使用代码并需要一个(返回)结果的地方都是有效的。同样的规则对try代码块catch从句有效。
- Kotlin中的范围是闭合的或者说是包含的。这意味着第二个值也始终是范围的一部分。
val oneToTen = 1..10 //1、2、3、4、5、6、7、8、9、10
这里插一句,Swift和Kotlin是不一样的,Swift总是左闭右开,而Kotlin始终是左右闭合。
- 自从Kotlin引入了 for( (index,element) in arrays.withIndex() ) 就不是很需要for i循环了。
for ((index, element) in list.withIndex()) {
println("index: $index : $element")
}
甚至可以这样:
for ((index, element) in (0..9).withIndex()) {
println("index: $index : $element")
}
如果你还是放不下for i循环的话,建议使用step关键词(步长,可以为负值哦,正值则每次加多少,负值则每次减多少)
//降序,从20开始一直降到1,每次降2步,即 20,18,16。。4,2 (为什么没有取到1呢,那是因为2-2=0,比1小了,而downTo 1,最小只能为1)
for (i in 20 downTo 1 step 2) {
println("$i : ${fizzBuzz(i)}")
}
//从2开始,每步加2,所以序列为 2,4,6,8,,18,20
for (i in 2..20 step 2) {
println("$i : ${fizzBuzz(i)}")
}
//没什么好说的,下面这个序列为2,4,6,,16,18(为什么取不到19呢,因为18+2=20 > 19)
for (i in 2..(20-1) step 2) {
println("$i : ${fizzBuzz(i)}")
}
//until关键词和上面的..数组范围类似,也是闭合区间。2,4,6,8,,,16,18,20
for (i in 2 until 20 step 2) {
println("$i : ${fizzBuzz(i)}")
}
- 在Kotlin中,类不能有静态成员。Kotlin里没有static关键词,不在class里的方法就是顶层函数,他就是静态方法。Java里可以这样引用: import 顶层方法所在的包名.顶层方法所在的文件名 然后就可以愉快的调用了。
/* Java */
import strings.JoinKt;
...
JoinKt.joinToString(list, ", ", "", "");
- Kotlin中的const关键词修饰的val 属性相当于Java的public static final
@file:JvmName("StringFunctions") // 1 指定类名的标注
package strings // 2 包声明跟在文件标注后面
const val UNIX_LINE_SEPARATOR = "\n"
//Java调用
StringFuntions.UNIX_LINE_SEPARATOR
/* const相当于Java的static final */
public static final String UNIX_LINE_SEPARATOR = "\n";
val description = "StringFunctions class description"
/* Java */
StringFunctions.getDescription()
Kotlin中的const只能修饰基础数据类型。
- 扩展函数不会因为类的继承关系而被覆盖:Kotlin以静态解析它们。
注意 如果类有一个成员函数跟一个扩展函数有着相同的签名,成员函数总是优先的。当你扩展类的API时,你应该记住这一点:如果你添加了一个跟你已定义类的客户端(调用者)的扩展函数具有同样的签名成员函数,同时客户端随后重新编译了他们的代码,它将会改变它的含义并开始指向一个新的成员函数。
一句话总结:就是如果签名相同的话,类的成员函数是优先的。
- Kotlin中class默认都是final,除非在class前面加上 open 关键词,否则这个类是不能继承的;类中的方法默认也是final的(哪怕你的类是open的,方法默认也是final的)如果想被子类重写,也要加上 open 关键词。override的方法默认就是open的,不需要加上 open 关键词(加上也没副作用)。对于final类(不加open的就是final类),在任何方法前加入open关键词是没有意义的,因为类都不能被继承,方法即使能被重写又有什么意义。
Kotlin中默认访问修饰符是public的,你再也不用到处写public了,仅仅在那些你需要保护的地方写非public修饰符。 - 在一个中缀调用,方法的名字 被放在目标 对象名 和 参数 之间,而且没有其他的分隔符。
中缀调用可以用于带一个参数的常规方法和扩展函数。为了使得函数可以使用中缀标记来调用,你需要用infix修饰符来标记它
infix fun Any.to(t:T) = Pair(this,t)
你可以为两个变量直接分配一对元素:
val (number, name) = 1 to "one"
这个特性叫做析构声明(destructuring declaration)
析构声明这一特性并不局限于元组。举个例子,你也可以为两个独立的变量key和value分配一个映射集合.这一特性对于循环也是有效的
for ((index, element) in collection.withIndex()) {
println("$index: $element")
}
- 本地函数:就是函数中嵌套函数。 嵌套的函数可以访问父函数的所有参数。
- Kotlin任何属性都必须初始化,即使是可空类型,也必须初始化成null或其他值,不允许不初始化。
- 嵌套类(不保存外部类的引用)与内部类(保存外部类的引用)
好了,先了解了嵌套类与内部类这个名词的区别。
Java中内部static类就是嵌套类,不引用外部类。没有static的类就是普通内部类,是引用外部类的。
Kotlin则不同,他没有static关键词,加了inner关键词的类才是内部类,才会引用外部类。而不加inner的嵌套类则不引用外部类。 - 可见性修饰符有助于限制访问你的代码中的声明。通过限制类实现细节的可见性,你可以确保你改变实现细节但不会有破坏依赖代码的风险。基本上,Kotlin中的可见性修饰符跟Java中的很相似。你会遇到同样的public, protected和private修饰符。但是默认的可见性是不同的:如果你省略了修饰符,(默认的)声明将会是public。
Java中的默认可见性package-private并不会在Kotlin中出现。Kotlin把包仅仅作为命名空间中的代码的一种组织方式,并没有用于可见性控制。
作为一个可选方案,Kotlin提供了一个新的可见性修饰符:internal,它意味着'模块内可见'。
一个模块是一组Kotlin文件编译在一起组成的。它也可以是一个IntelliJ IDEA模块、一个Eclipse项目、一个Maven或者Gradle项目,又或者Ant任务调用所编译的一组文件。 internal可见性的优势是它为你的模块的实现细节提供了实际的封装。使用Java,封装性很容易被破坏。因为外部代码可以在你所使用的同一个包内定义类,并由此获得包内声明的访问权。 更多跟Java的差异来自Kotlin在类的外部定义函数和属性的能力。你可以把这样的声明标记为private。这意味着“在包含文件内部可见”。如果一个类应该仅在一个文件中使用,你也可以让它私有。
注意Java和Kotlin的protected修饰符的表现差异。在Java中,你可以从同一个包中访问一个protected成员。但是Kotlin并不允许这样做。在Kotlin中,可见性规则是简单的。一个protected成员仅在类及其子类中可见。也要注意类的扩展函数并不能访问它的私有和保护成员。
修饰符 | 可见性 | 顶层声明 |
---|---|---|
public(默认) | 所有可见 | 在任何地方都能访问 |
internal | 模块可见 | 模块内可见 |
protected | 在子类可见 | 不可见 |
private | 类内部可见 | 同一个Kotlin文件可见 |
总结一下:protected与java中的是不一样的,他仅仅只能用于成员属性或函数,代表其只能被子类访问,不能用于class前。protected class A 在kotlin中是编译不通过的。如果你想限制类的访问性,请根据需要选择internal和private关键词。java中是没有private class的,但是kotlin中可以这么做,private class B 在kotlin中并不奇怪,private class在kotlin中代表的是在一个同一个.kt文件中这个class是可访问的,类中成员仅仅在类里面可访问.
二、 第二篇
- 老铁,重磅来了,比较对象是否相等不需要用equals了,可以用 ** == **了,就像比较int数字一样。(kotlin中的 ** == ** 比较的不是实例的引用地址)
Kotlin底层调用的仍然是equals.对于引用比较,kotlin使用 == 与Java中的 == 是一样的。最后,切记要在类中覆盖equals方法哦,否则还是只是比较引用地址。Kotlin只是将默认的==比较从比较引用地址改为equals了而已。
- 流弊特性:装饰者模式 *** by *** 关键词
class DelegatingCollection(innerList:Collection = ArrayList()) : Collection by innerList
一个简单的*** by **关键词将DelegatingCollection继承的Collection
- 更流弊的 object 关键词来了。此 *** object *** 非Java中的 ** Object 。(Java中的Object相当于Kotlin中的Any**)
object这个关键词定义一个类并且同时创建了这个类的一个实例
伴生对象*** companion ***:一个放置工厂函数和静态成员的地方。伴生对象拥有访问类的所有私有成员的权限。它是工厂模式的一个理想候选方案。伴生对象是声明在一个类中的常规对象。它可以被命名、可以实现一个接口,或者拥有扩展函数或熟悉。(注意是可命名 也可以不命名。如果你忽略了伴生对象的名字,分配给它的默认名称是Companion)
class A {
companion object {
fun bar() {
println("Companion object called")
}
}
}
//调用
A.bar()
//命了名的伴生对象
class Person(val name: String) {
companion object Loader {
fun fromJSON(jsonText: String): Person = ...
}
}
//调用
Person.Loader.fromJSON("xxxxxxxxxx")
- 对象表达式:匿名内部类的另一种表达方式
object关键字不仅可以被用在声明像单例那样的命名对象,也能用来声明匿名对象(anonymous objects)。匿名对象取代了Java中使用的匿名内部类。跟Java的匿名类一样,对象表达式中的代码可以访问创建它的函数中的变量。但跟Java不同的是,这并不局限于final变量。你也可以从一个对象表达式的内部修改变量的值。
fun countClicks(window: Window) {
var clickCount = 0 // 声明一个本地变量
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++ // 更新变量的值
}
})
// ...
}
- Kotlin 集合操作之 flatten
val list = listOf(listOf("Str1","Str3"), listOf("Str4","Str2"))
val listsOfList = list.flatten()
println("$list \n $listsOfList")
/**输出结果
[[Str1, Str3], [Str4, Str2]]
[Str1, Str3, Str4, Str2]
*/
flatten就是平铺,和flatmap类似,但是又不太一样,flatmap需求将每个element转换成一个list之后再平铺。而flatten是直接平铺,因为flatten所在的对象的element本身就是list了
- 可空对象扩展函数。 例子:
fun String?.isNullOrBlank() = this==null || this.isBlank()
- lateinit 关键词只能修饰** var 的变量,因为后面要对这个延迟初始化变量进行初始化。如果尚未初始化这个变量就去访问这个肯定会抛错(类似NPE)
这个特性适合用在依赖注入框架。例如Android中的Dagger一般在onCreate里注入,而非nullable的变量必须在构造函数执行完毕之前 完成 非空变量的初始化,所以lateinit就派上用场了,可以让其延迟在onCreate里进行初始化。 - ?.let 方法,类似Swift中的拆包,是将可空对象拆包成不可空对象(如果这个对象不是null的话),这样就能避免一大堆的 ?. 的语法。
fun testLetFun() {
getBestStudent()?.let {
student -> //这里的student变量其实可以省略,省略后下面的student都应该写成it,it指代的是当前所处花括号里的拆包对象。
println("Student $student")
student.emailAddress()?.let {
sendEmail(it) //这里的it指代的就是let拆包出来的email address.如果不想用it,也可以像上面那样使用声明一个拆包对象。
}
}
}
private fun Student.emailAddress(): String? = when (Random().nextInt(1)) {
0 -> "[email protected]"
else -> null
}
private fun getBestStudent(): Student? =
when (Random().nextInt(1)) {
0 -> Student("Jane", "初一(1)班")
else -> null
}
fun sendEmail(address: String) {
println("Send email to $address")
}
- 带类似T或者R参数的泛型参数,默认就是可空类型。
fun printHashCode(t: T)
{ println(t?.hashCode()) }
T 被推断为 Any?
要想泛型参数非空,则需要将泛型参数继承某个非空类型。
fun printHashCode(t: T)
{ println(t.hashCode()) }
现在T被推断为Any的子类,很显然是非空类型的。
Kotlin对那些有@Nullable 和 @NotNull 的Java注解会自动映射到Kotlin的 可空类型和非空类型。 如果Java里的类型并没有标注这两种注解,那么这个类型将成为平台类型(Platform Types),这意味着这个既可以是可空类型也可以是非可空类型,Kotlin调用者就像Java 调用那样需要对此可空情况进行负责(Java的锅,Kotlin没法判断他是否是可空还是不可空,只能要求调用者对此负责了。)
你既可以将平台类型赋值给一个Kotlin的可空类型,也可以赋值给Kotlin的非可空类型。注意赋值给非可空类型有风险。single/double exclamation (一个感叹号 / 两个感叹号)
在Kotlin中经常能看到数据类型后面跟着一个感叹号,比如 String!,这种数据类型其实就是平台数据类型(Platform Types),它是由Kotlin根据Java 代码推断出来的数据类型,他可能为null,也可能为non-null,Kotlin在调用的时候要注意在合适的时候检查是否非空,否则会发生NPE异常。另外这个单感叹号数据类型是不允许用户自己声明的,他只能由Kotlin根据Java api(Java代码)来推断出来。
对于双感叹号是Kotlin调用者强转一个可空类型到不可空类型的操作,
例如将String? 转换为 String ,
val notNullStr = getNullableStr()!!
但是有一点要强调,除非你明确的知道这个对象不可能为空(比如你之前检查过非空)才能这样做,否则一旦对象为空,必定会抛NPE错。
- Kotlin是不区别原始数据类型和包装类型的。
Kotlin 不允许隐式转换Java的基本数据类型,比如将Int转为Long。如需转换需要使用方法来显式转换。
val i = 1
val l: Long = i.toLong()
但在初始化变量的时候也会根据已有数据类型进行必要的转换。
fun foo(value: Long) = println(value) //复习一下,这个就是本地方法,作用域只能在本方法内
val byteNumber:Byte = 1
val willBeLong = byteNumber + 2L //Kotlin会在初始化一个变量时根据已有数据类型进行必要的类型转换,这里byteNumber就被转换成Long从而与Long进行计算
foo(willBeLong)
- *** Any *** 和 *** Any? *** : the root types
相当于Java的Object. Kotlin里的对象均继承自 Any - Unit 和 *** Nothing ***
Unit 相当于Java的void 他在Kotlin中是个单例,有且仅有一个Unit
Nothing : 啥也不返回,通常用在一个总会失败的方法中(总会抛某个异常的方法),这个方法不应该返回任何东西,哪怕是Unit - List
和 List 和 List ? 和 List ? 的区别
第一个List本身和元素都non-null
第二个List本身 non-null但是元素可能为null
第三个List本身可能为null 但是元素non-null
第四个List和元素都non-null
注意,如果将Kotlin中的List传到Java代码中,Kotlin没法保证Java代码不放non-null element到List中,作为调用者你要对此负责。也就是尽管Kotlin声明的Collection或List的element是non-null的,但在Java代码中他可以放null进去,Kotlin没法保证不放null元素。 - Array 就是Java里的数组。
arrayOf() :不能包含null元素的数组
arrayOfNulls() : 可以包含null元素的数组 - Kotlin的数组 Array
Array 里面本质上装的是装箱类型,如果为了使用Java基本数据类型以提高效率,请使用Kotlin的IntArray LongArray ByteArray BooleanArray CharArray - Collection 和 MutableCollection
CollectionType | read-only | mutable |
---|---|---|
List | listOf | arrayListOf |
Set | setOf | hashSetOf,linkedSetOf,sortedSetOf |
map | mapOf | hashMapOf,linkedMapOf,sortedMapOf |
三、operator overload and other conventions (运算符重载和其他约定)
- Kotlin的运算符重载其实就是约定,通过特别的方法名来实现的。(毕竟要和Java兼容嘛)
例如Kotlin方法里的 plus方法就可以用到Kotlin中的 *** + ***
- 加(+):plus
- 减(-):minus
- 乘(*):times
- 除(/): div
- 取模(%):rem (kotlin新版已经不建议用mod方法了,建议用rem)
data class Point(val x:Int,val y:Int){
operator fun plus(other: Point): Point = Point(x+other.x,y+other.y)
//plus 对应 + ,注意这里不能用add 这样的方法,因为operator 关键词不认识这样的方法名,也不知道和哪个操作符对应
}
/** 减法:minus */
operator fun Point.minus(other: Point) = Point(x-other.x,y-other.y)
/** 乘:times */
operator fun Point.times(other: Point) = Point(x*other.x,y*other.y)
/** 除:divide */
operator fun Point.div(other: Point) = Point(x/other.x,y/other.y)
operator fun Point.div(number:Int) = Point(x/number,y/number)
/** 取模:mod */
operator fun Point.rem(other: Point) = Point(x % other.x,y % other.y)
//其实这些加减乘除取模运算并不要求返回类型和操作类型一致。像下面注释了的方法也是可以的(返回了一个String)
//operator fun Point.rem(other: Point) = "$x %{other.x} , $y % ${other.y} "
//另外还可以继续重载运算符以和其他数据类型运算,例如Point与Int的取模运算
operator fun Point.rem(number: Int) = Point(x % number,y % number)
跟数学一样,Kotlin的运算符遵循数学乘除取模运算优先级高于加减法,同级别从左到右
定义了plus,那么就支持 + 和+=这两种操作。
如果你想重载+=操作的话,就写对应的操作符assign方法:
operator fun Point.plusAssign(other:Point):Unit
operator fun Point.minusAssign(other:Point):Unit
注意返回值必须是Unit
- unary 一元操作符
- unaryPlus 前面有个+号操作
- unaryMinus 前面有个-号操作
- not 非 前面有个!号操作
- inc 自增1 前面或后面有个++
- dec 自减1 前面或后面有个--
- 不能重载 *** == ***,他会自动调用这个类的 equals 方法
a == b 相当于
a?.equals(b) ?: b==null
- 比较大小:
// p1 < p2
p1.compareTo(p2) < 0
// p1 <= p2
p1.compareTo(p2) <= 0
// p1 > p2
p1.compareTo(p2) > 0
// p1 >= p2
p1.compareTo(p2) >= 0
// p1 == p2
p1.compareTo(p2) == 0
- 索引 [ ] 方法:
- set
a[b] = c => a.set(b,c) //将c的值设置到a的b位置
a[b,c] = d => a.set(b,c,d) //将d的值设置到a的由b,c确定的位置 - get
a[b] //获取a对象b位置的内容
a[b,c] //获取a对象由b,c确定的位置的内容
in 关键字 对应的是contains
a in c => c.contains(a)Kotlin不允许有raw type,每个对象必须有类型,比如声明一个List,你必须同时声明List里装什么对象,Kotlin不允许仅仅声明List而不声明List里装什么类型的情况