Kotlin系列一(基本语法)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yangyin3096/article/details/79955610

前言

Google在2017年的IO大会上宣布,将Kotlin编程语言定义为Android开发的官方语言,作为一名Android开发者,我们必须尽快了解和使用Kotlin语言。

不过Kotlin毕竟是语言级别的新事物,比起Java来说,从编程思想到代码细节都有不少变化。学习一门语言,我们最好先对这门语言有个整体的基本的了解,然后再去学习和使用,这样才能高效地掌握这门语言。

Kotlin的出现

Kotlin是基于JVM设计的编程语言,算是Java的改良版语言,它是一个开源项目的成果,拥有很高的声望,很多公司、组织、业界大犇都很喜欢她,Square公司的Jake大神(Dagger、ButterKnife、Retrofit、OkHttp…之父)就专门写了篇Using Project Kotlin for Android为Kotlin站台。

相对Java来说,Kotlin在编写代码时有如下优势:代码简洁高效、函数式编程、空指针安全、支持lambda表达式、流式API等。在执行效率上,Kotlin和Java具有同样的理论速度(都是编译成JVM字节码)。

另外,Kotlin和Java是互相完美兼容的,两种代码文件可以并存,代码可以互相调用、文件可以互相转换,库文件也可以无障碍地互相调用,使用Kotlin基本不会带来额外的成本负担。

Kotlin的特点

Kotlin作为Java的改良,在Android开发中有很多优势:

1)简化findViewById

我们知道,Android的架构里,xml布局文件和Activity是松耦合的,Activity中要使用界面元素,必须借助R文件对xml控件的记录,用findViewById找到这个元素。
Kotlin提供一种激进的方法,通过在gradule中引用applyplugin:‘kotlin-android-extensions’,彻底取消findViewById这个函数,在Activity中可以直接根据id使用界面元素。

2)没有“;”

在Kotlin语法里,代码行不需要用“;”结尾,什么都不写就好

3)重要的“:”

在Java里,“:”主要在运算符里出现(for/switch/三元运算符等)。
在Kotlin里,“:”的地位大大提升了,它的用途非常广泛,包括:
定义变量类型:
var name: String = “my name” //变量name为String类型

定义参数的类型:

fun makeTool(id: Int){ //参数id为Int类型

}

定义函数的返回值:

fun getAddr(id: Int):String{ //返回值为String类型

}

声明类/接口的继承:

class KotlinActivityUI: AnkoComponent{//继承AnkoComponent接口

使用Java类:

val intent = Intent(this, MainActivity::class.java) //需要用::来使用Java类,注意是两个“”

4)没有“new”

Kotlin实例化一个对象时不需要new关键字

var list = ArrayList()

5)变量、常量、类型推断

用var定义变量

var name: String = “my name”

用val定义常量(相当于final)

val TAG: String = “ClassName”

上面两个例子用:String来定义了数据类型,这个是可以省略的,Kotlin支持类型推断,这两句话你可以写成

var name = “my name”
val TAG = “ClassName”

6)初始化和延迟加载

在Java里,我们定义一个变量,可以不赋值(int和boolean会有默认值),但是Kotlin里必须为变量赋值,如果只写一个变量,却不赋值,像下面这样:
var name
编译器会报错,提示你未初始化,你必须赋值为0或者null,或者别的什么值。

不过,我们有时候就是不能在定义变量时就初始化它,比如在Android中我们经常预定义一个View控件而不初始化,但是直到onCreate或onCreateView时才初始化它。
针对这种情况,Kotlin提供了懒加载lazy机制来解决这个问题,在懒加载机制里,变量只有在第一次被调用时,才会初始化:
val对象,使用lazy懒加载,
var对象,使用lateinit延时加载

7)空指针安全

在Kotlin里,可以用“?”表示可以为空,也可以用“!!”表示不可以为空。

空指针安全并不是不需要处理空指针,你需要用“?”声明某个变量是允许空指针的,例如:
var num: Int? =null
声明允许为空时,不能使用类型推断,必须声明其数据类型

空指针虽然安全了,但对空指针的处理还是要视情况而定,有时候不处理,有时候做数据检查,有时候还需要抛出异常,这三种情况可以这样写:

val v1 = num?.toInt() //不做处理返回 null

val v2 = num?.toInt() ?:0 //判断为空时返回0

val v3 = num!!.toInt() //抛出空指针异常(用“!!”表示不能为空)

更多空指针异常处理,有一篇NullPointException 利器 Kotlin 可选型介绍的比较全面,值得借鉴

8)定义函数

在Kotlin语法里,定义函数的格式是这样的
fun 方法名(参数名:类型,参数名:类型…) :返回类型{

}

所以,一般来说,函数是这样写的
fun getAddress(id:Int,name:String): String{
return “got it”
}

由于Kotlin可以对函数的返回值进行类型推断,所以经常用“=”代替返回类型和“return”关键字,上面这段代码也可以写成
fun getAddress(id: Int, name: String) = { //用“=”代替return,返回String类型则交给类型推断
“got it” //return被“=”代替了
}

如果函数内代码只有一行,我们甚至可以去掉{}
fun getAddress(id:Int,name:String) = “got it” //去掉了{}

函数也允许空指针安全,在返回类型后面增加“?”即可
fun getAddress(id:Int,name:String) : String? = “got it”

有时候,函数的返回类型是个Unit,这其实就是Java中的void,表示没有返回
fun addAddress(id: Int, name: String): Unit{ //相当于java的void

}

不过,在函数无返回时,一般不写Unit
fun addAddress(id: Int, name: String){ //相当于java的void

}

9)用is取代了instance of

代码很简单
if(obj is String)…

10)in、区间和集合

Kotlin里有区间的概念,例如1…5表示的就是1-5的整数区间

可以用in判断数字是否在某个区间
if(x in 1…5){ …//检查x数值是否在1到5区间

可以用in判断集合中是否存在某个元素
if(name in list){…//检查list中是否有某个元素(比Java简洁多了)

可以用in遍历整个集合
for(i in 1…5){ …//遍历1到5
for(item in list){…//遍历list中的每个元素(相当于Java的for(String item : list))

另外,in在遍历集合时功能相当强大:
在遍历集合时,可以从第N项开始遍历
for(i in 3…list.size-2){…相当于for (int i = 3; i <= list.size()-2; i++)

可以倒序遍历
for(i in list.size downTo 0) {…相当于for (int i = list.size(); i >= 0; i–)

可以反转列表
for(i in (1…5).reversed())

可以指定步长
for(i in 1.0…2.0 step 0.3) //步长0.3

Kotlin里的集合还都自带foreach函数
list.forEach {…

11)用when取代了switch

switch在Java里一直不怎么给力,在稍老一些的版本里,甚至不支持String,Kotlin干脆用强大的when取代了switch

12)字符串模板

在Java里使用字符串模板没有难度,但是可读性较差,代码一般是
MessageFormat.format("{0}xivehribuher{1}xhvihuehewogweg",para0,para2);

在字符串较长时,你就很难读出字符串想表达什么,在kotlin里,字符串模板可读性更好
p a r a 0 x i v e h r i b u h e r {para0}xivehribuher {para1}xhvihuehewogweg”

13)数据类

数据类是Kotlin相对Java的一项重大改进,我们在Java里定义一个数据Model时,要做的事情有很多,例如需要定义getter/setter(虽然有插件代写),需要自己写equals(),hashCode(),copy()等函数(部分需要手写),但是在Kotlin里,你只需要用data修饰class的一行代码

data class Client(var id: Long, var name: String, var birth: Int, var addr: String)

Kotlin会自动帮你实现前面说的那些特性。

数据模型里经常需要一些静态属性或方法,Kotlin可以在数据类里添加一个companion object(伴随对象),让这个类的所有对象共享这个伴随对象(object在Kotlin中用来表示单例,Kotlin用Any来表示所有类的基类)

14)单例模式

单例是很常见的一种设计模式,Kotlin干脆从语言级别提供单例,关键字为object,如果你在扩展了Kotlin的IDE里输入singleton,IDE也会自动帮你生成一个伴随对象,也就是一个单例

如果一个类需要被定义为class,又想做成单例,就需要用上一节中提到的companion object

15)为已存在的类扩展方法和属性

为了满足开放封闭原则,类是允许扩展,同时严禁修改的,但是实现扩展并不轻松,在Java里,我们需要先再造一个新的类,在新类里继承或者引用旧类,然后才能在新类里扩展方法和属性,实际上Java里层层嵌套的类也非常多。

在Kotlin里,这就简洁优雅地多,她允许直接在一个旧的类上做扩展,即使这是一个final类。

例如,Android中常见的Toast,参数较多,写起来也相对繁琐,我们一般是新建一个Util类去做一个相对简单的函数,比如叫做showLongToast什么的,我们不会想在Activity或Fragment中扩展这个函数,因为太麻烦,我们需要继承Activity做一个比如叫ToastActivity的类,在里面扩展showLongToast函数,然后把业务Activity改为继承这个ToastActivity…

16)类的家族结构

Kotlin关于类的家族结构的设计,和Java基本相似,但是略有不同:

Object:取消,在Java里Object是所有类的基类,但在Kotlin里,基类改成了Any

Any:新增,Kotlin里所有类的基类

object:新增,Kotlin是区分大小写的,object是Kotlin中的单例类

new:取消,Kotlin不需要new关键字

private: 仍然表示私有

protected: 类似private,在子类中也可见

internal: 模块内可见

inner:内部类

public: 仍然表示共有,但是Kotlin的内部类和参数默认为public

abstract:仍然表示抽象类

interface:仍然表示接口

final:取消,Kotlin的继承和Java不同,Java的类默认可继承,只有final修饰的类不能继承;Kotlin的类默认不能继承,只有为open修饰的类能继承

open:新增,作用见上一条

static:取消!Java用static去共享同一块内存空间,这是一个非常实用的设计,不过Kotlin移除了static,用伴随对象(前面提到过的compaion object)的概念替换了static,伴随对象其实是个单例的实体,所以伴随对象比static更加灵活一些,能去继承和扩展。

继承:在Kotlin里,继承关系统一用“:”,不需要向java那样区分implement和extend,在继承多个类/接口时,中间用“,”区分即可,另外,在继承类时,类后面要跟()。所以在Kotlin里,继承类和接口的代码一般是这样的:

class BaseClass: Activity(), IBinder{ //示例

17)构造函数

在Java里,类的构造函数是这样的

public 类名作为函数名 (参数) {…}

Java里有时会重载多个构造函数,这些构造函数都是并列的

在Kotlin里,类也可以有多个构造函数(constructor),但是分成了1个主构造函数和N个二级构造函数,二级构造函数必须直接或间接代理主构造函数,也就是说,在Kotlin里,主构造函数有核心地位

主构造函数一般直接写在类名后面,像这么写

class ClientInfo(id: Long, name: String, addr: String){

这其实是个缩写,完全版本应该是

class ClientInfo constructor(id: Long, name: String, addr: String){

主构造函数的这个结构,基本决定了,在这个主构造函数里,没法写初始化代码…

而二级构造函数必须代理主构造函数,写出来的效果是这样的二级构造函数

18)初始化模块init

上一节提到过,主构造函数里不能写代码,这就很麻烦了,不过还好,Kotlin提供了初始化模块,基本上就是用init修饰符修饰一个{},在类初始化时执行这段儿代码

19)其他

Kotlin还有很多其他的语言特性,本文主要是为了建立对Kotlin的大概印象,更多细节就不再列举了,建议仔细阅读Kotlin官方文档,并且多动手写一些代码。

部分代码

新建类:

定义类使用class关键字

//类名称是KotlinDemo.kt
class KotlinDemo {
    
}

定义方法:

定义方法使用fun关键字

//无参 无返回值
fun method1() {
    //方法体
}

//有参(一个Int类型,一个String类型参数),无返回值
fun method2(i: Int, s: String) {
    //方法体
}

//有参,有返回值(Int类型返回值)
fun method3(i1: Int, i2: Int): Int {
    return i1 + i2
}

//有参,有返回值(没有方法体,直接在方法上返回)
fun method4(i1: Int, i2: Int): Int = i1 + i2

定义常量:

变量用val声明

//常量名称:TAG,数据类型:String
val TAG: String = "MainActivity"

定义变量:

变量用var声明

//变量名称:value, 数据类型:Int
var value: Int
value = 1

比较运算符:

两个等号 == 表示比较两个值大小, 三个等号 === 表示比较对象地址

fun method2() {
   val a = 1
   val b = 1
   val c = 2
   val d = 2
   Log.d(TAG, "a == b : ${a == b}") //打印结果 true
   Log.d(TAG, "a == c : ${a == c}") //打印结果 false
   Log.d(TAG, "a != c : ${a != c}") //打印结果 true
   Log.d(TAG, "a == b || a == c : ${a == b || a == c}") //打印结果 true
   Log.d(TAG, "a == c || b == d : ${a == c || b == d}") //打印结果 false
   Log.d(TAG, "a == b && c == d : ${a == b && c == d}") //打印结果 true
   Log.d(TAG, "a == b && a == c : ${a == b && a == c}") //打印结果 false

   val aa = "hello"
   val bb = "hello"
   val cc = "world"
   val dd = "world"
   Log.d(TAG, "aa === bb : ${aa === bb}") //打印结果 true
   Log.d(TAG, "aa === cc : ${aa === cc}") //打印结果 false
   Log.d(TAG, "aa !== cc : ${aa !== cc}") //打印结果 true
   Log.d(TAG, "aa === bb || aa === cc : ${aa === bb || aa === cc}") //打印结果 true
   Log.d(TAG, "aa === cc || bb === dd : ${aa === cc || bb === dd}") //打印结果 false
   Log.d(TAG, "aa === bb && cc === dd : ${aa === bb && cc === dd}") //打印结果 true
   Log.d(TAG, "aa === bb && aa === cc : ${aa === bb && aa === cc}") //打印结果 false
}

创建数组:

//创建Int类型数组
val arr: IntArray = intArrayOf(11, 12, 13)

数组遍历:

//数组遍历方式一
for (index in arr.indices) {
    Log.d(TAG, "index:$index,arr[index]:${arr[index]}")
}

//数组遍历方式二
for (value in arr) {
    Log.d(TAG, "value:$value")
}

//数组遍历方式三
for ((index, value) in arr.withIndex()) {
    Log.d(TAG, "index:$index, value:$value")
}

//数组遍历方式四
arr.forEach {
    Log.d(TAG, "forEach,value:$it")
}

创建集合:

//创建ArrayList集合
var dataList = ArrayList<DataBean>()
for (i in 0..20){
   dataList.add(DataBean("TestDat:$i"))
}

集合遍历:

//集合遍历方式一
for (index in dataList.indices) {
    Log.d(TAG, "index:$index,arr[index]:${dataList[index]}")
}

//集合遍历方式二
for (value in dataList) {
    Log.d(TAG, "value:$value")
}

//集合遍历方式三
for ((index, value) in dataList.withIndex()) {
    Log.d(TAG, "index:$index, value:$value")
}

//集合遍历方式四
dataList.forEach {
    Log.d(TAG, "forEach,value:$it")
}

循环语句:

//在0~2范围内递增循环
for (i in 0..2) {
    //在2~0范围内递减循环
    for (j in 2 downTo 0) {
        Log.d(TAG, "i:$i, j:$j")
    }
}

when语句:

//匹配具体的值
val a = 1
when (a) {
    1 -> Log.d(TAG, "a is:$a")
    11 -> Log.d(TAG, "a is:$a")
    else -> Log.d(TAG, "default") //以上匹配不为真默认执行的语句
}

//匹配范围
val a = 4
when (a) {
    in 1..5 -> Log.d(TAG, "$a in 1..5")
    !in 1..5 -> Log.d(TAG, "$a !in 1..5")
    else -> Log.d(TAG, "default") //以上匹配不为真默认执行的语句
}

//匹配数据类型
val a: Any = 4
when (a) {
    is String -> Log.d(TAG, "a is String")
    is Int -> Log.d(TAG, "a is Int")
    else -> Log.d(TAG, "default") //以上匹配不为真默认执行的语句
}

类的继承:

//父类
open class SuperClass {
    fun method() {

    }
}

//继承SuperClass
class SubClass: SuperClass() {
    fun method2() {
	    //调用父类的方法
        super.method();
    }
}

定义接口,和接口的抽象方法:

interface SuperInterface {
    //接口的抽象方法
    fun method()
}

接口的实现:

//定义接口
interface SuperInterface {
    //接口的抽象方法
    fun method()
}

//实现SuperInterface
class SubClass: SuperInterface {
    //实现接口的方法
    override fun method() {
		//方法体
		
    }
}

类的构造方法:

class SubClass(i: Int, s: String) {
    private var mI: Int = i
    private var mS: String = s
}

data class:

//数据类
data class DataBean(var name: String)

//添加数据
DataBean("TestData")

//获取数据
DataBean.name

猜你喜欢

转载自blog.csdn.net/yangyin3096/article/details/79955610