Kotlin 常用语法篇

0
回复
77
查看
[复制链接]

11

主题

16

帖子

681

安币

代码手工艺人

Rank: 4

发表于 2019-10-9 11:02:47 | 显示全部楼层 |阅读模式
习惯用法和规范类布局
通常,一个类的内容按以下顺序排列:
  • 属性声明与初始化块
  • 次构造函数
  • 方法声明
  • 伴生对象
不要按字母顺序或者可见性对方法声明排序,也不要将常规方法与扩展方法分开。而是要把相关的东西放在一起,这样从上到下阅读类的人就能够跟进所发生事情的逻辑。选择一个顺序(高级别优先,或者相反) 并坚持下去。
将嵌套类放在紧挨使用这些类的代码之后。如果打算在外部使用嵌套类,而且类中并没有引用这些类,那么把它们放到末尾,在伴生对象之后。
常量名称
标有 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()
为了确保数据类生成的代码的一致性,一般满足主构造函数需要至少有一个参数,主构造函数的所有参数需要标记为 val 或 var,而且数据类不能是抽象、开放、密封或者内部的
请注意,对于那些自动生成的函数,编译器只使用在主构造函数内部定义的属性。如需在生成的实现中排出一个属性,请将其声明在类体中:
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)过滤 listval 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]形式
k、v 可以改成任意名字。
“if”表达式fun foo(param: Int) { val result = if (param == 1) {    "one"   } else if (param == 2) {     "two"  } else {    "three"  }}使用条件语句
优先使用 try、if 与 when 的表达形式。例如:
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=0override fun OnClick(v:View){count++}})Lambda 表达式
作为函数参数的代码块, lambda表达式通常作为一个参数传入函数中,也可以单独存储到一个变量中; 在java 8 中jdk 中也支持了lambda编程风格,这也是java 语言在不断演变和优化中最让人望眼欲穿的功能, 那么使用lambda到底能带来那些有优势呢?
java中最普通的点击监听器实现:
button.setOnClickListener(new View.OnClickListener(){@overridepublic void OnClick(View view){// todo}})
通过匿名内部类去传入一个监听器实例,并实现监听器的click方法,当我们有多个view需要实现这种点击实现,那我们就得写多个这种实现,虽然写起来很简单,但是确实为我们增加了代码量,无论从语言角度还是设计模式角度看待这个问题,良好的编程风格主要原则之一就是避免代码在任何地方重复, 那么lambda表达式就很好解决这个问题, kotlin和java8 之后实现是:
button.setOnClickListener{  // todo  }
这段代码和java匿名内部类做了同样的事情,但是更加简单易读,lambda被当作只有一个方法的匿名对象的替代品使用;
lambda同样跟集合搭配使用是kotlin 的一大特色,如找到一个list 中年龄最大的persion,在java中普遍实现是你会引入两个中间变量,一个用来保存最大年龄,而另一个用来保存最大年龄的人,然后遍历这个列表,不断更新这两个变量:
public void findMaxAgePersion(List<Persion> persions){int maxAge=0Persion oldPersion=nullforEach(Persion persion:persions){if(persion.age>maxAge){maxAge=persion.ageoldPersion=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中在合适地方使用这些标准库函数可以帮助我们更加高效开发,同时使得我们代码结构和逻辑显得更加简洁和清晰;
函数(得函数者得天下)
  • [函数修饰符] <fun>[函数名称]([参数列表])[:返回值类型]{[函数体]}
  • [函数修饰符] <fun>[函数名称]([参数列表])=[表达式]
*注:[]中的内容可省略,编译器可进行类型推导得出
/** * 根据时间字符串返回日期 */private fun getDate(dateStr:String): Date {    val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")    return sdf.parse(dateStr)}/** * 传入两个整形数,计算他们的和并输出 */fun add(num1:Int,num2:Int){    println("$num1 + $num2 = ${num1+num2}")}/** * 传入姓名,输出 Hi [传入的姓名] */fun sayHi(name:String)=println("Hi $name")
  • 匿名函数
匿名函数就是没有函数名称的函数,但是匿名函数必须赋值给一个变量/常量
/** * 匿名函数 */val sayHi = fun(name:String)=println("Hi $name")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})// falseany
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” 返回的值是元素本身(接受者本身);
类成员
  • 属性:或者说成员变量,类范围内的变量
  • 方法:或者说成员函数,类范围内的函数
函数和方法的区别:
函数强调功能本身,不考虑从属
方法的称呼通常是从类的角度出发
只是叫法不同而已
  • 定义属性
构造方法参数中val/var 修饰的都是属性
类内部也可以定义属性
class Hello(val aFiled:Int,notAField:Int){    var anotherField:Float = 3f}
  • 属性访问控制
属性可以定义getter/setter
val a: Int=0    get()=field    var b: Float = 0f    get(){        return field / 3;    }    set(value){field = value}
  • 属性初始化
属性的初始化尽量在构造方法中完成
无法在构造方法中初始化,尝试降级为局部变量
var 用 lateinit 延迟初始化,val 用 lazy 延迟初始化
可空类型谨慎用 null 直接初始化

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )