原文地址
前言
默认有Java
基础,或者其他静态语言基础。本文主要用于速通和温习,不会设计过于细节用法,并且主要针对服务端的开发,关于多平台的开发参考本站《Kotlin多平台开发流程》
基础
-
在服务端开发时,可以和
Java
完全兼容开发,就和Ts
和Js
一样可以混用,可以同时使用两者的库包 -
服务端
Spring
框架脚手架项目创建可以使用Spring Start -
分号可以省略
-
注释和大多数静态语言相同
-
构建类对象不需要
new
关键字,直接调用构造函数即可 -
函数和对象可以写在类外,作为包级方法/对象被公共调用
-
基本内置类型:
Byte
,Short
,Int
,Long
,Float
,Double
,UByte
,UShort
,UInt
,ULong
,Boolean
,Char
,String
,Array
,Boolean/Byte/Char/Double/Float/Double/Int/Long/ShortArray
,使用和长度和其他静态语言基本相同 -
变量声明类型后置
val a : Int
,如果可以根据右侧表达式推断类型,则可以不声明val b = 1
,使用val
和var
修饰符区分是否为只读,只读变量只能赋值一次 -
Unit
类型某种程度代表void
,可以省略 -
Kotlin
支持字符串模板可以在字符串中直接使用表达式,val s2 = "${s1.replace("is", "was")}, but now is $a"
-
同样,函数返回值和参数的声明格式为
fun sum(a: Int, b: Int): Int { return a + b }
,可以简写为表达式fun sum(a: Int, b: Int) = a + b
-
函数和
C++
一样支持默认值参数,同时在调用时候支持命名调用fun printMessageWithPrefix(message: String, prefix: String = "Info") = println("[$prefix] $message") fun main() { printMessageWithPrefix("Hello", "Log") printMessageWithPrefix("Hello") printMessageWithPrefix(prefix = "Log", message = "Hello") }
-
支持本地函数,即函数内声明函数,本地函数可以直接使用外部函数内的变量
fun dfs(graph: Graph) { val visited = HashSet<Vertex>() fun dfs(current: Vertex) { if (!visited.add(current)) return for (v in current.neighbors) dfs(v) } dfs(graph.vertices[0]) }
-
默认所有方法不能被重写,如果需要重写,使用
open
修饰被重写的方法,override
修饰重写方法 -
默认所有类不能被继承,如果需要继承类,使用
open
修饰父类open class Parent
,继承写法同C++
-
类属性不仅可以在类体中,还可以直接写在声明中,声明中的变量即为默认构造函数所需要的变量,没有类声明则默认构造函数为无参构造函数,类似
C++
中的构造函数的初始化列表class Rectangle(var height: Double, var length: Double) { var perimeter = (height + length) * 2 }
-
条件语句的基本写法和其他静态语言相同,但是可以综合三目表达式进行简写
fun maxOf(a: Int, b: Int) = if (a > b) a else b
-
循环语句格式也基本相同,支持
for
、do-while
、while
,但是for
循环迭代修饰符需要改成迭代关键字for (item in items) { println(item) } for (index in items.indices) { println("item at $index is ${items[index]}") }
-
switch
改为when
,实际执行上更加类似于if-elseif-else
的简写,而不是switch
fun describe(obj: Any) = when (obj) { 1 -> "One" "Hello" -> "Greeting" is Long -> "Long" !is String -> "Not a string" else -> "Unknown" }
-
集合
Collections
包含List
,Set
,Map
,又根据是否可修改叠加修饰Mutable
,使用in
进行存在判断或者迭代,和Java
一样支持各种流操作。Map
有两种取值方法使用[]
或者getValue
,区别在于对于不存在的键返回null
还是NoSuchElementException
,可以在创建时使用withDefault
自定义默认值,就可以让getValue
达到和[]
相同的效果或者特定的实现for (item in items) { println(item) } if(item in items) { println("exist") } val fruits = listOf("banana", "avocado", "apple", "kiwifruit") fruits .filter { it.startsWith("a") } .sortedBy { it } .map { it.uppercase() } .forEach { println(it) }
-
Kotlin
支持Int/CharRange
等Range
数据结构,用于存储离散数据数据,可以使用1..10
,'a'..'z'
进行初始化,使用in
进行条件判断或者正反迭代,迭代时可以自定义步长if (x in 1..10) { println("in range") } for (x in 1..5) { print(x) } for (x in 1..10 step 2) { print(x) } for (x in 9 downTo 0 step 3) { print(x) }
-
为了防止空指针,所有类都可以跟一个
?
修饰符,表达该对象是可以为null
的,该种对象在没有进行显示/隐式的空判断就调用内部函数时,编译器就会报错,从而避免运行时的空指针,?.
表达不为null
时调用的表达式,而?:
表达为空时执行的表达式//val a : Int = null //complie fail var b : Int? = null //compile pass val a : Int //a = null //compile fail val ret = b?.toString() ?: "null" //compile pass
-
自动类型转换,
Kotlin
中的is
对应Java
中的instanceof
,但是在判断成功后Kotlin
会将该种类型转为为相对应的类,减少编码成本。同样在进行NULL
判断后,带有?
修饰符的类型对象也会自动转化为不会为NULL
的普通类型对象fun getStringLength(obj: Any): Int? { if (obj is String) { return obj.length } return null } fun deleteNull(a : String?) : String = a ?: "empty"
拓展
-
泛型的写法基本和其他静态语言差不多
class MutableStack<E>(vararg items: E) { private val elements = items.toMutableList() fun push(element: E) = elements.add(element) fun peek(): E = elements.last() fun pop(): E = elements.removeAt(elements.size - 1) fun isEmpty() = elements.isEmpty() fun size() = elements.size override fun toString() = "MutableStack(${elements.joinToString()})" } fun <E> mutableStackOf(vararg elements: E) = MutableStack(*elements)
-
运算符重载,熟悉
C++
的小伙伴肯定不陌生,在Kotlin
我可以通过Operation Function
和Infix Function
和实现类似功能。对于运算符重载,只有特定的关键字可以被运算符替代,比如times
和*
,div
和/
等,更多对应关系参考Operator overloading//operation fun main() { operator fun Int.times(str: String) = str.repeat(this) println(2 * "Bye ") } //infix fun main() { infix fun Int.mytimes(str: String) = str.repeat(this) println(2 mytimes "Bye ") }
而
Infix Function
则可以随意指定合理的函数名称,由于Kotlin
在类属性写法上没有public
、private
等范围限制,也不需要像C++
一样通过友元来突破访问限制,本质上还是外部的函数调用,比如对上例而言,实际上调用的是fun mytimes(this: Int, str: String) = str.repeat(this)
在不涉及到继承重写等情况下,这种重载可以将我们想要的方法新增到无法修改的第三方包的类中,拥有类似作用的还有
Kotlin
的Extension Functions and Properties
机制,用于新增方法和属性到指定类中data class Item(val name: String, val price: Float) data class Order(val items: Collection<Item>) fun Order.maxPricedItemValue(): Float = this.items.maxByOrNull { it.price }?.price ?: 0F fun Order.maxPricedItemName() = this.items.maxByOrNull { it.price }?.name ?: "NO_PRODUCTS" val Order.commaDelimitedItemNames: String get() = items.map { it.name }.joinToString() fun main() { val order = Order(listOf(Item("Bread", 25.0F), Item("Wine", 29.0F), Item("Water", 12.0F))) println("Max priced item name: ${order.maxPricedItemName()}") println("Max priced item value: ${order.maxPricedItemValue()}") println("Items: ${order.commaDelimitedItemNames}") }
但由于本身不是真正修改了类所以在继承等情况不会按照预期运行
fun main() { open class Shape class Rectangle: Shape() fun Shape.getName() = "Shape" fun Rectangle.getName() = "Rectangle" fun printClassName(s: Shape) { println(s.getName()) } printClassName(Rectangle()) //Shape }
在上例中实际调用的函数为
fun getName(this: Shape) = "Shape"
,并不会表现出多态性,了解更多Extensions -
Koltin
函数可变长度参数,使用vararg
关键字fun printAllWithPrefix(vararg messages: String, prefix: String) { for (m in messages) println(prefix + m) } printAllWithPrefix( "Hello", "Hallo", "Salut", "Hola", "你好", prefix = "Greeting: " )
传入后
message
的类型可以认为是Array<T>
,如果需要作为可变长度参数作为另一个函数的参数则需要增加修饰符*
,例如fun printAllWithPrefix(vararg messages: String, prefix: String) = printAllWithPrefixOther(*messages, prefix = prefix)
-
Kotlin
包含四种特殊类data class
,enum class
、sealed class
和object
,-
data class
方便创建数据类,将数据属性放到初始化列表中,该种类会自动生成copy
,get
等方法data class User(val name: String, val id: Int) { // 1 override fun equals(other: Any?) = other is User && other.id == this.id // 2 }
-
enum class
枚举类和其他语言基本类似,enum class Color(val rgb: Int) { RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF), YELLOW(0xFFFF00); fun containsRed() = (this.rgb and 0xFF0000 != 0) }
-
sealed class
,该类可以继承,但是只能被同包下其他的类继承,不能被不同包的类继承 -
object
,懒汉单例模式的简单写法,即使在多线程下,该种类只会被实例化一次object DoAuth { fun takeParams(username: String, password: String) = println(" parameters = $username:$password") } fun main(){ DoAuth.takeParams("foo", "qwerty") }
object
的使用场景一般还有匿名对象和静态函数-
匿名对象
val helloWorld = object { val hello = "Hello" val world = "World" override fun toString() = "$hello $world" } print(helloWorld)
-
静态函数,但是官方文档更建议使用包级方法代替这种静态方法写法
class BigBen { companion object Bonger { fun getBongs(nTimes: Int) { for (i in 1 .. nTimes) { print("BONG ") } } } } fun main() { BigBen.getBongs(12) }
-
-
-
Kotlin
同样支持Lambda
表达式,可以通过类型推断和当前对象it
来简略表达式val upperCase1: (String) -> String = { str: String -> str.uppercase() } // pass val upperCase2: (String) -> String = { str -> str.uppercase() } // pass val upperCase3 = { str: String -> str.uppercase() } // pass // val upperCase4 = { str -> str.uppercase() } // error, the compiler has no chance to infer the type val upperCase5: (String) -> String = { it.uppercase() } // pass val upperCase6: (String) -> String = String::uppercase // pass
-
熟悉
C++
的小伙伴肯定了解,除了使用类继承实现多态,我们还可以使用函数指针来实现多态。Kotlin
同样支持函数指针的实现//函数作为参数 fun sum(x: Int, y: Int) = x + y fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int { return operation(x, y) } fun main() { val sumResult = calculate(4, 5, ::sum) val mulResult = calculate(4, 5) { a, b -> a * b } } //函数作为返回值 fun square(x: Int) = x * x fun operation(): (Int) -> Int { return ::square } fun main() { val func = operation() println(func(2)) }
-
Destructuring Declarations
,如果你只需要一个对象中的某个成员,那么使用析构声明的语法可以简化代码结构,更多使用参考Destructuring Declarationsval (x, y, z) = arrayOf(5, 10, 15) val (min, max) = findMinMax(listOf(100, 90, 50, 98, 76, 83)) data class User(val username: String, val email: String) val (_, emailAddress) = User("Mary", "mary@somewhere.com")
常用
集合处理
只读集合List
,Set
,Map
,可写集合MutableList
,MutableSet
,MutableSet
,初始化方法以List
举例有listof
,mutableListof
,emptyList
等,底层数据结构和其他语言基本类似。注意,如果将可变集合转为不可变集合时,由于指针相同,此时改变可变集合,不可变集合也会改变
-
元素正向过滤
filter
val numbers = listOf(1, -2, 3, -4, 5, -6) val positives = numbers.filter { x -> x > 0 } val negatives = numbers.filter { it < 0 } val strings = listOf("1", "2", " ", "") println(strings.filter(String::isNotBlank)) println(strings.filter(String::isNotEmpty))
-
元素映射
map
val numbers = listOf(1, -2, 3, -4, 5, -6) val doubled = numbers.map { x -> x * 2 } val tripled = numbers.map { it * 3 }
-
元素存在性查询
any
,all
,none
val numbers = listOf(1, -2, 3, -4, 5, -6) numbers.any { it < 0 } numbers.all { it < 6 } numbers.none { it > 6 }
-
元素获取
find
,findLast
,first
,firstOrNull
,last
,lastOrNull
,min
,minOrNull
,max
,maxOrNull
,getOrElse
val words = listOf("Lets", "find", "something", "in", "collection", "somehow") val findFirst = words.find { it.startsWith("some") } //something val findFirst2 = words.find { it.startsWith("somexx") } //null val findLast = words.findLast { it.startsWith("some") } //somehow val first0 = words.first() //Lets val first = words.first { it.startsWith("some") } //something //val first2 = words.first { it.startsWith("somexx") } // NoSuchElementException val first3 = words.firstOrNull{ it.startsWith("somexx") } //null val last0 = words.last() //somehow val last = words.last { it.startsWith("some") } //somehow val list = listOf(0, 10, 20) println(list.getOrElse(1) { 42 }) //10 println(list.getOrElse(10) { 42 }) //42 val map = mutableMapOf<String, Int?>() println(map.getOrElse("x") { 1 }) //1
-
元素统计
count
,支持条件统计val numbers = listOf(1, -2, 3, -4, 5, -6) val totalCount = numbers.count() val evenCount = numbers.count { it % 2 == 0 }
-
元素排序
sort
,sortedDescending
,支持条件排序val shuffled = listOf(5, 4, 2, 1, 3, -10) val natural = shuffled.sorted() val descendingBy = shuffled.sortedByDescending { abs(it) }
-
集合类型转化,同属性去重
associateBy
,同属性聚合groupBy
,分割partition
,嵌套列表平铺flatMap
,双列表同索引组合zip
data class Person(val name: String, val city: String, val phone: String) val people = listOf( Person("John", "Boston", "+1-888-123456"), Person("Sarah", "Munich", "+49-777-789123"), Person("Svyatoslav", "Saint-Petersburg", "+7-999-456789"), Person("Vasilisa", "Saint-Petersburg", "+7-999-123456")) val phoneBook = people.associateBy { it.phone } // Map<phone, person> val cityBook = people.associateBy(Person::phone, Person::city) // Map<phone, city> val peopleCities = people.groupBy(Person::city, Person::name) // Map<city, List<name>> val numbers = listOf(1, -2, 3, -4, 5, -6) val evenOdd = numbers.partition { it % 2 == 0 } val (positives, negatives) = numbers.partition { it > 0 } val fruitsBag = listOf("apple","orange","banana","grapes") val clothesBag = listOf("shirts","pants","jeans") val cart = listOf(fruitsBag, clothesBag) // List<List<String>> val mapBag = cart.map { it } // List<List<String>> val flatMapBag = cart.flatMap { it } // List<String> val A = listOf("a", "b", "c") val B = listOf(1, 2, 3, 4) val resultPairs = A zip B // List<Pairs> size=3 val resultReduce = A.zip(B) { a, b -> "$a$b" } // List<String> size=3
匿名函数
类型 | 对象使用 | 返回值 | 使用场景 |
---|---|---|---|
let | 使用对象调用(块内自定义该对象名称,或者使用默认的it ) | 最后一个表达式 | 以外部函数为主的计算 |
run | 使用对象调用(块内方法默认都是由该对象发起,即this ) | 最后一个表达式 | 以成员函数为主的计算 |
with | 对象作为参数(块内方法默认都是由该对象发起,即this ) | Unit | 成员函数为主的非计算逻辑 |
apply | 使用对象调用(块内方法默认都是由该对象发起,即this ) | 返回自身 | 对象属性初始化 |
also | 使用对象调用(块内自定义该对象名称,或者使用默认的it ) | 返回自身 | 涉及外部函数的对象初始化 |
val empty = "test".let {
print(it)
it.isEmpty()
}
val len = "test".run {
println("\tis empty? " + isEmpty())
println("\tlength = $length")
length
}
with(configuration) {
println("$host:$port")
}
val jake = Person()
val stringDescription = jake.apply {
name = "Jake"
age = 30
about = "Android developer"
}.toString()
val jake2 = Person("Jake", 30, "Android developer")
.also {
writeCreationLog(it)
}
代理模式
Kotlin
对于代理模式的使用,有进一步的简化。使用代理模式可以在不干涉原本继承体系情况对类方法进行增加和拓展,遵从合成复用原则,更多的使用组合解决问题,减少耦合提高可拓展性。首先我们回顾下Java
中代理模式的写法:
interface Animal {
void roar();
}
class Tiger implements Animal {
@Override
public void roar() {
System.out.print("owu");
}
}
class Bear implements Animal {
@Override
public void roar() {
System.out.print("aoo");
}
}
class ZooAnimal implements Animal {
private Animal animal;
ZooAnimal(Animal animal) {
this.animal = animal;
}
@Override
public void roar() {
animal.roar();
System.out.println(", I want to leave");
}
}
@Test
void test() {
new ZooAnimal(new Bear()).roar();
new ZooAnimal(new Tiger()).roar();
}
在Kotlin
中,支持部分方法增强,不需要覆写所有方法(默认和by
标识的对象实现相同),增强类ZooAnimal
的写法一般为:
class ZooAnimal(val animal: Animal) : Animal by animal {
override fun roar() {
animal.roar()
println(", I want to leave")
}
}
-
代理属性方法
import kotlin.reflect.KProperty class Example { var p: String by Delegate() override fun toString() = "Example Class" } class Delegate() { operator fun getValue(thisRef: Any?, prop: KProperty<*>): String { return "$thisRef, thank you for delegating '${prop.name}' to me!" } operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) { println("$value has been assigned to ${prop.name} in $thisRef") } } fun main() { val e = Example() println(e.p) //Example Class, thank you for delegating 'p' to me! e.p = "NEW" //NEW has been assigned to p in Example Class }
既然我们可以直接代理属性方法,也就是意味着我们可以通过这种代理是实现类属性对象的单例创建。这些常用的代理已经被内置到
Kotlin
标准库当中了,使用lazy
实现,blockingLazy
支持多线程下的单例属性创建class LazySample { init { println("created!") } val lazyStr: String by lazy { print("computed! ") "data" } } fun main() { val sample = LazySample() // created! println("${sample.lazyStr}") // computed! data println("${sample.lazyStr}") // data }
类似的,我们可以通过
Map
初始化对象,在使用MutableMap
下,该映射图甚至可以跟随对象var
属性的变化而变化class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } fun main() { val user = User(mapOf( "name" to "John Doe", "age" to 25 )) }
打印日志
pirnt("this is something")
pirntln("this is something")
或者,导入包
<dependency>
<groupId>io.github.oshai</groupId>
<artifactId>kotlin-logging-jvm</artifactId>
<version>${version}</version>
</dependency>
private val log = KotlinLogging.logger {}
@Component
class SomeService {
fun output() {
val time = System.currentTimeMillis()
log.info { "[op:SomeService:output] cur time = $time" }
}
}