使用Kotlin开发Android(一)
前言
为什么要去学习Kotlin来开发 Android?“Google 在 Google I/O 2017 宣布支持 Kotlin 为 Android”这一句大概足以让我们开发者去尝试这门新语言了吧。Kotlin是一种基于jvm的静态类型编程语言,由 JetBrains 开发。
Kotlin有哪些好处呢?
- 相比java更简洁~短小
精悍~(略略略),代码行数减少约40%。 它也更安全,NullPointerException算是大家见过最多的异常了吧,而kotlin恰恰可以干掉它,就问你开不开森- 扩展函数,智能类型转换,不用再见到那该死的findViewById了
- 无缝对接Java,可以java代码与kotlin代码互相调用
- 一键java转kotlin,简化现有代码的迁移
- 可进行android开发,服务器端开发,web开发,桌面开发等
语法
基础语法
变量
- 声明变量使用
var
,声明常量使用val
,声明时kotlin语言是可以自动推断出字段类型的.
fun main(args: Array<String> {
var quantitly = 10
val price: Double = 20.3
val name: String = "大米"
println("单价:$price")
println("数量:$quantity")
println("产品:$name 总计:${quantity * price}")
语句
- in关键字的使用: 判断一个对象是否在某一个区间内
//如果存在于区间(1, y-1)
if(x in 1..y-1)
print("OK")
//如果x不存在于array中
if(x !in 0..array.lastIndex)
//打印1到5
for(x in 1..5)
//遍历集合
for(name in names) {
println(name);
}
//如果names集合中包含text对象
if(text in names)
- when表达式: 类似于Java中的switch,但kotlin更加智能,可以自动判断参数的类型.
fun cases(obj: Any) {
when(obj) {
1 -> print("第一项")
"hello" -> print("这个是字符串")
is Long -> print("这是一个Long类型的数据")
!is String -> print("这不是String类型的数据")
else -> print("else类似于Java中的default")
}
}
- 智能类型推测: 判断一个对象是否为一个类的实例, 可以使用is关键字.与Java中的instanceof关键字类似,但在kotlin中如果已经确定了一个对象的类型,可以在接下来的代码块中直接作为这个确定类型使用.
fun getStringLength(obj: Any): Int? {
if(obj is String) {
//做过类型判断以后,obj会被系统自动转换为String类型
return obj.length
}
//同时还可以使用!is, 来取反
if(obj !is String) {
}
//代码块外部的obj仍然是Any类型的引用
return null
}
!!
操作符: 将任何值转换为非空类型,若该值为空则抛出异常.如下所示:var a = null a!! //运行代码,抛出KotlinNullPointerException
空安全
可空类型与非空类型
在Kotlin中,类型系统区分一个引用可以容纳null(可空引用)还是不能容纳(非空引用).例如,String类型的常规变量不能容纳null.
var a = "a" a = null
如果要允许为空,我们可以声明一个变量为可空字符串,在字符串类型后面加一个问号
?
,写作String?
,如下所示:var b: String? = "b" b = null
安全调用操作符
接着上面的代码,如果你调用
a
的方法或者访问它的属性,不会出现NullPointerException,但如果调用b
的方法或者访问它的属性,编译器会报告一个错误.b.length
这个时候可以使用安全调用操作符,写作
?.
,在b
后面加安全调用操作符,表示如果b
不为null则调用b.length
,如下所示:b?.length
安全调用操作符还能链式调用,如果该链式调用中任何一个属性为null,整个表达式都会返回null.如果要只对非空值执行某个操作,安全调用操作符可以与
let
一起使用val listWithNulls: List<String?> = listOf("A", null, "B") for (item in listWithNulls) { item?.let { println(item) } }
函数
- 函数的声明: 函数使用关键字
fun
声明.
fun say(str: String): String {
return str
}
//可以简写为:
fun say(str: String): String = str
//如果是返回Int类型,那么可以不写返回类型
fun getIntValue(value: Int) = value
- 函数的默认参数:
//你可以调用say(),来得到默认的字符串"hello",也可以自己传入参数say("world")来得到传入参数值.
fun say(str: String = "hello"): String = str
//多行参数的写法:
fun say(firstStr: String = "Hello", lastStr: String = "World") {
}
- 可变参数
//在Java中,我们可以表示一个可变参数
public boolean hasEmpty(String... strArray) {
for(String str: strArray) {
if("".equals(str) || str == null)
return true;
}
return false;
}
//在kotlin中,使用关键字vararg来表示
fun hasEmpty(vararg strArray: String?): Boolean {
for(str in strArray) {
if("".equals(str) || str == null)
return true
}
return false
}
- 单表达式函数
//函数体内只有一条语句,且有返回值
fun foo(): String {
return "abc"
}
//这时可以省略大括号,变成单表达式函数
fun foo() = "abc"
- 扩展函数: 你可以给父类添加一个方法,这个方法将可以在所有子类中使用.
fun Activity.toast(message: CharSequence, duration: Int = Toas.lENGTH_SHORT) {
}
- 将函数作为参数
fun lock<T>(lock: Lock, body: () ->T) : T(lock.lock() try {return body()}
Kotlin与Java混编
将Java转为Kotlin
- 使用kotlin插件
在Kotlin中调用Java代码
- 如果一个Java方法返回void, 对应的在Kotlin代码中它将返回
Unit
.在kotlin中可以省略这个返回类型. Java中有static关键字,在kotlin中没有这个关键字,你需要使用
@JvmStatic
替代这个关键字.同样,在kotlin中也有很多的关键字是Java中没有的.例如in
,is
,data
等.如果Java中使用了这些关键字,需要加上反引号(`)转义来避免冲突.//java代码中有个方法叫 is() public void is() { } //转换为kotlin代码需要加反引号转义 fun `is`() { }
在Java中调用Kotlin代码
- 如果想在Java中通过类名调用一个kotlin类的方法,你需要给这个方法加入
@JvmStatic
注解(这个注解只在jvm平台有用).否则你必须通过对象调用这个方法.
StringUtils.isEmpty("hello");
StringUtils.INSTANCE.isEmpty2("hello");
//
object StringUtils {
@JvmStatic fun isEmpty(str: String): Boolean {
return "" == str
}
fun isEmpty2(str: String):Boolean {
return "" == str
}
}
class StringUtils {
companion object {
fun isEmpty(str: String): Boolean {
return "" == str
}
}
}
companion object
表示外部类的一个伴生对象,你可以理解为外部类自动创建了一个对象作为自己的field
. Java在调用时,可以这样写:StringUtils.isEmpty().
Kotlin的类特性
构造函数
Kotlin的构造函数可以写在类头中,跟在类名后面,如果有注解还需要加上关键字
construtor
.这种写法声明的构造函数,我们称之为主构造函数.例如下面我们为Person
创建带一个String
类型参数的构造函数.class Person(private val name: String) { fun sayHello() { println("hello $name") } }
在构造函数中声明的参数,它们默认属于类的公有字段,可以直接使用,如果你不希望别的类访问到这个变量,可以用
private
修饰.在主构造函数中不能有任何代码实现,如果有额外的代码需要在构造方法中执行,你需要放到init
代码块中执行.同时,在本示例中由于需要更改name参数的值,我们将val改为var,表明name参数是一个可改变的参数.
class Person(private var name: String) {
init {
name = "HeHong Liu"
}
internal fun sayHello() {
println("hello $name")
}
}
- 如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数.构造函数的可见性是public.如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的主构造函数.另外,在JVM上,如果主构造函数的所有的参数都有参数值,编译器会生成一个额外的无参构函数,它将使用默认值.
次级构造函数
- 一个类当然会有多个构造函数的可能,只有主构造函数可以写在类头中,其他的次级构造函数就要写在类体中了.
class Person(private var name: String) {
private var description: String? = null
init {
name = "HeHong Liu"
}
constructor(name: String, description: String) : this(name) {
this.description = description
}
internal fun sayHello() {
println("hello $name")
}
}
- 这里我们让次级构造函数调用了主构造函数,完成name的赋值.由于次级构造函数不能直接将参数转换为字段,所以需要手动声明一个description字段,并为description字段赋值.
修饰符
open
: Kotlin默认会为每个变量和方法添加final
修饰符.这么做的目的是为了程序运行的性能.为每个类加了final
也就是说,在kotlin中默认每个类都是不可被继承的.如果你确定这个类是会被继承,那么你需要给这个类添加open
修饰符.接口中的属性和方法都是open的,不用另外加open标识.internal
: 在Kotlin中,默认的访问权限是public
, 也就是说不加访问权限修饰符的就是public
的.而多增加了一种访问修饰符叫internal
,它是模块级别的访问权限.何为模块,我们称被一起编译的一系列Kotlin文件为一个模块.在IDEA中可以很明确的看到一个module
就是一个模块,当跨module
的时候就无法访问另一个module
的internal
变量或方法.private
: 本类内部(包含其所有成员)都可见.protected
: 只在本类内部+子类中可见.public
: 能见到类声明的任何客户端都可以见到其public成员.
field标识符
- 如果不想定义一个字段来保存属性的值,可以使用field标识符,如下所示:
class User {
var username: String = "LLhon"
get() = field
set(value) {
field = value
}
}
嵌套类
- 用
inner
关键字标记嵌套类, 可以通过外部类的实例调用嵌套类.
class User {
var age: Int = 0
inner class UserName {
}
}
var userName: User.UserName = User().UserName()
一些特殊的类
- 枚举类
- sealed密封类
- data数据类: data修饰的类称之为数据类.它通常用在我们写的一些POJO类上.当data修饰后,会自动将所有成员用
operator
声明,即为这些成员生成类似Java的getter/setter方法.
类的扩展
扩展方法
- 在Java开发的时候,经常会写一大堆的Utils类.如果每个类在想要用这些工具类的时候,他们自己就已经具备了这些工具方法多好,Kotlin的类扩展方法就是这个作用.
fun Activity.toast(msg: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, msg, duration).show()
}
>首先是一个`fun`关键字,紧接着是要扩展哪个类的类名,点方法名,然后是方法的声明和返回值以及方法体.
强转与智能转换
- 在Kotlin中,用
is
来判断一个对象是否是某个类的实例,用as
来做强转.Kotlin有一个很好的特性,叫智能转换
.就是当已经确定一个对象的类型后,可以自动识别为这个类的对象,而不用再手动强转.
fun main(args: Array<String>) {
var animal: Animal? = xxx
if(animal is Dog) {
//在这里animal被当作Dog的对象来处理
animal.bark()
}
}
伴生对象
- 由于Kotlin没有静态方法.在大多数情况下,官方建议是简单地使用包级函数.如果你需要写一个可以无需用一个类的实例来调用,但需要访问类内部的函数(例如:工厂方法或单例),你可以把它写成一个用
companion
修饰的对象内的方法.
class StringUtils {
companion object {
fun isEmpty(str: String): Boolean {
return "" == str
}
}
}
单例类设计
- 伴生对象更多的用途是用来创建一个单例类.如果只是简单的写,直接用伴生对象返回一个
val
修饰的外部类对象就可以了,但是更多的时候我们希望在类被调用的时候才去初始化他的对象.以下代码将线程安全问题交给虚拟机在静态内部类加载时处理,是一种推荐的写法:
class Single private constructor() {
companion object {
fun get(): Single{
return Holder.instance
}
}
private object Holder {
val instance = Single()
}
}
- 使用伴生对象实现静态类成员的功能,如下所示:
//Java写法
class User {
static User instance = new User();
public void printlnUser() {
}
}
User.instance.printlnUser()
//Kotlin写法
class User {
companion object {
var instance = User()
}
fun printlnUser {
}
}
User.instance.printlnUser()
函数与闭包
Unit
- 如果一个函数是空函数,比如Android开发中的TextWatch接口,通常只会用到一个方法,但必须把所有方法都重写一遍,就可以通过这种方式来简写.
editText.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) = Unit
override fun beforeTextChanged(s: CharSequence?,start: Int,count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
})
> Unit表示的是一个值的类型,这种类型类似于Java中的`void`类型
Nothing
- Nothing也是一个值的类型.如果一个函数不会返回(也就是说只要调用这个函数,那么在它返回之前程序肯定出错了,比如一定会抛出异常的函数),理论上你可以随便给他一个返回值,通常我们会声明为返回
Nothing
类型.
闭包
- 以下代码,if语句仍旧是一个闭包.但通常情况下,我们所说的闭包是Lambda表达式.
fun main(args: Array<String>) {
test
}
val test = if (5 > 3) {
print("yes")
} else {
print("no")
}
> 注意: 闭包是不能有变长参数的.
自执行闭包
- 自执行闭包就是在定义闭包的同时直接执行闭包,一般用于初始化上下文环境.例如:
{x: Int, y: Int ->
println("${x + y}")
}(1, 3)
Lambda表达式
- 以下代码首先声明了一个名为printMsg的Lambda,它接受一个String类型的值作为参数,然后在main函数中调用它.如果还想省略,你还可以在调用时直接省略invoke,像函数一样使用.
val printMsg = {msg: String ->
println(msg)
}
fun main(args: Array<String>) {
printMsg.invoke("hello")
}
//更简洁的写法
fun main(args: Array<String>) {
printMsg("hello")
}
- Lambda表达式还有非常多的语法糖,比如
- 当参数只有一个的时候,声明中可以不用显示声明参数,在使用参数时可以用
it
来替代那个唯一的参数. - 当有多个用不到的参数时,可以用下划线来替代参数名(1.1以后的特性),但是如果已经用下划线来省略参数时,是不能使用
it
来替代当前参数的. - Lambda最后一条语句的执行结果表示这个Lambda的返回值.
- 当参数只有一个的时候,声明中可以不用显示声明参数,在使用参数时可以用
高阶函数
- Lambda表达式最大的特点是可以作为参数传递.当定义一个闭包作为参数的函数,称这个函数为高阶函数.
fun main(args: Array<String>) {
log("world", printMsg)
}
val printMsg = {str: String ->
println(str)
}
val log = {str: String, printLog: (String) -> Unit ->
printLog(str)
}
内联函数
集合泛型与操作符
Kotlin中的集合接口
- 所有类声明的泛型尖括号里面如果加入了
out
关键字,则说明这个类的对象是只读的,例如它只有:get(),size()等方法,而没有set(),remove()等方法.相反地,如果没有加out
关键字,或者换一种记法:如果开头是MutableXXX
那就是一个跟Java中用法一致的集合类.
in与out
- 与
out
对应的,还有一个in
.表示泛型参数是只写的.泛型的in
关键字一般用在我们定义的代码中.在看下面这个demo之前,你需要知道一点:Kotlin的泛型是支持协变的.例如下面这段代码:
open class A
open class B: A()
open class B; A啊
val mutableList: MutableList<B> = mutableListOf(B(), B(), C())
val list: List<A> = mutableList;
- 在Kotlin中,可以直接把
List<C>
的对象赋值给List<A>
.泛型声明中,用in
声明泛型参数,表示这个参数只能是入参,不能对外返回这个参数
open class A
open class B: A()
open class C: B()
class TypeArray<in A> {
//in修饰了A,表示A是可以作为参数的.
fun getValue(a: A): Int? {
return a?.hashCode()
}
//这段代码是非法的,因为A不能被返回
fun getA(a: A): A? {
return a
}
}
> 总结: 用out来修饰T,只能消费T类型,不能返回T类型,用in来修饰T,只能返回T类型,不能消费T类型.简单来说就是in是消费者,out是生产者.
集合的初始化
- 在Kotlin中,集合类一般不使用构造方法去初始化,而是使用同一的入口方法,例如初始化一个
MutableList
,我们使用的是如下代码:
val mutableList = mutableListOf(0, 1, 2, 3)
- 类似的初始化集合对象的方法还有:
//创建一个List<>对象
var list = listOf(0, 1, 2)
//创建一个Set<>对象
val ss = setOf(1, 2, 4)
操作符
- 在Kotlin中,操作符本质上是方法调用.例如List的
forEach
的实现原理实际上是定义了一个这样的方法.为Iterable增加了一个扩展方法,叫forEach
,它接收一个T作为参数,并返回Unit的闭包作为参数.
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
待更……(我也不知道会拖更到啥时候~o( =∩ω∩= )m)