Kotlin 常用语法篇

1,635 阅读12分钟

习惯用法和规范

类布局

通常,一个类的内容按以下顺序排列:

  • 属性声明与初始化块
  • 次构造函数
  • 方法声明
  • 伴生对象

不要按字母顺序或者可见性对方法声明排序,也不要将常规方法与扩展方法分开。而是要把相关的东西放在一起,这样从上到下阅读类的人就能够跟进所发生事情的逻辑。选择一个顺序(高级别优先,或者相反) 并坚持下去。

将嵌套类放在紧挨使用这些类的代码之后。如果打算在外部使用嵌套类,而且类中并没有引用这些类,那么把它们放到末尾,在伴生对象之后。

常量名称

标有 const 的属性,或者 val 属性的对象应该使用大写、下划线分隔的名称:

const val MAX_COUNT = 8  //const属于编译期常量
val USER_NAME_FIELD = "UserName"

保存带有行为的对象或者可变数据的顶层/对象属性的名称应该使用常规驼峰名称:

val mutableCollection: MutableSet<String> = HashSet()

保存单例对象引用的属性的名称可以使用与 object 声明相同的命名风格:

val PersonComparator: Comparator<Person> = ...

Lambda 表达式参数

在简短、非嵌套的 lambda 表达式中建议使用 it 用法而不是显式声明参数。而在有参数的嵌套 lambda 表达式中,始终应该显式声明参数。

在 lambda 表达式中返回

避免在 lambda 表达式中使用多个返回到标签。请考虑重新组织这样的 lambda 表达式使其只有单一退出点。 如果这无法做到或者不够清晰,请考虑将 lambda 表达式转换为匿名函数。

不要在 lambda 表达式的最后一条语句中使用返回到标签。

data数据类

data class User(val name: String, val age: Int)

Kotlin编译器会自动从主构造函数中声明的所有属性并会为User类提供以下功能:

  • 所有属性的 getters (对于 var 定义的还有 setters)
  • equals()
  • hashCode()
  • toString()
  • copy()

为了确保数据类生成的代码的一致性,一般满足主构造函数需要至少有一个参数,主构造函数的所有参数需要标记为 valvar,而且数据类不能是抽象、开放、密封或者内部的

请注意,对于那些自动生成的函数,编译器只使用在主构造函数内部定义的属性。如需在生成的实现中排出一个属性,请将其声明在类体中:

data class Person(val name: String) {var age: Int = 0}

toString()equals()hashCode() 以及 copy() 的实现中只会用到 name 属性,并且只有一个 component 函数 component1()。虽然两个 Person 对象可以有不同的年龄,但它们会视为相等。

复制

在很多情况下,我们需要复制一个对象改变它的一些属性,但其余部分保持不变。 copy() 函数就是为此而生成。对于上文的 User 类:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

我们可以写成:

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

过滤 list

val positives = list.filter { x -> x > 0 }

或者可以更短:

val positives = list.filter { it > 0 }

遍历 map/pair型list

val map = hashMapOf("name" to "zhangsan","age" to "26","address" to "hangzhou")
   for ((k, v) in map) {
        println("$k -> $v")
        println("$k -> ${map[k]}")
   }

map的访问支持 map[key]形式

kv 可以改成任意名字。

“if”表达式

fun foo(param: Int) { 
val result = if (param == 1) {
    "one" 
  } else if (param == 2) { 
    "two"
  } else {
    "three"
  }
}

使用条件语句

优先使用 tryifwhen 的表达形式。例如:

return if (x) foo() else bar() 
return when(x) {
 0 -> "zero"
else -> "nonzero"
}

优先选用上述代码而不是:

if (x)
   return foo()
else
   return bar() 
when(x) {
      0 -> return "zero"
      else -> return "nonzero"
}

注:二元条件优先使用 if 而不是 when,如果有三个或多个选项时优先使用 when

对一个对象实例调用多个方法 (with)

class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}
val myTurtle = Turtle()
    with(myTurtle) { // 画一个 100 像素的正方形
       penDown()
       for(i in 1..4) {
       forward(100.0)
       turn(90.0)
       }
      penUp()
}

对象声明

Kotlin中object关键字在多种情况下出现,包括下面讲到的“伴生对象”,“对象表达式”以及现在讲的“对象声明”都使用了object关键字,可见object关键字用法多么广发和强大;

object关键字出现他们都遵循同样的核心理念:这个关键字定义了一个类,并创建了该类的实例,也就是说用object关键字在定义该类的同时创建了该类的对象;

在开发中我们通常会使用到单例模式,java中单例通过static字段存储实例对象,并将构造私有化,通过暴露出一个静态方法用来唯一访问实例,在kotlin中可以直接通多object声明这样的一个类,通过这种“对象声明”方式将类的声明和类的唯一实例结合在一起。

与类声明一样,一个对象声明同样可以包括属性,方法,初始化语句块等声明,唯一不允许的是构造方法,与普通类实例不同,对象声明在定义的时候就已经被创建,不需要在其他地方调用构造方法;

object Persion{

fun add(var a : Int , var b:Int):Int{

return a+b

}

}

val sum=Persion.add(3,5) //单例调用

伴生对象

伴生对象也叫类内部的对象,其声明可以用 companion 关键字标记:

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

可以省略伴生对象的名称,在这种情况下将使用名称 Companion

class MyClass {
     companion object { }
}
val x = MyClass.Companion

注意:伴生对象的成员看起来像java中的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:

interface Factory<T> {
    fun create(): T
}

class MyClass {
      companion object : Factory<MyClass> {
          override fun create(): MyClass = MyClass() 
      }
}
val f: Factory<MyClass> = MyClass

当然,在 JVM 平台,如果使用 @JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段。

对象表达式

object关键字不仅可以用来声明一个单例对象、伴生对象,也可以用来声明一个匿名对象,匿名对象替代java中的匿名内部类的用法。

button.setOnClickListener(object: View.OnClickListener(){ override fun OnClick(v:View){}})

除了去掉了对象名字以外,语法与对象声明相同,对象表达式声明了一个类并创建了该类的实例,

但并没有为这个类或实例分配一个名字,通常来讲,他们都不需要一个名字,因为你会将这个对象作为一个函数的参数,如果需要分配一个名字给这个对象,你可以将这个对象存储到一个变量中:

val listener=object: View.OnClickListener(){ override fun OnClick(v:View){}}

与java的匿名对象不同的是,java匿名对象只能扩展一个类或者实现一个接口,kotlin的对象表达式可以实现多个接口或者不实现接口。

需要注意的是,Kotlin的对象表达式并不是单例类型的,每次执行到该位置时候,就会重新创建一个新的实例。

例外与java最大不同是kotlin的对象表达式中可以访问被创建函数中的局部变量,但是在java中访问被限制在final类型变量上,但是kotlin中解除了这个限制,这就代表kotlin中对象表达式可以访问并修改被创建函数的局部变量,

button.setOnClickListener(object: View.OnClickListener(){

var count=0

override fun OnClick(v:View){

count++

}

})

Lambda 表达式

作为函数参数的代码块, lambda表达式通常作为一个参数传入函数中,也可以单独存储到一个变量中; 在java 8 中jdk 中也支持了lambda编程风格,这也是java 语言在不断演变和优化中最让人望眼欲穿的功能, 那么使用lambda到底能带来那些有优势呢?

java中最普通的点击监听器实现:

button.setOnClickListener(new View.OnClickListener(){

@override

public void OnClick(View view){

// todo

}

})

通过匿名内部类去传入一个监听器实例,并实现监听器的click方法,当我们有多个view需要实现这种点击实现,那我们就得写多个这种实现,虽然写起来很简单,但是确实为我们增加了代码量,无论从语言角度还是设计模式角度看待这个问题,良好的编程风格主要原则之一就是避免代码在任何地方重复, 那么lambda表达式就很好解决这个问题, kotlin和java8 之后实现是:

button.setOnClickListener{  // todo  }

这段代码和java匿名内部类做了同样的事情,但是更加简单易读,lambda被当作只有一个方法的匿名对象的替代品使用;

lambda同样跟集合搭配使用是kotlin 的一大特色,如找到一个list<Persion> 中年龄最大的persion,在java中普遍实现是你会引入两个中间变量,一个用来保存最大年龄,而另一个用来保存最大年龄的人,然后遍历这个列表,不断更新这两个变量:

public void findMaxAgePersion(List<Persion> persions){

int maxAge=0

Persion oldPersion=null

forEach(Persion persion:persions){

if(persion.age>maxAge){

maxAge=persion.age

oldPersion=persion

}

}

println(oldPersion)

}

List persions=new ArrayList<Persion>()
persions.add(new Persion("zhangli",16))
persions.add(new Persion("wangpeng",22))
findMaxAgePersion(persion)

Kotlin中实现:

val persions=listOf(Persion("zhangli",16),Persion("zhangpeng",26))

val persion=persions.maxBy{it.age}

println(persion)

我们通常在java中对集合做的大多数事情可以通过使用lambda或成员引用的库函数来更好的表达,这样代码就少很多,也变得更容易理解;

lambda语法结构:

{x:Int, y:Int -> x+y }

Kotlin的lambda表达式始终用花括号包围,花括号中通过 箭头(->)将实参和函数体分开,

val sum={x:Int, y:Int -> x+y }

println(sum(3, 5)) // lambda表达式存储到变量中,可以当做普通函数通过实参正常调用


Kotlin中对lambda有些语法约定:

  • 如果lambda表达式作为函数最后一个实参,可以将lambda表达式放到括号外面;
  • 当lambda作为函数唯一的一个实参时,可以将函数括号直接省略;
  • 当有多个实参时候,即可以选择把lambda留在括号内强调它是一个实参,也可以放到括号外边;
  • 当函数有多个lambda实参需要传入,不能超过一个lambda表达式放到外面;


我们拿上面的persions.maxBy{it.age}案例来说:

亦可以写成:

    persions.maxBy({persion:Persion -> persion.age})

作为最后一个参数,可将lambda放到括号外边:

   persions.maxBy(){ persion:Persion -> persion.age }

作为唯一参数,可省略函数空括号:

   persions.maxBy{ persion:Persion -> persion.age }

省略lambda参数类型(上下文自动推断类型)

   persions.maxBy{ persion-> persion.age }   

使用默认参数名称

 persions.maxBy{ it.age }  //it 是自动默认生成的参数

集合的函数式API

在kotlin中有很多跟集合操作相关的扩展函数,这种扩展函数无疑为我们减轻了很多负担,这使得Kotlin相比java语法显得更加简单和高效的地方之一,kotlin中在合适地方使用这些标准库函数可以帮助我们更加高效开发,同时使得我们代码结构和逻辑显得更加简洁和清晰;

  • filter

filter函数遍历集合,并返回符合传入lambda表达式为ture条件的集合元素,首先明白我们操作的是集合结构,并且返回的也是集合,filter函数帮助我们过滤出lambda表达式中符合条件的元素,举例说明:

var list=listOf(1,2,3,4)
prinltn (list.filter() { it % 2==0 })
// [2,4]  只有偶数留下来

上面结果返回的是一个新集合,集合中只包含那边符合lambda中判断式的元素,即:filter函数选出了匹配给定判定式的元素;

val persions= listOf(Persion("zhangsan",30),Persion("wangwu",32),Persion("liuxing",21))

println(persions.filter{

it.age<30

})

// Persion("liuxing",21)

  • map

filter函数不会改变这些元素的值,帮助我们从这些集合中筛选出符合条件的元素,如果需要对集合元素做修改或者变换需要用到map操作符;

map函数对集合中每个元素应用给定的函数并把结果放到一个新集合中;

var list=listOf(1,2,3,4)
prinltn (list.map { it * it })
// [1,4,9,16]  

同filter函数,返回的是一个新的集合,原集合数据并没有修改或者破坏,并且新集合包含的元素个数不会变化,只是对集合中元素应用了给定的函数变换,即:map对集合中每个元素应用了lambda表达式;

val persions= listOf(Persion("zhangsan",30),Persion("wangwu",32),Persion("liuxing",21))

println(persions.map{

     it.name

})

// [zhangsan,wangwu,liuxing]

也可以使用成员引用写法:

println(persions.map(Persion::name))

  • all

all函数检查集合中所有元素是否满足给定判定式,返回值是Boolean类型的,例如:

val peoples=listOf(Persion("zhangsan",27),Persion("wangwu",22))

println(peoples.all{it.age>25})

// false
  • any

any函数检查集合中是否存在某个元素符合给定判断式,返回值也是Boolean类型,例如:

val peoples=listOf(Persion("zhangsan",27),Persion("wangwu",22))

println(peoples.any{it.age>25})

// true

 注意:!all 加上条件等价于用any加上这个条件的取反来替换,反之亦然!

  • count


如果你想知道有多少个元素符合条件,可以使用count函数将结果返回,例如:

println(peoples{

it.age>20

})

// 2 
  • find

要找到第一个满足给定判定式的元素,可以使用find函数,例如:

println(peoples.find{
it.age>25}
)
// [ "zhangshan", 27 ]
  • with

"with" 函数用来对同一个对象执行多次操作,不需要反复引用对象名称;

"with" 返回的值是执行lambda代码的结果,该结果就是lambda中最后一行代码的值;

  • apply

"apply" 函数用来对同一个对象执行多次操作,也不需要反复引用对象名称;

"apply" 返回的值是元素本身(接受者本身);