HelloKotlin kotlin入门
关于kotlin,谷歌已经在2019年将它扶正为安卓第一开发语言。作为一名安卓开发的混子,还是有必要学习一下。虽然现在手头上没有kotlin的项目,不过提前做些准备和了解还是很有必要的。
关于kotlin,它与java的不同要提前了解一下。kotlin是兼容java 的,可以使用java所开发的大部分库,相当于可以无缝的使用以前安卓开发中的优秀的开源库,无疑是非常友好的,没有切换负担,大部分框架和开源库都可以直接使用。
关于kotlin和java的异同,在这之前,我一直有个误区。首先,我们知道目前的编程语言有很多种,可以大致分为两种,编译型语言和解释型语言。什么是编译型语言?编译型语言的首先将源代码编译生成机器语言,再由机器运行机器码(二进制)。像C/C++等都是编译型语言。什么是解释型语言?解释性语言在运行程序的时候才翻译,比如解释性basic语言,专门有一个解释器能够直接执行basic程序,每个语句都是执行的时候才翻译。这样[解释性语言]。 在学习kotlin之前,我一直认为java是一种编译型语言,但其实,没有那么单纯的界限。首先java是编译型的。因为所有的JAVA代码都是要编译的,.java不经过编译就什么用都没有。其次它也是解释型的。因为java代码编译后不能直接运行,它是解释运行在JVM上的,所以它是解释运行的,那也就算是解释型的了。除此之外,JVM为了效率,都有一些JIT优化。他又会把.class的二进制代码编译为本地的代码直接运行,所以,又是编译的。
因此kotlin语言做到的就是同样适用安卓虚拟机来运行.class文件,但是这个.class文件并不是java编译来的,而是kotlin编译来的。但对虚拟机来说它们具有同样的效果,因此kotlin可以兼容java。
有什么地方不同?
1.变量
kotlin变量类型只有两种定义关键字
1.val :不可变的变量,类似于java中被final修饰的变量
2.var :可变的变量
val a=0
var b=1
2.变量的类型
与java的变量类型大致相同,不过变量并不由关键字(int,long,short...)来声明了,而是改为首字母大写的Int,Long,Short...等对象数据类型,拥有各自的方法和继承结构。
//定义初始值类型
var c=10 :Int
3.函数
关键字 fun 来定义函数
例子:
fun add(add1: Int, add2: Int): Int {
return add1 + add2
}
kotlin中的代码每行结束没有了 分号;只需要换行即可 。除此之外,kotlin还支持当函数中只有一行代码的时候,允许我们不编写函数体{},直接只使用一行代码写在函数定义的尾部,中间用等号连接。例如例子可以简化为
fun add(add1: Int, add2: Int): Int =add1 + add2
还可以简化一下,kotlin支持自定类型推导
fun add(add1: Int, add2: Int)=add1 + add2
函数可以设置参数的默认值:方法如下
fun add(add1: Int, add2: Int=0): Int {
return add1 + add2
}
当有默认参数的时候,我们值传递一个参数进入方法中,怎么去对应,到底是哪个方法来接收传递的参数呢?,还存在能会因为参数类型问题提示错误。这时可以使用kotlin的键值对形式传参:
class test{
fun main(){
myPrint(str="100")
fun myPrint(str: String, num: Int=0) {
println("str,$num")
}
}
当没有传入参数2的时候,就会默认为0
4.逻辑控制语句
4.1 if条件语句
与java的if条件语句几乎完全相同,不同的部分是:
1.kotlin中if是可以直接带返回值的
2.if的返回值可以由条件中的最后一行代码来作为返回值
fun larger1(num1: Int, num2: Int): Int {
val result=if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
}
//带有返回值,且最后一行作为返回值,自动推导类型
fun larger2(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}
//当代码只有一行的时候,函数可以不写函数体
fun larger3(num1: Int, num2) = if (num1 > num2) {
num1
} else {
num2
}
4.2 switch (when) 条件语句
抱歉,kotlin中是没有switch语法的,但是取而代之的是更好的when条件语句,为什么when能取而代之?
对比switch,switch只允许传入整型或者短于整型的变量作为条件,JDK1.7以后才支持字符串。但when可以传入任意类型的参数。switch每个条件都需要break,否则将会顺序执行,这是很多莫名其妙的bug的源头,难免有时候会忘记,或者copy的时候错位了。这些问题在when条件语句中,就告别了以上麻烦。
//与if一样,同样具有返回值。
fun getScore1(name: String) = when (name) {
"Tom" -> {
100
}
"Jerry" -> 80
else -> 0
}
//is 关键字类似于java中的instanceof
fun checkNumber(num :Number){
when(num){
is Int ->{
Log.d(TAG,"num is Int")
}
is Double ->{
Log.d(TAG,"num is Double")
}
else ->{
Log.d(TAG,"num is not supprot")
}
}
}
//还可以不传入参数,骚操作
fun getScore2(name: String) = when {
name=="Tom" -> {
100
}
name=="Jerry" -> 80
name=="Bob" -> 70
name.startsWith("J")->60
else -> 0
}
通过getScore2()方法我们意外的发现,kotlin中判断字符串是否相等,居然使用“==”来实现的,没错,kotlin中判断两个字段串或者对象,可以直接受用“==”,而不是equals()。
4.3 While循环语句
与java中的While语句一模一样没有任何区别
4.4 for循环语句
与java中有很大不同,java中的for-i循环被移除掉了,改造了for-each,变成了kotlin中的加强版:for-in
前提知识:区间
var a=0..10
表明 a是一个位于[0,10]的整数
// step 1 步长1 每次都在a的基础上+1
fun forIn(range1:Int,range2: Int){
for (a in range1..range2 step 1){
println(a)
}
}
//until [range1,range2),不包含range2
fun forIn2(range1:Int,range2: Int){
for (a in range1 until range2 step 2){
println(a)
}
}
//downTo降序
fun forIn3(range1:Int,range2: Int){
for (a in range1 downTo range2 step 2){
println(a)
}
}
5.面向对象
面向对象的概念在很多高级语言中都有体现,java,C++...还有kotlin都有面向对象的概念。面向对象是什么?可能对于没有接触过程序开发的人来讲,一些定义好的概念,总有点摸不着头脑。通俗的来讲,面向对象,就是将一系列具有相同目的结果的属性和方法进行封装。比如车子,具有引擎,车轮,和行驶的功能。封装在一起,就是一个类,而当我们需要用到“车子”完成的一些事情的时候,只需要操纵这个“车子”类就行了,这就是面向对象。面向对象又拥有多态,继承这些特性。直接将概念还是太模糊了,需要我们实际的使用,自然就会理解了。
5.1类和对象
与java相同kotlin有类和对象的概念。新建一个kotlin的类:
class Car{
var engine=null
var speed=null
fun start(){
println("start car")
}
fun stop(){
println("stop car")
}
}
上述是一个kotlin类的基本实现,同样使用class关键字来声明一个类。但是注意到我们没有了java的作用域。其实kotlin默认的作用域就是public。 java中的作用域关键字有4个:public,private,protected,default(默认),kotlin也有4个:public ,private,protected,internal。具体作用域:public,private相同。protected在java中表示当前类、子类和同一包路径下的类可见,在kotlin中只表示当前类和子类可见。default为java的默认关键字,表示同一包路径下可见,在kotlin中没有default,默认的是public,但是有了新的关键字internal,表示同一模块下可见。
实例化一个类:
val car=Car()
car.engine="v8"
car.speed="100km/h"
car.start()
注意到,没有了new关键字,kotlin 类的实例化,不需要new关键字。在kotlin中只需要将类的构造函数写出即可实例化一个类,结尾也没有分号;,kotlin的最简化设计原则,将new关键字取消了。
5.2类的继承和构造
继承是kotlin面向对象很重要的一个特性,与现实场景相同, 要定义一辆车,每辆车都有车牌,引擎,和车架等。但是不同品牌的车,又有独特的地方,因此我们在定义不同品牌的车子时,其实不用每次都把共有的一些属性重复的定义。而是可以直接继承一个共有的父类Car。
open class Car{
var engine=null
var speed=null
}
class BMW :Car(){
val name:String="BMW"
}
class AUDI:Car(){
val name:String="AUDI"
}
注意到,Car这个类跟之前发生了一些变化,在class前面多了一个open关键字。在kotlin中,默认新建一个类,前面都会被加上类似于java中final的定义,不允许被继承,因为类和变量一样,都好都是不可变的,控制风险,当我没说需要它变时我们再加上关键字open来声明它,让它可以被继承。
其次,java中继承关键字 extends,在kotlin中是一个冒号 :
注意继承Car,但是在Car后面又多了一个括号呢? 我们在java中继承的时候并没有这个括号,其实kotlin继承的时候也可以不用括号。加上括号的涉及到kotlin的构造函数。
5.2.1构造函数
构造函数在java中也有,但在kotlin中构造函数分为两种,主构造函数和次构造函数。
一个类被创建,默认的就会有一个不带参数的构造函数。我们也可以给他加上参数。主构造函数的特点是,没有函数体,直接在类名后面的括号中,加入参数即可。
class BENCH (val name:String):Car(){
}
构造函数中的参数,可以不用重新赋值,因此可以直接使用val,一般情况下,我们的属性没有要求需要被重新复制的,我们都直接使用val来声明。那主构造函数由于没有函数体,那怎么实现一些逻辑呢?这里kotlin为我们提供了init结构体:
class BENCH (val name:String):Car(){
init{
plrintln("我是BENCH "+name+"的结构体")
}
}
那阐述完kotlin的主构造函数,跟上面的继承的时候我们可以在类名后面加括号有什么关系呢?
我们知道在java中子类继承父类,子类的构造函数要调用父类的构造函数,这个规定在kotlin中同样存在。
因此我们在继承的时候,子类必须调用父类的构造函数,因此kotlin中我们需要在继承的时候就将调用哪个构造函数给表明出来,默认是()空的,如果我们的父类的主构造函数的参数不是空的,那在继承的时候,我们就要在冒号:后面直接将父类的主构造函数写出来。
open class Car(val engine:String,val speed:String){
}
class BMW (val name:String,engine:String,speed:String):Car(engine,speed){
}
注意:这里我们在BMW类中的构造函数中,加入了Car构造函数中需要用到的两个变量,但是我们却没有给他们添加val或者var来声明,因为在kotlin中主构造函数中被val或者car声明的参数都会自动转换为该类的字段,就会导致与父类的字段同名发生冲突,因此在这里,我们engine和speed不需要用关键字来声明,让他的作用域仅限于主构造函数中即可。
次构造函数:一个类只能有一个主构造函数,但是可以有很多次构造函数,且次构造函数具有函数体,但是当既有主构造函数又有次构造函数的时候,所有的次构造函数都必须调用主构造函数(或者间接调用)
open class Car(val engine:String){
}
class BMW(val name:String,engine:String):Car(String){
//直接调用主构造函数
construtor(name:String):this(name,""){
}
//直接调用柱构造函数
construtor():this("",""){
}
//间接调用了第一个次构造函数construtor(name:String):this(name,"")
construtor():this(""){
}
}
还有一种特殊情况,就是只有次构造函数,没有主构造函数,当一个类没有显示的定义主构造函数时,他就没有主构造函数,但继承的父类又有主构造函数。次构造函数使用super关键字来调用父类的主构造函数。
open class Car(val engine:String){
}
class BMW:Car{
construtor():super("BMW"){
}
PS:这里的Car后面没有括号是因为:如果子类没有显示的定义构造函数,且又有次构造函数,那么父类后面的括号就可以不写。
给主构造函数的每个参数设定默认值之后就可以采用任何参数组合的方式进行实例化,因此我们也就很少会用到次构造函数了
open class Car(val engine:String){
}
class BMW(val name:String="",engine:String=""):Car(engine){
}
class test{
fun main(){
BMW()
BMW(name="BMW")
BMW(engine="V8")
BMW("BMW","V8")
}
}
5.2.2接口
kotlin的接口与java的接口用法几乎一致,接口是实现多态贬称的重要组成,java是一种单继承的语言,一个类只能继承一个父类,但可以实现很多接口,kotlin也是这样。单继承,多实现。
interface BuildCar{
fun buildEngine()
fun changeColor()
}
class BMW :Car(),BuildCar{
override fun buildEngine(){
}
override fun changeColor(){
}
java中关键字继承的关键字是extends,接口实现的关键字是implements,在kotlin中同意都是冒号:,多实现与java一样,用逗号 ,隔开。
除此之外,kotlin中的接口支持默认实现,就是实现的类,不必强制实现接口的所有方法。当接口的函数拥有函数体,这个函数体的内容就是他的默认实现,而有函数体的接口方法,可以被选择性的实现或者不实现。
interface BuildCar{
fun buildEngine()
fun changeColor(){
println("默认颜色")
}
}
class BMW :Car(),BuildCar{
override fun buildEngine(){
}
5.2.3数据类和单例类
数据类:在java中我们在各个场景中都会用导数据类,比如数据库,服务器的数据映射到内存中,平常使用的开发模式MVC,MVP,MVVM中的M就是数据类型。在java中数据类型拥有成员变量,和方法,方法具体有get、set,hashCode,equals,toString等。每次新建一个数据类型,都要做这些大量的重复工作,比较麻烦。在kotlin中这些步骤都被简化提炼为一个关键字:data。只需要在新建的类前面加上 data关键字,则表明当前类是数据类型,就会自动生成数据类型需要使用到的方法。
/**
**当一个类中没有任何代码的时候,尾部的大括号可以省略不写
**/
data class Car(val name:String ,val speed:String)
单例类 同样在java中,我们会用到很多全局的类,没有必要创建第二次,拥有一个实例即可。因此单例模式,也是java中用的最常见的一种。既然如此的常见,那么再kotlin中为我们提供了单例类的关键字来直接实现一个单例类:object。
data class Car(val name:String ,val speed:String)
object class CarMannager{
fun buildCar(val name:String,val speed String):Car{
return Car(name,speed)
}
}
class test{
fun main(){
CarMannager.buildCar("test","test")
}
}
6 空指针检查
空指针问题在java开发可谓是一个老熟人了,很多时候系统崩掉都可能是因为一个空指针。为了避免这个问题kotlin做了一系列的空指针检查操作来避免这个高频率的异常。
首先,kotlin会在编译器,就对代码进行检查,有空指针异常的情况都会被提示出来,要求判空。
data class Car(val name:String ,val speed:String)
object class CarMannager{
fun changeCar(car:Car):Car{
car.name="new Name"
car.speed="fast"
}
}
class test{
fun main(){
CarMannager.changeCar(null)
}
}
以上代码中的changeCar方法就存在空风险,有可能传进来的car是null,而你又在null的基础上访问name和speed重新赋值就会抛出空指针异常。在java中我们可以通过if判断,来规避null风险,但是在一套大型的系统中,这样的风险还有很多。为了快速有效的避免空问题,kotlin提供我们一些方法。
首先,是我们提到的将空指针异常检测提前到了编译器,不允许有空指针风险,否则会报错。以上的代码,就存在空指针,因此是无法通过编译的。那我们又需要一些地方可以为空怎么办呢,kotlin中使用问号? 来表示可以为空。
data class Car(val name:String ,val speed:String)
object class CarMannager{
fun changeCar(car:Car?):Car{
car?.name="new Name"
car?.speed="fast"
}
}
class test{
fun main(){
CarMannager.changeCar(null)
}
}
在允许参数为空的类型被调用时,编译器会提醒我们,这里可能为空,无法通过编译。我们需要使用到kotlin的判空操作符 ?.,一个问号一个点。来做判空处理,当为空的时候什么都不做,当不为空的时候正常执行。
还有操作符 ?:。一个问号一个冒号,这个操作符的两边都可以接受一个表达式,当操作符左边的表达式不为空的时候就返回左边表达式的值,否则就返回右边表达式的值。
class test{
fun main(){
//返回一个字符串的长度,如果为空就返回0
fun getLenght(str:String?)=str?.lenght ?:0
}
}
还有有些情况是编译器提醒我们可能为空,但我们已经做了判断。可以使用!!来强制通过编译
class test{
var str:String?=""
fun main(){
//返回一个字符串的长度,如果为空就返回0
if(str!=null){
upperCase()
}
fun upperCase(){
//强制通过编译
str!!.toUpperCase()
}
}
}