Kotlin语法初探

本文参考资料

《kotlin实践》
《Kotlin中文文档》
《kotlin中国 - Kotlin 极简教程》
《zxc123e的CSDN博客》

引言

最近接手一个项目是用IDE把之前的Java代码转成了Kotlin的,索性现在就多花些时间来窥探一下Kotlin,计划在接下来的开发中使用Kotlin来完成。

Kotlin应该怎么读

Kotlin在2016年发布了其第一个稳定版本v1.0,紧跟着在2017年的Google I/O大会就正式宣布被Android平台支持。

Kotlin 具有很多下一代编程语言,静态语言特性:如类型推断、多范式支持、可空性表达、扩展函数、模式匹配等。

其主要设计目标:

  • 创建一种兼容 Java 的语言
  • 让它比 Java 更安全,能够静态检测常见的陷阱。如:引用空指针
  • 让它比 Java 更简洁,通过支持 variable type inference,higher-order functions (closures),extension functions,mixins and first-class delegation 等实现。
  • 让它比最成熟的竞争对手 Scala语言更加简单。

函数编程核心概念

头等函数

把函数当做值来使用,可以用变量保存它,把它当做参数传递,或者当做其他函数的返回值。

不可变性

使用不可变对象,创建后状态不能再变化。

表达式函数体

1
2
3
fun max(a:Int, b:Int): Int{
return if(a > b) a else b
}

在kotlin中除了(for、do和do\while)以外大多数控制结构都是表达式。

1
fun max(a:Int, b:Int): Int = if(a > b) a else b

可变变量和不可变变量

val : 不可变变量

var :可变变量

vararg : 可变参数(类似Java中的String…,用于参数声明)

可以使用abstract修饰属性,必须在子类初始化。

1
2
3
4
5
6
7
abstract class Person{
abstract val name : String
}

class XiaoQiang : Person(){
override val name : String = "xiaoqiang"
}

延迟初始化

一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检查。

为处理这种情况,你可以用 lateinit 修饰符标记该属性:

1
2
3
4
5
6
7
8
9
10
11
public class MyTest {
lateinit var subject: TestSubject

@SetUp fun setup() {
subject = TestSubject()
}

@Test fun test() {
subject.method() // 直接解引用
}
}

字符串模板

1
2
3
4
5
6
7
var a = 1
// 模板中的简单名称:
val s1 = "a is $a"

a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"

类和属性

类的定义

在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后。

1
class Person(val name:String, var age:Int? = null)

val 声明了只读属性(只有getter)
var 声明了读写属性(有getter和setter)

主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class InitOrderDemo(name: String) {

val firstProperty = "First property: $name".also(::println)

init {
println("First initializer block that prints ${name}")
}

val secondProperty = "Second property: ${name.length}".also(::println)

init {
println("Second initializer block that prints ${name.length}")
}
}

初始化顺序自上而下。

如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:

1
class Customer public @Inject constructor(name: String) { …… }

自定义访问器

1
2
3
4
5
6
class Person(val name:String, var age:Int? = null){
val isMarried: Boolean
get() {
return age > 30
}
}

枚举和when

在kotlin中enum需要出现在class前面才有意义。

1
enum class Color {RED, YELLOW, BLUE}

和Java一样,枚举并不是值的列表,可以给枚举声明属性和方法。

1
2
3
enum class Color(var r: Int, var g: Int, var b: Int){
RED(255, 0, 0), YELLOW(255, 255, 0), BLUE(0, 0, 255)
}

和if相似,when是一个有返回值的表达式

1
2
3
4
5
6
7
fun getColor(color: Color){
when(color){
Color.RED -> "红色"
Color.YELLOW -> "黄色"
Color.BLUE -> "蓝色"
}
}

可以写成这样

1
2
3
4
fun getColor(color: Color) = when(color){
Color.RED -> "A"
Color.YELLO, Color.BLUE -> "B"
}

类型智能转换

1
2
3
4
5
6
7
8
9
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// ‘obj’ 在该条件分支内自动转换成 ‘String’
return obj.length
}

// 在离开类型检测分支后,‘obj’ 仍然是 ‘Any’ 类型
return null
}

在kotlin中is和Java中的instanceOf类似,但是不需要强制转换,在用is检查过某种类型后,编译器会自动完成转换。

和when一起使用,when不仅仅检查是否相等,而且可以检查类型。

1
2
3
4
5
6
7
8
fun  eval(e: Expr): Int =
when(e){
is Num -> e.value
is Sum ->
eval(e.right) + eval(e.left)
else ->
println("else")
}

代码块可以作为if和when的分支,最后一个表达式就是结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun  eval(e: Expr): Int =
when(e){
is Num -> {
println("test")
println("hhhhh")
e.value //最后一个表达式
}
is Sum -> {
println("hei")
eval(e.right) + eval(e.left)
}
else ->
println("else")
}

函数

创建集合

Kotlin没有自己的集合类,而是采用Java标准的集合类

1
2
val list = arrayListOf(1, 2, 3)
val map = hashMapOf(1 to "one", 2 to "two")

函数参数

1
joinToString(collection, separator = " ", prefix = " ", postfix = ".")

kotlin中可以显式的标明一些参数的名称,也可以设置参数的默认值

1
2
3
4
5
6
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String

顶层函数和属性

在Java中函数都属于某个类,而在Kotlin中函数可以脱离类存在,这种函数叫做顶层函数。

在顶层函数中我们可以编写常用的工具方法,在Kotlit中不用使用工具类来包含对应的工具方法。

1
2
3
4
5
package com.test.util;

fun dp2px(val dp: Int): Int {

}

假设上面的文件名字叫ConvertUtil.kt,Kotlin会编译生成对应的Java类如下:

1
2
3
4
5
6
7
package com.test.util;

public class ConvertUtil{
public static int dp2px(int dp){

}
}

如果我们要改变生成的类的名字,需要添加@JvmName注释

1
2
3
4
5
6
7
@file:JvmName("TestUtil")

package com.test.util;

fun dp2px(val dp: Int): Int {

}

和函数一样,属性也可以放到文件的顶层

1
var redpointCount = 0

如果要定义常量可以使用const来修饰

1
const val TYPE_NEW = "happy new year";

扩展函数

扩展函数其实是定义在类外面的成员函数。

1
2
3
package strings

fun String.lastChar(): Char = this.get(this.length - 1)

在扩展函数中可以直接访问被扩展类的其他方法和属性,但是不能访问私有的或者是受保护的成员。

循环

for循环

for 循环可以对任何提供迭代器(iterator)的对象进行遍历,这相当于像 C# 这样的语言中的 foreach 循环。

1
2
3
4
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}

如需在数字区间上迭代,请使用区间表达式:

1
2
3
4
5
6
for (i in 1..3) {
println(i)
}
for (i in 6 downTo 0 step 2) {
println(i)
}

如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:

1
2
3
for (i in array.indices) {
println(array[i])
}

或者你可以用库函数 withIndex:

1
2
3
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}

遍历map

1
2
3
for ((k, v) in map) {   //k、v 可以改成任意名字
println("$k -> $v")
}

while循环

while 与 do..while和Java使用类似

1
2
3
4
5
6
7
while (x > 0) {
x--
}

do {
val y = retrieveData()
} while (y != null) // y 在此处可见

循环中kotlin支持break和continue,return操作。

在 Kotlin 中任何表达式都可以用标签label来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签。

1
2
3
4
5
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}

对象和接口

接口

1
2
3
interface Clickable{
fun click()
}

实现接口

1
2
3
class Button : Clickable{
override fun click() = println("i was clicked !")
}

在kotlin中使用overried修饰符是强制要求的。

和Java一样,一个类可以实现多个接口,但只能继承自一个类。

抽象类

1
2
3
abstract class Clickable{
abstract fun click()
}

继承类

1
2
3
class Button : Clickable() {
override fun click() = println(" i was clicked ! ")
}

上面的继承为什么Clickable()有个小括号,这是因为抽象类有构造方法。

在kotlin中构造方法分为主构造方法(类体外部声明)和从构造方法(类体内部声明)。

1
class User(val nickname: String)

上面的User类有一个主构造方法

1
2
3
4
5
6
7
class User constructor(_nickname: String){
val nickname: String

init{
nickname = _nickname
}
}

1
2
3
class User constructor(_nickname: String){
val nickname = _nickname
}

constructor关键字用来表示一个从构造或者主构造方法的声明。
init关键字引入一个初始化语句块。

open和final

Java的类和方法默认是open的,而在kotlin中类和方法默认是final的,也就是说默认不具有被继承性。

1
2
3
4
open class RichButton : Clickable{
open fun click()
fun longclick() //不可被重写
}

但是如果一个类被声明为abstract的(也就是上面说的抽象类),这个类相当于默认是open的(包括所有方法)

public protected 和 private

在kotlin中和Java类似,默认是public的。

修饰符类成员顶层声明
public所有地方可见所有地方可见
internal模块中可见模块中可见
protected子类中可见——
private类中可见文件中可见

内部类和嵌套类

分类在Java中在kotlin中
嵌套类(不持有外部类的引用)static class Aclass A
内部类(持有外部类的引用)class Ainner class A

在kotlin中要引用外部类的实例使用this@Outer

1
2
3
4
5
class Outer{
inner class Inner{
fun getOuterReference(): Outer = this@Outer
}
}

data修饰符

1
data class  Client(val name: String, val postalCode: Int)

如果需要一个数据容器(需要重写toString、equals、hashCode),添加data修饰符会自动生成。

object关键字

1
2
3
object SingleInstance{

}

object关键字类不具有构造方法,而且只有一个实例就是它本身。

伴生对象

在kotlin中类不能拥有静态成员,Java的static关键字并不是kotlin的一部分。

1
2
3
4
5
6
7
8
9
class A{
companion object{
fun bar(){
println(" ")
}
}
}

直接调用:A.bar()

使用companion关键字修饰object的嵌套类类似于Java中的静态方法和静态成员。

其他

获取javaClass

kotlin中的Class与Java不同,kotlin中有一个自己的Class叫做KClass

person::class 和Person::class都是获取kotlin的KClass,所以println(person::class == Person::class) 为true。

我们可以从kotlin的KClass获取到java的Class,person::class.java就是如此,先获取到kotlin的KClass然后再获取javaClass。

1
2
3
4
5
companion object {
fun toMain(context: Context): Intent{
return Intent(context, MainActivity::class.java)
}
}

评论

Ajax Android AndroidStudio Animation Anroid Studio AppBarLayout Banner Buffer Bulma ByteBuffer C++ C11 C89 C99 CDN CMYK COM1 COM2 CSS Camera Raw, 直方图 Chrome ContentProvider CoordinatorLayout C语言 DML DOM Dagger Dagger2 Darktable Demo Document DownloadManage Element Error Exception Extensions File FileProvider Fresco GCC Git GitHub GitLab Gradle Groovy HTML5 Handler HandlerThread Hexo Hybrid I/O IDEA IO ImageMagick IntelliJ Intellij Interpolator JCenter JNI JS Java JavaScript JsBridge Kotlin Lab Lambda Lifecycle Lint Linux Looper MQTT MVC MVP Maven MessageQueue Modbus Momentum MySQL NDK NIO NexT Next Nodejs ObjectAnimator Oracle VM Permission PhotoShop Physics Python RGB RS-232 RTU Remote-SSH Retrofit Runnable RxAndroid RxJava SE0 SSH Spring SpringBoot Statubar Task Theme Thread Tkinter UI UIKit UML VM virtualBox VS Code ValueAnimator ViewPropertyAnimator Web Web前端 Workbench api apk bookmark by关键字 compileOnly css c语言 databases demo hexo hotfix html iOS icarus implementation init jQuery javascript launchModel logo merge mvp offset photos pug query rxjava2 scss servlet shell svg tkinter tomcat transition unicode utf-8 vector virtual box vscode 七牛 下载 中介者模式 串口 临潼石榴 主题 书签 事件 享元模式 仓库 代理模式 位运算 依赖注入 修改,tables 光和色 内存 内核 内部分享 函数 函数式编程 分支 分析 创建 删除 动画 单例模式 压缩图片 发布 可空性 合并 同向性 后期 启动模式 命令 命令模式 响应式 响应式编程 图层 图床 图片压缩 图片处理 图片轮播 地球 域名 基础 增加 备忘录模式 外观模式 多线程 大爆炸 天气APP 太白山 头文件 奇点 字符串 字符集 存储引擎 宇宙 宏定义 实践 属性 属性动画 岐山擀面皮 岐山肉臊子 岐山香醋 工具 工厂模式 年终总结 开发技巧 异常 弱引用 恒星 打包 技巧 指针 插件 摄影 操作系统 攻略 故事 数据库 数据类型 数组 文件 新功能 旅行 旋转木马 时序图 时空 时间简史 曲线 杂谈 权限 枚举 架构 查询 标准库 标签选择器 样式 核心 框架 案例 桥接模式 检测工具 模块化 模板引擎 模板方法模式 油泼辣子 泛型 洛川苹果 浅色状态栏 源码 瀑布流 热修复 版本 版本控制 状态栏 状态模式 生活 留言板 相册 相对论 眉县猕猴桃 知识点 码云 磁盘 科学 笔记 策略模式 类图 系统,发行版, GNU 索引 组件 组合模式 结构 结构体 编码 网易云信 网格布局 网站广播 网站通知 网络 美化 联合 膨胀的宇宙 自定义 自定义View 自定义插件 蒙版 虚拟 虚拟机 补码 补齐 表单 表达式 装饰模式 西安 观察者模式 规范 视图 视频 解耦器模式 设计 设计原则 设计模式 访问者模式 语法 责任链模式 贪吃蛇 转换 软件工程 软引用 运算符 迭代子模式 适配器模式 选择器 通信 通道 配置 链表 锐化 错误 键盘 闭包 降噪 陕西地方特产 面向对象 项目优化 项目构建 黑洞
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×