昨天搭好了 Android Studio今天正式进入 Kotlin 语言基础。写 Android 应用本质上就是用 Kotlin 描述界面长什么样、数据怎么流转、用户操作怎么响应这些最终都会落到变量、类型、函数和字符串拼接这四个基本功上。今天把它们逐个搞清明天就能直接用 Kotlin 写出有逻辑的程序代码了。变量val和varKotlin 声明变量用val或var。二者的区别不在于有没有值而在于赋值之后还能不能改。val声明的变量只能赋值一次之后不能再指向其他对象。这跟 Java 里的final是同一个意思。varvariable 的缩写声明的变量则可以反复赋值。举个例子funmain(){// val 声明后不可变——适合作为常量或一次性计算结果valappNameHelloAndroid// 应用名确定之后不需要改// var 声明后可以重新赋值——适合表示会变化的状态varclickCount0// 每次点击都要累加clickCountclickCount1}如果把appName声明成var代码也能跑但意图就模糊了,读代码的人会预期这个值后面可能被修改就会额外留意赋值的位置。反过来如果clickCount用val编译器直接报错Val cannot be reassigned逼你在声明时就明确它的角色。Kotlin 支持类型推断多数情况下不需要手写类型编译器能从右边的值反推出来valversion1// 推断为 Intvalratio0.618// 推断为 Double需要显式声明时把类型写在变量名后面用冒号分隔valuserId:Int1varmessage:StringHello类型必须显式声明的场景主要有三种一是声明时不赋值稍后才初始化编译器没法猜类型二是类型推断出的不是你想要的那个比如你想要Long但编译器推成Int三是作为类属性或函数参数Kotlin 要求写明类型。还有一种常见情况是先声明再初始化valcontent:String// 只声明不赋值// ... 一些逻辑之后 ...contentresult// 唯一一次赋值机会这里需要注意val的不可变指的是变量指向的引用不可变不是说对象内部不可变。比如val指向一个可变的列表你仍然能往里添加删除元素只是不能让这个变量再指向另一个列表。数据类型Kotlin 里的所有东西都是对象数字、布尔值、字符没有 Java 那种基本类型和包装类型的区分。日常开发中最常用的数值类型就四种Int整数、Long大整数、Double高精度小数、Float低精度小数。类型大小字面量示例说明Int32 位val a 42默认整数类型Long64 位val a 42L注意末尾的LDouble64 位val a 3.14默认浮点类型Float32 位val a 3.14f注意末尾的f数字字面量可以用下划线分隔提高大数可读性1_000_000与1000000完全等价。Kotlin 有一个新手很容易踩到的坑整数除法会丢掉小数部分不会自动升级成浮点数。Java 开发者尤其要小心这个行为两种语言一样但在 Kotlin 里因为类型系统更严格出错时定位更明显valhalf5/2// 结果是 2不是 2.5——两个 Int 相除结果还是 Intvalcorrect5/2.0// 结果是 2.5——其中一个操作数是 Double第二个坑是 Kotlin 不做隐式类型转换。不能直接把Int赋给Long变量也不能把Float传给参数类型为Double的函数valcount:Int42valbigCount:Longcount// 编译错误type mismatchvalbigCount:Longcount.toLong()// 正确做法——显式调用转换函数对于每种数字类型都提供了toByte()、toShort()、toInt()、toLong()、toFloat()、toDouble()六个转换函数。设计上之所以不做隐式转换是为了避免精度丢失时悄无声息凡是转换必须在代码里写出来让 review 的人能看见。Boolean类型只有true和false两个值用在条件判断中。Char表示单个字符用单引号括起来val letter A。String是字符串用双引号不可变所有对字符串的操作都返回新字符串不会修改原值valoriginalKotlinvalupperoriginal.uppercase()// 返回 KOTLINoriginal 依然是 Kotlin函数声明函数用fun关键字后面跟函数名、括号内的参数列表、冒号后的返回值类型然后是花括号内的函数体funadd(a:Int,b:Int):Int{returnab}Kotlin 中函数可以定义在文件顶层不需要先写一个 class 把它包起来这一点和 Java 不同。对于 Android 开发来说你可能会把一个页面里的辅助逻辑比如格式化时间、校验手机号等写成顶层函数放在对应的包下面外部直接按包名 import 就能用不需要套一层 Util 类。如果函数体只有一条表达式可以把花括号和return都省掉换成等号连接funadd(a:Int,b:Int):Intab这种单表达式函数连返回值类型都可以省略编译器自动推断funadd(a:Int,b:Int)ab// 推断返回 Int函数没有有意义返回值时返回类型是Unit相当于 Java 的void可以省略不写funshowResult(value:Int){println(计算结果$value)// 不需要 return 语句}参数可以有默认值这在实际项目中非常有用。比如一个网络请求函数默认超时可以设成 5 秒特殊场景下才传入自定义值fundivide(a:Double,b:Double1.0):Doublea/bdivide(10.0)// 10.0b 使用默认值 1.0divide(10.0,2.0)// 5.0覆盖默认值调用函数时可以按参数名传值不按顺序这就是命名参数。当一个函数有多个带默认值的参数你只想改其中一个时命名参数就省去了逐个补位的麻烦funcreateUser(name:String,age:Int0,city:String未知){println($name,$age,$city)}createUser(Tom,city上海)// age 用默认值 0city 覆盖为 上海传入函数的参数在函数体内部是只读的——相当于隐式val不能对参数重新赋值。这个约束在函数体较长、逻辑复杂时能防止无意中改掉了上游传进来的值排错时少一个变量污染源。字符串模板字符串模板是 Kotlin 最顺手的语法之一在双引号包裹的字符串里$加变量名就能直接把变量的值嵌入到字符串中不用再写拼接valnameTomvalscore95println(学生:${name}, 成绩:${score})// 学生: Tom, 成绩: 95花括号里可以放任意 Kotlin 表达式函数调用、算术运算甚至条件判断都可以。如果不加花括号$只会把紧跟在它后面的那一个标识符当作变量名所以$score等价于${score}但$score分会被解析成变量名叫score分而不是scorevalbase100println(两倍是${base*2})// 两倍是 200println(大写${name.uppercase()})// 大写TOMprintln(得分$score分)// 编译错误找不到变量 score分println(得分${score}分)// 正确写法需要输出一个实际的美金符号$时写${$}内层是一对单引号括起来的 Charprintln(价格${$}99)// 价格$99多行字符串用三引号包裹里面也可以嵌入模板表达式换行会原样保留不需要\n转义valhtml div classcard h1${name}/h1 p分数${score}/p /div .trimIndent()trimIndent()会统一去除每行前面的公共缩进让输出看起来紧凑整齐。字符串模板把变量、表达式和文本自然地写在一起代码和最终输出的对应关系一目了然。用拼接好几个变量时很容易漏掉空格或搞混引号闭合模板写法则直接杜绝了这类低级错误。练习理解了这四个概念之后用两个练习来巩固。练习一写计算器函数。定义加减乘除四个函数每个接收两个Double参数返回Double结果。在main里分别调用并打印输出。参考实现funadd(a:Double,b:Double)abfunsubtract(a:Double,b:Double)a-bfunmultiply(a:Double,b:Double)a*b// 除数为 0 时直接返回 0避免运行时异常fundivide(a:Double,b:Double)if(b!0.0)a/belse0.0funmain(){valx15.0valy4.0println($x$y${add(x,y)})println($x-$y${subtract(x,y)})println($x*$y${multiply(x,y)})println($x/$y${divide(x,y)})}divide里加了对除数为零的保护——这在实际项目中是必要的因为Double / 0.0会返回Infinity而不会直接抛异常代码能继续执行但后续逻辑可能因此出错。更稳妥的做法是判断之后抛一个有意义的异常这里为了让示例保持简单直接返回零。练习二写 BMI 计算器。接收身高单位米和体重单位千克两个参数计算 BMI 并打印判定结果。BMI 公式是体重除以身高的平方。判定标准参考中国标准低于 18.5 偏瘦18.5~23.9 正常24.0~27.9 偏重28.0 及以上肥胖。funcalculateBMI(weight:Double,height:Double):Double{returnweight/(height*height)}fungetBMICategory(bmi:Double):String{returnwhen{bmi18.5-偏瘦bmi24.0-正常bmi28.0-偏重else-肥胖}}funmain(){valweight70.0// 千克valheight1.75// 米// 保留一位小数避免输出一长串数字valbmiString.format(%.1f,calculateBMI(weight,height))valcategorygetBMICategory(bmi.toDouble())println(身高${height}m体重${weight}kg)println(BMI${bmi}${category})}这段代码把计算和分类拆成了两个函数职责单一各自独立可测。String.format(%.1f, ...)把 BMI 值截断到一位小数打印结果干净——70.0 / (1.75 * 1.75)精确结果是22.857...格式化后输出22.9刚好落在正常范围内。when表达式的条件是从上到下依次匹配的把区间的下界从小到大排好就不需要写bmi 18.5 bmi 24.0这种双重条件了。这两个练习覆盖了今天所有内容用val声明不变的入参和计算结果用Double做小数运算并处理类型转换用fun定义带返回值和默认参数的函数用字符串模板把计算结果整洁地拼进输出信息。把代码敲一遍、改几个参数跑一跑这四个概念就真正内化了。