# Kotlin编码规范
# 开始学习
# 类布局
通常,一个类的内容按以下顺序排列:
- 属性声明与初始化块
- 次构造函数
- 方法声明
- 伴生对象。
# 接口实现布局
在实现一个接口时,实现成员的顺序应该与该接口的成员顺序相同。
# 命名规则
Kotlin 遵循 Java 命名约定。
类与对象的名称以大写字母开头并使用驼峰:
open class DeclarationProcessor { …… }
object EmptyDeclarationProcessor : DeclarationProcessor() { …… }
函数、属性与局部变量的名称以小写字母开头、使用驼峰而不使用下划线:
fun processDeclarations() { …… }
var declarationCount = …… // 公有成员变量
private var _num = …… // 私有成员变量,使用下划线开头_
常量名称(标有 const
的属性,或者保存不可变数据的没有自定义 get
函数的顶层/对象 val
属性)应该使用大写、下划线分隔的名称:
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"
保存带有行为的对象或者可变数据的顶层/对象属性的名称应该使用常规驼峰名称:
val mutableCollection: MutableSet<String> = HashSet()
对于枚举常量,可以使用大写、下划线分隔的名称 (enum class Color { RED, GREEN }
)也可使用以大写字母开头的常规驼峰名称,具体取决于用途。
# 格式化
在大多数情况下,Kotlin 遵循 Java 编码规范。
# 类头格式化
具有少数主构造函数参数的类可以写成一行,对于多个接口,对于很长超类型列表的类可进行换行。
class Person(id: Int, name: String) // 风格一
class Person( // 风格二
id: Int,
name: String,
surname: String
) : Human(id, name) { …… }
class Person( // 风格三
id: Int,
name: String,
surname: String
) : Human(id, name),
KotlinMaker { …… }
# 修饰符、关键字
如果一个声明有多个修饰符,请始终按照以下顺序安放:
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
# 注解格式化
注解通常放在单独的行上,在它们所依附的声明之前,并使用相同的缩进,无参数的注解可以放在同一行:
@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude
@JsonExclude @JvmField
var x: String
# 函数格式化
如果函数签名不适合单行,请使用以下语法:即 name: type。
fun methodName( // 函数名
arg1: Int = defaultValue, // 参数一 不能为空值,不传该参数时使用默认值。
arg2: ArgType? = defaultValue, // 参数二 可以为空值,值为空时使用默认值。
arg3: AnotherArgType // 参数三 不能为空值,必传参数
): ReturnType { // 返回类型
// 函数体
}
# Unit
如果函数返回 Unit(同 Java void
类型),那么应该省略返回类型:
fun foo() { // 这里省略了“: Unit”
……
}
对于由单个表达式构成的函数体,优先使用表达式形式。
fun foo(): Int { // 不良
return 1
}
fun foo() = 1 // 良好
# 属性格式化
对于非常简单的只读属性,请考虑单行格式:
val isEmpty: Boolean get() = size == 0
对于复杂的属性,总是将 get
与 set
关键字放在不同的行上:
val foo: String // val 只有 get 获取值
get() { …… }
var num = 0 // set 做越界判断
set(value) {
field = when {
value < MIN -> MIN
value > MAX -> MAX
else -> value
}
}
# 语言特性的惯用法
# 不可变性
优先使用不可变(而不是可变)数据。初始化后未修改的局部变量与属性,总是将其声明为 val
而不是 var
。
总是使用不可变集合接口(Collection
, List
, Set
, Map
)来声明无需改变的集合。使用工厂函数创建集合实例时,尽可能选用返回不可变集合类型的函数:
// 不良:使用可变集合类型作为无需改变的值
fun validateValue(actualValue: String, allowedValues: HashSet<String>) { …… }
// 良好:使用不可变集合类型
fun validateValue(actualValue: String, allowedValues: Set<String>) { …… }
// 不良:arrayListOf() 返回 ArrayList<T>,这是一个可变集合类型
val allowedValues = arrayListOf("a", "b", "c")
// 良好:listOf() 返回 List<T>
val allowedValues = listOf("a", "b", "c")
# 可变数量的参数(Varargs
)
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
// 例一、数组转List队列
val list1 = asList(1, 2, 3) // 打印:1 2 3
// 例二、数组转List队列,使用伸展(spread)操作符(在数组前面加 *)
val temp = arrayOf(7, 8, 9)
val list2 = asList(-1, 0, *temp, 4) // 打印:-1 0 7 8 9 4
# 使用条件语句
优先使用 try
、if
与 when
的表达形式。二元条件优先使用 if
而不是 when
例如:
// if 语句
return if (x) foo() else bar() // 良好
if (x) // 不良
return foo()
else
return bar()
// when 语句
return when(x) { // 良好
0 -> "zero"
else -> "nonzero"
}
when(x) { // 不良
0 -> return "zero"
else -> return "nonzero"
}
// try 语句
try {
……
} finally {
// 清理
}
# 区间上循环
使用 until
(半区间)函数在一个区间上循环:
// 正序循环
for (i in 0..n - 1) { …… } // 不良
for (i in 0 until n) { …… } // 良好
// 逆序循环
for (i in n downTo 0) { …… }
// 队列遍历
list.forEach { …… }
# 返回和跳转
Kotlin 有三种结构化跳转表达式:
- return 默认从最直接包围它的函数或者匿名函数 (opens new window)返回。
- break 终止最直接包围它的循环。
- continue 继续下一次最直接包围它的循环。
- 当要返一个回值的时候,解析器优先选用标签限制的 return@标签 value
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop // 标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。
// if (……) @continue@loop // 标签限制的 continue 继续标签指定的循环的下一次迭代。
}
}
// 例一、普通函数返回return
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // 非局部直接返回到 foo() 的调用者
print(it)
}
println("这一点是不可达到的")
}
// 例二、使用显式标签自定义 @lit
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // 局部返回到该 lambda 表达式的调用者,即 forEach 循环
print(it)
}
println("显式标签完成")
}
// 例三、使用系统隐式标签 @forEach
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach // 局部返回到该 lambda 表达式的调用者,即 forEach 循环
print(it)
}
println("隐式标签完成")
}
// 例四、使用匿名函数
fun foo() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return // 局部返回到匿名函数的调用者,即 forEach 循环
print(value)
})
print("匿名函数完成")
}
# 类与继承
Kotlin 中使用关键字 class 声明类
class Empty // 从 Any 隐式继承
class Invoice { ... }
# 构造函数
在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后,不能包含业务代码。
// 主构造函数
class Person constructor(firstName: String) { ... }
// 没有任何注解或可见性修饰符,可以省略这个 constructor 关键字
class Person(firstName: String) { ... }
// 有注解或有可见性修饰符,必须带上 constructor 关键字
class Person @JvmOverloads constructor(firstName: String) { ... }
//class Person private constructor(firstName: String) { ... }
// 多个构造函数,执行顺序(主构造函数 -> 初始化块init -> 次构造函数)
class Person(val name: String) { // 主构造函数
init { // 初始化块
println("Init block")
}
constructor(name: String, parent: Person) : this(name) { // 次构造函数
parent.children.add(this)
}
}
# 继承
如果派生类有一个主构造函数,其基类型可以(并且必须) 用基类的主构造函数参数就地初始化。
open class Base(p: Int) // open 显式定义超类
class Derived(p: Int) : Base(p)
如果类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。
// 例一、没有定义主构造函数
class CustomView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
...
}
// 例二、定义主构造函数,且使用注解@JvmOverloads来涵盖所有可能的构造函数
class CustomView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
FrameLayout(context, attrs, defStyleAttr) {
}
# 覆盖方法
我们之前提到过,Kotlin 力求清晰显式。与 Java 不同,Kotlin 对于可覆盖的成员(我们称之为开放)以及覆盖后的成员需要显式修饰符open:
open class Base { // open 显式定义超类
var count = 0 // 不可覆盖变量
open var empty = false // open 定义显式变量
open fun v() { ... } // open 定义显式函数
fun k() { ... } // 不可覆盖函数
}
class DerivedA(s: String) : Base(s) {
override var empty = true // 覆盖open变量
override fun v() {} // 覆盖open函数
}
open class DerivedB(s: String) : Base(s) {
override var empty = true // 覆盖open变量
final override fun v() {} // 覆盖open函数,同时禁止被子类覆盖
}
DerivedA.v() 函数上必须加上 override 修饰符。如果没写,编译器将会报错。 如果超类的函数没有标注 open 如 Base.k()
,那么子类中不允许定义相同签名的函数, 不论加不加 override。将 open 修饰符添加到 final 类(即没有 open 的类)的成员上不起作用。标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用 final 关键字:
# 调用超类实现
派生类中的代码可以使用 super 关键字调用其超类的函数与属性访问器的实现:
open class Foo {
open fun f() { println("Foo.f()") }
open val x: Int get() = 1
}
class Bar : Foo() {
override fun f() {
super.f() // 调用 Foo 实现的 f()
println("Bar.f()")
}
override val x: Int get() = super.x + 1
// 内联内部类中访问外部类的超类,可以通过由外部类名限定的super关键字来实现:super@Outer:
inner class Baz { // Baz -> Java 格式 public final class Baz
fun g() {
super@Bar.f() // 调用 Foo 实现的 f() -> Java 格式 Bar.super.f();
println(super@Bar.x) // 使用 Foo 实现的 x -> Java 格式 Bar.super.getX();
}
}
}
# 覆盖规则
在 Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super,如 super<Base>
:
open class A {
open fun f() { print("A") }
}
// A -> Java 格式
// public static class A {
// public void f() {
// String var1 = "A";
// System.out.print(var1);
// }
// }
interface B { // 接口成员默认就是“open”的
fun f() { print("B") } // 接口具体实现
// fun f() // 不实现业务,则不会生成 class DefaultImpls
}
// B -> Java 格式
// public interface B {
// void f();
// public static final class DefaultImpls {
// public static void f(B $this) {
// String var1 = "B";
// System.out.print(var1);
// }
// }
// }
class C() : A(), B {
// 编译器要求覆盖 f():
override fun f() { // 选择调用具体的超类函数来消除歧义
super<A>.f() // 调用 A.f()
super<B>.f() // 调用 B.f()
}
}
// C -> Java 格式
// public static final class C extends A implements B {
// public void f() {
// super.f();
// B.DefaultImpls.f(this);
// }
// }
同时继承 A
与 B
没问题,是 f()
由 C
继承了两个实现,所以我们必须在 C
中覆盖 f()
并且提供我们自己的实现来消除歧义。
# 数据类 data class
- 主构造函数需要至少有一个参数;
- 主构造函数的所有参数需要标记为
val
或var
; - 数据类不能是抽象、开放、密封或者内部的(
abstract
,inner
,open
,sealed
); - 数据类默认生成
equals()/hashCode()
、toString()
、componentN()
、copy()
函数
data class User(val name: String, val age: Int)
val user = User(name = "Jack", age = 1)
// 解构声明(对象解构成多个变量,必须按顺序解构参数)user.component1(),user.component2()..;
val (name1, age1) = user
val olderUser = user.copy(age = 2)
// 关于解构映射 map
for ((key, value) in map) {
// 使用该 key、value 做些事情
}
# 密封类 sealed class
- 密封类用来表示受限的类继承结构
- 一个密封类是自身抽象的 (opens new window),它不能直接实例化并可以有抽象(abstract)成员。
- 密封类不允许有非private 构造函数(其构造函数默认为 private)。
- 请注意,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。
sealed class Expr {
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
// 使用密封类
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// else -> { // 不再需要 else ,因为我们已经覆盖了所有的情况
// Double.NaN
// }
}
}
// -> Java 格式
// public abstract static class Expr {
// public static final class Const extends Expr
// public static final class Sum extends Expr
// public static final class NotANumber extends Expr
// ...
// }
# 嵌套类与内部类
类可以标记为 inner 以便能够访问外部类的成员。内部类会带有一个对外部类的对象的引用:
class Outer {
private val bar: Int = 1
// 嵌套类不能访问外部类成员
class Nested { // -> Java 格式 public static final class Nested
fun foo() = 2
}
// 内部类可以访问外部类成员,
inner class Inner { // -> Java 格式 public final class Inner
fun foo() = bar // 带有一个对外部类的对象的引用,可以直接使用bar
fun acc(){
val t = this // this = Inner
t.foo()
val o = this@Outer // this@Outer = Nested
o.bar
}
}
}
# 对象 object
# object 对象表达式
有时候,我们需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类。 Java 用匿名内部类*** 处理这种情况。Kotlin 用对象表达式和对象声明*对这个概念稍微概括了下。
- 匿名对象可以用作只在本地和私有作用域中声明的类型。
- 如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型。
- 如果你没有声明任何超类型,就会是
Any
。在匿名对象中添加的成员将无法访问。 - 创建匿名内部类的形式:
object: ClassName {...}
// 例一、 如果我们只需要“一个对象而已”,并不需要特殊超类型,那么我们可以简单地写:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y) // adHoc可以访问x y,因为adHoc是在foo()作用域中。 —— 参考第1点
}
// 例二、如果超类型有一个构造函数,则必须传递适当的构造函数参数给它。
interface A { …… }
open class B(x: Int) {
public open val y: Int = x
}
val b: B = object : B(1), A {
override val y = 15
}
print("${b.y}") // 输出 15, b可以访问y值,因为b有超类型B自动转成B类型对象。 ——参考第2点
// 例三、
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any,(new Object对象)
fun foo2() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 正确 foo可以访问x,因为foo()是私有作用域中。 —— 参考第1点
val x2 = foo2().x // 错误 未能解析的引用“x”, —— 参考第1、3点
}
}
# object 对象声明
对象声明的初始化过程是线程安全的,可以有超类。在Java中,单例的声明可能具有多种方式:如懒汉式、饿汉式、静态内部类、枚举等; 在Kotlin中,单例模式的实现只需要一个 object
关键字即可;所有里面定义的函数 都是默认 public static final class XXXX
object ProviderManager { // 定义一个单例对象
fun registerProvider(provider: String) { …… }
@JvmStatic
fun unregisterProvider{ …… }
}
object DefaultListener : MouseAdapter() { // 可以有超类
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
}
// ProviderManager 反编译后的java文件:
// public final class ProviderManager {
// public static final ProviderManager INSTANCE;
// public final void registerProvider(@NotNull String provider) { }
// @JvmStatic
// public static final void unregisterProvider() { }
// static {
// INSTANCE = new ProviderManager();
// }
// }
// INSTANCE调用 ProviderManager.INSTANCE.registerProvider("abc")
ProviderManager.registerProvider("abc")
// 静态函数调用 ProviderManager.unregisterProvider()
ProviderManager.unregisterProvider()
# 伴生对象
class MyClass{
fun age() { }
// fun bar() { } 错误,伴生对象中加了@JvmStatic注解,默认会在MyClass生成同名静态函数
// @JvmStatic fun name() { } 错误,class不接受@JvmStatic注解
companion object {
fun foo() { }
// 在伴生对象中的函数加@JvmStatic
@JvmStatic fun bar() { }
}
}
// MyClass.kt 反编译生成的 MyClass.java
public static final class MyClass {
public final void age() { }
@JvmStatic
public static final void bar() { // 伴生对象中的函数加@JvmStatic,生成的静态函数
Companion.bar();
}
public static final class Companion {
public final void foo() { }
@JvmStatic
public final void bar() { }
...
}
}
// 例一、
class MyClass {
companion object Factory { // 类内部的对象声明可以用 companion 关键字标记:
fun create(): MyClass = MyClass()
}
}
// 例二、
class MyClass {
companion object { // companion object 省略名称
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create() // 使用
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员。 例如还可以实现接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
# 对象表达式和对象声明之间的语义差异
对象表达式和对象声明之间有一个重要的语义差别:
对象表达式是在使用他们的地方立即执行(及初始化)的;
对象声明是在第一次被访问到时延迟初始化的;
伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
# 高阶函数与 Lambda 表达式
高阶函数是将函数用作参数或返回值的函数。
# Lambda 表达式
lambda 表达式总是括在花括号中,完整语法形式的参数声明放在花括号内,并有可选的类型标注,函数体跟在一个 ->
符号之后。如果推断出的该 lambda 的返回类型不是 Unit
,那么该 lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。
val sum: (Int, Int) -> Int = { x, y -> x + y } // 具体返回 Int
val sum: (Int, Int) -> Unit = { x, y -> x + y } // 具体返回 Unit
val sum = { x: Int, y: Int -> x + y} // 省略形式,反推返回值是 Int
val sum = label@{ x: Int, y: Int -> x + y return@label} // 省略形式,反推返回值是 Unit
// 函数 max 是一个高阶函数,它接收一个函数作为第二参数,类型是 (Int, Int) -> Int 的表达式
fun max(x: Int, less: (Int, Int) -> Int): Int {
return x+less(x,x)
}
val sum1 = { x: Int, y: Int -> 2 + 3 } // 可以推断中表达式(Int, Int) -> Int
println(max(1, sum1)) // 输出 6
val sum2 = { x: Int, _: Int -> x - 3 } // 未使用的参数可以用_下划线取代
println(max(1, sum2)) // 输出 -1
// Android项目中的常用的Lambda表达式
widget.setOnClickListener { println("view id ${it.id}") } // 默认隐式参数 it
widget.setOnClickListener { view -> // 显式声明参数 view
println("view id ${view.id}")
}
widget.setOnKeyListener { view, keyCode, event -> // 显式声明多个参数
println("view id ${view.id}, keyCode $keyCode, event action ${event.action}")
true
}
# 匿名函数
匿名函数就是无需定义函数名称,其他的和普通函数定义语法一样, 匿名函数参数总是在括号内传递, Lambda表达式与匿名函数 两者间的显著区别:
- Lambda没有显式指定返回值类型的时候,kotlin可以推断出来。
- 匿名函数需要显示指定返回值类型。
- 非局部返回的行为。一个不带标签的 return 语句总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return将从包含它的函数返回,而匿名函数中的 return将从匿名函数自身返回。
val sum3 = fun(x: Int, y: Int): Int = x + y
// 或
val sum3 = fun(x: Int, y: Int): Int {
return x + y
}
// 根据上面Lambda表达式案例中max()高阶函数的定义
println(max(1, sum3)) // 输出 3
# 带有接收者的函数
带有接收者的函数类型,例如 A.(B) -> C
,可以用特殊形式的函数字面值实例化—带有接收者的函数字面值。
open class Parent {
fun fa() { …… }
fun fb() { …… }
}
class Child : Parent() {
fun fc() { …… }
fun fd() { …… }
}
fun runCall(method: Child.() -> Unit): Parent {
val child = Child() // 创建接收者对象
child.method() // 将该接收者对象传给该 lambda 表达式,表达式中有了该对象的引用
return child
}
runCall { // this = Child, 表达式中有了Child对象的引用 this
fa()
this.fb()
fc()
this.fd()
}
# This 表达式
为了表示当前的 接收者 我们使用 this
表达式:
- 在【类】的成员中,
this
指的是该类的当前对象。 - 在【扩展函数】或者【带有接收者的函数字面值】中,
this
表示在点左侧传递的 接收者 参数。
要访问来自外部作用域的this
(一个【类】或者【扩展函数】,或者带标签的【带有接收者的函数字面值】)我们使用this@label
,其中 @label
是一个代指 this
来源的标签:
class A { // 隐式标签 @A
inner class B { // 隐式标签 @B
fun Int.foo() { // 隐式标签 @foo
val a = this@A // A 的 this
val b = this@B // B 的 this
val c = this // foo() 的接收者,一个 Int
val c1 = this@foo // foo() 的接收者,一个 Int
val funLit1 = lambda@ fun String.() {
val d = this // funLit1 的接收者,一个String
}
val funLit2 = { s: String ->
// foo() 的接收者,因为它包含的 lambda 表达式,没有任何接收者
val d1 = this // foo() 的接收者,一个 Int
}
}
}
}
# 内联函数(内联属性...)
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。 即那些在函数体内会访问到的变量。内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销
fun sayHello(){ // 正常函数
var a = 10
}
// inline 内联函数, 参数函数sum 默认也为内联函数
inline fun sayWorld(a:Int, sum:(c:Int, d:Int) -> Int){
var b = 20
}
// inline 内联函数, 参数函数sum 禁用内联noinline
inline fun sayWorld(a:Int, noinline sum:(c:Int, d:Int) -> Int){
var b = 20
}
sayHello() // 内联后 sayHello()
sayWorld() // 内联后 var b = 20
println("end") // 内联后 println("end")
# 内联函数的非局部返回
fun foo() {
ordinaryFunction {
return // 代码错误:不能使 `foo` 在此处返回,
}
}
fun foo() {
ordinaryFunction label@{ // 使用标签(显示或隐式都可以)
return@label // 正确:可以使 `foo` 在此处返回
}
}
fun foo() {
inlined { // 使用内联函数
return // OK:该 lambda 表达式是内联的
}
}
# 内联函数具体化的类型参数Reified
- 我们使用
reified
修饰符来限定类型参数,现在可以在函数内部访问它了,和一个普通的类一样。由于函数是内联的,不需要反射。 - 普通的函数(未标记为内联函数的)不能有具体化参数。不具有运行时表示的类型。
inline fun <reified T> getClassSimpleName() = T::class.simpleName
inline fun <reified T> getMembersOf() = T::class.members
// Activity 使用
inline fun <reified T : Activity> Activity.startActivity() {
startActivity(Intent(this, T::class.java))
}
// Gson 使用
inline fun <reified T: Any> Gson.fromJsonNew(json: String): T{
return fromJson(json, T::class.java)
}
// 打印
println(getClassSimpleName<String>().joinToString("\n")) // 输出:String
println(getMembersOf<String>().joinToString("\n")) // 输出:String 里面的所有成员
// 使用
activity.startActivity<DetailActivity>()
gson.fromJsonNew<User>()
# 函数使用类型别名
如果有一个在代码库中多次用到的函数类型或者带有类型参数的类型,那么最好为它定义一个类型别名:
typealias PersonIndex = Map<String, Person> // Map
typealias MouseClickHandler = (Any, MouseEvent) -> Unit // Function2
typealias BindData<T> = (view: View, data: T, more: Boolean) -> Unit // Function3
# 扩展函数
我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:
open class C
class D: C()
fun C.foo() = "c" // 定义接收者类型C的扩展函数
fun D.foo() = "d" // 定义接收者类型D的扩展函数
fun outFoo(c: C) {
println(c.foo())
}
outFoo(D()) //输出 "c",因为调用的扩展函数只取决于参数 c 的声明类型,该类型是`C`类。
如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字,都适用给定的参数,这种情况总是取成员函数。 例如:
class C {
fun foo() { println("member") } // 成员函数
}
fun C.foo() { println("extension") } // 扩展函数与成员函数 相同
fun C.foo(i: Int) { println("extension") } // 扩展函数与成员函数 不同
println(C().foo()) // 它将输出“member”,而不是“extension”。
println(C().foo(10)) // 它将输出“extension”
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数与属性:
class CustomClass {
companion object { }
}
fun CustomClass.Companion.foo() { …… } // 为伴身对象定义扩展函数
CustomClass.foo() // 只需用类名作为限定符去调用他们
# 使用扩展函数
每当你有一个主要用于某个对象的函数时,可以考虑使其成为一个以该对象为接收者的扩展函数。为了尽量减少 API 污染,尽可能地限制扩展函数的可见性。根据需要,使用局部扩展函数、成员扩展函数或者具有私有可视性的顶层扩展函数。
// 例一、获取List最后一个索引
val <T> List<T>.lastIndex: Int
get() = size - 1
// 例二、标准库中的定义 StandardKt
public inline fun <T, R> T.let(block: (T) -> R): R {
……
return block(this)
}
// 例三、自定义View的扩展函数
inline fun View.onClickDelayed(
delayed: Int = ViewUtils.DEF_CLICK_DELAYED,
noinline lis: (View) -> Unit
) { …… }
// 例二、例子函数类型 block: (T) -> R
var str = "name"
var aUnit = str?.let { } // let 返回 = Unit
var aInt = str?.let { it.length } // let 返回 = Int
var aString = str?.let { it.substring(1) } // let 返回 = String
# 委托
Derived
类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base
,但请注意,以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现:
属性委托语法格式:val/var <属性名>: <类型> by <表达式> 例 var p: String by Delegate()
interface Base {
val message: String
fun printMessage()
fun printMessageLine()
fun print()
}
class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x = $x"
override fun printMessage() {
println("BaseImpl 输出 printMessage")
}
override fun printMessageLine() {
println("BaseImpl 输出 printMessageLine")
}
override fun print() {
println("BaseImpl 输出 $message")
}
}
// by-子句表示b将会在Derived中内部存储,实现:Base委托给b后不需要显式重写Base中的接口函数
class Derived(b: Base) : Base by b {
// 在 b 的 `print` 实现中不会访问到这个属性
override val message = "Message of Derived"
override fun printMessage() {
println("Derived 输出 printMessage")
}
}
val b = BaseImpl(10)
val derived = Derived(b)
derived.printMessage() // :Derived 输出 printMessage
derived.printMessageLine() // :BaseImpl 输出 printMessageLine
derived.print() // :BaseImpl 输出 BaseImpl: x = 10,不是Message of Derived
println(derived.message) // :Message of Derived
# 泛型
class Box<T>(t: T) {
var value = t
}
val box: Box<Int> = Box<Int>(1) // 默认定义对象
val box = Box(10) // 10具有类型 Int,所以编译器知道我们说的是 Box<Int>。
# 型变:声明处型变
out 修饰符称为协变类型参数 ; in 修饰符称为逆变类型参数。 简单来说就是out只能作为返回值; in是只能作为参数,
interface SourceOut<out T> { // <out T>对应于 Java 的 <? extends Object> 本身以及它的子类
fun next(): T // 正确,只能作为返回值
// fun next(t: T) // 编译错误 类型参数T被声明为“out”,但在类型T的“in”位置出现
}
// 如果型变参数声明为 out 类型,允许在构造函数中出现赋值,
// class SourceOut<out T>(val value: T){ ... }
interface SourceIn<in T> { // <in T>对应于 Java 的 <? super Object> 本身以及它的超类
fun next(t: T): Int // 正确,只能作为参数
// fun next(): T // 编译错误 类型参数T被声明为“in”,但在类型T的“out”位置出现
}
class SourceOutImpl : SourceOut<String> {
override fun next(): String = "SourceOutImpl"
}
class SourceInImpl : SourceIn<Number> {
override fun next(t: Number): Int = 100
}
fun SourceOutIn(sout: SourceOut<String>, sin: SourceIn<Number>) {
val oOut: SourceOut<Any> = sout // String类型转Any(与Java相反)
val outNext = oOut.next()
val oIn: SourceIn<Int> = sin // Number类型转Int(类似Java强制转换)
val inNext = oIn.next(10)
}
// 输出
SourceOutIn(SourceOutImpl(), SourceInImpl())
# 使用处型变:类型投影
class Array<T>(val size: Int) {
fun get(index: Int): T { …… }
fun set(index: Int, value: T) { …… }
}
// Array<Any> 本身
fun copy1(from: Array<Any>, to: Array<Any>) { …… }
// Array<out Any> 本身以及它的子类
fun copy2(from: Array<out Any>, to: Array<Any>) { …… }
// Array<in String> 本身以及它的超类
fun fill(dest: Array<in String>, value: String) { …… }
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "$it" }
copy1(ints, any) // 其类型为 Array<Int> 但此处期望 Array<Any> 编译错误不通过
copy2(ints, any) // 其类型为 Array<out Any> 允许自身以及子类类型 编译正确
val strs: Array<CharSequence> = arrayOf("a","b","c")
fill(strs, "abc") // 其类型为 Array<in String> 允许自身以及它的超类 编译正确
# 泛型函数
不仅类可以有类型参数。函数也可以有。类型参数要放在函数名称之前:
fun <T> singletonList(item: T): List<T> { // 普通泛型函数
// ……
}
fun <T> T.basicToString() : String { // 泛型扩展函数
// ……
}
val sl1 = singletonList<Int>(1)
// 或可以省略能够从上下文中推断出来的类型参数
val sl2 = singletonList(1)
# 泛型约束
能够替换给定类型参数的所有可能类型的集合可以由泛型约束限制。
fun <T : Comparable<T>> sort(list: List<T>) { …… } // 定义泛型约束
// 编译通过OK。Int 是 Comparable<Int> 的子类型
sort(listOf(1, 2, 3))
// 编译错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
sort(listOf(HashMap<Int, String>()))