Kotlin入门
第一行代码——Android(第3版)
系列文章旨在记录作者学习第一行代码第三版kotlin部分的历程
文章中的demo:https://github.com/yaoyifei1216/AndroidProgramming/tree/master/KotlinStarted
本篇文章作为kotlin入门内容主要包括变量和函数 程序控制语句 面向对象编程 以及两个常见的类模型
变量
kotlin使用var和val关键字用来修饰两种类型的变量
val:声明的变量不可变,类似于java的final
var:声明一个可变变量
区别于java要在变量前声明类型,kotlin因为其类型推导机制,可以实现仅仅用两个关键字就可以实现变量的声明
类型推导机制:如果要把一个整型变量赋值给a,那么a肯定就是整型变量
注意:类型推导机制并不是完美的,如果对一个变量延迟赋值的话,这个时候就需要显式的声明变量类型了
与java不同的是,kotlin中没有基本的数据类型,全部使用了对象数据类型,如使用Int而不是int
Java中final关键字没有被合理使用的问题:
在Java中,除非你主动在变量前声明了final关键字,否则这个变量就是可变的。然而这并不是一件好事,当项目变得越来越复杂,参与开发的人越来越多时,你永远不知道一个可变的变量会在什么时候被谁给修改了,即使它原本不应该被修改,这就经常会导致出现一些很难排查的问题。
那么我们应该什么时候使用val,什么时候使用var呢?这里我告诉你一个小诀窍,就是永远优先使用val来声明一个变量,而当val没有办法满足你的需求时再使用var。这样设计出来的程序会更加健壮,也更加符合高质量的编码规范。
函数
1 | fun methodName(param1: Int, param2: Int): Int { |
fun:关键字,声明函数必须要有的
methodName:声明的函数名称,depends on you,最好做到见名知意
(param1: Int, param2: Int):函数参数,可为空,可多个,参数声明:参数名: 参数类型
Int:返回值类型,如果不写就是没有返回值
大括号里就是方法体了
1 | private fun maxValue(a: Int, b: Int): Int { |
一个kotlin语法糖:当一个函数中只有一行代码时,Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可。
比如我们刚才编写的maxValue()函数就只有一行代码,于是可以将代码简化成如下形式:
1 | private fun maxValue(a: Int, b: Int): Int = max(a, b) |
程序的逻辑控制
if条件语句
Kotlin中的if语句和Java中的if语句几乎没有任何区别,下面的例子是max函数的具体实现
1 | private fun maxValue1(a: Int, b: Int): Int { |
Kotlin中的if语句相比于Java有一个额外的功能,它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。因此,上述代码就可以简化成如下形式:
1 | private fun maxValue2(a: Int, b: Int): Int { |
When选择语句
Kotlin中的when语句有点类似于Java中的switch语句,但它又远比 switch语句强大得多
when语句允许传入一个任意类型的参数,然后可以在when的结构体中定义一系列的条件,格式是:
1 | 匹配值 -> { 执行逻辑 } |
我们准备编写一个查询考试成绩的功能,输入一个学生的姓名,返回该学生考试的分数。
如果用上一小节学习的if语句来实现这个功能,代码如下:
1 | fun getScore(name: String) = if (name == "Tom") { |
实在有点冗余,如果使用when是这样的:
1 | fun getScore(name: String) = when (name) { |
可以看出when和if一样是可以有返回值的
如果执行逻辑只有一行的话,大括号可以省略,上述的例子可以体现这一点
除了精确匹配之外,when语句还允许进行类型匹配。什么是类型匹配呢?举个例子。定义一个checkNumber()函数,如下所示:
1 | fun checkNumber(num: Number) { |
Number类型是Kotlin内置的一个抽象类,像Int、Long、Float、Double等与数字相关的类都是它的子类
is关键字类似于instanceof关键字
while和for循环语句
Kotlin也提供了while循环和for循环,其中while循环不管是在语法还是使用技巧上都和Java中的while循环没有任何区别,重点是for
Kotlin在for循环方面做了很大幅度的修改,Java中最常用的for-i循环在Kotlin中直接被舍弃了,而Java中另一种for-each循环则被Kotlin进行了大幅度的加强,变成了for-in循环
首先需要了解区间的概念:kotlin中使用关键字.. 声明一个闭区间,0..10 表示[0,10]
Kotlin中可以使用until关键字来创建一个左闭右开的区间,0 until 10表示[0,10)
1 | //循环打印0到10 |
默认情况下,for-in循环每次执行循环时会在区间范围内递增1,相当于Java for-i循环中i++的效果,而如果你想跳过其中的一些元素,可以使用step关键字:
1 | //循环打印0到10中的偶数 相当与i = i + 2 |
.. 和until关键字都要求区间的左端必须小于等于区间的右端,使用downTo可以创建一个降序的闭区间
1 | //循环打印10到0 |
for-in循环并没有传统的for-i循环那样灵活,但是却比for-i循环要简单好用得多,而且足够覆盖大部分的使用场景。
如果有一些特殊场景使用for-in循环无法实现的话,我们还可以改用while循环的方式来进行实现。
面向对象
类与对象
声明一个Person类,结构如下:
1 | class Person { |
声明一个Person对象的实例,并给其赋值,随后使用其的方法
1 | var p = Person() |
面向对象编程最基本的用法:
先将事物封装成具体的类,然后将事物所拥有的属性和能力分别定义成类中的字段和函数,接下来对类进行实例化,再根据具体的编程需求调用类中的字段和方法。
继承
下面学习一下继承,声明一个Student类,并继承Person类
分两步,第一步需要先使Person可以继承
Java中一个类本身就可以被继承为,但是在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。之所以这么设计,其实和val关键字的原因是差不多的,因为类和变量一样,最好都是不可变的,而一个类允许被继承的话,它无法预知子类会如何实现,因此可能就会存在一些未知的风险。
做法很简单,在Person类之前加上open修饰符就好了 open class Person
第二步,要让Student类继承Person类。在Java中继承的关键字是extends,而在Kotlin中变成了一个冒号,写法如下:
1 | class Student : Person() { |
通过这两步就完成了Student类的继承,这里重点说一下Person()后面的这一对括号,涉及到Kotlin的构造函数
构造函数
任何一个面向对象的编程语言都会有构造函数的概念,Kotlin中也有,但是Kotlin将构造函数分成了两种:主构造函数和次构造函数。
主构造函数将会是你最常用的构造函数,每个类默认都会有一个不带参数的主构造函数
当然也可以显式地给它指明参数像这样:
1 | class Student(val sno: String, val grade: Int) : Person() { |
这里我们将主构造函数里加入了两个字段,那么在对Student类进行实例化的时候,必须传入构造函数中要求的所有参数。比如:
1 | val student = Student("a123", 5) |
主构造函数没有函数体,如果想在主构造函数中编写一些逻辑,可以写在Kotlin给我们提供的一个init结构体里面,像这样:
1 | class Student(val sno: String, val grade: Int) : Person() { |
Java中继承特性中的一个规定,子类中的构造函数必须调用父类中的构造函数,这个规定在Kotlin中也要遵守。
所以Person()的括号就是表示Student调用Person类的无参构造函数
当然了如果Person类的主构造函数里有参数,这里也要相应的提供参数,举个例子:
1 | open class Person(val name: String, val age: Int) { |
注意:在Student类的主构造函数中增加name和age这两个字段时,不能再将它们声明成val,因为在主构造函数中声明成val或者var的参数将自动成为该类的字段,这就会导致和父类中同名的name和age字段造成冲突。因此,这里的name和age参数前面我们不用加任何关键字,让它的作用域仅限定在主构造函数当中即可
先小结一下:
- Kotlin将构造函数分成了两种:主构造函数和次构造函数
- Kotlin主构造函数没有函数体,如果想在主构造函数中编写一些逻辑,写在init结构体里面
- Kotlin中子类中的构造函数必须调用父类中的构造函数
综合以上几点就可以理解为什么要加上()了
次构造函数
- 任何一个类只能有一个主构造函数,但是可以有多个次构造函数。
- 次构造函数也可以用于实例化一个类,这一点和主构造函数没有什么不同,只不过它是有函数体的。
- Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。
1 | class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) { |
接口
Kotlin中一个类最多只能继承一个父类,但是可以实现任意多个接口
1 | //声明一个接口Study,有两个未实现方法 |
Kotlin还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。另外Java在JDK 1.8之后也开始支持这个功能了
1 | //如果这里有默认实现了readBooks() |
可见性修饰符
- Kotlin中public修饰符是默认项
- Kotlin抛弃了Java中的default可见性
- Kotlin引入了一种新的可见性概念,只对同一模块中的类可见,使用的是internal修饰符。比如我们开发了一个模块给别人使用,但是有一些函数只允许在模块内部调用,不想暴露给外部,就可以将这些函数声明成internal。
数据类和单例类
数据类
在一个规范的系统架构中,数据类通常占据着非常重要的角色,它们用于将服务器端或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。
MVC、MVP、MVVM之类的架构模式,不管是哪一种架构模式,其中的M指的就是数据类。
数据类通常需要重写equals()、hashCode()、toString()这几个方法。
一个基本的数据类的java写法:
1 | public class Cellphone { |
一个基本的数据类的ktolin写法:
1 | data class Cellphone(val brand: String, val price: Double) |
也就是说一个data关键字就替我们做了实现了很多没有实际逻辑意义的代码,声明了data类就不再需要重写上述的方法了
测试一下:
1 | val cellphone1 = Cellphone("Samsung", 1299.99) |
单例类
我们希望某个类在全局最多只能拥有一个实例,这时就可以使用单例模式
java下的单例模型
1 | public class Singleton { |
首先构造方法私有化,然后给外界提供了一个getInstance()静态方法用于获取Singleton的实例,如果缓存的Singleton实例不为null就直接返回,否则新建一个再返回
kotlin的单例模型
1 | object Singleton { |
Kotlin中我们不需要私有化构造函数,也不需要提供getInstance()这样的静态方法,只需要把class关键字改成object关键字,一个单例类就创建完成了。
本篇文章作为入门内容到此就先告一段落,后续文章将会介绍kotlin的更多特性