【码上开学】Kotlin 的变量、函数和类型

implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
👇
implementation “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version”

}

也就是说,如果你是要创建一个新项目,记得把语言选择为 Kotlin,项目创建完成后你就可以用 Kotlin 来写它了。

给现有项目添加 Kotlin 支持

如果是现有的项目要支持 Kotlin,只需要像上面这样操作,把两个 build.gradle 中标注的代码对应贴到你的项目里就可以了。

笔者建议刚开始学习的时候还是新建一个基于 Kotlin 的项目,按照上面的步骤练习一下。

初识 MainActivity.kt

前面我们提到,如果新建的项目是基于 Kotlin 的,IDE 会帮我们创建好 MainActivity,它其实是有一个 .kt 的文件后缀名(打开的时候可以看到)。

Kotlin 文件都是以 .kt 结尾的,就像 Java 文件是以 .java 结尾。

我们看看这个 MainActivity.kt 里到底有些什么:

package org.kotlinmaster
👆
import android.os.Bundle
👆
import androidx.appcompat.app.AppCompatActivity
👇
class MainActivity : AppCompatActivity() {
👆
👇 👇 👇 👇
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

乍一看,「👆」标注的 package import class 这些 Java 里的东西,Kotlin 也有;但是也有一些以「👇」标注的在 Java 里是没见过的。

为了暂时避开这些干扰,我们自己新建一个文件。

  • 在新建 Java Class 的入口下面可以看见一个叫 「Kotlin File/Class」 的选项,这就是我们新建 Kotlin 文件的入口
  • New Kotlin File/Class
  • Name: Sample
  • Kind: Class

创建完成后的 Sample.kt

package org.kotlinmaster

class Sample {}

这个类仅包含 packageclass 两个关键字,我们暂时先看成和 Java 差不多(其实真的就是差不多)的概念,这样就都是我们熟悉的东西了。

接下来,让我们开始学习基础语法吧。


变量

变量的声明与赋值

这里讲一个 Java 和 Kotlin 命名由来的小插曲。

我们知道 Java 就是著名的爪哇岛,爪哇岛盛产咖啡,据说就是一群研究出 Java 语言的牛人们在为它命名时由于闻到香浓的咖啡味,遂决定采用此名称。

Kotlin 来源于芬兰湾中的 Kotlin 岛。

因此,我们在代码段的开头以「☕️」来表示 Java 代码段,「🏝️」来表示 Kotlin 代码段。

我们回忆下 Java 里声明一个 View 类型的变量的写法:

☕️
View v;

Kotlin 里声明一个变量的格式是这样的:

🏝️
var v: View

这里有几处不同:

  • 有一个 var 关键字
  • 类型和变量名位置互换了
  • 中间是用冒号分隔的
  • 结尾没有分号(对,Kotlin 里面不需要分号)

看上去只是语法格式有些不同,但如果真这么写,IDE 会报错:

🏝️
class Sample {
var v: View
// 👆这样写 IDE 会报如下错误
// Property must be initialized or be abstract
}

这个提示是在说,属性需要在声明的同时初始化,除非你把它声明成抽象的。

  • 那什么是属性呢?这里我们可以简单类比 Java 的 field 来理解 Kotlin 的 Property,虽然它们其实有些不一样,Kotlin 的 Property 功能会多些。

  • 变量居然还能声明成抽象的?嗯,这是 Kotlin 的功能,不过这里先不理它,后面会讲到。

属性为什么要求初始化呢?因为 Kotlin 的变量是没有默认值的,这点不像 Java,Java 的 field 有默认值:

☕️
String name; // 👈默认值是 null
int count; // 👈默认值是 0

但这些 Kotlin 是没有的。不过其实,Java 也只是 field 有默认值,局部变量也是没有默认值的,如果不给它初始值也会报错:

☕️
void run() {
int count;
count++;
// 👆IDE 报错,Variable ‘count’ might not have been initialized
}

既然这样,那我们就给它一个默认值 null 吧,遗憾的是你会发现仍然报错。

🏝️
class Sample {
var v: View = null
// 👆这样写 IDE 仍然会报错,Null can not be a value of a non-null type View
}

又不行,IDE 告诉我需要赋一个非空的值给它才行,怎么办?Java 的那套不管用了。

其实这都是 Kotlin 的空安全设计相关的内容。很多人尝试上手 Kotlin 之后快速放弃,就是因为搞不明白它的空安全设计,导致代码各种拒绝编译,最终只能放弃。所以咱先别急,我先来给你讲一下 Kotlin 的空安全设计。

Kotlin 的空安全设计

简单来说就是通过 IDE 的提示来避免调用 null 对象,从而避免 NullPointerException。其实在 androidx 里就有支持的,用一个注解就可以标记变量是否可能为空,然后 IDE 会帮助检测和提示,我们来看下面这段 Java 代码:

☕️
@NonNull
View view = null;
// 👆IDE 会提示警告,‘null’ is assigned to a variable that is annotated with @NotNull

而到了 Kotlin 这里,就有了语言级别的默认支持,而且提示的级别从 warning 变成了 error(拒绝编译):

🏝️
var view: View = null
// 👆IDE 会提示错误,Null can not be a value of a non-null type View

在 Kotlin 里面,所有的变量默认都是不允许为空的,如果你给它赋值 null,就会报错,像上面那样。

这种有点强硬的要求,其实是很合理的:既然你声明了一个变量,就是要使用它对吧?那你把它赋值为 null 干嘛?要尽量让它有可用的值啊。Java 在这方面很宽松,我们成了习惯,但 Kotlin 更强的限制其实在你熟悉了之后,是会减少很多运行时的问题的。

不过,还是有些场景,变量的值真的无法保证空与否,比如你要从服务器取一个 JSON 数据,并把它解析成一个 User 对象:

🏝️
class User {
var name: String = null // 👈这样写会报错,但该变量无法保证空与否
}

这个时候,空值就是有意义的。对于这些可以为空值的变量,你可以在类型右边加一个 ? 号,解除它的非空限制:

🏝️
class User {
var name: String? = null
}

加了问号之后,一个 Kotlin 变量就像 Java 变量一样没有非空的限制,自由自在了。

你除了在初始化的时候可以给它赋值为空值,在代码里的任何地方也都可以:

🏝️
var name: String? = “Mike”

name = null // 👈原来不是空值,赋值为空值

这种类型之后加 ? 的写法,在 Kotlin 里叫可空类型

不过,当我们使用了可空类型的变量后,会有新的问题:

由于对空引用的调用会导致空指针异常,所以 Kotlin 在可空变量直接调用的时候 IDE 会报错:

🏝️
var view: View? = null
view.setBackgroundColor(Color.RED)
// 👆这样写会报错,Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type View?

「可能为空」的变量,Kotlin 不允许用。那怎么办?我们尝试用之前检查一下,但似乎 IDE 不接受这种做法:

🏝️
if (view != null) {
view.setBackgroundColor(Color.RED)
// 👆这样写会报错,Smart cast to ‘View’ is impossible, because ‘view’ is a mutable property that could have been changed by this time
}

这个报错的意思是即使你检查了非空也不能保证下面调用的时候就是非空,因为在多线程情况下,其他线程可能把它再改成空的。

那么 Kotlin 里是这么解决这个问题的呢?它用的不是 . 而是 ?.

🏝️
view?.setBackgroundColor(Color.RED)

这个写法同样会对变量做一次非空确认之后再调用方法,这是 Kotlin 的写法,并且它可以做到线程安全,因此这种写法叫做「safe call」。

另外还有一种双感叹号的用法:

🏝️
view!!.setBackgroundColor(Color.RED)

意思是告诉编译器,我保证这里的 view 一定是非空的,编译器你不要帮我做检查了,有什么后果我自己承担。这种「肯定不会为空」的断言式的调用叫做 「non-null asserted call」。一旦用了非空断言,实际上和 Java 就没什么两样了,但也就享受不到 Kotlin 的空安全设计带来的好处(在编译时做检查,而不是运行时抛异常)了。

以上就是 Kotlin 的空安全设计。

理解了它之后再来看变量声明,跟 Java 虽然完全不一样,只是写法上不同而已。

很多人在上手的时候都被变量声明搞懵,原因就是 Kotlin 的空安全设计所导致的这些报错:

  • 变量需要手动初始化,如果不初始化的话会报错;
  • 变量默认非空,所以初始化赋值 null 的话报错,之后再次赋值为 null 也会报错;
  • 变量用 ? 设置为可空的时候,使用的时候因为「可能为空」又报错。

明白了空安全设计的原理后,就很容易能够解决上面的问题了。

关于空安全,最重要的是记住一点:所谓「可空不可空」,关注的全都是使用的时候,即「这个变量在使用时是否可能为空」。

另外,Kotlin 的这种空安全设计在与 Java 的互相调用上是完全兼容的,这里的兼容指:

  • Java 里面的 @Nullable 注解,在 Kotlin 里调用时同样需要使用 ?.

☕️
@Nullable
String name;

🏝️
name?.length

  • Java 里面的 @Nullable 和 @NonNull 注解,在转换成 Kotlin 后对应的就是可空变量和不可空变量,至于怎么将 Java 代码转换为 Kotlin,Android Studio 给我们提供了很方便的工具(但并不完美),后面会讲。

☕️
@Nullable
String name;
@NonNull
String value = “hello”;

🏝️
var name: String? = null
var value: String = “hello”

空安全我们讲了这么多,但是有些时候我们声明一个变量是不会让它为空的,比如 view,其实在实际场景中我们希望它一直是非空的,可空并没有业务上的实际意义,使用 ?. 影响代码可读性。

但如果你在 MainActivity 里这么写:

🏝️
class MainActivity : AppCompatActivity() {
👇
var view: View = findViewById(R.id.tvContent)
}

虽然编译器不会报错,但程序一旦运行起来就 crash 了,原因是 findViewById() 是在 onCreate 之后才能调用。

那怎么办呢?其实我们很想告诉编译器「我很确定我用的时候绝对不为空,但第一时间我没法给它赋值」。

Kotlin 给我们提供了一个选项:延迟初始化。

延迟初始化

具体是这么写的:

🏝️
lateinit var view: View

这个 lateinit 的意思是:告诉编译器我没法第一时间就初始化,但我肯定会在使用它之前完成初始化的。

它的作用就是让 IDE 不要对这个变量检查初始化和报错。换句话说,加了这个 lateinit 关键字,这个变量的初始化就全靠你自己了,编译器不帮你检查了。

然后我们就可以在 onCreate 中进行初始化了:

🏝️
👇
lateinit var view: View
override fun onCreate(…) {

👇
view = findViewById(R.id.tvContent)
}

哦对了,延迟初始化对变量的赋值次数没有限制,你仍然可以在初始化之后再赋其他的值给 view

类型推断

Kotlin 有个很方便的地方是,如果你在声明的时候就赋值,那不写变量类型也行:

🏝️
var name: String = “Mike”
👇
var name = “Mike”

这个特性叫做「类型推断」,它跟动态类型是不一样的,我们不能像使用 Groovy 或者 JavaScript 那样使用在 Kotlin 里这么写:

🏝️
var name = “Mike”
name = 1
// 👆会报错,The integer literal does not conform to the expected type String

// Groovy
def a = “haha”
a = 1
// 👆这种先赋值字符串再赋值数字的方式在 Groovy 里是可以的

「动态类型」是指变量的类型在运行时可以改变;而「类型推断」是你在代码里不用写变量类型,编译器在编译的时候会帮你补上。因此,Kotlin 是一门静态语言。

除了变量赋值这个场景,类型推断的其他场景我们之后也会遇到。

val 和 var

声明变量的方式也不止 var 一种,我们还可以使用 val:

🏝️
val size = 18

val 是 Kotlin 在 Java 的「变量」类型之外,又增加的一种变量类型:只读变量。它只能赋值一次,不能修改。而 var 是一种可读可写变量。

var 是 variable 的缩写,val 是 value 的缩写。

val 和 Java 中的 final 类似:

☕️
final int size = 18;

不过其实它们还是有些不一样的,这个我们之后再讲。总之直接进行重新赋值是不行的。

可见性

看到这里,我们似乎都没有在 Kotlin 里看到类似 Java 里的 public、protected、private 这些表示变量可见性的修饰符,因为在 Kotlin 里变量默认就是 public 的,而对于其他可见性修饰符,我们之后会讲,这里先不用关心。

至此,我相信你对变量这部分已经了解得差不多了,可以根据前面的例子动手尝试尝试。

函数

Kotlin 除了变量声明外,函数的声明方式也和 Java 的方法不一样。Java 的方法(method)在 Kotlin 里叫函数(function),其实没啥区别,或者说其中的区别我们可以忽略掉。对任何编程语言来讲,变量就是用来存储数据,而函数就是用来处理数据。

函数的声明

我们先来看看 Java 里的方法是怎么写的:

☕️
Food cook(String name) {

}

而到了 Kotlin,函数的声明是这样:

🏝️
👇 👇
fun cook(name: String): Food {

}

  • 以 fun 关键字开头
  • 返回值写在了函数和参数后面

那如果没有返回值该怎么办?Java 里是返回 void:

☕️
void main() {

}

Kotlin 里是返回 Unit,并且可以省略:

🏝️
👇
fun main(): Unit {}
// Unit 返回类型可以省略
fun main() {}

函数参数也可以有可空的控制,根据前面说的空安全设计,在传递时需要注意:

🏝️
// 👇可空变量传给不可空参数,报错
var myName : String? = “rengwuxian”
fun cook(name: String) : Food {}
cook(myName)

// 👇可空变量传给可空参数,正常运行
var myName : String? = “rengwuxian”
fun cook(name: String?) : Food {}
cook(myName)

// 👇不可空变量传给不可空参数,正常运行
var myName : String = “rengwuxian”
fun cook(name: String) : Food {}
cook(myName)

可见性

函数如果不加可见性修饰符的话,默认的可见范围和变量一样也是 public 的,但有一种情况例外,这里简单提一下,就是遇到了 override 关键字的时候,下面会讲到。

属性的 getter/setter 函数

我们知道,在 Java 里面的 field 经常会带有 getter/setter 函数:

☕️
public class User {
String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

它们的作用就是可以自定义函数内部实现来达到「钩子」的效果,比如下面这种:

☕️
public class User {
String name;
public String getName() {
return this.name + " nb";
}
public void setName(String name) {
this.name = "Cute " + name;
}
}

在 Kotlin 里,这种 getter / setter 是怎么运作的呢?

🏝️
class User {
var name = “Mike”
fun run() {
name = “Mary”
// 👆的写法实际上是👇这么调用的
// setName(“Mary”)
// 建议自己试试,IDE 的代码补全功能会在你打出 setn 的时候直接提示 name 而不是 setName

println(name)
// 👆的写法实际上是👇这么调用的
// print(getName())
// IDE 的代码补全功能会在你打出 getn 的时候直接提示 name 而不是 getName
}
}

那么我们如何来操作前面提到的「钩子」呢?看下面这段代码:

🏝️
class User {
var name = “Mike”
👇
get() {
return field + " nb"
}
👇 👇
set(value) {
field = "Cute " + value
}
}

格式上和 Java 有一些区别:

  • getter / setter 函数有了专门的关键字 get 和 set
  • getter / setter 函数位于 var 所声明的变量下面
  • setter 函数参数是 value

除此之外还多了一个叫 field 的东西。这个东西叫做「Backing Field」,中文翻译是幕后字段后备字段(马云背后的女人😝)。具体来说,你的这个代码:

🏝️
class Kotlin {
var name = “kaixue.io”
}

在编译后的字节码大致等价于这样的 Java 代码:

☕️
public final class Kotlin {
@NotNull
private String name = “kaixue.io”;

@NotNull
public final String getName() {
return this.name;
}

public final void setName(@NotNull String name) {
this.name = name;
}
}

上面的那个 String name 就是 Kotlin 帮我们自动创建的一个 Java field。这个 field 对编码的人不可见,但会自动应用于 getter 和 setter,因此它被命名为「Backing Field」(backing 的意思是在背后进行支持,例如你闯了大祸,我动用能量来保住你的人头,我就是在 back you)。

所以,虽然 Kotlin 的这个 field 本质上确实是一个 Java 中的 field,但对于 Kotlin 的语法来讲,它和 Java 里面的 field 完全不是一个概念。在 Kotlin 里,它相当于每一个 var 内部的一个变量。

我们前面讲过 val 是只读变量,只读的意思就是说 val 声明的变量不能进行重新赋值,也就是说不能调用 setter 函数,因此,val 声明的变量是不能重写 setter 函数的,但它可以重写 getter 函数:

🏝️
val name = “Mike”
get() {
return field + " nb"
}

val 所声明的只读变量,在取值的时候仍然可能被修改,这也是和 Java 里的 final 的不同之处。

关于「钩子」的作用,除了修改取值和赋值,也可以加一些自己的逻辑,就像我们在 Activity 的生命周期函数里做的事情一样。

类型

讲完了变量和函数,接下来我们可以系统性地学习下 Kotlin 里的类型。

基本类型

在 Kotlin 中,所有东西都是对象,Kotlin 中使用的基本类型有:数字、字符、布尔值、数组与字符串。

🏝️
var number: Int = 1 // 👈还有 Double Float Long Short Byte 都类似
var c: Char = ‘c’
var b: Boolean = true
var array: IntArray = intArrayOf(1, 2) // 👈类似的还有 FloatArray DoubleArray CharArray 等,intArrayOf 是 Kotlin 的 built-in 函数
var str: String = “string”

这里有两个地方和 Java 不太一样:

  • Kotlin 里的 Int 和 Java 里的 int 以及 Integer 不同,主要是在装箱方面不同。

Java 里的 int 是 unbox 的,而 Integer 是 box 的:

☕️
int a = 1;
Integer b = 2; // 👈会被自动装箱 autoboxing

Kotlin 里,Int 是否装箱根据场合来定:

🏝️
var a: Int = 1 // unbox
var b: Int? = 2 // box
var list: List = listOf(1, 2) // box

Kotlin 在语言层面简化了 Java 中的 int 和 Integer,但是我们对是否装箱的场景还是要有一个概念,因为这个牵涉到程序运行时的性能开销。

因此在日常的使用中,对于 Int 这样的基本类型,尽量用不可空变量。

  • Java 中的数组和 Kotlin 中的数组的写法也有区别:

☕️
int[] array = new int[] {1, 2};

而在 Kotlin 里,上面的写法是这样的:

🏝️
var array: IntArray = intArrayOf(1, 2)
// 👆这种也是 unbox 的

简单来说,原先在 Java 里的基本类型,类比到 Kotlin 里面,条件满足如下之一就不装箱:

  • 不可空类型。

  • 使用 IntArray、FloatArray 等。

类和对象

现在可以来看看我们的老朋友 MainActivity 了,重新认识下它:

🏝️
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

}
}

我们可以对比 Java 的代码来看有哪些不同:

☕️
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {

}
}

  • 首先是类的可见性,Java 中的 public 在 Kotlin 中可以省略,Kotlin 的类默认是 public 的。

  • 类的继承的写法,Java 里用的是 extends,而在 Kotlin 里使用 :,但其实 : 不仅可以表示继承,还可以表示 Java 中的 implement

举个例子,假设我们有一个 interface 叫 Imple:

🏝️
interface Impl {}

Kotlin 里定义一个 interface 和 Java 没什么区别。

☕️
public class Main2Activity extends AppCompatActivity implements Impl { }

🏝️
class MainActivity : AppCompatActivity(), Impl {}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

3496749493)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

[外链图片转存中…(img-UmcGgj8w-1713496749494)]

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值