Kotlin语言主要有以下几个特点:
高度兼容Java:Kotlin也可以调用传统Java语言的各种类库。由于最终都是编译为JVM字节码运行,Kotlin可以与Java存在于同一个项目中,互相调用。开发者可以部分改造项目中的代码,尝试其新语言特性。官方提供Java代码到Kotlin代码的转换工具。开发者可以把现有的Java/Android项目一键转换为Kotlin项目,实现零成本的改造。
代码简洁高效:语法精炼,无处不在的类型推断,自动生成通用方法,支持Lamda表达式,使Kotlin的代码行数远低于传统Java代码。而代码量的压缩,客观上会提高开发效率。
函数式编程:支持让函数作为参数或者返回的高阶函数用法,支持流式API,支持扩展方法。
常量和变量
可变变量定义:var 关键字
不可变变量定义:val 关键字,只能赋值一次的变量(类似Java中final修饰的变量)
常量与变量都可以没有初始化值,但是在引用前必须初始化
编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。
val a: Int = 1 // 显式标明类型,立即赋值
val b = 2 // 自动推断出 Int
类型
var c : Int ? = null // 如果没有初始值类型不能省略
c = 3 // 明确赋值
类的定义
在Kotlin中所有类都有一个共同的超类Any
Kotlin中也使用class关键字定义类,所有类都继承于Any类,类似于Java中Object类的概念。类实例化的形式也与Java一样,但是去掉了new关键字。
无参构造函数
如果没有构造函数,将会默认一个无参数构造函数
如果主构造函数的所有的参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值。
//如构造函数为私有,需用private修饰
class DontCreateMe public constructor () {
}
class Customer(val customerName: String = “”){}
主构造函数
带有类名的为主构造函数(只有一个)
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块
主构造函数中声明的属性可以是可变的(var)或只读的(val)
//如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在constructor前面:
class Customer public @Inject constructor(name: String) { …… }
//无修饰可不写constructor关键字
class Customer (name: String) {
var a :Int = 1
init{……}
}
次构造函数
不带类名并且有constructor关键字修饰的函数为次构造函数(可以一个或多个),并且只能存在主构造函数代码块之内
//如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
继承父类及实现借口
//:在这代表继承或实现
class DemoEntity:BaseEntity(),Serializable {
var title : String?=null
}
重要关键字
Any
在Kotlin中所有类都有一个共同的超类Any,Any并不是Object
open
修饰类:表示能被继承
修饰方法:表示需要重写
final、open、abstract、override对比
修饰符 相应类的成员 注解
final 不能被重写 在kotlin中默认所有的方法和类都是final属性
open 可以被重写 需要被明确指出
abstract 必须要重写 不能被实例化,默认具有open属性
override 覆写超类的方法 如果没有被指定为final,则默认具有open属性
companion 伴生对象
//可以省略Factory
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
内部类和匿名内部类
内部类
//如果需要调用成员变量,需要用inner修饰内部类
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
匿名内部类
//匿名内部类
textView.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
//…
}
})
字符串模板
val book = Book(“Thinking In Java”, 59.0f, “Unknown”)
val extraValue = “extra”
Log.d(“MainActivity”, “book.name = b o o k . n a m e ; b o o k . p r i c e = {book.name}; book.price= book.name;book.price={book.price};extraValue=$extraValue”)
函数的定义
函数定义使用关键字 fun,参数格式为:参数 : 类型
//Unit代表无返回值,可不写
fun demo(){
…
}
fun test():Unit{
…
}
//有返回值
fun add(a:Int,b:Int):Int{
return a+b
}
//可以为参数设置默认值
fun total(a:Int = 12,b:Int = 13):Int{
return a*b
}
//也可以
fun sbu(a:Int = 12,b:Int = 13):Int = a-b
函数的变长参数可以用 vararg 关键字进行标识
fun vars(vararg v:Int){
}
Kotlin中基本数据类型
类型 位宽度
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8
十进制:123
长整型以大写的 L 结尾:123L
16 进制以 0x 开头:0x0F
2 进制以 0b 开头:0b00001011
注意:8进制不支持
Doubles 默认写法: 123.5, 123.5e10
Floats 使用 f 或者 F 后缀:123.5f
可以使用下划线使数字常量更易读
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_011010
比较数字
在 Kotlin 中,三个等号 = = = 表示比较对象地址,两个 == 表示比较两个值大小。
val a: Int = 10000
println(a === a) // true,值相等,对象地址相等
//经过了装箱,创建了两个不同的对象
val boxedA: Int? = a
val anotherBoxedA: Int? = a
//虽然经过了装箱,但是值是相等的,都是10000
println(boxedA === anotherBoxedA) // false,值相等,对象地址不一样
println(boxedA == anotherBoxedA) // true,值相等
注意:整数-128-127变量地址相同,这点同java。
类型转换
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
位操作符
kotlin位操作
Kotlin基础的容器
Kotlin有3类基本的容器:集合Set、队列List、映射Map,每类容器又分为只读与可变2中类型,即有6种容器。
容器默认是只读容器
容器的公共方法
isempty:判断该容器是否为空。
isNotEmpty:判断该容器是否非空。
clear:清空该容器。
contains:判断该容器是否包含指定元素。
count:获取该容器包含的元素个数,也可通过size属性获得元素数量。
iterator:获取该容器的迭代器。
另外, Kotlin允许在声明容器变量时就进行初始赋值,如同对数组变量进行初始化那样。
kotlin的容器 容器名 初始化方法
只读集合 set setOf
可变集合 MutableSet mutableSetOf
只读队列 List listOf
可变队列 MutableList mutableListOf
只读映射 Map mapOf
可变映射 MutableMap mutableMapOf
集合:Set/MutableSet
集合特性:
元素无序,不可以下标访问
元素唯一性,通过哈希值校验是否唯一,存在相同元素则覆盖
可变集合MutableSet特性:
MutableSet不可以修改某个元素
集合时无序的,所有MutableSet的add、remove操作的都是元素,而不是某个位置
集合遍历
与Java一样,3中遍历方式
第一种方式:for-in循环,item代表元素
/**
第二种方法:forEach循环,it代表元素
/**
第三种方法:迭代器,
通过hasNext()判断是否存在下一个节点
通过next()获取下一个节点,迭代器指向该元素地址
/**
队列:List/MutableList
队列特性:
元素之间按照顺序排列
队列能够通过get方法获取指定位置的元素,也可以直接通过下标获得该位置的元素。
Mutablelist的add方法每次都是把元素添加到队列末尾,也可指定添加的位置
Mutablelist的set方法允许替换或者修改指定位置的元素
Mutablelist的 removeat方法允许删除指定位置的元素。
可变队列MutableList特性:
可排序,sort系列方法
/**
队列遍历
队列除了拥有跟集合一样的三种遍历方式(for-n循环、迭代器遍历、 for Each遍历)外,还多了一种按元素下标循环遍历的方式
映射Map/MutableMap
映射特性:
保存的是键值对
元素唯一,put时,判断key是否存在,存在则value覆盖
操作大多是针对key的
声明初始化方式不一样,采用的是Pair(键名,键值)
/**
说明:其遍历方式3中:for-in 、forEach、iterator三种,不同的是得到的it是键值对,通过it.key、it.value拿到键和值。
MutableMap 添加元素的方式:
/**
put、set、[]都可以添加成功;set()调用的是put()
逻辑语句
when语句
Kotlin中的when语句取代了Java中的switch-case语句,功能上要强大许多,可以有多种形式的条件表达。与if-else一样,Kotlin中的when也可以作为逻辑表达式使用。
// 逻辑表达式的使用
fun judge(obj: Any) {
when (obj) {
1 -> println(“是数字1”)
-1, 0 -> println(“是数字0或-1”)
in 1…10 -> println(“是不大于10的正整数”)
“abc” -> println(“是字符串abc”)
is Double -> println(“类型是双精度浮点数”)
else -> println(“其他操作”)
}
}
if(if…else if…)用法同java
for语句、while语句、continue语句和break语句等逻辑都与Java基本一致,这里不再赘述。
空指针安全
Kotlin中,当我们定义一个变量时,其默认就是非空类型。如果你直接尝试给他赋值为null,编译器会直接报错。Kotlin中将符号==“?”==定义为安全调用操作符。变量类型后面跟?号定义,表明这是一个可空类型。同样的,在调用子属性和方法时,也可以用字符?进行安全调用。Kotlin的编译器会在写代码时就检查非空情况,因此下文例子中,当s2有前置条件判断为非空后,即便其本身是可空类型,也可以安全调用子属性或方法。对于ifelse结构的逻辑,Kotlin还提供了“?:”操作符,极大了简化了代码量又不失可读性。Kotlin还提供“!!”双感叹号操作符来强制调用对象的属性和方法,无视其是否非空。这是一个挺危险的操作符,除非有特殊需求,否则为了远离NPE,还是少用为妙。
?:表示当前是否对象可以为空
!!: 表示当前对象不为空的情况下执行
var s1: String = “abc”
s1 = null // 这里编译器会报错
var s2: String? = “abc”
s2 = null // 编译器不会报错
var l1 = s1.length // 可正常编译
var l2 = s2.length // 没有做非空判断,编译器检查报错
if (s2 != null) s2.length // Java式的判空方案
s2?.length // Kotlin的安全调用操作符?。当s2为null时,s2?.length也为null
if (s2 != null) s2.length else -1 // Java式判空的ifelse逻辑
s2?.length ?: -1 // Kotlin的elvis操作符
s2!!.length // 可能会导致NPE
类的声明可用lateinit(延迟加载)
lateinit var entity: BaseEntity
1
var、val修饰的变量默认是private修饰,加lateinit后是public修饰
扩展方法和属性
相信作为一个Java/Android开发者,大家都写过很多Base类,继承原生父类的同时,封装一些通用方法,供子类使用。亦或是把这类通用方法,专门放置到一个XXUtils类里,作为工具类出现。这样做是为了代码结构的清晰,但也是一种无奈。由于无法修改原生类的内容,我们只能借助继承或者以面向方法的思维来写工具类。这点在Kotlin里得到了完美解决。Kotlin支持在包范围内对已存在的类进行方法和属性扩展。
// 扩展方法
fun Context.showLongToast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
// 扩展属性
val ArrayList.lastIndex: Int get() = size -1
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showLongToast("hello") // 给Context类扩展了showToast方法,可以在任何有context的地方直接调用了
var mList = ArrayList()
mList.lastIndex // 任何ArrayList均可以调用该属性
...
}
...
}
需要注意两点:1.扩展需要在包级范围内进行,如果写在class内是无效的。2.已经存在的方法或属性,是无法被扩展的,依旧会调用已有的方法。
简洁数据类
相信大家都写过数据类,或者自动化生成的数据model,通常都是由多个属性和对应的getter、setter组成。当有大量多属性的数据类时,不仅这些类会因为大量的getter和setter方法而行数爆炸,也使整个工程方法数骤增。Kotlin中也做了这层特性优化,提供了数据类的简单实现。不用再写get和set方法,这些都由编译器背后去做,你得到的是一个清爽干净的数据类。具体使用参考下面的例子。
data class Student (
var name: String,
var age: Int,
var hobby: String?,
var university: String = “NJU”
)
fun printInfo() {
var s: Student = Student(“Ricky”, 25, “playing Gwent”)
println("${s.name} is from ${s.university}, ${s.age} years old, and likes ${s.hobby}")
}
Anko
Anko是Jetbrains官方提供的一个让Kotlin开发更快速简单的类库,旨在使代码书写更加清晰易懂,形式上为DSL编程。
No findViewById
Android开发过程一定都写过大量的findViewById。这本身就是个消耗资源的方法,编码时还极为不便,需要强制转换为具体的View类型才能调用其方法。通过引入支持注解的库,可以使这个过程略微简单化一些。
// 传统Android中的View内容初始化
TextView tvName = (TextView) this.findViewById(R.id.tv_name);
tvName.setText(“aaaa”);
// 注解方式
@BindView(R.id.tv_name)
TextView tvName;
tvName.setText(“aaaa”);
Kotlin给出了一种最为简便的方式。
import kotlinx.android.synthetic.main.activity_main.* // activity_main为具体的布局xml文件名
…
tvName.text = “aaaa”;
Simple startActivity
通常,在Android里,当我想打开一个新页面,并给它传递一些参数时,我需要按照如下的方式编码。
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra(“userid”, 10001);
intent.putExtra(“username”, “ricky”);
startActivity(intent);
而强大的Anko给我们提供了一种极为方便的写法。无论是无参还是有参,还是需要RequestCode
startActivity()
startActivity(“userid” to 10001, “username” to “ricky”)
startActivityForResult(101, “userid” to 10001, “username” to “aaaa”)
DSL Layout
上面说到不再使用findViewById,还是基于使用xml来编写Android页面这个基础。倘若你想彻底革新写法,换一种更直观的DSL方式来写Android页面呢?Anko就提供了这样的方案。相比之下,除了可读性增加之外,也节约了解析xml文件消耗的资源。
inner class LoginAnkoUI : AnkoComponent {
override fun createView(loginAnkoUI: AnkoContext): View {
return with(loginAnkoUI) {
verticalLayout {
val textView = textView(“用户名”) {
textSize = sp(15).toFloat()
textColor = context.resources.getColor(R.color.black)
}.lparams {
margin = dip(10)
height = dip(40)
width username= matchParent
}
val username = editText(“输入…”)
button(“登录”) {
onClick { view ->
toast(“Hello, ${username.text}!”)
}
}
}
}
}
}
只要最后在Activity里加一句调用,便可以使用Anko写的页面了
LoginAnkoView().setContentView(this@LoginActivity)
1
总结
Kotlin作为一个JVM上的新语言,充分兼容了老大哥Java的诸多功能,又构建了很多自身优秀特性,提供了大量便捷易懂、结构清晰的开发形式。本文仅能展现出其诸多特点的一点皮毛,更多新特性还需要各位看官自己在实际使用中挖掘。