likes
comments
collection
share

01 Kotlin 的变量、函数和类型

作者站长头像
站长
· 阅读数 17

为项目添加 Kotlin 语言的支持

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

  • 项目根目录下的 build.gradle01 Kotlin 的变量、函数和类型

  • app 目录下的 build.gradle

01 Kotlin 的变量、函数和类型

Kotlin定义变量

  • var: variable的缩写,声明变量的修饰符

var view: View

  • val: value的缩写,声明只读变量的修饰符,只能复制一次,不能修改

val a: Int=10

Kotlin类型推断

如果声明时直接赋值,则可以不写变量类型。例如:val a=10

注意:类型推断与动态类型不同,如下所示。

  1. 动态类型:变量类型在运行时可改变。
  2. 类型推断:代码中不用写变量类型,编译器会帮忙补上。

因此,Kotlin 是一门静态语言。

01 Kotlin 的变量、函数和类型

Kotlin 空安全设计

空安全设计的目的:为了防止调用空对象,出现NullPointerException错误 Kotlin中所有变量不允许为空

lateinit

作用:让IDE不要对这个变量进行检查和报错 适用场景:我很确定这个变量到使用的时候不为空,但是在声明的时候暂时还不能给它赋值

lateinit var view: View

可空类型:类型后加 ? 作用:解除变量的非空限制

var name:String? = null

注意:"?"包含了非空检查.

Java中使用可能为空的变量时,我们一般会通过写if(name==null)来检查变量是否为空。但是if(name==null)检查不一定能保证name不为空,因为在多线程的情况下,别的线程可能会在检查非空后,将name的值变为null。

Kotlin中使用可能为空的变量的做法:(线程安全)

class User{
    var name:String? = null
}
...
 println(name?.length) //会对变量做一次非空确认后再调用方法
 println(name!!.length) //也可以用双感叹号,写法不同而已

总结:报错情况

  • 变量需要手动初始化,所以不初始化会报错
  • 变量默认非空,所以初始化时赋值为null也报错
  • 用?设置的可空变量使用时报错

注意:讨论可空/不可空,都是针对变量在使用时的情况

Kotlin的函数声明

  • 以 fun 关键字开头
  • 返回值写在了函数和参数后面
fun cook(name: String): Food {
    ...
}

如果没有返回值:

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)

Kotlin中的属性的 getter/setter 函数

实例:在如下例子中,调用 hello.name 方法 , 实际上调用的是 hello.setName 方法

class Hello {
    var name = "Tom"
    var age = 18
}

fun main() {
    var hello = Hello()
    hello.name = "Jack"
}

幕后字段

在Kotlin中属性作为一级语言特性,通常情况下集幕后字段(field储值变量)+ 访问器(getter读访问器、setter写访问器)于一身,无论是声明【var age: Int】、赋值【user.age = 18】、取值【println(user.age)】,从字面上看都是 age 这个属性本身,是一个整体。而只有在我们需要自定义访问器的时候才会区分这三者。

例如自定义 setter 的时候,如果不写成幕后字段 field = value,不管是 age = value 还是 this.age = value 都会报错,因为属性 age 的赋值就是setter,显然不能递归调用。

如果属性的访问器至少有一个使用默认实现,或者自定义的访问器中使用了 field ,那么就会提供幕后字段,用 field 关键字表示,主要用于自定义 getter/setter 时使用,也只能在 getter/setter 中访问。

实例:

class Demo{
    var id: Long = 0
        get() = field
        set(value) { field = value }
}

个人理解:field是一个中转变量

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 里面,条件满足如下之一就不装箱:

  • 不可空类型。
  • 使用 IntArray、FloatArray 等。

Kotlin中的类与对象

Kotlin写法和Java写法的不同

//Kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
    }
}
//Java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
    }
}

写法对比

对比点JavaKotlin
可见性需要写明默认为public,可以省略
类的继承的写法用extends:
构造方法的写法public MainActivity() { }(可省略)单独用了一个 constructor 关键字来和其他的 fun 做区分:class MainActivity constructor() : AppCompatActivity() { }
override@Override 是注解的形式override 变成了关键字,省略了 protected 关键字,也就是说,Kotlin 里的 override 函数的可见性是继承自父类的
类的继承的写法MainActivity 可继承,因为Java 里只有加了 final 关键字的类才是 final 的。MainActivity 无法继承(无法作为父类),因为Kotlin 里的类默认是 final 的
实例化一个类Activity activity = new NewActivity();var activity: Activity = NewActivity()

Kotlin的open 关键字

Kotlin的类默认不可继承,所以要想让Kotlin里的MainActivity可以继承:使用open关键字

open class MainActivity : AppCompatActivity() {}

但是要注意,此时 NewActivity 仍然是 final 的,也就是说,open 没有父类到子类的遗传性。 override 是有遗传性的,如果要关闭override 的遗传性,只需要这样即可:

open class MainActivity : AppCompatActivity() {
    // 👇加了 final 关键字,作用和 Java 里面一样,关闭了 override 的遗传性
    final override fun onCreate(savedInstanceState: Bundle?) {
        ...
    }
}

Kotlin的abstract 关键字

Kotlin中abstract 关键字修饰的类无法直接实例化,并且通常来说会和 abstract 修饰的函数一起出现:

abstract class MainActivity : AppCompatActivity() {
    abstract fun test()
}

但是子类如果要实例化,还是需要实现这个 abstract 函数的:

class NewActivity : MainActivity() {
    override fun test() {}
}

Kotlin中的类型的判断和强转

Kotlin中使用 is 关键字进行「类型判断」,并且因为编译器能够进行类型推断,可以帮助我们省略强转的写法:

//Kotlin中的写法
fun main() {
    var activity: Activity = NewActivity()
    if (activity is NewActivity) {
        // 👇的强转由于类型推断被省略了
        activity.action()
    }
}
//相当于Java中这么写
void main() {
    Activity activity = new NewActivity();
    if (activity instanceof NewActivity) {
        ((NewActivity) activity).action();
    }
}

Kotlin中类的强转调用:使用 as 关键字

fun main() {
    var activity: Activity = NewActivity()
    (activity as NewActivity).action()
}

更安全的强转写法:使用 as?,可以更优雅地处理强转出错的情况。

fun main() {
    var activity: Activity = NewActivity()
    // 👇'(activity as? NewActivity)' 之后是一个可空类型的对象,所以,需要使用 '?.' 来调用
    (activity as? NewActivity)?.action()
}