一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
这里整理android开发必备的kotlin学习(资料整理于郭神的《第一行代码》第三版)。
为什么用kotlin
Google在2017年的I/O大会上宣布,Kotin正式称谓Android的一级开发语言,AndroidStudio也对Kotlin进行了全面的支持。两年后在2019年的I/O大会上宣布,Kotlin已经成为Android第一开发语言。Google推荐开发者使用Kotlin来编写Android程序,并且未来提供的官方API也会优先考虑Kotlin。
Java作为一种解释性语言:java虚拟机不直接和编写的Java代码打交道,而是和编译之后生成的class文件打交道。如果开发了一门新语言,再做一个编译器,让这门语言的代码编译成同样规格的class文件,那么Java虚拟机也能识别,这就是Kotlin的工作原理。
为什么Kotlin备受欢迎呢?
- Kotlin语法简洁;
- Kotlin语法高级,开发效率大大提升;
- Kotlin语法安全,几乎杜绝了空指针全球崩溃率最高的异常;
- Kotlin可以直接调用Java编写的代码。
1、变量
- val来声明不可变的变量(对应java中的final);
- var来声明可变的变量(对应java中的非final变量);
- lateinit:延迟赋值,需要显式的声明变量的类型。
private var adapter :Adapter?=null
adapter=Adapter()
adapter?.setNewData(data)
//日常使用的时候将adapter设置为全局变量,但如果代码里有了越来越多的全局变量实例时,就需要写大量的判空处理。
//那么使用延迟初始化关键字:lateinit,这样kotlin可以不在开始的时候对变量赋值为null了。
//注:如果使用lateinit关键字,那么要确保它在被任何地方调用之前已经完成了初始化工作。
//如何判断变量是否已经完成了初始化?
if(!::adapter.isInitialized)(){
}
tips:
- kotlin里每一行代码是不用加分号的;
- 永远优先使用val来声明一个变量,当val无法满足需求时再使用var;
- Kotlin中判断字符串或对象是否相等,可以直接使用==关键字。
2、函数
- 不使用new创建对象;
- 如果类需要被集成,需要加上open关键字;
- 可见修饰符:默认为public;
- fun是定义函数的关键字。参数的格式是 参数名:参数类型。
fun largeNumber(num1:Int,num2:Int):Int{
return max(num1,num2)
}
// public 方法则必须明确写出返回类型
public fun sum(a: Int, b: Int): Int = a + b
//无返回值的函数(类似Java中的void):
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
// 如果是返回 Unit类型,则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) {
print(a + b)
}
语法糖
//如果一个函数中只有一行代码是,kotlin允许我们不必编写函数体
fun largeNumber(num1:Int,num2:Int):Int = max(num1,num2)
//kotlin出色的类型推导机制:
fun largeNumber(num1:Int,num2:Int) = max(num1,num2)
//内嵌表达式
val brand = "apple"
val price = 139
println(brand = $band , price = $price)
//函数默认值
//我们可以在定义函数的时候给任意参数设定一个默认值,这样调用此函数时就不会强制要求调用方为此参数传值。
标准函数
Kotlin系列之let、with、run、apply、also函数的使用
kotlin标准函数指的是Standard.kt文件中定义的函数,任何kotlin代码都可以自由的调用所有标准函数。
//let函数可以(配合?.)处理全局判空的问题。
fun doStudy(study:Study?){
study?.let{stu->
stu.readBooks()
stu.doHomeWork()
}
}
fun doStudy(study:Study?){
study?.readBooks()
study?.doHomeWork()
}
//lambda语法特性:lambda表达式的参数列表只有一个参数时,可以不用声明参数名,直接使用it关键字来代替
fun doStudy(study:Study?){
study?.let{
it.readBooks()
it.doHomeWork()
}
}
//主要用途
object.let{
it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法
...
}
//另一种用途 判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
it.todo()
}
//with函数接收两个参数,第一个参数是任意类型的对象,第二个参数时lambda表达式,
(适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法)
val list = listOf("Apple","Banana","Orange","Pear")
val result = with(StringBuilder()){
append("start eating fruite./")
for(fruit in list){
apend(fruit).append("\n")
}
append("ate all fruits")
toString()
}
println(result)
//run(run函数不能直接调用,一定要调用某个对象的run函数才行)
//(适用于let,with函数任何场景,因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理)
val list = listOf("Apple","Banana","Orange","Pear")
val result = StringBuilder().run{
append("start eating fruite./")
for(fruit in list){
apend(fruit).append("\n")
}
append("ate all fruits")
toString()
}
println(result)
//apply(无法返回制定值,会自动返回调用对象本身)
//(apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。)
val intent = Intent(context,SecondAct::class.java)
intent.putExtra("param1","data1")
intent.putExtra("param2","data2")
context.startActivity(intent)
val intent = Intent(context,SecondAct::class.java).applay{
putExtra("param1","data1")
putExtra("param2","data2")
}
context.startActivity(intent)
静态函数
public class Util{
pulic static void doAction(){
System.out.println("do something");
}
}
//kotlin中推荐使用单例类来实现
object Util{
fun doAction(){
println("do something")
}
}
//使用单例类会将整个类的所有方法变为静态方法,如果只希望让类中的某一个方法变为静态,则使用companion object
class Util{
fun doAction1(){
println("do something")
}
companion object{//companion object关键字会在Util类内部创建一个伴生类。
fun doAction2(){
println("do something2")
}
}
}
class Util{
fun doAction1(){
println("do something")
}
companion object{//companion object关键字会在Util类内部创建一个伴生类。
@Jvmstatic //加上这个注解,java才能按照静态方法来调用。这个注解只能加在单例类或者companion object方法上
fun doAction2(){
println("do something2")
}
}
}
扩展函数
//定义:在不修改某个类的源码的情况下,仍可以打开这个类,向该类添加新的函数。
//比如:
object StringUtil{
fun letterCount(str:String):Int{
var count = 0
for(char in str){
if(char.isLetter){
count++
}
}
return count
}
}
//java里一般都这样写,但有了扩展函数,我们就可以使用面向对象的思维来实现这个功能。比如:
//扩展函数的语法结构:只需要在函数名前加上一个ClassName,就表示将函数添加到指定类中了。
fun ClasName.methodName(param1:Int,param2:Int):Int{
return 0
}
//比如我们希望在String类中添加一个扩展函数,所以需要显创建一个String.kt文件。
//(文件名没有要求,但建议向那个类中添加扩展函数,就定一个同名的kotlin文件,方便以后查找。)
fun String.letterCount():Int{
var count = 0
for(char in this){
if(char.isLetter()){
count++
}
}
return count
}
//定义好扩展函数后,我们只需要这样写
val count = "1324sdafdsADSFAsdfdsA".lettersCount()
infix函数
使用它,相对于调用一个函数,可以更接近于使用英语的语法来编写程序。
比如:A.to(B)可以写成 A to B。
//简单的例子:String类中的startsWith()函数:
if("hello world").startWith("hello"){
//
}
//借助infix函数,我们可以使用更具有可读性的语法来表达这段代码:新建一个infix文件,编写代码:
infix fun String.beginsWith(prefix:String) = startWith(prefix)
//解释:除去infix关键字,这是一个String类的扩展函数,我们给String类添加了一个beginsWith()
函数,它的内部实现调用的String类的startsWith()函数。
//加上了infix关键字后,一个beginsWith()函数就变成了一个infix函数,我们可以使用特殊的语法糖格式调用beginsWith()函数
if("hello world" beginsWith "hello"){
//todo
}
//infix函数允许我们将函数调用时的小数点,括号等计算机相关的语法去掉,而使用一种更接近英语的语法来编写程序,使代码看起来更加具有可读性
//更多例子
infix fun <T> Collection<T>.has(element:T)=contains(element)
val list = listOf("apple","Banana","Orange")
if(list has "Banana"){
//todo
}
内联函数(泛型相关)
使用内联函数将泛型实例化。使用inline关键字来修饰函数,在声明泛型的地方加上reified关键字来表示泛型要实例化。
inline fun <reified T> getGEnericTyoe{}
//常见的情况
val intent = Intent(context,MainActivity::class.java)
context.startActivity(intent)
//这样写代码 MainActivity::class.java 很难受,可以使用内联函数优化一下
inline fun <reified T> startActivity(context:Context){
val intent = Intent(context,T::class.java)
context.startActivity(intent)
}
//然后这样写代码
startActivity<MainActivity>(context)
//如果需要传参,那么可以这样写
```kotlin
inline fun <reified T> startActivity(context:Context,block:Intent.()->Unit){
val intent = Intent(context,T::class.java)
intent.block()
context.startActivity(intent)
}
startActivity<MainActivity>(context){
putExtra("param1","data")
}
3、逻辑控制
//if语句
private fun getMax1(a: Int, b: Int): Int {
return if (a > b) {
a
} else {
b
}
}
private fun getMax2(a: Int, b: Int) = if (a > b) a else b
//when语句
private fun getScore(name: String) = if (name == "imfondof") {
100
} else if (name == "fondof") {
90
} else {
0
}
private fun getScore(name: String) = when (name) {
"imfondof" -> {
100
}
"fondof" -> {
90
}
else -> {
0
}
}
private fun getScore(name: String) = when (name) {
"imfondof" -> 100
"fondof" -> 90
else -> 0
}
//is 类型匹配
private fun checkNumber(num: Number){
is Int -> printLn("number is Int")
is Double -> printLn("number is Double")
else -> printLn("number not support")
}
//可以不在when传入参数(不常用,但是很强)
private fun getScore2(name: String) = when {
name.startsWith("imfondof") -> 100
name == "fondof" -> 90
else -> 0
}
//循环
fun test() {
for (i in 1..10) {
print(i)
}
}
fun test() {
for (i in 0 until 10 step 2) {
print(i)
}
}
fun test() {
for (i in 10 downTo 1) {
print(i)
}
}
// 10 9 8 7 6 5 4 3 2 1
可变长参数函数:函数的变长参数可以用 vararg 关键字进行标识:
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
// 测试
fun main(args: Array<String>) {
vars(1,2,3,4,5) // 输出12345
}
4、面向对象
类与构造函数
Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。(如果需要设置为可以继承,在类前添加关键字open就可以了。不是使用extrends,只使用冒号就可以了。)
open class Person{}
class Student : Person(){}
//Person类后边为什么加上括号?这就涉及到Kotlin的主构造函数和次构造函数。
//子类的构造函数必须调用父类中的构造函数。
open class Person(val name : String,val age :Int){}
class Student(val sno : String,val grade :Int,val name : String,val age :Int):
Person(name,age){}
//当一个类既有主构造函数又有次构造函数,所有的次构造函数都必须调用主构造函数。
class Student(val sno : String,val grade :Int,val name : String,val age :Int):Person(name,age){
constructor(name:String,age:Int):this("",0,name,age){ }
constructor():this("",0){ }
}
数据类
传统的java数据类,需要重写构造方法、get、set方法。但Kotlin只需要data一个关键字就够了。
//Kotlin会根据主构造函数中的参数自动生成equals、hashCode、toString方法。
data class CellPhone(val brand :String,val price :Double)
单例类
使用Kotlin创建一个单例类很简单,只需要将class关键字改为object关键字即可。
object Singleton{
fun singletonTest(){
println("singletonTest is called")
}
}
//调用方法:
Singleton.singletonTest()
密封类
Kotlin中引入了新可见性概念,只对同一模块中的类可见,使用的是internal修饰符。
如果我们开发了一个模块给别人用,但是有一些函数只允许在模块内部使用,不想暴露给外部,就可以讲这些函数声明城internal。
//密封类通常结合Recyclerview适配器中的ViewHolder一起使用。
//案例:
interface Result
class Success(val msg:String):Result
class Failure(val error:Exception):Result
fun getResultMsg(result:Result) =when(result){
is Success -> result.msg
is Failure -> result.error.message
else -> throw IllegalArgumentException()
}
//这个方法里不得不写一个else条件,否则kotlin会认为这里缺少条件分支。
//但如果使用密封类就不不同了:(sealed class,使用也非常简单)
sealed class Result
class Success(val msg:String):Result()
class Failure(val error:Exception):Result()
fun getResultMsg(result:Result) =when(result){
is Success -> result.msg
is Failure -> result.error.message
}
//将interface关键字改为sealed class,且因为它是一个类,每次继承的时候在result后边加上括号。
接口
允许对接口进行默认实现。
interface Study{
fun readBooks()
fun doHomeWork(){
println("do homework default implementation")
}
}
5、空指针检查
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常,另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理
//类型后面加?表示可为空
var age: String? = "23"
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1
//当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。
当 str 中的字符串内容不是一个整数时, 返回 null:
fun parseInt(str: String): Int? {
// ...
}
//以下实例演示如何使用一个返回值可为 null 的函数:
fun main(args: Array<String>) {
if (args.size < 2) {
print("Two integers expected")
return
}
val x = parseInt(args[0])
val y = parseInt(args[1])
// 直接使用 `x * y` 会导致错误, 因为它们可能为 null.
if (x != null && y != null) {
// 在进行过 null 值检查之后, x 和 y 的类型会被自动转换为非 null 变量
print(x * y)
}
}
//let是一个函数,提供了函数式API的编程接口。,并将原始调用对象作为参数传递到Lambda表达式中。
obj.let{obj2 ->
//todo
}
fun doStudy(study:Study?){
study?.readBooks()
study?.doHomework()
}
//--->
fun doStudy(study:Study?){
study?.let{stu ->
stu.readBooks()
stu.doHomework()
}
}
//--->(当Lambda表达式的参数列表中只有一个参数时,可以不用声明参数名,使用it关键字来代替)
fun doStudy(study:Study?){
study?.let{
it.readBooks()
it.doHomework()
}
}
6、Lambda编程(函数式API)
Lambda定义:一小段可以作为参数传递的代码
//最外层是一对大括号,如果有参数传入Lambda表达式的话,需要声明参数列表,参数列表结尾使用一个 -> 符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码,最后一行代码会自动作为Lambda表达式的返回值。
{参数1:参数类型,参数2:参数类型 -> 函数体}
//常用的函数式API:map函数、filter函数
list.map(it.toUppercaCase)
list.filter(it.length<=5)
//常用的函数式API:any和all函数
list.any(it.length<5) //list里是否存在5个字母以内的单词
list.all(it.length<5) //list里是否所有的单词都在5个字母以内
java函数式API的使用
如果在kotlin代码中调用了一个java代码,且该方法接受一个java但抽象接口参数,就可以使用函数式API
(比如常见的Runnable接口)
new Thread(new Runnable{
@Override
Public void run(){
...
}
}).start();
Thread{
...
}.start()
Button.setOnClickListener(new View.OnClickListener(){
@Override
Public void Onclick(){
...
}
})
button.setOnClickListener{
}
其他
顶层方法
顶层方法指的是没有定义在任何类中的方法,kotlin编译器会将所有的顶层方法全部编译成静态方法。
//创建kotlin文件:New--Kotlin File Class,创建类型选择File。
//如果我们的kotlin文件为:Helper.kt,那么Kotlin会为我们创建一个HelperKt的java类,在java里使用HelperKt.doSomething()。
fun doSomething(){
println("do something")
}