Kotlin 学习记录之 Kotlin 新特性

今天给大家分享的是 Kotlin 这门语言有别的于 Java 的一些新特性,在 Kotlin 没有发布的时候,我们使用 Java 进行 Android 开发,现在呢?Google 已经向宣布了,Kotlin 也可以进行 Android 开发(毕竟如果可以不用 Java 的话,也就不用和甲骨文有版权纠纷了),而且学习成本也很低,因为简单的语法可以使得开发人员快速上手,且和 Java 百分百兼容,你的项目可以不用从头开始再写一遍,而是可以两种语言共存,从而慢慢的过渡到 Kotlin。

我们可以在哪里学习 Kotlin 呢?

当我们点进 Kotlin 的官网时,可以看到官网是如何介绍它的呢?


image.png
  • 简洁,大大减少样板代码的数量,如数据类,Lambda,单例创建等等。
  • 安全,避免类的错误,如空指针异常,可空类型,类型检测与自动转换等等。
  • 互操作,开发时可利用JVM,Android和浏览器的现有库。
  • 工具友好,任何 Java IDE 或者命令行都可以进行开发。

在介绍今天要讲内容之前,我想先来介绍 Kotlin 的一些基本使用,也就是一些基本语法,然后我再讲新特性的时候也就更容易看得懂了。

1.如何定义变量

//  In Java
//  变量
int index = 0;

//  常量
final PI = 3.14;

//  In Kotlin 
//  变量
var index: Int = 0
//   或者使用类型推导,编译器会知道 index 是 Int 类型
var index = 0

//  常量
val PI = 3.14

2.如何定义方法

//  In Java 
public int sum(int a, int b) {
    return a + b;
}

//  In Kotlin
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

//  表达式函数体
//  这里使用了类型推导
fun sum(a: Int, b: Int) = a + b

3.如何定义类

In Java  
interface AC {}

class Super {
    public Super() {}
}

class Sub extends Super implements AC{

    public Sub () {
        super();
    }
}

//  In Kotlin

//  默认会自动生成一个无参构造
open class B

//  这里必须显示的调用父类的构造方法
//  如果父类的的构造带参那么也必须调用带参的构造
class RB : B()

interface AC {
    //  带默认实现的方法
    //  Java 8 之后也支持接口方法的默认实现,不过需要声明 default 关键字

    //  由于 Kotlin 是以 Java 6 为目标设计的,所以默认方法在转成 Java 代码后其实是
    //  接口中有一个默认实现了接口的静态类
    fun showOff() = println("Clickable 默认实现")
}

//  没有参数的话可以省略 ()
//  也可以省略大括号
//  Kolin 的类默认是 public 和 final,想要被集成需要改为 open 的
//  (p: Int) 这里被括号围起来的语句就叫做主构造方法
open class Super(p: Int)

//  这里需要写 Super(10) 是因为需要调用父类的构造函数
//  而接口 AC 并不存在构造直接写 AC 即可
//  无论是继承还是现实都是通过 : 实现
//  constructor 用来声明一个构造(主构造),constructor 关键字可省略
class Sub constructor(s: Int): Super(s), AC {
    //  init 表示引入一个初始化语句块,这种语句块会在类被创建时执行,并且与主构造方法一起使用
    init {
        this.s = 0
    }
}

//  实例化
//  In Java 
Sub sub = new Sub(2);

//  In Kotlin
var sub = Sub(3)

接下来开始介绍今天的重点,Kotlin 的一些新特性~

1.扩展函数/成员(给别人的类添加方法)

//  在扩展函数中可以直接访问被扩展的类的其他方法和属性
//  但扩展函数并不允许打破它的封装性,所以扩展函数不能访问私有或是受保护的成员
//  对应 Java 代码中 扩展函数是 static final 的,所以扩展函数是不能被子类重写的
//  扩展函数并不是类的一部分,因为扩展函数是声明在类外部的
//  如果扩展函数与成员函数签名相同,往往成员函数会被优先使用

//  书中解释说扩展函数无非就是静态函数的一个高效的语法糖

//  如:为 String 扩展一个 重复多次的字符串方法
//  String.multiply 前面为为 String 扩展,后面为方法名
//  int 为重复次数 返回一个 String
fun String.multiply(int: Int): String {
    val stringBuild = StringBuffer()
    for (i in 0 until int) {
        //  这时候里面就有了一个 this,而这个 this 就指代调用的这方法的对象了
        stringBuild.append(this)
    }
    return stringBuild.toString()
}

//  Kotlin 中的调用
//  直接用字符串调用该方法即可
println("Jaaaelu ".multiply(8))

//  Java 中的调用
//  Java 中如何调用呢? 类名.方法名
//  这里其实相当于静态方法
//  如 Expand.multiply("Jaaaelu ", 8)

//  给 String 扩展成员常量
//  并不能进行初始化,因为没有合适的地方来存放值
val String.test: String
    get() = "abc"

var StringBuilder.lastChar
    //  var 必须要有 getter 和 setter
    get() = get(length - 1)
    set(value) = this.setCharAt(length - 1, value)

2.默认参数

//  Kotlin 可以给函数参数定义默认值,这样大大降低了重载函数的必要性,
//  而且命名函数让多参数函数的调用更加易读。

//  我们现在为 Collection 写一个集合拼接成字符串的一个扩展方法
//  拼接时我们指定前缀、后缀和分隔符
//  这里使用了默认参数,为参数指定默认的值,这样在调用方法的时候就
//  可以不指定参数或者只为某个参数指定值
fun <T> Collection<T>.collectionJoinToString(separator: CharSequence = ", ",
                                             prefix: CharSequence = "", 
                                             postfix: CharSequence = ""): String {
    val result = StringBuilder(prefix)

    for ((index, value) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(value)
    }

    result.append(postfix)
    return result.toString()
}

//  调用
val set = setOf(1, 2, 5)
//  全部使用默认值
println(set.collectionJoinToString())
//  为第一个参数赋值
println(set.collectionJoinToString("-"))
//  配合命名参数,第一个参数值使用默认值,只为指定的参数赋值
println(set.collectionJoinToString(prefix = "[", postfix = "]"))

//  如果在方法上面添加 @JvmOverloads 那么在 Java 中调用也会生成该函数的各个重载版本

3.object 关键字

//  object 关键字的核心用途就是定义一个类时同时创建一个实例。

//  一些不同的使用场景:
//  1.对象声明是定义单例的一种方式。
//  2.伴生对象可以持有工厂方法和其他与这个类相关,但在调用时并不依赖类实例的方法。
//  它们的成员可以通过类名来访问。
//  3.对象表达式用来替代 Java 的匿名内部类。

//  1.创建单例
//  引入 object 后,与类一样依然可以有属性、方法、初始化语句等声明,
//  但是唯一不允许的就是构造方法(主构造、从构造)都没有
object Patroll :Driver{
    override fun drive() {
        println("日常飙车")
    }

    private val allEmployees = arrayListOf<Person>()

    fun calculateSalary() {
        for (person in allEmployees) {
            //  ...
        }
        println("Jaaaelu")
    }
}

//  调用
val p1:Driver = Patroll
val p2 = Patroll

//  2.伴生对象
//  关键字 companion,使用后可以通过类名访问这个对象的方法属性,看上去非常像是静态调用
//  可以访问私有
class ACompanion {

    private fun privateFun() {
        println("私有方法")
    }

    companion object {
        val PI = 3.14

        fun bar() {
            println("测试方法")
        }
    }
}

//  调用
println(ACompanion.PI)

//  3.匿名内部类的新写法
//  object 除了声明单例外还能用来声明匿名对象(也就是 Java 中的匿名内部类)
//  于声明对象不同,匿名对象不是单例,比如每次执行 other()。都会创建新的对象实例

var count = 0

fun other() {
        //  除了去掉名字外,语法与对象声明相同
        val a = object : Driver {

            override fun drive() {
                count++
                //  也可以访问外面的内容
                println("匿名内部类 $count")
            }
        }
        println(a)
        a.drive()
}

4.Lambda 表达式

//  Lambda 表达式本质上就是可以传递给其他函数的一小段代码(作为函数参数的代码块)
//  函数式表层提供了另一种解决方案:把函数作为值来看待

//  基础语法
//  { x: Int, y: Int -> x + y }     其中 x: Int, y: Int 为参数, x + y 为函数体。
//  Kotlin 中的 Lambda 始终用花括号包围,只需箭头就将参数列表与函数体分开。

//  可以将 Lambda 赋值给变量或者常量,然后调用
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))

//  正常 sum
//  相当于(Int, Int) -> Int
fun sumOld(arg1: Int, arg2: Int): Int {
    return arg1 + arg2
}

//  Lambda 表达式形式的 sum
val sumLambda = { arg1: Int, arg2: Int -> arg1 + arg2 }

//  Lambda 表达式形式的 sum 且其中还能有其他语句
//  相当于(Int, Int) -> Int
val sumAndPrint = { arg1: Int, arg2: Int ->
    println("$arg1 + $arg2 = ${arg1 + arg2}")
    //  最后一行表示你的返回值
    arg1 + arg2
}

//  没有参数和返回值的时候,可以直接将其写在大括号内
val printHello = {
    //  相当于函数体
    println("Hello")
}

//  在作用域中访问变量
//  捕捉的原理:当你捕捉 final 变量时,它的值和使用这个值的 Lambda 代码一起存储。
//  对于非 final 变量来说,它的值被封装在一个特殊的包装器中,这样你就可以改变这个值,
//  这个包装器的引用会和 Lambda
//  代码一起被存储。

fun printProblemCounts(responses: Collection<String>) {
    //  默认情况下,局部变量的生命周期被限制在声明这个变量的函数中
    //  但如果被 Lambda 捕捉了,那么使用这个变量的代码可以被存储并稍后再执行
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach {
        if (it.startsWith("4")) {
            //  Kotlin 中允许在 Lambda 中访问非 final 变量,甚至修改它们
            //  从 Lambda 内访问外部变量,我们称这些变量被 Lambda 捕捉
            //  就像例子中的 clientErrors 以及 serverErrors
            clientErrors++
        } else if (it.startsWith("5")) {
            serverErrors++
        }
    }
    println("$clientErrors client errors, $serverErrors server errors")
}

//  Lambda 的实现细节:从 Kotlin 1.0 开始,每个 Lambda 表达式都会被编译成一个匿名类,
//  除非它是一个内联 Lambda
//  不过这里所说的匿名类只对期望函数式接口的 Java 方法有效

//  内联函数:消除 Lambda 带来的运行时开销
//  例如 Lambda 表达式会被正常的编译为匿名类,这样每次调用 Lambda 就会创建一个类,
//  这样会带来额外的开销。
//  Kotlin 提供了 inline 修饰符标记一个函数
//  内联函数被编译后,它的字节码连同传递给它的 Lambda 的字节码被插入到调用函数的代码中,
//  这使得函数调用相比于直接编写相同的代码,不会产生额外的运行时开销。

inline fun <T> synchronized(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    } finally {
        lock.unlock()
    }
}

val l = Lock()
synchronized(l) {
    println("J")
}

//  上面代码会编译为
l.lock()
try {
    println("J")
} finally {
    l.unlock()
}

//  内联函数的限制
//  由于不是所有的 Lambda 函数都可以被内联,当函数内敛的时候,
//  作为参数的 Lambda 表达式的函数会直接替换掉最终生成代码中。
//  这也限制了函数体中对应的(Lambda)参数的使用,如果(Lambda)参数被调用,
//  这样的代码能被容易地内联。
//  但如果(Lambda)在某个地方被保存起来了,以便后面继续使用,Lambda 表达式的代码将不能被内联。

//  例如系统提供的这个函数,它没有直接调用作为 transform 参数传递进来的函数,
//  而是将这个函数传递给了一个类的构造,构造方法将它保存在一个属性中,
//  所以 transform 需要被编译为标准的非内联的表示法,即一个实现了函数接口的匿名类。
//  fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
//      return TransformingSequence(this, transform)
//  }

//  如果一个函数期望两个或者更多 Lambda 参数,可以选择只内联一些参数。
inline fun foo(inlined: () -> Unit, noinline noinlined: () -> Unit) {

}

//  决定何时将函数声明成内联
//  使用 inline 关键字只能提高带有 Lambda 参数的函数的性能。
//  对于普通的函数调用,JVM 已经提供了强大的内联支持。
//  在使用 inline 关键字时,应该注意代码的长度,把一些和 Lambda 无关的方法抽取到一个
//  独立非内联函数中,从而减少字节码拷贝长度。


//  对于 Koltin 集合来说,提供了更多高阶函数
//  高阶函数就是以另一个函数作为参数或者返回值的函数
//  在 Kotlin 中函数可以用 Lambda 或者函数引用来表示
//  因此,任何以 Lambda 或者函数引用作为参数货返回值的函数(或者两者都是)都是高阶函数

//  以集合为例:
//  检查集合中所有元素是否都符合某个条件,可以通过 all 或 any 来实现
//  all 判断所有元素是否都满足该条件
//  any 判断元素列表中是否存在至少一个匹配元素
//  count 对满足该表达式的元素计数
//  find 找到一个满足 Lambda 的第一个元素
//  filter 会从集合中一处你不想要的元素
//  map 函数对集合的每一个元素应用给定的函数,并把结果收集到一个新集合
//  flatMap 可将多列表平铺
//  groupBy 把列表转为分组的 map
//  等等 
//  不过对于某些高阶函数来说,如做 map 或 filter 操作时,这些函数都会创建中间集合
//  如果元素数量过多,这种链式调用就会变得十分低效
//  所以这时候就引入序列,将上述操作变成使用序列,而不是直接返回集合
//  序列:惰性集合操作,由于序列中的元素求值是惰性的,因此,
//  可以使用序列更高效的对集合元素执行链式操作,而不需要创建额外中间集合来保存中间过程中产生的结果
//  惰性操作是对元素逐个处理

//  先将集合转为序列,然后进一步进行函数操作,最后再将序列转为集合
//  这时就不会创建中间集合
//  下面的计算中.map(Person::name).filter { it.startsWith("A") } 就是中间操作
//  而 toList() 是末端操作
//  由于中间操作时惰性的,所以 map 和 filter 变换被延期了
//  末端操作才会触发被延期的计算
//  序列的执行顺序是按照元素来的,所以打印出来的内容是 map 第一个元素,filter 第一个元素,如此往下
//  而我们正常集合操作的话就是先全部 map,然后再全部 filter
println(people
            .asSequence()
            .map(Person::name)
            .filter { it.startsWith("A") }
            .toList())

5.可空性

//  Kotlin 对可空类型的支持,可以帮助我们在编译期,检测出潜在的 NullPointerException 错误。
//  Kotlin 提供了像安全调用(?.)、Elvis 运算符(?:)、
//  非空断言(!!)及 let 函数这样的工具来简洁的处理可空类型。

//  String = String,不能存储 null 引用
//  所以默认情况下类型都是非空的
fun strLen(s: String) = s.length

//  String? = String or null
//  这里是显式的标记出使用可空的 String
//  s?.length 表示如果 s 不为空会返回 s.length 否则返回 null
//  相当于 if (s != null) s.length else null
fun strLenCanNullNoException(s: String?) = s?.length

//  带有默认值 ?. 后面跟的就是默认值
fun strLenCanNullNoExceptionAndWithDefault(s: String?) = s?.length ?: 0

//  虽然可以用作默认值,当然也可以用于其他功能,如抛出异常
fun strLenThrowException(s: String?) = s?.length ?: throw NullPointerException("字符串为空")

//  s!!.length 表示如果 s 为 null 会直接抛出空指针异常,不为 null 则可以返回 s.length
//  一般情况下,只有我们知道 s 一定不为 null 的时候使用
//  !! 也叫做非空断言,尽量不在一行代码中使用多个非空断言,
//  因为你很难分清除是哪个 !! 让你程序抛出空指针异常
fun strLenCanNullWillException(s: String?) = s!!.length

val p: Person? = Person("Gzw", 23)
//  sendEmail 接收一个非空类型的 Person,所以需要先进行空判断,判断通过再继续
if (p != null) sendEmail(p, "啦啦啦啦啦啦")
//  还可以使用另一种方式 let
//  如果 p 不为空,那么 it 就不为空,如果 p 为空什么都不会发生
//  这里必须是 ?. 的调用方式,否则会报错
p?.let { sendEmail(it, ",,,,,") }

//  Kotlin 中所以泛型类和泛型函数的类型参数默认都是可空的
fun <T> printlnHashCode(t: T) {
    println(t?.hashCode())
}

//  T 被推导为 Any?
printlnHashCode(null)

//  为类型参数添加非空上界后,现在的 T 就不是可空的了
fun <T: Any> printlnHashCodeNotNull(t: T) {
    println(t.hashCode())
}

//  Java 中的类型在 Kotlin 中被解释成平台类型,允许开发者把他们当做可空或非空来对待。
//  Java 中 @Nullable + Type = Type? / @NotNull + Type = Type
//  Java 中的 Type = Kotlin 中的 Type? or Type
//  如 Java 中的 ArrayList<String> 在 Kotlin 中被当做了 ArrayList<String?>?

//  表示基本数字的类型(如 Int)看起来用起来都像普通的类,但通常会被编译成 Java 基本数据类型。
//  可空基本类型(如 Int?)对应着 Java 中的装箱基本数据类型(如 Integer)。
//  Any 类型是所有其他类型的超类型,类型于 Java 中的 Object。而 Unit 类比于 void。

6.集合

//  创建可空性的集合时,需要注意的时候,要小心决定什么是可空的,
//  是元素可空还是集合本身可空,还是两者都可为空?
//  List<Int?>、List<Int>?、List<Int?>?

//  Kotlin 中把访问集合数据的接口和修改集合数据的接口分开了
// (只读集合 Collection 与可变集合 MutableCollection)
//  kotlin.collections.Collection 只读集合提供了 size、iterator、contains 等操作来查看读取数据
//  kotlin.collections.MutableCollection 可变的集合提供了 add、remove、clear 等修改集合的操作,
//  不过 MutableCollection 继承自 Collection,所有也拥有那些读取操作
//  如果函数接收 Collection 而不是 MutableCollection,
//  那么就很容易知道这个方法不会修改集合,只会读取集合数据。

//  只读集合并不一定是不可变的,当只读集合和可变集合指向同一个集合对象的时候,可变可以进行操作,
//  然后只读读取操作后的集合。
//  只读集合并不总是线程安全的。
//  即使在 Kotlin 中将集合声明成只读,Java 代码也能够修改这个集合,
//  因为 Java 中并不区分只读集合和可变集合。

//  List 创建集合函数,只读 -> listOf,可变 -> mutableListOf、arrayListOf
//  Set 创建集合函数,只读 -> setOf,可变 -> mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
//  Map 创建集合函数,只读 -> mapOf,可变 -> mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf

//  只读集合并一定是不可变的
var mutable: MutableCollection<Int> = mutableListOf(1, 2, 3, 4, 5)
val c: Collection<Int> = mutable
var m: MutableCollection<Int> = mutable

//  Array<Int> 将会是一个包含装箱整型的数组
//  IntArray 是基础类型 int 的数组,也就是 Java 中的 int[],这样效率会更高一些

//  通过 Java 方法操作了只读集合

7.运算符

//  Kotlin 允许使用对应名称的函数来重载一些标准的数学运算,但是不能定义自己的运算符

//  可重载的二元算数运算符
//  a * b -> times
//  a / b -> div
//  a % b -> mod
//  a + b -> plus
//  a - b -> minus

//  特殊运算符
//  shl -> 带符号左移
//  shr -> 带符号右移
//  ushr -> 无符号右移
//  and -> 按位与
//  or -> 按位或
//  xor -> 按位异或
//  inv -> 按位取反

//  一元运算符
//  +a -> unaryPlus
//  -a -> unaryMinus
//  !a -> not
//  ++a, a++ -> inc
//  --a, a-- -> dec

data class Point(val x: Int, val y: Int) {
    //  重载 plus 运算符
    //  且并不要求两个运算数是相同类型,返回值也可以不同
    //  不支持交换性
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }

    operator fun unaryMinus(): Point {
        return Point(-x, -y)
    }
}

//  也相当于 Point(10, 20).plus(Point(20, 10))
println(Point(10, 20) + Point(20, 10))

//  复合运算符
//  += 可以被转换为 plus 或者 plusAssign(但最好不要同时重载这两个方法)
//  point = point + Point(10, 20)
point += Point(10, 20)
println(point)

//  把运算符定义为扩展方法
operator fun Point.minus(other: Point): Point {
    return Point(x - other.x, y - other.y)
}

//  等号运算符:equals,在 Kotlin 中使用 == 会被转成 equals
//  而 != 也会被转成 equals,只是返回的结果是相反的
//  == 与 != 可用于空运算数,因为这些运算符实际上会检查运算数是否为 null
//  如 a == b,会先检查 a 是否为空,如果不是,就会调用 a.equals(b),
//  否则两个都是 null 的时候才会返回 true  a == b -> a?.equals(b) ?: (b == bull)

//  Comparable 便于比较值
class P(val x: Int, val y: Int) : Comparable<P> {

    //  用于比较一个对象是否大于另一个对象
    //  比较运算符 >, <, >=, <= 将被转换为 compareTo
    //  即 a > b -> a.compareTo(b) >= 0
    override fun compareTo(other: P): Int {
        return compareValuesBy(this, other, P::x, P::y)
    }

    //  重写 equals 方法
    //  这里我们虽然是重载了比较运算符 equals,但是方法前缀不是 operator 而是 override,
    //  因为 equals 是 Any 中定义的方法,equals 不能实现扩展函数,
    //  因为集成自 Any 类的实现始终优先于扩展方法
    override fun equals(other: Any?): Boolean {
        //  先检查是否为同一个对象
        //  这里 === 与 Java 中的 == 完全相同
        // (检查两个参数是否是同一个对象的引用,基本类型的话会比较值)
        //  === 不能被重载
        if (other === this) return true
        //  检查参数类型
        if (other !is P) return false
        //  检查值
        return other.x == x && other.y == y
    }
}

//  in 运算符会被转为 contains
//  a in b -> a.contains(b)

//  rangeTo 是 Comparable 的扩展函数
//  rangeTo 运算符的优先级低于算数运算符
//  start..end -> start.rangeTo(end)
//  日期区间
val now = LocalDate.now()
//  未来十天
val vacation = now..now.plusDays(10)
println(now.plusDays(1) in vacation)
(1..10).forEach(::println)

//  for 循环中也可以使用 in 运算符,但这种情况下和上面的表示含义不同,它被用来执行迭代
//  for (x in list) {...} 实际上被转为 list.iterator(),然后就像 Java 中一样,
//  反复调用 hasNext 和 next
for (x in 1..10) {
    print("$x ")
}

//  这种写法叫字符串模板
//  $ 后面跟着变量就可以直接打印出来变量的值
//  如果想要打印 $ 直接写出来就行了
println("Hello $name !")
//  In Java
System.out.println(“Hello ” + name + " !")  
//  但如果 $ 后面还有其他字符,那么 $ 就需要使用转义字符 \$
println("\$name")
//  ${ 里面可以放各种表达式 }
println("Hello ${if (args.isNotEmpty()) args[0] else "Kotlin"} !")

//  until 用来构建一个开区间,然后用 in 判断是否在区间内
//  10..20 表示闭区间为 10 - 20, 10 until 20 位开区间表示 10 - 19

8.其他

//  1.内部类和嵌套类
//  内部类和嵌套类:默认是嵌套类
//  Kotlin 中的嵌套类一般情况下不能访问外部类的实例

class Button : View {

    override fun getCurrentState(): State = ButtonState()

    //  这个类与 Java 中的静态嵌套类类似,而不是内部类
    //  所以 ButtonState 不会持有外部引用
    class ButtonState : State {

        override fun toString(): String {
            return "ButtonState"
        }
    }

    //  inner class 与 Java 的内部类类似
    //  所以 OtherButtonState 会持有外部类的应用
    inner class OtherButtonState : State {

        //  拿到外部类的引用
        fun getOutReference(): Button = this@Button
    }
}

//  2.修饰符
//  Kotlin 中的默认可见性是 public 的
//  Java 中的默认可见性是包私有

//  Kotlin 中有一个不同的修饰符 internal,表示模块内可见

//  修饰符 public 对于类成员与顶层声明都表示所有地方可见
//  修饰符 internal 对于类成员与顶层声明都表示模块中可见
//  修饰符 protected 对于类成员表示子类可见
//  (与 Java 中不同,Java 还允许同包内的文件访问 protected)
//  修饰符 private 对于类成员表示类中可见,对于顶层声明表示文件中可见

//  3.局部函数
class User(val id: Int, val name: String, val address: String)

//  一般写法
fun saveUser(user:User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty name")
    }
    if (user.address.isEmpty()) {
        throw IllegalArgumentException("Can't save user ${user.id}: empty address")
    }

    //  然后存储逻辑省略
}

//  通过提取局部函数和扩展来避免重复
fun saveUserWithOptimize(user:User) {
    user.validateBeforeSave()

    //  然后存储逻辑省略
}

//  通过提取局部函数来避免重复
fun User.validateBeforeSave() {
    //  声明一个局部函数来进行字段校验
    fun validate(value:String, fieldName:String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user $id: empty $fieldName")
        }
    }

    validate(name, "name")
    validate(address, "address")
}

//  4.类委托(帮助避免在代码中出现许多相似的委托方法)
//  实现接口后可以通过 by 关键字将接口的实现委托到另一个对象
//  这样看上去我们需要重写的方法就都消失了,其实是会去执行被委托的对象
class DelegateCollection<T>(private val innerList:ArrayList<T>) : Collection<T> by innerList {

    //  虽然委托给了别人执行,但是仍然可以重写,最终会执行你重写的方法
    override fun contains(element: T): Boolean {
        return false
    }
}

// 5.中缀表达式
//  中缀表达式(调用只有一个参数的函数时,使得代码更简练)
//  简单的自定义函数
//  当然中缀
infix fun Any.toAny(other:Any) = Pair(this, other)

//  调用
println(2 toAny 3)

就到这里了...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345