三篇文章带你快速入门 Kotlin(中)
标准函数和静态方法
标准函数
Kolin的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数。
虽说标准函数并不多,但是想要一次行全部学完还是比较吃力的。因为我们这里主要学习几个常用的函数。
with
with函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。
示例代码如下:
val result = with(obj) {
// 这里是obj的上下文
"value" // with函数的返回值
}
fun with() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val result = with(StringBuilder()) {
append("with\nstart\n")
for (fruit in list) {
append(fruit + "\n")
}
append("end")
toString()
}
println(result)
}
run
run函数的用法和使用场景其实和with函数是非常类似的,只是稍微做了一些语法改动而已。首先run函数是不能直接调用的,而是一定要调用某个对象的run函数才行;其次run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。其他方面和with函数是一样的,包括也会使用Lambda表达式中的最后一行代码作为返回值返回。
示例代码如下:
val result = obj.run {
// 这里是obj的上下文
"value" // run函数的返回值
}
fun run() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val result = StringBuilder().run {
append("run\nstart\n")
for (fruit in list) {
append(fruit + "\n")
}
append("end")
toString()
}
println(result)
}
apply
apply函数和run函数也是极其类似的,都是要在某个对象上调用,并且只接收一个Lambda参数,也会在Lambda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身。
示例代码如下:
val result = obj.apply {
// 这里是obj的上下文
}
// result == obj
fun apply() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val result = StringBuilder().apply {
append("apply\nstart\n")
for (fruit in list) {
append(fruit + "\n")
}
append("end")
}
println(result.toString())
}
定义静态方法
静态方法指的就是那种不需要创建实例就能调用的方法,所有主流的编程语言都会支持静态方法这个特性。
一般工具类通常没有创建实例的必要,基本是全局通用。
Kotlin没有直接定义静态方法的关键字,但是提供了一些语法特性来支持类似于静态方法调用的写法,如单例类,companion object等,这些语法特性基本可以满足我们平时的开发需求了。
然而如果你确确实实需要定义真正的静态方法, Kotlin仍然提供了两种实现方式:注解和顶层方法。
@JvmStatic注解
class Util {
companion object {
@JvmStatic
fun doAction() {
println("do action2")
}
}
}
顶层方法
是指那些没有定义在任何类中的方法、
只要你定义了顶层方法,那么它就一定是静态方法
fun doSomething() {
println("do something")
}
Java中调用kotlin静态方法
在Java中调用Kotlin的顶层方法你会发现找到,时因为Java没有顶层方法的概念,Java会创建一个StaticMethodKt的Java类,然后在调用doSomething()的静态方法。
public class JavaTest {
public void test() {
Util1.doAction2();
StaticMethodKt.doSomething();
}
}
延迟初始化和密封类
对变量进行延迟初始化
延迟初始化使用的是lateinit关键字,它可以告诉Kotlin编译器,我会在晚些时候对这个变量进行初始化,这样就不用在一开始的时候将它赋值为null了。
该修饰符只能用于在类体中的属性。
class Dog() {
lateinit var say: String
fun call() {
say = "小狗叫"
}
}
检测一个 lateinit var 是否已初始化
if (foo::bar.isInitialized) {
println(foo.bar)
}
Kotlin提供了一个lazy()函数,该函数接受一个lambda表达式作为参数,并返回一个Lazy<T>对象
如下示例:
class Dog {
val bark: String by lazy {
println("第一次访问时执行代码块")
"狗叫"
}
}
密封类
密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
要声明一个密封类,需要在类名前面添加 sealed
修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
- 一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。
- 密封类不允许有非-private 构造函数(其构造函数默认为 private)。
- 请注意,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。
- 使用密封类的关键好处在于使用
when
表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个else
子句了。当然,这只有当你用when
作为表达式(使用结果)而不是作为语句时才有用。
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
示例
sealed class Apple {
abstract fun taste()
}
open class RedFuji : Apple() {
override fun taste() {
println("红富士苹果香甜可口!")
}
}
data class Gala(var weight: Double) : Apple() {
override fun taste() {
println("嘎啦果,重量为$weight")
}
}
fun main() {
var ap1: Apple = RedFuji()
var ap2: Apple = Gala(2.3)
ap1.taste()
ap2.taste()
judge(ap1)
judge(ap2)
}
fun judge(ap: Apple) {
when (ap) {
is RedFuji -> println("红富士苹果香甜可口!")
is Gala -> println("嘎啦果")
}
}
扩展函数和运算符重载
扩展函数
扩展函数表示即使在不修改某个类的源码的情况下,仍然可以打开这个类,向该类添加新的函数。
其语法结构非常简单,如下所示:
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
思考统计字符串中字符的数量,一般会写一个单例模式或静态模式
object StringUtil {
fun lettersCount(str: String): Int {
var count = 0
for (char in str) {
if (char.isLetter()) {
count++
}
}
return count
}
}
一般扩展函数定义在顶层方法中。这样可以让扩展函数拥有全局的访问域,扩展函数在很多情况下可以让API变得更加简洁,丰富,更加面向对象。
fun String.lettersCount(): Int {
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
调用
fun main() {
val str = "ABC123xyz!@#"
val count = StringUtil.lettersCount(str)
println("count $count")
println("ex count ${str.lettersCount()}")
}
重载运算符
Kotlin的运算符重载允许我们让任意两个对象进行相加,或者是进行更多其他的运算操作。
这里以加号运算符为例,如果想要实现让两个对象相加的功能,那么它的语法结构如下:
class Obj {
operator fun plus(obj: Obj): Obj {
// 处理相加的逻辑
}
}
语法糖表达式和实际调用函数对照表如下所示:
语法糖表达式 | 实际调用函数 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a++ | a.inc() |
a-- | a.dec() |
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a == b | a.equals(b) |
a > b****a < b****a >= b****a <= b | a.compareTo(b) |
a..b | a.rangeTo(b) |
a[b] | a.get(b) |
a[b] = c | a.set(b, c) |
a in b | b.contains(a) |
示例
例如两个Money对象 运算符重载使用的二是operator关键字
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
operator fun plus(newValue: Int): Money {
val sum = value + newValue
return Money(sum)
}
}
fun main() {
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
println(money3.value)
val money4 = money3 + 20
println(money4.value)
}
对其他运算符重载参照表格对应的方法即可
if ("hello".contains("he")) {
println("hello contains in ")
}
if ("he" in "hello") {
println("he in ")
}
随机重复字符串,核心思想就是将传入的字符串重复N次,如果我们能够使用str*n这种写法来表示str字符串重复n次
fun getRandomLengthString(str: String): String {
val n = (1..20).random()
val builder = StringBuilder()
repeat(n) {
builder.append(str)
}
return builder.toString()
}
Kotlin中已经为咱们提供了一个用于字符串重复N遍的repeat函数 可以改写成如下
operator fun String.times(n: Int) = repeat(n)
最终我们可以简写成如下方式
fun getRandomLengthString1(str: String) = str * (1..20).random()
高阶函数详解
高阶函数和Lambda的关系密不可分的。
定义:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。
fun example(func: (String, Int) -> Unit) {
func("hello", 123)
}
Lambda表达式调用高阶函数
Kotlin还支持其他多种方式来调用高阶函数,比如Lambda表达式,匿名函数,成员引用等。
其中Lambda表达式也是最普通的高阶函数调用方式。
示例
val result1 = num1AndNum2(num1, num2) { n1, n2 ->
n1 + n2
}
模仿标准函数apply功能的高阶函数
注意这个函数类型参数声明方式和我们前面学习语法有些不同:
它在函数类型的前面加上了一个StringBuilder.的语法结构。
其实这才是定义高阶函数完成的语法规则,在函数类型的前面机上ClassName.表示这个函数类型在定义在哪个类当中的
这样定义的好处就是:当我们调用build函数时传入的lambda表达式就会自动拥有StringBuilder的上下文.
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
内联函数的作用
内联函数可以将使用Lambda表达式带来的运行时开销完全消除。它的工作原理并不复杂,就是Kotlin编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了。
inline
定义内联函数很简单,只需要在定义高阶函数时加上inline关键字的声明即可,如下所示:
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
noinlline
noinlline部分禁止内联
使用inline修饰函数之后,所有传入该函数的Lambda表达式或函数都会被内联化,如果希望函数中某一个或几个函数类型的形参不会被内联化,则可使用noinline修饰
inline fun inlineTest(block: () -> Unit, noinline blokc2: () -> Unit) {
}
内联函数与非内联函数还有一个重要的区别:那就是内联函数所引用的Lambda表示大是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回。
将高阶函数生命成内联函数是一种良好的编程习惯,事实上,绝大多数高阶函数是可以直接声明成内联函数的,但是也有少部分例外情况。
crossinline
一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局部控制流。为了标识这种情况,该 lambda 表达式参数需要用 crossinline
修饰符标记
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ……
}
高阶函数的应用
我们对以下内容进行简化扩展
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name", "yx")
editor.putInt("age", 30)
editor.apply()
我们可以使用高阶函数简化SharedPreferences的用法,如下所示:
示例如下:
fun SharedPreferences.open(block: SharedPreferences.Editor.() -> Unit) {
val editor = edit()
editor.block()
editor.apply()
}
定义好了open函数之后,以后在项目中使用SharedPreferences存储数据就会更加方便了,写法如下:
getSharedPreferences("data", Context.MODE_PRIVATE).open {
putString("name", "yx")
putInt("age", 30)
}
泛型和委托
泛型
在一般编程模式下,我们需要给任何一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会有更好的拓展性。
泛型主要有两种定义方式,一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是<T>。当然括号内的T并不是固定要求的,事实上你使用任何英文字母或单词都可以,但是通常情况下,T是一种约定俗成的泛型写法。
如果要定义一个泛型类,就可以这么写:
class MyClass<T> {
fun method(param: T): T {
return param
}
}
在调用MyClass类和method()方法的时候,可以将泛型指定成具体的类型,如下所示:
val myClass = MyClass<Int>()
val result = myClass.method(123)
而如果不想定义一个泛型类,只是想定义一个泛型方法,只需要将定义泛型的语法结构写在方法上面就可以了,如下所示:
class MyClass {
fun <T> method(param: T): T {
return param
}
}
此时的调用方式也需要进行相应的调整:
val myClass = MyClass()
val result = myClass.method<Int>(123)
可以看到,现在是在调用method()方法的时候指定泛型类型了。另外,Kotlin还拥有非常出色的类型推导机制,例如传入了一个Int类型的参数,它能够自动推导出泛型的类型就是Int型,因此这里也可以直接省略泛型的指定:
val myClass = MyClass()
val result = myClass.method(123)
类委托
类委托的核心思想在于将一个类的具体实现委托给另一个类去完成。但是委托也有一定的弊端,如果接口中的待实现方法比较少还好,要是有几十甚至上百个方法的话,每个都去这样调用辅助对象中的相应方法实现,写起了就非常复杂了。这个问题在Kotlin中可以通过类委托的功能来解决。
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int = helperSet.size
override fun contains(element: T): Boolean = helperSet.contains(element)
override fun containsAll(elements: Collection<T>): Boolean = helperSet.containsAll(elements)
override fun isEmpty(): Boolean = helperSet.isEmpty()
override fun iterator(): Iterator<T> = helperSet.iterator()
}
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
fun helloWorld() = println("Hello World")
override fun isEmpty() = false
}
现在MySet就成为了一个全新的数据结构类,它不仅永远不会为空,而且还能打印helloWorld(),至于其他Set接口中的功能,则和HashSet保持一致。这就是Kotlin的类委托所能实现的功能。
属性委托
委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。
我们看一下委托属性的语法结构,如下所示:
class MyClass {
var p by Delegate()
}
这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了的Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。
class MyClassProp {
var p by Delegate()
}
class Delegate {
var propValue: Any? = null
operator fun getValue(myClassProp: MyClassProp, property: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myClassProp: MyClassProp, property: KProperty<*>, any: Any?) {
propValue = any
}
}
委托的应用
实现一个自己的Lazy函数
val p by lazy{ ... }
- lazy是一个高阶函数
- lazy函数会创建并返回一个Delegate对象
- 当我们调用p属性的时候,其实调用的是Delegate对象的getValue()方法
- getValue方法又会调用lazy函数传入的Lambda表达式
- p属性得到的值是Lambda表达式中最后一行代码
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any?, property: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
}
fun <T> later(block: () -> T) = Later(block)
val p by later {
println("第一次加载")
"123"
}
fun main() {
println("p$p")
println("p$p")
}
网友评论