Kotlin开发Android


看书总是觉得都懂了,实际操作二百五,试着默写出来才是真的懂,就是写写有点累。
主要是看(郭霖的《第一行代码》第三版)

Kotlin简单语法

变量

1. 关键字var和val

1.var 和val的区别
var:可以改变的变量
val :第一次赋值后,无法进行更改的变量
注意:当变量赋值的时候,首先选择val进行,如果val不满足要求的情况下才选择var,这样能够使得代码更加健壮。

var a=10
a=20  //正确 变量可变所以可以
val b=20
b=20 //错误 不可变的变量 再次进行赋值操作 

2.不必显式声明变量类型

kotlin 具备强大的推导能力,所以变量的定义的时候无需显示声明。
但是延迟赋值的时候,就需要显示声明了,声明的方法是 变量名后面:加类型
延迟赋值:通常使用了lateinit ,在对象的属性声明的时候不初始化,在用到的时候再初始化。

val a=10 //kotlin 推导得出a是Int类型
val b:Int =10;//显式声明Int类型,并赋值10,这时候不需要进行推导了,但是这里的显式申明可要可不要

3. 变量的类型

如上面例子所示,和java相比,Int 是大写的,这代表Kotlin放弃了基础数据类型,全部选择的是对象数据类型。Int,Long,Short,Float,Double,Boolean,Byte,Char

函数

函数的定义

函数的定义fun关键字+函数名+(参数:变量类型)+:返回值类型+函数体。无返回值时不需要声明返回值类型。

//无返回值
fun saySomething(msg:String){ 
    print(msg)
}
//多参数
fun saySomething(msg:String,name:String){ 
    print(name+"说"+msg)
}

//有返回值
fun saySomething(msg:String):Int{ 
    print(msg)
    return 1
}


语法糖

当函数只有一条语句的时候,直接写在函数的后面用等号连接

//语法糖版本
fun saySomething(msg:String)= print(msg)

流程控制

条件控制

if

if 语句和大多数的语言里面的if语句相类似,唯一新增了一个就是可以有返回值,返回值是每个条件里面的最后一行

//传统写法
fun largerNumber(num1:Int,num2:Int):Int{
	var max=0;
	if (num1>num2){
	 max= num1 
	} else{
	 max=num2
	}
	return max
}

//新特性的改写
fun largerNumber(num1:Int,num2:Int):Int{
	if (num1>num2){
	  num1 
	} else{
	  num2
	}
}


//语法糖  因为只有一句语句( if和else是整体,结果就是返回一个值) ,套用上面的语法糖所以可以用等号连接
fun largerNumber(num1:Int,num2:Int):Int= if (num1>num2) num1 else num2


//当然既然采用了语法糖用等于号连接,那么也可以省略显式声明,让kotlin自己推导类型
fun largerNumber(num1:Int,num2:Int)= if (num1>num2) num1 else num2
when

类似于switch,但是强于switch,变量不只局限于整形或者是字符串。而且不需要break语句。 【匹配值】->{执行逻辑},当执行逻辑只有一行代码,{}可以省略不写。而且when和if一样也是可以有返回值的,

//传统写法
fun getScore(name:String):Int {
 var score =0
    when(name){
        "Tom"->{ score =66 }  //不省略{}写法
        "ANN"-> score =90 //省略写法
        else -> score =0
    }
    return score 
}

//语法糖写法 when整体是一个语句
fun getScore(name:String)= when(name){
	  "Tom"->66
	   "ANN"-> 90
        else -> 0
}

//不带参数的when写法
fun getScore(name:String)= when(){
	  name=="Tom"->66 // 前面写匹配的条件 
	  name== "ANN"-> 90
        else -> 0
}


when 还支持类型的匹配,其中关键字is.
Number 是抽象类,Int之类和数字相关的类都是它的字类

fun checkNumber(num:Number){
 when(num){
 is Int -> println(" is Int")
  is Double -> println(" is Double ")
  else -> println(" other Type ")
 }
}

循环

while

这个写法和java中一样的,或者是C啥的,只要有基础的都知道,所以直接来个例子吧

fun main(){
  var i=1;
  while(i<10)  {
      print("第"+i+"次")
      i=i+1
  }


}

do-while 和while的区别。
do-while语句不管条件是否满足,肯定执行一次。而while是只有条件满足才会进入。

fun main(){
  var i=10;
  do {
   print("当前i="+i)
  }while (i<9)
}
for- in

和java中常用的for-i循环不同,kotlin舍弃了这个循环,增强了for-each循环这个模式,也就是现在的for-in循环,能够满足大多数的场景,如果遇到不满足的场景可以选择while语句进行实现。

fun main(){
    for ( i in 1..10){//..为关键字,1..10 代表创建了 [1,10]闭区间的,并且默认步长为1,可以省略,也就是i的递增顺序是 i=i+1
        println("当前"+i)
    }

}

//写完整
fun main(){
    for ( i in 1..10 step 1){  // step 关键字,步长。当然可以设置其他的数字,用来跳过某些值
        println("当前"+i)
    }

}

// 当遇到长度为10的数组,其实下标是[0,10) 也就是左闭右开,用until
fun main(){
    for ( i in 0 until 10 ){ //同样省略步长 默认为1,用until关键字就是创建了 [0,10)区间
        println("当前"+i)
    }

}

//刚刚的例子是递增,那么有时候要递减 downTo
fun main(){
    for ( i in 10 downTo 0 ){ //downTo ,创建了两端闭合降序的[10,0]区间
        println("当前"+i)
    }

}

Kotlin 面向对象的编程语言

类与对象

普通类

class 关键字,和java一样的,唯一的不同的,当实例化的时候,不需要new关键字,调用 val person =Person(); 用val是因为,我们首选应当是val,当val不满足才选用var

 class Person {
    var name="" ;
    var age =0 ;
}
类的继承

和java不同,java里面的类默认是可以继承的,但是kotlin里面,只有写有关键字open的类能继承,当然抽象类肯定是可以继承。
改写Peson类

open class Person {
    var name="" ;
    var age =0 ;
}

然后创建Student 类去继承

class Student :Person(){
    var sno=""
    var grade=0

}

这时候会发现:是继承的关键字,但是为什么是Person(),咋比java多了个()呢?
这涉及到了主构造函数,和次级构造函数,我们现在记住一个死理
首先一定要知道,当子类去继承父类的时候,子类的构造函数一定要调用父类的构造函数。

构造函数

一个类有两种构造函数,主构造函数,次级构造函数,类会默认带有一个无参的主构造函数。
同时kotlin规定了一个类只能有一个主构造函数,但是能有多个次构造函数
注意点只能有一个主构造函数,代表可以有0个或者1个主构造函数
多个次构造函数,代表可以有0到多个。
(不是说默认带着无参的主构造函数吗?为什么又可以0个,0个就是当你没显式声明主构造函数的参数,但是又定义了次级构造函数。简单而言,相当于默默无闻的主构造函数被高调的次级构造函数,顶替了,只有当高调的主构造函数,才能不被次级构造函数给顶替)。

所以厉害关系排行榜
显式声明的主构造函数>次级构造函数>默认的主构造函数
(默认的主构造函数相较于前两者,更特别一点,它一旦遇到更厉害的角色出现,就被顶替消失了)

主构造函数

每个类默认会带有一个无参数的主构造函数,但是主构造函数也可以显示的声明参数,参数写的位置就在类名的后面。如果显示声明了参数,之后调用的话,就要传入参数了。
主构造函数重要的是,它是没有函数体的。如果想要主构造函数执行某些代码,必须采用init关键字。


open  class Person { //其实自带一个Person()的主构造函数
    var name="" ;
    var age =0 ;
}

// 显示声明主构造函数的参数
open class (val name:String,val age:Int) {
 //其实自带一个Person()的主构造函数,你会发现我没有在类里面定义name和age字段了
 //因为写在主构造函数里面的传入参数,如果前面带有val或者var关键字自动会变成类的字段的

}



//由于主构造函数没有函数体,那我们想用主构造函数构造的时候还要执行某些语句怎么办
//那么有 init结构体,专门为主构造函数设计的
 open class Person(val name:String,val age:Int) {

     init {
         print("初始化")
     }
}

那么现在再来看原来的代码下图为例,是不是就好理解很多。

1 Person类里面没有次级构造函数,Person类后面没跟()说明没显示声明主构造函数,所以拥有一个自带无参数的主构造函数。
2. Student 类里面的: 表达了 Student继承了Person类
3. Student 和Person类情况一样,只有默认的主构造函数。
(同时要知道,继承的话子类必须实现父类的构造函数,现在子类只有默认的主构造函数,父类也是默认的主构造函数,所以子类的主构造函数必须实现父类的主构造函数)
4.class Student :Person() 里面的() 代表 Student默认的主构造函数去实现了Person里面的默认的主构造函数。

所以方便记忆的话,可以当类的:后面出现()的时候就代表了是子类的主构造函数,在实现父类的某一个构造函数。那到底是实现的是父类哪一个构造函数呢?就看()里面的参数,和哪个对的上就是哪个。

open  class Person { //其实自带一个Person()的主构造函数
    var name="" ;
    var age =0 ;
}

class Student :Person(){ //子类一定要调用父类的构造函数,父类的构造函数只有Person()
    var sno=""
    var grade=0

}

同理当父类显示声明了主构造函数的参数的话呢?

 open class Person(val name:String,val age:Int) {//只有一个主构造函数 Person(name,age)

     init {
         print("初始化")
     }
}

//当Person变成上面这样的话,这样固然可以,但是每个人都是一样的名字了
class Student :Person("小小","12"){
    var sno=""
    var grade=0
}


//较为正确的应该是,构造Student的时候,输入学号,年级,名字和年龄。但是很奇怪的是,为什么name和age这两个字段咋前面没有变量的标识符,val或者var呢?
//因为之前说了如果写在主构造函数里面用val和var,就默认变成这个类的字段,那么Student里面S有name和age字段了,但是Person不是也有age和name吗,会冲突的。
//所以只是让它前面没有标识符,作用域只在主构造函数里面。
class Student(val sno:String,val grade:Double,name:String,age:Int) :Person(name,age){
}

次级构造函数

constructor 关键字定义,当既有主构造函数,又有次级构造函数时候。次级构造函数必须直接或者间接的调用主构造函数。
所以在这

次级构造函数 --》 主构造函数 --》父构造函数
(这个调用满足俩要求,次级构造函数必须直接或者间接的调用主构造函数,子构造函数必须实现主的构造函数)
主构造函数 --》父构造函数 (子构造函数必须实现父的构造函数)

class Student(val sno:String,val grade:Double,name:String,age:Int) :Person(name,age){

    constructor( sno:String,grade:Double,name:String):this(sno,grade,name,0){//构造函数1 ,直接调用主构造函数
    }

     constructor( sno:String,name:String):this(sno,2.0,name){//构造函数1 ,间接调用主构造函数
    }
}

特殊例子,有次级构造函数,但是没显示声明主构造函数,所以只有次级构造函数。没有主自然不调用。
Student 是继承Person 类的,那么子构造函数必须实现父的构造函数,那么自然就是这个次级构造函数,直接去继承父亲的构造函数了。

class Student :Person {
    var sno="";
    var grade=0.0;

    constructor(  //次级构造函数里面不能有var和val,那么自然也无法自动生成字段,那么只能在类里面手动定义了。
    sno:String,grade:Double,name:String,age:Int):super(name,age){
    }

}
接口

kotlin的接口中支持,对函数的默认实现。

interface Study {
    fun readBooks()
    fun doHomeWork(){ //函数的默认实现
                println("做作业")
    }
    
}

类对接口的实现也是用:关键字,然后可以发现没有对doHomeWork函数进行实现,因为Study 接口中有默认实现,所以不实现是不会报错的。但是如果readBooks函数没进行重写实现,那么就会报错的,因为接口中这个方法是没有实现的。

Kotlin中使用override关键字来重写父类和实现接口中的函数。

class Student(name:String,age:Int) :Person(name,age),Study{
    var sno="";
    var grade=0.0;

    constructor( ):this("小小",1){
    }

    override fun readBooks() {
       println("读书");
    }
}
可见性

Kotlin的默认可见性是Public。
在这里插入图片描述

//没写可见性
 open class Person(val name:String,val age:Int) {

     init {
         print("初始化")
     }
}

//上面等同,但是多此一举了
public open class Person(val name:String,val age:Int) {

     init {
         print("初始化")
     }
}
数据类

数据类,只要前面写上data关键字,其实Kotlin 默认实现了 equals(),hashCode(),toString()方法

//由于这个类里面没有其他代码的时候,就可以省略掉大括号了
//相信你们应该还记得吧  主构造函数里面出现了var 或者val 代表着这个字段就是这个类的字段(属性)
data class Cellphone(var name:String,var price :Double)
单例类

全局只有最多拥有一个实例的单例类,只需要object关键字就行。

object Singleton {
    fun doText(){ //里面定义的方法
        print("测试")
    }
}

调用单例类的方法很简单,有点像是调用静态方法

Singleton .doText();//但是实际上 kotlin给我们在背后创建了实例,而且全局有且只有一个实例。

Lambda表达式

Lambda是一小段可以作为参数传递的代码片段。什么意思呢,通常写函数的时候,我们定义的参数是Int,String 啊之类的,但是现在写函数的时候我们可以选择传入的参数是一个Lambda的表达式,(也就是一小段代码作为参数)。实不相瞒,个人认为这个东东很像回调函数。

Lambda式子的语法结构,函数体中最后一行代码就是lambda的返回值
{参数名1:参数类型,参数名字:参数类型 -> 函数体}

以一个我写的无病呻吟的一个传入参数是Lambda表达式的函数textLambda为例子。

//参数名为test  然后参数的类型是 一个Lambda表达式
//并且这个Lambda 会有一个String类型的传入参数,和Int类型的返回值
//也就是说 这个函数已经规定了这个Lambda 传入参数是什么类型,返回值是什么类型。
fun textLambda(test:(String) -> Int){
    val str1="aaa"
    val num= test(str1)
    if (num>5) print("短字符串")
    else print("长字符串")
}

那么调用的时候,我就要传入一个符合这个函数要求的lambda表达式

//调用的时候,str1:String-> 8 就是我写的lambda表达式
//lambda表达式中 有textLambda传给我的str1这个参数,但是我没用到
//结果 输出 短字符串
textLambda ({str1:String-> 8})

// 用传给我的字符串的长度作为返回值
//判定结果 长字符串
textLambda ({str1:String-> str1.length})

//kotlin规定 当lambda参数是函数的最后一个参数,可以将表达式移到()后面
textLambda (){str1:String-> str1.length}
//当 lambda参数是函数的唯一一个参数的时候,可以省略掉()
textLambda {str1:String-> str1.length}

//kotlin的推导机制,可以让我们省略掉lambda参数列表的类型声明
textLambda {str1-> str1.length}
//当只有lambda只有一个参数的时候,参数名称可以不写 直接用it指代
textLambda {it.length}

通过以上的例子就会发现,其实Lambda表达式和Int之类的没什么区别,Int代表的是一个值,但是Lambda代表的是一段代码罢了。对于参数是Lambda表达式的函数而言,它会告诉你它要一个Lambda表达式,然后这个表达式中它会告诉你,它将会给你一个什么类型的参数,然后它需要你返回给它一个什么类型的结果。至于中间的过程它一概不管。

JAVA函数式API

当kotlin调用java代码的时候,也可以使用JAVA函数式API。但是要满足以下条件:**调用的java方法接收一个java单抽象方法的接口参数。**以上的句子很难懂但是没关系,例子来解释,Android的java开发中经常要开线程,Thread方法很熟悉吧,就以他为例子。
解释:

 java代码中,看下定义:Thread 方法,参数是Runnable 类型的参数
  public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
再看下Runnable 的定义,会发现它是一个接口,并且只有一个抽象方法也就是run方法。
public interface Runnable {
 public abstract void run();
}
那么Thread方法它就是满足上面那段话的,首先Thread 是一个java方法,并且它接收的参数Runnable是一个接口,然后这个接口里面有一个抽象方法。
所以说当kotlin中调用Thread方法中那个接口参数的单抽象方法可以写成Lambda表达式

实战

//java中调用thread的写法(匿名类的写法)
   new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("线程");
           }
       });
       
// 原模原样改造成kotlin的话,没有new关键字,方法的关键字是fun(匿名类的写法采用了object关键字)
Thread(object: Runnable {
          override fun run() {
               print("线程");
           }
       })
// 但是刚才说了,这个 Thread是满足java函数式api的,也就是说可以简写,因为只有Runnable只有一个待实现方法,所以可以不显示重写run方法
Thread( Runnable {
  print("线程");
       })   

//  Thread方法里不沉溺在一个以上的java单抽象方法接口参数,可以省略接口名字
Thread( {
  print("线程");
       })
       
// lambada表达式是方法的最后一个参数
Thread() {print("线程")}     
 // lambada表达式只有一个lambada表达式的参数,省略括号
 Thread{print("线程")}     

这里的java函数式API的使用,都是限定于从kotlin中调用java方法,并且单抽象方法也必须用java语言定义的。

空指针

kotlin默认的是变量不为空。

?

fun doStudy(study:Study){ //这里的study参数传空就会报错
}

如果想要空,后面加上一个?,表示可以为空,但是这时候需要自己把空异常都处理掉。

fun doStudy(study:Study?){ //这里的study参数传空就会报错
}

?.

代表当对象为空不做任何事情,当对象不为空调用方法

例如
if(a!=null){
a.doSomething()
}

//可以变成

a?.doSomething()

?:

val c=a?:b  //意思是当a不为空 返回a,但是当a为空返回b

!!

非空断言,当可为空的全局变量的时候,在不为空的时候才调用某个方法,但是kotlin没办法知道

var content:String?="hello"
fun main(){
   if(content!=null){
      doUpperCase()
   }

}
fun doUpperCase(){
   content.toUpperCase(); //代码会异常
}

采用!!,告诉kotlin 这个我自己确定不为空,有异常的话我来解决。

var content:String?="hello"
fun main(){
   if(content!=null){
      doUpperCase()
   }

}
fun doUpperCase(){
   content!!.toUpperCase();
}

let

一个函数,提供了函数式api接口,并将原始调用对象作为参数传递到lambda表达式里面。它配合?.可以发挥很大作用。而且let可以处理全局变量的判空问题。

a?.doSomething()
a?.doStudy()

//上面其实a是否为空每一句都判断了,代码复杂了。但是用let函数,在外面先判断一次,然后就做操作

a?.let{
a1-> a.doSomething()
a.doStudy()
}

//再次简化,当lambda表达式里面只有一个传入参数的时候,可以声明直接用it来指代
a?.let{
it.doSomething()
it.doStudy()
}

字符串内嵌表达式

在字符串中允许${}的方式来内嵌,可以把一堆+给省略掉了。

   val name="小明"
   val age="20"
   println("你好,"+name+",原来你已经"+age+"岁了呀!")
   println("你好,${name},原来你已经${age}岁了呀!")
-----------------结果--------------------
你好,小明,原来你已经20岁了呀!
你好,小明,原来你已经20岁了呀!

函数设置参数默认值

fun main(){
    doPrint()
    doPrint(name = "小花") //键值对的传参方式,可以无视顺序
    doPrint(age = 10)

}
fun doPrint(name:String="小明",age:Int=20){ //设置默认值 
    println("你好,${name},原来你已经${age}岁了呀!")
}
----------------结果----------------
你好,小明,原来你已经20岁了呀!
你好,小花,原来你已经20岁了呀!
你好,小明,原来你已经10岁了呀!

延迟初始化

使用lateinit关键字,使用了这个关键字之后,我们可以不需要定义的时候就设置默认值,可以后面用到的时候初始化。
这个的作用是可以让我们用到全局变量少很多非空校验,当然这个的前提是你确保用到这个变量的时候已经完成初始化才行。

lateinit var a:String //没有初始值
var b=""   //必须设置初始值

判断是否进行过初始化。 ::a.isInitialized 代表a是否进行过初始化,没有的话进行初始化。

 if(!::a.isInitialized){
            a=""
        }

密封类

普通写法,创建一个Result.kt文件,也就是kotlin文件。
interface Result
class Success(val msg:String):Result()
class Failure(val error:Exception):Result()
为了过编译一定要写个else,但是实际结果肯定不会有else。
还有就是当Result又多了一个Unknown类继承它,但是方法里面没有去判断,走到else导致系统抛出异常,程序崩溃。

fun getResultMsg(result:Result)=when(result){
is Success ->result.msg
is Failure->result.error.message
else throw IllegalArgumentException()
}

但是密封类的写法,创建一个Result.kt文件。
sealed class Result
class Success(val msg:String):Result()
class Failure(val error:Exception):Result()
不需要写else,而且如果后续增加了Unknown继承它,kotlin会强制你每个都进行判断,否则编译通不过。密封类是一个可继承的类,所以当其他类继承它的时候要实现它的主构造函数,所以是:Result()

fun getResultMsg(result:Result)=when(result){
is Success ->result.msg
is Failure->result.error.message
}

密封类和子类只能定义在同一个文件的顶层位置,不能嵌套在其他类里面。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值