本期做者:java
视频:扔物线(朱凯)android
文章:hamber(罗琼)面试
你们好,我是扔物线,我唠叨两句就滚。编程
欢迎你们来到码上开学 Kotlin 系列上手教程。你们久等了,其实我也早就被大家催得不想活了,奈何我事情太多啊。好比我要旅游吧?我要陪老婆吧?我要陪孩子吧?我要打孩子吧?我要打老婆吧?并且你们知道,我如今开了在线的 Android 进阶课程,我得花大量时间精力在课程上面吧?否则客户爸爸一个差评那我就要被团队砍死啊。数组
不过无论怎么样,码上开学终于开始生产了,并且咱们是备了一些存货的哟。废话很少说,视频伺候!安全
若是你看不到上面的哔哩哔哩视频,能够点击 这里 去哔哩哔哩或者 这里 去 YouTube 看。服务器
如下内容来自文章做者 hamber。多线程
在 Google I/O 2019 上,Google 宣布 Kotlin 成为 Android 的第一开发语言。这对于开发者来说意味着,未来全部的官方示例会首选 Kotlin,而且 Google 对 Kotlin 在开发、构建等各个方面的支持也会更优先。app
在这个大环境下,Kotlin 已经做为不少公司的移动开发岗面试的考察点之一,甚至做为 HR 简历筛选的必要条件。所以,学会并掌握 Kotlin 成了 Android 开发者的当务之急。编程语言
「Kotlin 真的有那么好吗」「到底要不要学 Kotlin」这样的问题很快就要过期了,码上开学这个项目的目的并不在于向各位安利 Kotlin,而在于怎样让但愿学习 Kotlin 的人最快速地上手。
咱们的目的很是明确:这是一份给 Android 工程师的 Kotlin 上手指南。其中:
学习 Kotlin 的第一步就是要为项目添加 Kotlin 语言的支持,这很是简单。
若是你要新建一个支持 Kotlin 的 Android 项目,只须要以下操做:
别的都和建立一个普通的 Android 项目同样,建立出的项目就会是基于 Kotlin 的了。
所谓「基于 Kotlin」,意思有两点:
IDE 帮你自动建立出的 MainActivity
是用 Kotlin 写的:
package org.kotlinmaster
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
...
}
复制代码
扫一眼就好,不用读代码,咱们后面都会讲。
项目中的 2 个 bulid.gradle
文件比 Java 的 Android 项目多了几行代码(以「👇」标注),它们的做用是添加 Kotlin 的依赖:
项目根目录下的 build.gradle
:
buildscript {
👇
ext.kotlin_version = '1.3.41'
repositories {
...
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0-beta05'
👇
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
复制代码
app 目录下的 build.gradle
:
apply plugin: 'com.android.application'
👇
apply plugin: 'kotlin-android'
...
android {
...
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
👇
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
...
}
复制代码
也就是说,若是你是要建立一个新项目,记得把语言选择为 Kotlin,项目建立完成后你就能够用 Kotlin 来写它了。
若是是现有的项目要支持 Kotlin,只须要像上面这样操做,把两个 build.gradle
中标注的代码对应贴到你的项目里就能够了。
笔者建议刚开始学习的时候仍是新建一个基于 Kotlin 的项目,按照上面的步骤练习一下。
前面咱们提到,若是新建的项目是基于 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 里是没见过的。
为了暂时避开这些干扰,咱们本身新建一个文件。
建立完成后的 Sample.kt
:
package org.kotlinmaster
class Sample {}
复制代码
这个类仅包含 package
和 class
两个关键字,咱们暂时先当作和 Java 差很少(其实真的就是差很少)的概念,这样就都是咱们熟悉的东西了。
接下来,让咱们开始学习基础语法吧。
这里讲一个 Java 和 Kotlin 命名由来的小插曲。
咱们知道 Java 就是著名的爪哇岛,爪哇岛盛产咖啡,听说就是一群研究出 Java 语言的牛人们在为它命名时因为闻到香浓的咖啡味,遂决定采用此名称。
Kotlin 来源于芬兰湾中的 Kotlin 岛。
所以,咱们在代码段的开头以「☕️」来表示 Java 代码段,「🏝️」来表示 Kotlin 代码段。
咱们回忆下 Java 里声明一个 View 类型的变量的写法:
☕️
View v;
复制代码
Kotlin 里声明一个变量的格式是这样的:
🏝️
var v: View
复制代码
这里有几处不一样:
var
关键字看上去只是语法格式有些不一样,但若是真这么写,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 的空安全设计。
简单来讲就是经过 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 的空安全设计所致使的这些报错:
?
设置为可空的时候,使用的时候由于「可能为空」又报错。明白了空安全设计的原理后,就很容易可以解决上面的问题了。
关于空安全,最重要的是记住一点:所谓「可空不可空」,关注的全都是使用的时候,即「这个变量在使用时是否可能为空」。
另外,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 是一门静态语言。
除了变量赋值这个场景,类型推断的其余场景咱们以后也会遇到。
声明变量的方式也不止 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 {
...
}
复制代码
那若是没有返回值该怎么办?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
关键字的时候,下面会讲到。
咱们知道,在 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 有一些区别:
除此以外还多了一个叫 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<Int> = 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 {}
复制代码
构造方法的写法不一样。
Java 里省略了默认的构造函数:
☕️
public class MainActivity extends AppCompatActivity {
// 👇默认构造函数
public MainActivity() {
}
}
复制代码
Kotlin 里咱们注意到 AppCompatActivity 后面的 ()
,这其实也是一种省略的写法,等价于:
🏝️
class MainActivity constructor() : AppCompatActivity() {
👆
}
复制代码
不过其实更像 Java 的写法是这样的:
🏝️
// 👇注意这里 AppCompatActivity 后面没有 '()'
class MainActivity : AppCompatActivity {
constructor() {
}
}
复制代码
Kotlin 把构造函数单独用了一个 constructor
关键字来和其余的 fun
作区分。
override 的不一样
@Override
是注解的形式。override
变成了关键字。protected
关键字,也就是说,Kotlin 里的 override
函数的可见性是继承自父类的。除了以上这些明显的不一样以外,还有一些不一样点从上面的代码里看不出来,但当你写一个类去继承 MainActivity
时就会发现:
Kotlin 里的 MainActivity 没法继承:
🏝️
// 👇写法会报错,This type is final, so it cannot be inherited from
class NewActivity: MainActivity() {
}
复制代码
缘由是 Kotlin 里的类默认是 final 的,而 Java 里只有加了 final
关键字的类才是 final 的。
那么有什么办法解除 final 限制么?咱们可使用 open
来作这件事:
🏝️
open class MainActivity : AppCompatActivity() {}
复制代码
这样一来,咱们就能够继承了。
🏝️
class NewActivity: MainActivity() {}
复制代码
可是要注意,此时 NewActivity 仍然是 final 的,也就是说,open
没有父类到子类的遗传性。
而刚才说到的 override
是有遗传性的:
🏝️
class NewActivity : MainActivity() {
// 👇onCreate 仍然是 override 的
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
复制代码
若是要关闭 override
的遗传性,只须要这样便可:
🏝️
open class MainActivity : AppCompatActivity() {
// 👇加了 final 关键字,做用和 Java 里面同样,关闭了 override 的遗传性
final override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
复制代码
Kotlin 里除了新增了 open
关键字以外,也有和 Java 同样的 abstract
关键字,这俩关键字的区别就是 abstract
关键字修饰的类没法直接实例化,而且一般来讲会和 abstract
修饰的函数一块儿出现,固然,也能够没有这个 abstract
函数。
🏝️
abstract class MainActivity : AppCompatActivity() {
abstract fun test()
}
复制代码
可是子类若是要实例化,仍是须要实现这个 abstract 函数的:
🏝️
class NewActivity : MainActivity() {
override fun test() {}
}
复制代码
当咱们声明好一个类以后,咱们就能够实例化它了,实例化在 Java 中使用 new
关键字:
☕️
void main() {
Activity activity = new NewActivity();
}
复制代码
而在 Kotlin 中,实例化一个对象更加简单,没有 new
关键字:
🏝️
fun main() {
var activity: Activity = NewActivity()
}
复制代码
经过 MainActivity
的学习,咱们知道了 Java 和 Kotlin 中关于类的声明主要关注如下几个方面:
刚才讲的实例化的例子中,咱们其实是把子类对象赋值给父类的变量,这个概念在 Java 里叫多态,Kotlin 也有这个特性,但在实际工做中咱们极可能会遇到须要使用子类才有的函数。
好比咱们先在子类中定义一个函数:
🏝️
class NewActivity : MainActivity() {
fun action() {}
}
复制代码
那么接下来这么写是没法调用该函数的:
🏝️
fun main() {
var activity: Activity = NewActivity()
// 👆activity 是没法调用 NewActivity 的 action 方法的
}
复制代码
在 Java 里,须要先使用 instanceof
关键字判断类型,再经过强转来调用:
☕️
void main() {
Activity activity = new NewActivity();
if (activity instanceof NewActivity) {
((NewActivity) activity).action();
}
}
复制代码
Kotlin 里一样有相似解决方案,使用 is
关键字进行「类型判断」,而且由于编译器可以进行类型推断,能够帮助咱们省略强转的写法:
🏝️
fun main() {
var activity: Activity = NewActivity()
if (activity is NewActivity) {
// 👇的强转因为类型推断被省略了
activity.action()
}
}
复制代码
那么能不能不进行类型判断,直接进行强转调用呢?可使用 as
关键字:
🏝️
fun main() {
var activity: Activity = NewActivity()
(activity as NewActivity).action()
}
复制代码
这种写法若是强转类型操做是正确的固然没问题,但若是强转成一个错误的类型,程序就会抛出一个异常。
咱们更但愿能进行安全的强转,能够更优雅地处理强转出错的状况。
这一点,Kotlin 在设计上天然也考虑到了,咱们可使用 as?
来解决:
🏝️
fun main() {
var activity: Activity = NewActivity()
// 👇'(activity as? NewActivity)' 以后是一个可空类型的对象,因此,须要使用 '?.' 来调用
(activity as? NewActivity)?.action()
}
复制代码
它的意思就是说若是强转成功就执行以后的调用,若是强转不成功就不执行。
好了,关于 Kotlin 的变量、函数和类型的内容就讲到这里,给你留 2 道思考题吧:
子类重写父类的 override
函数,可否修改它的可见性?
如下的写法有什么区别?
🏝️
activity as? NewActivity
activity as NewActivity?
activity as? NewActivity?
复制代码
View
),在 onCreate 函数中初始化它。View?
类型的方法,传入刚才的 View
类型属性,并在该方法中打印出该 View?
的 id。