为什么是Kotlin?首先它与scala语言一样的外形,省去了大量java式的八股代码。代码非常简洁,同样的功能会大大减少代码量,方便阅读和维护。
其次在java的API基础上做了大量封装,常用的操作进行了封装,从而进一步简化开发的代码。并且基于JVM可以和java无缝结合。也可以编译为javascript,也作为Android的第一语言,使前后端使用同一个语言成为了可能。
跟java不一样的是Kotlin不受文件名约束,一个文件中可以有多个类,这些类的名字不必和文件名一样。可以在文件中直接定义方法(此时相当于java中 static 修饰的方法,同样的属性也可以直接定义),这种申明叫做 顶层申明 ,这时候所有的这些类和方法都通过 包名 查找。这里包名相当于C#中 命名空间 的概念。
包的申明和java一样:
package com.forpast.kotlin.test
报名顶层(紧挨着包申明)就可以申明函数,假如我们现在在一个HelloWord.kt文件中申明max函数:
fun max(a:Int,b:Int): Int{
return if(a>b) a else b
}
这种方法我们在java类该如何调用?此时max方法编译后就是一个名为HelloWorldKt的类中的静态方法。
*** Java ***
import com.forpast.kotlin.test.HelloWorldKt;
public class Hello {
public static void main(String[] args){
int k = HelloWorldKt.max(5,6);
}
}
如果你不想要这样的默认的类名怎么办?
文件顶部添加注解:
@file:JvmName("HelloWord")
这样就可以通过HelloWord为类名进行调用。
$
引用变量,利用${ ... }
存放表达式fun main(args: Array) {
//变量引用
val name = "Kotlin"
println("Hello, $name!”)
//表达式
if (args.size > 0)
println("Hello, ${args[0]}!") // 1 使用`${}`语法来插入args数组的第一个元素
}
class Person(
val name: String, // 1 只读属性生成和琐碎获取器
var isMarried: Boolean // 2 可写属性:一个字段,一个获取函数和一个设置函数
)
创建类Person,同时创建类主构造函数,该构造函数需要两个参数name和isMarried。同时申明为类属性(var或val修饰时)。其中val修饰表示不可修改,该属性只提供get方法(对java来说),var修饰表示可修改,同时提供get和set方法。
编译器会对属性默认提供访问器,也可以自己定义,如:
val isSquare: Boolean
get() { // 1 属性的获取函数声明
return height == width
}
Kotlin创建函数时是可以指定默认值的,调用时可以只给未初始化的参数赋值(指定参数),构造函数同样可以这样。这样就可以通过使用同一个构造函数而传入参数不一样来代替java的多构造函数的情况。
class Name(val name:String = "name",val size:Int){
fun paramTest(miniName:String = "n"){ //含默认值的参数
println(miniName)
}
}
fun main(args: Array<String>) {
Name(size = 4).paramTest() //指定参数,只给未初始化参数传参
Name(size = 4,name = "name2").paramTest("n2") //指定参数传参,可以任意顺序
}
Kotlin是没有switch的,转而使用when,并且when功能强大很多。
我们现在有一个Color的枚举类:
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
用when对Color进行判断
fun getMnmonic(color: Color) = // 1 直接返回一个when表达式
when (color) { // 2 如果颜色等于枚举常量,返回对应的字符串
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Grave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
println(getMnemonic(Color.BLUE))
when匹配对象并执行计算:
interface Expr
class Num(val value: Int) : Expr // 1 带有一个属性、值而且实现了Expr接口的简单的值对象 类
class Sum(val left: Expr, val right: Expr) : Expr // 2 求和操作的参数可以是任意的Expr:N um对象或者其他的Sum对象
fun eval(e: Expr): Int =
when (e) {
is Num -> //判断是否是Num类
e.value
is Sum ->
eval(e.right) + eval(e.left) // 2 这里使用了智能类型转换
else ->
throw IllegalArgumentException("Unknown expression")
}
when 对判断对象计算
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$i "
}
//带步进控制的循环
for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
enum class Color2(
val r: Int, val g: Int, val b: Int // 1 声明枚举常量的属性
) {
RED(255, 0, 0),
ORANGE(255, 265, 0), // 2 当每个常量被创建时指定属性值
YELLOW(255, 255, 0),
GREEN(0, 255, 0),
BLUE(0, 0, 255),
INDIGO(75, 0, 130),
VIOLET(238, 130, 238); // 3 分号(;)在这里是必须的
fun rgb()=(r*256+g)*256+b //4 在枚举类中定义了一个方法
}
println(Color.BLUE.rgb())
常量只能直接定义在文件里面或object中,不能放在class类中。直接通过文件进行访问,就跟直接访问文件中的方法一样。使用const关键字进行定义:
const val CONST_NAME:String = "CONST_NAME"
注意:由于Kotlin自己能进行类型推导,这里的String类型的申明是可以去掉的,但如果后面是个表达式的赋值,建议还是进行申明类型,以方便其他人阅读。
使用关键字:vararg 和java中 ‘…’ 不同直接加在参数申明之前,和java一样传递的是数组,在引用时需要你显示对数组进行拆箱(unpack the array),这个特性称做使用一个展开操作符(spread operator),就是在对应的参数前放置 * 符号
fun listTest(vararg str:String){
}
fun main(args: Array<String>) {
val list = listOf("args: ", *args) // 1展开操作符解压(unpack)数组内容
println(list)
}
使用关键字: infix 比如to方法
1.to("one") // 1 以常规的方式来调用
1 to "one" // 2 使用中缀标记来调用函数
//to函数返回一个元组
infix fun Any.to(other: Any) = Pair(this, other)
val (number, name) = 1 to “one"
对String扩展一个中缀方法
infix fun String.strIn(name:String) = "${this} in $name"
println("infix test" strIn "kotlin") //infix test in kotlin
println("12.345-6.A".split(".", "-"))
可以看出来有两个特点,1.支持同时多个分隔符,2.对点 ‘.’ 不需要转义。 如果确实需要正则表达式匹配来拆分需要显示创建正则表达式(字符串.toRegex())
多行字符串使用三引号包含 “”” … “”” 并且中间特殊字符不需要转义,保留换行、空格等等。
val str = """ string kkkk \\\\
...
< eee >
!@#@###$%#$%#% """
println(str)
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
numbersTo100.forEach(::println)
let默认当前这个对象作为闭包的it参数,所以在表达式内使用 it 引用当前对象,可以通过return 指定返回值。
//@kotlin.internal.InlineOnly
//public inline fun T.let(block: (T) -> R): R = block(this)
//let返回 传入表达式返回的值,对象作为了表达式参数('it ->'可省略 )
fun letTest(name:String):String {
name.let {
it -> return it + " with let"
}
}
println(letTest("name")) // name with let
调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,使用 this 引用当前对象(可省略),并返回该对象
//@kotlin.internal.InlineOnly
//public inline fun T.apply(block: T.() -> Unit): T { block(); return this }
//传入一个无返回值的表达式,apply方法返回当前对象
fun alphabet():String = StringBuilder().apply{
for (letter in 'A'..'Z') {
this.append(letter)
}
}.toString()
可以直接使用 with 对象的属性和方法。使用 this 引用当前对象(可省略),可以通过return 指定返回值
//@kotlin.internal.InlineOnly
//public inline fun with(receiver: T, block: T.() -> R): R = receiver.block()
// with方法也是返回表达式返回的值,不同的是表达式是对对象的扩展
fun alphabet(): String = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
this.append("\nNow I know the alphabet!")
}
return toString()
}
使用 this 引用当前对象,可通过return返回值
//@kotlin.internal.InlineOnly
//public inline fun T.run(block: T.() -> R): R = block()
//返回表达式返回的值,表达式是对当前对象的扩展
fun runTest(name:String): String {
name.run {
return this + " with run"
}
}
使用 it 为当前对象,自动返回当前对象
//public inline fun T.also(block: (T) -> Unit): T { block(this); return this }
//传入一个没有返回值的表达式,以当前对象为表达式传入参数(传入部分表达式可省略和let一样),最后返回当前对象。
fun alsoTest(name:String):String {
return name.also {
it + " with also"
}
}
println(runTest("name")) // name with also
特别说明:以上几点let,with,apply,run, also。使用上其实都差不多,只需要记住以下两点:
1).要容错的参数后加 ’ ? ’ (参数申明时,在类型后面),若参数为null,这表达式返回null,不会抛空指针异常。
fun printAllCaps(s: String?) {
val allCaps: String? = s?.toUpperCase()
println(allCaps)
}
2).在参数后面加 ’ ?: ’ 若为null就返回符号后面的值
fun Person.countryName() = company?.address?.country ?: "Unknown"
3).在参数后面加 ’ as? ’ 判断参数是否为符号后面的(子)类型,如果是直接以后面类型返回,如果不是返回null
fun equals(o: Any?): Boolean {
val otherPerson = o as? Person ?: return false
return otherPerson.firstName == firstName && otherPerson.lastName == lastName
}
4).参数后面加 ’ !! ’ 如果参数为null,就抛空指针异常
fun ignoreNulls(s: String?) {
val sNotNull: String = s!!
println(sNotNull.length)
}
val binaryReps = HashMap() // 1 使用了TreeMap,因此键值是有序的
for (c in 'A'..'F') { // 2 使用字符范围,从A到F遍历字符
val binary = Integer.toBinaryString(c.toInt()) // 3 把ASCII编码转换成二进制
binaryReps[c] = binary // 4 以c为键把数值保存在映射集
}
for ((letter, binary) in binaryReps) { // 5 遍历一个映射集,吧映射的键跟值分配给两个变量
println("$letter = $binary")
}
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) { // 1 通过索引遍历集合
list.plus("222")
println("$index: $element")
}
使用in检查
fun isLetter(c: Char) = c in listOf('a') || c in 'A'..'Z'
when 中的in
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!" // 1 判断值是否在0到9的范围内
in 'a'..'z',
in 'A'..'Z' -> "It's a letter!" // 2 你可以合并多个范围
else -> "I don't know..."
}
接口可以包含默认的实现方法,抽象属性。
继承使用冒号‘:’而不是extends,多个继承之间逗号隔开。
interface InterfaceTest : Clickable {
val str: String //抽象属性
fun method() //抽象方法
fun printMethod() { //实现的方法
println("method")
}
}
类实现接口一样使用冒号,实现方法必须有override修饰
class Button(val int: Int) : Clickable{
constructor(name:String,size: Int):this(size){
}
override fun click() = println("I was clicked")
fun show() = super.showOff()
}
通过上面的例子可以知道,一个类可以有一个主构造函数,多个次构造函数,其中次构造函数必须继承主构造函数。创建类时如果没有显示创建主构造函数,编译器将创建一个默认无参数的主构造函数。
创建构造函数用关键字 constructor ,如果创建类时使用注解,那么创建主构造函数也需要该关键字。 次构造函数的参数不能再声明为类属性,也就是不能有val或var修饰
如果超类为带参数的构造函数,必须在继承时初始化超类构造函数:
open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { … }
也可以继承父类的构造函数,次构造函数之间可以委托:
class MyButton : View {
constructor(ctx: Context): this(ctx, MY_STYLE) { // 1 委托给类中的其他构造函数
// ...
}
constructor(ctx: Context, attr: AttributeSet): super(ctx, attr) {
// ...
}
}
如果一个类多继承,并且超类有同名方法时,通过指定类名来调用:
super<Clickable>.showOff()
注意:Kotlin的类和方法默认是 final 的。 如果你想允许一个类创建子类,你需要用 open 修饰符标记这个类。另外,你需要添加 open 修饰符到每个可以被覆盖的属性或者方法。如果你覆盖了一个基类或者接口的成员,覆盖的方法将会是默认 open的。如果你想改变这个可继承性并禁止你的类的子类覆盖你的实现,你可以显式的把覆盖后的成员标记为final。你也可以使用abstract修饰为抽象,这样就必须被覆盖实现。在接口中,你不能使用 final, open 或者 abstract 。接口中的成员总是为 open 属性。你不能将其声明为 final 。如果接口没有代码体,那么它为abstract ,但是关键词并不是必须的。
可见性修饰: 默认的声明将会是 public 。 Java中的默认可见性 package-private 并不会在Kotlin中出现。Kotlin把包仅仅作为命名空间中的代码的一种组织方式,并没有用于可见性控制。 作为一个可选方案,Kotlin提供了一个新的可见性修饰符: internal ,它意味着’模块内可见’。
注意:在扩展方法时可见性不能增加可见性,比如方法默认是public,不能扩展低于public可见性修饰的类。
类中可以嵌套内部类,默认是static的,目的是为类解决隐式保留外部类引用问题(这一点和java不同)。如果此时确实需要引用外部类,可以增加 inner 修饰符,申明为内部类。
类B中定义类A | java | kotlin |
---|---|---|
嵌套类(不保存外部类的引用) | 静态类A | 类A |
内部类(保存外部类的引用) | 类A | 内部类A |
Kotlin引用外部类实例的语法也跟Java不同。你写 this@Outer 来访问 Inner 类的 Outer 类。
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}
密封类: 关键字 sealed ,我们知道when在进行判断时,必须要用 else,如果使用 密封类 限制了其他可能性,就可以不需要 else 了。
sealed class Expr { // 1 把一个基类标记为密封类
class Num(val value: Int) : Expr() // 2 列出所有可能的子类作为嵌套类
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int = when (e) { // 3 "when"表达式覆盖了所有可能的情况,所以不需要"else"
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left)
}
注意: sealed 修饰符意味着类是开放的。 你不需要一个显式的 open 修饰符
你不能声明一个密封接口。因为编译器不能够保证程序无法在Java代码中实现这个接口。
使用这个方法,当你添加一个新的子类时, when 表达式返回一个值并且编译失败,同时指出你的代码哪里需要修改。
前面说了我们可以为类属性自定义setter和getter的方法。其中在setter内部可以使用标记符来访问支持字段
class UserTest(val name: String) {
var address: String = "unspecified”
set(value: String) {
println("""Address was changed for $name:"$field" -> "$value".""".trimIndent()) // 1 读取支持字段的值
field = value // 2 更新支持字段的值
}
}
注:编译器将会为属性产生支持字段如果你显式的引用它或者使用了默认访问器的实现。如果你提供了一个不使用 field 的自定义访问器实现,支持字段不会出现。
类属性其他特性:
使用 data 修饰类,可以让值-对象类变得更加友好,自动生成toString,equals 和 hashCode等方法。
注意:没有在主构造函数中声明的属性没有参与等价性检查和哈希值计算。
类委托 by 关键字:
class DelegatingCollection<T>(
innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList{}
val delegating = DelegatingCollection<String>()
delegating.plus("test")
也可以利用override覆盖方法。
单例对象 使用 object 修饰,编译器将生成一个单例对象,注意object对象不能有构造函数
object SingletonObj{
fun test(){
println("xxx")
}
}
**Kotlin**
SingletonObj.test()
**Java**
SingletonObj.INSTANCE.test();
在class类中可以定义单例对象object。此时可以通过 类名.对象名 进行访问普通属性和方法,但是私有成员是不能访问的。如果需要互相访问私有成员,这时候需要用到伴生对象。
伴生对象,使用关键字 companion 修饰,可以与伴生类之间互相访问私有成员
class CompanionClass(val name:String){
private val age1 = 10
fun testCompan():Int{
return age2 + 2 //访问私有成员,只能访问伴生对象的私有成员
}
companion object{
private val age2 = 20
fun testCompanion():Int{
return CompanionClass("name").age1 + 3 //访问私有成员,普通单例对象也可以访问
}
}
}
注意:使用伴生对象时可以不定义对象名,而普通单例对象必须定义。可以直接通过类名直接访问伴生对象的方法。如:
CompanionClass.testCompanion(),而非伴生对象需要带上对象名。
对伴生对象进行方法扩展:
fun CompanionClass.Companion.extensionMethod() = println("extensionMethod")
调用同样直接通过类名调用 CompanionClass.extensionMethod()
object 关键字还用于匿名类
interface Clickable {
fun click()
fun showOff() = println("I'm focusable!")
}
//---------------------
val click = object : Clickable{
override fun click(){
println("click")
}
}
click.click()
click.showOff()
val sum = {x: Int, y: Int -> x + y }
val sum: (Int, Int) -> Int = { x, y -> x + y } //两者等价
println(sum(1,2))
如果函数最后一个参数是lambda表达式,最后一个参数(lambda表达式)可以独立出来在’{ }’中
object LambdaTest{
fun lambdaMethod(x: Int,y: Int,body:(x:Int,y:Int) -> Int){
val result = body(x,y)
println(result)
}
}
fun main(args: Array) {
LambdaTest.lambdaMethod(5,6,{x,y->x*y})
LambdaTest.lambdaMethod(5,7){
x,y->x+y
}
}
val list = listOf(1, 2, 3, 4)
list.filter { it % 2 == 0 }
list.map { it * it }
//集合分组
val list = listOf("a", "ab", “b”)
println(list.groupBy(String::first)) //方法引用
//整合多个集合为一个集合
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() }) //[a, b, c, d, e, f]
//流处理
people.asSequence()
.map(Person::name)
.filter { it.startsWith("A") }
.toList()
集合检查:
val canBeInClub27 = { p: Person -> p.age <= 27 }
val people = listOf(Person("Alice", 27), Person("Bob", 31))
println(people.all(canBeInClub27))//是否所有都满足表达式
println(people.any(canBeInClub27))//是否有成员满足表达式
println(people.count(canBeInClub27))//统计满足表达式的成员个数
println(people.find(canBeInClub27))//查找满足条件的成员
我们可以为某个类定义它的操作方法,比如 + - * /等,然后就可以通过 对象1 + 对象2 这样来调用
先来看一个例子:
data class Point(val x: Int, val y: Int){
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
println(Point(1,2) + Point(2,3)) // Point(x=3, y=5)
所以知道操作符表达式对应的方法名就简单了。方法需要 operator 修饰。
看看操作符与方法名对应表:
Expression | Function name |
---|---|
a*b | times |
a+b | plus |
a-b | minus |
a/b | div |
a%b | mod |
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a,a++ | inc |
–a,a– | dec |
我们也可以给多属性的类定义map方式的取值操作方法(map[key]),方法名 get ,现在对Point扩展一个取值操作方法:
operator fun get(index:Int):Int{
return when(index){
0 -> x
1 -> y
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
val p = Point(3,5)
println(p[0]) // 3
println(p[1]) // 5
范围操作符号 :start .. end 方法名是 rangeTo
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
访问器操作方法定义,方法名 getValue setValue
class Delegate {
private var value:String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
//这里可以自定义 获取值的逻辑
println("$thisRef, thank you for delegating '${property.name}' to me!")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
//这里可以自定义 设置值的逻辑
println("$value has been assigned to '${property.name} in $thisRef.'")
this.value = value
}
}
//创建一个类,把属性的操作(设置值和获取值)委托给Delegate的实现
class Example {
var p: String by Delegate()
}
fun main(args: Array) {
val e = Example()
e.p = "hello"
println(e.p)
}
//打印内容:
//hello has been assigned to 'p in com.forpast.brandon.test.Example@46ee7fe8.'
//com.forpast.brandon.test.Example@46ee7fe8, thank you for delegating 'p' to me!
//hello
上面一个例子使用了委托,通过这样的方式,可以把一个类的属性操作委托给另一个类。有时候需要把某个系列类的属性操作进行一定的算法处理,这种情况下该系列所有类都可以委托给一个实现了这种处理的类。不仅如此,通过这样委托的方式可以监控类属性的所有变化。
class User {
var name: String by Delegates.observable("" ) {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array) {
val user = User()
user.name = "first" //<no name> -> first
user.name = "second" // first -> second
}
这里使用了可见性 observable ,如果想要拦截和否决可以使用vetoable()来代替。
通过委托把属性存入map
class User(map: Map) {
val name: String by map
val age: Int by map
}
fun main(args: Array) {
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name) // John Doe
println(user.age) // 25
}
这里是val的属性,当然也可以对var修饰属性生效,这时候需要Map 换成 MutableMap
泛型总的来说和java差距不是特别大,如:
class Box<T>(t: T) {
var value = t
}
val box: Box = Box(1)
java中有一些问题,比如 String 是 Object的子类,但是List
却不是List
的子类,
所以下面这样是有问题的:
// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // 类型不匹配,直接报错
为了解决这个问题,kotlin在泛型中引入了 out 来解决,同时表示该泛型只能作为返回类型,它修饰的泛型叫:covariant ,翻译为中文:协变 ,相当于java中 Collection
这样的泛型申明。
abstract class SourceT> {
abstract fun nextT(): T
}
fun demo(strs: Source) {
val objects: Source = strs // This is OK, since T is an out-parameter
// ...
}
与 out 对应的就是 in 表示该泛型只能用来作为输入参数,它修饰的泛型叫:contravariant 中文:逆变
相当于java Collection
这样的泛型申明。
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable) {
x.compareTo(1.0)
val y: Comparable = x // OK!
}
主要就是两个点:
Kotlin中注解和java的注解使用方式是差不多的。
自定义注解,和利用反射查找:
@Retention
@Target(AnnotationTarget.FUNCTION)
annotation class MyMethod(val name:String) //自定义注解
class MethodTest{
@MyMethod("FirstMethod")
fun meTest(){
}
//利用反射查找注解
fun printName(){
val kClass = this.javaClass.kotlin
kClass.memberFunctions.forEach({
val anno = it.findAnnotation()
if(anno != null)
println(anno.name)
})
}
}
invoke: ImportantIssuesPredicate类继承了一个表达式 (Issue) -> Boolean
该方法必须通过invoke实现。
invoke的方法可以直接通过对象调用:predicate(i1)
data class Issue(
val id: String,
val project: String,
val type: String,
val priority: String,
val description: String
)
class ImportantIssuesPredicate(val project: String) : (Issue) -> Boolean {
override fun invoke(issue: Issue): Boolean {
return issue.project == project && issue.isImportant()
}
private fun Issue.isImportant(): Boolean {
return type == "Bug" &&
(priority == "Major" || priority == "Critical")
}
}
fun main(args: Array) {
val i1 = Issue("IDEA-154446", "IDEA", "Bug", "Major", "Save settings failed")
val i2 = Issue("KT-12183", "Kotlin", "Feature", "Normal","Intention: convert several calls on the same receiver to with/apply")
val predicate = ImportantIssuesPredicate("IDEA")
// 查找满足过滤条件的所有Issue对象,过滤条件在 invoke中
for (issue in listOf(i1, i2).filter(predicate)) {
println(issue.id)
}
}