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 }
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 } }
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 ; }
类的继承
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 ,间接调用主构造函数 } }
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
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 }
密封类和子类只能定义在同一个文件的顶层位置,不能嵌套在其他类里面。
到此这篇kotlin开发ios app_安卓原生开发的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/kotlinkf/1112.html