本文已收录至学习笔记大全:JavaKotlinAndroidGuidejava
做者:leavesCgit
[TOC]github
按照国际惯例,学习一门新的语言一般都是从 Hello World 开始的,在这里也不例外编程
package main
fun main(args: Array<String>) {
println("Hello World")
}
复制代码
从这个简单的函数就能够列出 kotlin 和 Java 的几点不一样数组
println
代替了 System.out.println
,这是 kotlin 标准库提供的对 Java 标准库函数的封装此外,kotlin 的最新版本已经能够省略 main
方法的参数了安全
kotlin 文件都以一条 package 语句开头,文件中定义的全部声明(类、函数和属性)都会被放到这个包中。若是其余文件中定义的声明也有相同的包,这个文件能够直接使用它们,若是包不相同则须要导入它们数据结构
包的声明应处于源文件顶部,import 语句紧随其后多线程
package base
import java.text.SimpleDateFormat
import java.util.*
复制代码
kotlin 不区分导入的是类仍是函数,容许使用 import
关键字导入任何种类的声明。此外,也能够在包名称后加上 .* 来导入特定包中定义的全部声明,这不只会让包中定义的类可见,也会让顶层函数和属性可见并发
package learn.package2
val index = 10
fun Test(status: Boolean) = println(status)
class Point(val x: Int, val y: Int) {
val isEquals1: Boolean
get() {
return x == y
}
val isEquals2
get() = x == y
var isEquals3 = false
get() = x > y
set(value) {
field = !value
}
}
复制代码
package learn.package1
import learn.package2.Point
import learn.package2.Test
import learn.package2.index
fun main() {
val point = Point(10, index)
Test(true)
}
复制代码
Java 语言规定类要放到和包结构匹配的文件夹目录结构中,而 kotlin 容许把多个类放到同一个文件中,文件名也能够任意选择。kotlin 也没有对磁盘上源文件的布局强加任何限制,包层级结构不须要遵循目录层级结构 ,但最好仍是遵循 Java 的目录布局并根据包结构把源码文件放到相应的目录中框架
若是包名出现命名冲突,可使用 as 关键字在本地重命名冲突项来消歧义
import learn.package1.Point
import learn.package2.Point as PointTemp //PointTemp 能够用来表示 learn.package2.Point 了
复制代码
kotlin 中也有一个相似的概念能够用来重命名现有类型,叫作类型别名。类型别名用于为现有类型提供一个替代名称,若是类型名称比较长,就能够经过引入一个较短或者更为简略的名称来方便记忆
类型别名不会引入新的类型,依然相应其底层类型,因此在下述代码中输出的 class 类型是一致的
class Base {
class InnerClass
}
typealias BaseInner = Base.InnerClass
fun main() {
val baseInner1 = Base.InnerClass()
val baseInner2 = BaseInner()
println(baseInner1.javaClass) //class test2.Base$InnerClass
println(baseInner2.javaClass) //class test2.Base$InnerClass
}
复制代码
在 Java 中,大部分的变量是可变的(非 final 的),意味着任何能够访问到这个变量的代码均可以去修改它。而在 kotlin 中,变量能够分为 可变变量(var) 和 不可变变量(val) 两类
声明变量的关键字有两个:
不可变变量在赋值以后就不能再去改变它的状态了,所以不可变变量能够说是线程安全的,由于它们没法改变,全部线程访问到的对象都是同一个,所以也不须要去作访问控制。开发者应当尽量地使用不可变变量,这样可让代码更加接近函数式编程风格
编程领域中也推崇一种开发原则:尽量使用 val,不可变对象及纯函数来设计程序。这样能够尽可能避免反作用带来的影响
fun main() {
//只读变量即赋值后不能够改变值的变量,用 val 声明
//声明一个整数类型的不可变变量
val intValue: Int = 100
//声明一个双精度类型的可变变量
var doubleValue: Double = 100.0
}
复制代码
在声明变量时咱们一般不须要显式指明变量的类型,这能够由编译器根据上下文自动推导出来。若是只读变量在声明时没有初始值,则必须指明变量类型,且在使用前必须确保在各个分支条件下变量能够被初始化,不然编译期会报异常
fun main() {
val intValue = 1002222222222
val doubleValue = 100.0
val longValue = 100L
//若是只读变量在声明时没有初始值,则必须指明变量类型
val intValue2: Int
if (false) {
intValue2 = 10
}
println(intValue2) //error, Variable 'intValue2' must be initialized
}
复制代码
与 Java 不一样,kotlin 并不区分基本数据类型和它的包装类,在 kotlin 中一切都是对象,能够在任何变量上调用其成员函数和属性。kotlin 没有像 Java 中那样的原始基本类型,但 byte、char、integer、float 或者 boolean 等类型仍然有保留,可是所有都做为对象存在
对于基本类型,kotlin 相比 Java 有几点特殊的地方
//在 kotlin 中,int、long、float 等类型仍然存在,可是是做为对象存在的
val intIndex: Int = 100
//等价于,编译器会自动进行类型推导
val intIndex = 100
//数字类型不会自动转型,必需要进行明确的类型转换
val doubleIndex: Double = intIndex.toDouble()
//如下代码会提示错误,须要进行明确的类型转换
//val doubleIndex: Double = intIndex
val intValue: Int = 1
val longValue: Long = 1
//如下代码会提示错误,由于二者的数据类型不一致,须要转换为同一类型后才能进行比较
//println(intValue == longValue)
//Char 不能直接做为数字来处理,须要主动转换
val ch: Char = 'c'
val charValue: Int = ch.toInt()
//如下代码会提示错误
//val charValue: Int = ch
//二进制
val value1 = 0b00101
//十六进制
val value2 = 0x123
复制代码
此外,kotlin 的可空类型不能用 Java 的基本数据类型表示,由于 null 只能被存储在 Java 的引用类型的变量中,这意味着只要使用了基本数据类型的可空版本,它就会被编译成对应的包装类型
//基本数据类型
val intValue_1: Int = 200
//包装类
val intValue_2: Int? = intValue_1
val intValue_3: Int? = intValue_1
//== 比较的是数值相等性,所以结果是 true
println(intValue_2 == intValue_3)
//=== 比较的是引用是否相等,所以结果是 false
println(intValue_2 === intValue_3)
复制代码
若是 intValue_1 的值是100,就会发现 intValue_2 === intValue_3 的比较结果是 true,这就涉及到 Java 对包装类对象的重复使用问题了
kotlin 与 Java 同样用 String 类型来表示字符串,字符串是不可变的,可使用索引运算符访问[]
来访问包含的单个字符,也能够用 for 循环来迭代字符串,此外也能够用 + 来链接字符串
val str = "leavesC"
println(str[1])
for (c in str) {
println(c)
}
val str1 = str + " hello"
复制代码
kotlin 支持在字符串字面值中引用局部变量,只须要在变量名前加上字符 $ 便可,此外还能够包含用花括号括起来的表达式,此时会自动求值并把结果合并到字符串中
val intValue = 100
//能够直接包含变量
println("intValue value is $intValue") //intValue value is 100
//也能够包含表达式
println("(intValue + 100) value is ${intValue + 100}") //(intValue + 100) value is 200
复制代码
若是你须要在原始字符串中表示字面值($)字符(它不支持反斜杠转义),能够用下列语法:
val price = "${'$'}100.99"
println(price) //$100.99
复制代码
kotlin 中的数组是带有类型参数的类,其元素类型被指定为相应的类型参数,使用 Array 类来表示, Array 类定义了 get 与 set 函数(按照运算符重载约定这会转变为 [ ] )以及 size 属性等
建立数组的方法有如下几种:
//包含给定元素的字符串数组
val array1 = arrayOf("leavesC", "叶", "https://github.com/leavesC")
array1[0] = "leavesC"
println(array1[1])
println(array1.size)
//初始元素均为 null ,大小为 10 的字符数组
val array2 = arrayOfNulls<String>(10)
//建立从 “a” 到 “z” 的字符串数组
val array3 = Array(26) { i -> ('a' + i).toString() }
复制代码
须要注意的是,数组类型的类型参数始终会变成对象类型,所以声明 Array< Int > 将是一个包含装箱类型(java.lang.Integer)的数组。若是想要建立没有装箱的基本数据类型的数组,必须使用一个基本数据类型数组的特殊类
为了表示基本数据类型的数组,kotlin 为每一种基本数据类型都提供了若干相应的类并作了特殊的优化。例如,有 IntArray、ByteArray、BooleanArray 等类型,这些类型都会被编译成普通的 Java 基本数据类型数组,好比 int[]、byte[]、boolean[] 等,这些数组中的值存储时没有进行装箱,而是使用了可能的最高效的方式。须要注意,IntArray 等并非 Array 的子类
要建立一个基本数据类型的数组,有如下几种方式:
//指定数组大小,包含的元素将是对应基本数据类型的默认值(int 的默认值是 0)
val intArray = IntArray(5)
//指定数组大小以及用于初始化每一个元素的 lambda
val doubleArray = DoubleArray(5) { Random().nextDouble() }
//接收变长参数的值来建立存储这些值的数组
val charArray = charArrayOf('H', 'e', 'l', 'l', 'o')
复制代码
Any 类型是 kotlin 全部非空类型的超类型,包括像 Int 这样的基本数据类型
若是把基本数据类型的值赋给 Any 类型的变量,则会自动装箱
val any: Any = 100
println(any.javaClass) //class java.lang.Integer
复制代码
若是想要使变量能够存储包括 null 在内的全部可能的值,则须要使用 Any?
val any: Any? = null
复制代码
kotlin 中的 Unit 类型相似于 Java 中的 void,能够用于函数没有返回值时的状况
fun check(): Unit {
}
//若是返回值为 Unit,则能够省略该声明
fun check() {
}
复制代码
Unit 是一个完备的类型,能够做为类型参数,但 void 不行
interface Test<T> {
fun test(): T
}
class NoResultClass : Test<Unit> {
//返回 Unit,但能够省略类型说明,函数也不须要显式地 return
override fun test() {
}
}
复制代码
Nothing 类型没有任何值,只有被当作函数返回值使用,或者被当作泛型函数返回值的类型参数使用时才会有意义,能够用 Nothing 来表示一个函数不会被正常终止,从而帮助编译器对代码进行诊断
编译器知道返回值为 Nothing 类型的函数从不正常终止,因此编译器会把 name1 的类型推断为非空,由于 name1 在为 null 时的分支处理会始终抛出异常
data class User(val name: String?)
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun main() {
val user = User("leavesC")
val name = user.name ?: fail("no name")
println(name) //leavesC
val user1 = User(null)
val name1 = user1.name ?: fail("no name")
println(name1.length) //IllegalStateException
}
复制代码
kotlin 中的函数以关键字 fun 做为开头,函数名称紧随其后,再以后是用括号包裹起来的参数列表,若是函数有返回值,则再加上返回值类型,用一个冒号与参数列表隔开
//fun 用于表示声明一个函数,getNameLastChar 是函数名
//空括号表示该函数无传入参数,Char 表示函数的返回值类型是字符
fun getNameLastChar(): Char {
return name.get(name.length - 1)
}
复制代码
//带有两个不一样类型的参数,一个是 String 类型,一个是 Int 类型
//返回值为 Int 类型
fun test1(str: String, int: Int): Int {
return str.length + int
}
复制代码
此外,表达式函数体的返回值类型能够省略,返回值类型能够自动推断,这种用单行表达式与等号定义的函数叫作表达式函数体。但对于通常状况下的有返回值的代码块函数体,必须显式地写出返回类型和 return 语句
//getNameLastChar 函数的返回值类型以及 return 关键字是能够省略的
//返回值类型能够由编译器根据上下文进行推导
//所以,函数能够简写为如下形式
fun getNameLastChar() = name.get(name.length - 1)
复制代码
若是函数没有有意义的返回值,则能够声明为 Unit ,也能够省略 Unit
如下三种写法都是等价的
fun test(str: String, int: Int): Unit {
println(str.length + int)
}
fun test(str: String, int: Int) {
println(str.length + int)
}
fun test(str: String, int: Int) = println(str.length + int)
复制代码
为了加强代码的可读性,kotlin 容许咱们使用命名参数,即在调用某函数的时候,能够将函数参数名一块儿标明,从而明确地表达该参数的含义与做用,可是在指定了一个参数的名称后,以后的全部参数都须要标明名称
fun main() {
//错误,在指定了一个参数的名称后,以后的全部参数都须要标明名称
//compute(index = 110, "leavesC")
compute(index = 120, value = "leavesC")
compute(130, value = "leavesC")
}
fun compute(index: Int, value: String) {
}
复制代码
能够在声明函数的时候指定参数的默认值,从而避免建立重载的函数
fun main() {
compute(24)
compute(24, "leavesC")
}
fun compute(age: Int, name: String = "leavesC") {
}
复制代码
对于以上这个例子,若是按照常规的调用语法时,必须按照函数声明定义的参数顺序来给定参数,能够省略的只有排在末尾的参数
fun main() {
//错误,不能省略参数 name
// compute(24)
// compute(24,100)
//能够省略参数 value
compute("leavesC", 24)
}
fun compute(name: String = "leavesC", age: Int, value: Int = 100) {}
复制代码
若是使用命名参数,能够省略任何有默认值的参数,并且也能够按照任意顺序传入须要的参数
fun main() {
compute(age = 24)
compute(age = 24, name = "leavesC")
compute(age = 24, value = 90, name = "leavesC")
compute(value = 90, age = 24, name = "leavesC")
}
fun compute(name: String = "leavesC", age: Int, value: Int = 100) {
}
复制代码
可变参数可让咱们把任意个数的参数打包到数组中传给函数,kotlin 的语法相比 Java 有所不一样,改成经过使用 varage 关键字声明可变参数
例如,如下的几种函数调用方式都是正确的
fun main() {
compute()
compute("leavesC")
compute("leavesC", "叶应是叶")
compute("leavesC", "叶应是叶", "叶")
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
复制代码
在 Java 中,能够直接将数组传递给可变参数,而 kotlin 要求显式地解包数组,以便每一个数组元素在函数中可以做为单独的参数来调用,这个功能被称为展开运算符,使用方式就是在数组参数前加一个 *
fun main() {
val names = arrayOf("leavesC", "叶应是叶", "叶")
compute(* names)
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
复制代码
kotlin 支持在函数中嵌套函数,被嵌套的函数称为局部函数
fun main() {
compute("leavesC", "country")
}
fun compute(name: String, country: String) {
fun check(string: String) {
if (string.isEmpty()) {
throw IllegalArgumentException("参数错误")
}
}
check(name)
check(country)
}
复制代码
这里须要先区分“语句”和“表达式”这两个概念。语句是能够单独执行,可以产生实际效果的代码,表现为赋值逻辑、打印操做、流程控制等形式,Java 中的流程控制(if,while,for)等都是语句。表达式能够是一个值、变量、常量、操做符、或它们之间的组合,表达式能够看作是包含返回值的语句
例如,如下的赋值操做、流程控制、打印输出都是语句,其是做为一个总体存在的,且不包含返回值
val a = 10
for (i in 0..a step 2) {
println(i)
}
复制代码
再看几个表达式的例子
1 //字面表达式,返回 1
++1 //自增,返回 2
//与 Java 不一样,kotlin 中的 if 是做为表达式存在的,其能够拥有返回值
fun getLength(str: String?): Int {
return if (str.isNullOrBlank()) 0 else str.length
}
复制代码
if 的分支能够是代码块,最后的表达式做为该块的返回值
val maxValue = if (20 > 10) {
println("maxValue is 20")
20
} else {
println("maxValue is 10")
10
}
println(maxValue) //20
复制代码
如下代码能够显示地看出 if 的返回值,彻底能够用来替代 Java 中的三元运算符,所以 kotlin 并无三元运算符
val list = listOf(1, 4, 10, 4, 10, 30)
val value = if (list.size > 0) list.size else null
println(value) //6
复制代码
若是 if 表达式分支是用于执行某个命令,那么此时的返回值类型就是 Unit ,此时的 if 语句就看起来和 Java 的同样了
val value1 = if (list.size > 0) println("1") else println("2")
println(value1.javaClass) //class kotlin.Unit
复制代码
若是将 if 做为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式须要有 else 分支
when 表达式与 Java 中的 switch/case 相似,可是要强大得多。when 既能够被当作表达式使用也能够被当作语句使用,when 将参数和全部的分支条件顺序比较直到某个分支知足条件,而后它会运行右边的表达式。若是 when 被当作表达式来使用,符合条件的分支的值就是整个表达式的值,若是当作语句使用, 则忽略个别分支的值。与 Java 的 switch/case 不一样之处是 When 表达式的参数能够是任何类型,而且分支也能够是一个条件
和 if 同样,when 表达式每个分支能够是一个代码块,它的值是代码块中最后的表达式的值,若是其它分支都不知足条件将会求值于 else 分支
若是 when 做为一个表达式使用,则必须有 else 分支, 除非编译器可以检测出全部的可能状况都已经覆盖了。若是不少分支须要用相同的方式处理,则能够把多个分支条件放在一块儿,用逗号分隔
val value = 2
when (value) {
in 4..9 -> println("in 4..9") //区间判断
3 -> println("value is 3") //相等性判断
2, 6 -> println("value is 2 or 6") //多值相等性判断
is Int -> println("is Int") //类型判断
else -> println("else") //若是以上条件都不知足,则执行 else
}
复制代码
fun main() {
//返回 when 表达式
fun parser(obj: Any): String =
when (obj) {
1 -> "value is 1"
"4" -> "value is string 4"
is Long -> "value type is long"
else -> "unknown"
}
println(parser(1))
println(parser(1L))
println(parser("4"))
println(parser(100L))
println(parser(100.0))
}
value is 1
value type is long
value is string 4
value type is long
unknown
复制代码
此外,When 循环也能够不带参数
when {
1 > 5 -> println("1 > 5")
3 > 1 -> println("3 > 1")
}
复制代码
和 Java 中的 for 循环最为相似的形式是
val list = listOf(1, 4, 10, 34, 10)
for (value in list) {
println(value)
}
复制代码
经过索引来遍历
val items = listOf("H", "e", "l", "l", "o")
//经过索引来遍历List
for (index in items.indices) {
println("${index}对应的值是:${items[index]}")
}
复制代码
也能够在每次循环中获取当前索引和相应的值
val list = listOf(1, 4, 10, 34, 10)
for ((index, value) in list.withIndex()) {
println("index : $index , value :$value")
}
复制代码
也能够自定义循环区间
for (index in 2..10) {
println(index)
}
复制代码
while 和 do/while 与 Java 中的区别不大
val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
while (index < list.size) {
println(list[index])
index++
}
复制代码
val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
do {
println(list[index])
index++
} while (index < list.size)
复制代码
kotlin 有三种结构化跳转表达式:
在 kotlin 中任何表达式均可以用标签(label)来标记,标签的格式为标识符后跟 @ 符号,例如:abc@ 、fooBar@ 都是有效的标签
fun main() {
fun1()
}
fun fun1() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
loop@ for (it in list) {
if (it == 8) {
continue
}
if (it == 23) {
break@loop
}
println("value is $it")
}
println("function end")
}
复制代码
value is 1
value is 4
value is 6
value is 12
function end
复制代码
kotlin 有函数字面量、局部函数和对象表达式。所以 kotlin 的函数能够被嵌套
标签限制的 return 容许咱们从外层函数返回,最重要的一个用途就是从 lambda 表达式中返回。一般状况下使用隐式标签更方便,该标签与接受该 lambda 的函数同名
fun main() {
fun1()
fun2()
fun3()
fun4()
fun5()
}
fun fun1() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
}
fun fun2() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return@fun2
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
}
//fun3() 和 fun4() 中使用的局部返回相似于在常规循环中使用 continue
fun fun3() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return@forEach
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}
fun fun4() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach loop@{
if (it == 8) {
return@loop
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}
fun fun5() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) {
//局部返回到匿名函数的调用者,即 forEach 循环
return
}
println("value is $value")
})
println("function end")
}
复制代码
Ranges 表达式使用一个 .. 操做符来声明一个闭区间,它被用于定义实现了一个 RangTo 方法
如下三种声明形式是等价的
var index = 5
if (index >= 0 && index <= 10) {
}
if (index in 0..10) {
}
if (index in 0.rangeTo(10)) {
}
复制代码
数字类型的 ranges 在被迭代时,编译器会将它们转换为与 Java 中使用 index 的 for 循环的相同字节码的方式来进行优化
Ranges 默认会自增加,因此若是像如下的代码就不会被执行
for (index in 10..0) {
println(index)
}
复制代码
能够改用 downTo 函数来将之改成递减
for (index in 10 downTo 0) {
println(index)
}
复制代码
能够在 ranges 中使用 step 来定义每次循环递增或递增的长度:
for (index in 1..8 step 2){
println(index)
}
for (index in 8 downTo 1 step 2) {
println(index)
}
复制代码
以上声明的都是闭区间,若是想声明的是开区间,可使用 until 函数:
for (index in 0 until 4){
println(index)
}
复制代码
扩展函数 reversed()
可用于返回将区间反转后的序列
val rangeTo = 1.rangeTo(3)
for (i in rangeTo) {
println(i) //1 2 3
}
for (i in rangeTo.reversed()) {
println(i) //3 2 1
}
复制代码
kotlin 中的类和方法默认都是 final 的,即不可继承的,若是想容许建立一个类的子类,须要使用 open 修饰符来标识这个类,此外,也须要为每个但愿被重写的属性和方法添加 open 修饰符
open class View {
open fun click() {
}
//不能在子类中被重写
fun longClick() {
}
}
class Button : View() {
override fun click() {
super.click()
}
}
复制代码
若是重写了一个基类或者接口的成员,重写了的成员一样默认是 open 的。例如,若是 Button 类是 open 的,则其子类也能够重写其 click() 方法
open class Button : View() {
override fun click() {
super.click()
}
}
class CompatButton : Button() {
override fun click() {
super.click()
}
}
复制代码
若是想要类的子类重写该方法的实现,能够显式地将重写的成员标记为 final
open class Button : View() {
override final fun click() {
super.click()
}
}
复制代码
public 修饰符是限制级最低的修饰符,是默认的修饰符。若是一个定义为 public 的成员被包含在一个 private 修饰的类中,那么这个成员在这个类之外也是不可见的
protected 修饰符只能被用在类或者接口中的成员上。在 Java 中,能够从同一个包中访问一个 protected 的成员,但对于 kotlin 来讲,protected 成员只在该类和它的子类中可见。此外,protected 不适用于顶层声明
一个定义为 internal 的包成员,对其所在的整个 module 可见。若是它是一个其它领域的成员,它就须要依赖那个领域的可见性了。好比,若是咱们写了一个 private 类,那么它的 internal 修饰的函数的可见性就会限制于它所在的这个类的可见性
咱们能够访问同一个 module 中的 internal 修饰的类,可是其它 module 是访问不到该 internal 类的,该修饰符可用于对外发布的开源库,将开源库中不但愿被外部引用的代码设为 internal 权限,可避免对外部引用库形成混淆
根据 Jetbrains 的定义,一个 module 应该是一个单独的功能性的单位,能够看作是一块儿编译的 kotlin 文件的集合,它应该是能够被单独编译、运行、测试、debug 的。至关于在 Android Studio 中主工程引用的 module,Eclipse 中在一个 workspace 中的不一样的 project
private 修饰符是限制级最高的修饰符,kotlin 容许在顶层声明中使用 private 可见性,包括类、函数和属性,这表示只在本身所在的文件中可见,因此若是将一个类声明为 private,就不能在定义这个类以外的地方中使用它。此外,若是在一个类里面使用了 private 修饰符,那访问权限就被限制在这个类里面,继承这个类的子类也不能使用它。因此若是类、对象、接口等被定义为 private,那么它们只对被定义所在的文件可见。若是被定义在了类或者接口中,那它们只对这个类或者接口可见
修饰符 | 类成员 | 顶层声明 |
---|---|---|
public(默认) | 全部地方可见 | 全部地方可见 |
internal | 模块中可见 | 模块中可见 |
protected | 子类中可见 | |
private | 类中可见 | 文件中可见 |
在 kotlin 中,类型系统将一个引用分为能够容纳 null (可空引用)或者不能容纳 null(非空引用)两种类型。 例如,String 类型的常规变量不能指向 null
var name: String = "leavesC"
//编译错误
//name = null
复制代码
若是但愿一个变量能够储存 null 引用,须要显式地在类型名称后面加上问号
var name: String? = "leavesC"
name = null
复制代码
问号能够加在任何类型的后面来表示这个类型的变量能够存储 null 引用:Int?、Doubld? 、Long?
等
kotlin 对可空类型的显式支持有助于防止 NullPointerException 致使的异常问题,编译器不容许不对可空变量作 null 检查就直接调用其属性。这个强制规定使得开发者必须在编码初期就考虑好变量的可赋值范围并为其各个状况作好分支处理
fun check(name: String?): Boolean {
//error,编译器不容许不对 name 作 null 检查就直接调用其属性
return name.isNotEmpty()
}
复制代码
正确的作法事显式地进行 null 检查
fun check(name: String?): Boolean {
if (name != null) {
return name.isNotEmpty()
}
return false
}
复制代码
安全调用运算符:?.
容许把一次 null 检查和一次方法调用合并为一个操做,若是变量值非空,则方法或属性会被调用,不然直接返回 null
例如,如下两种写法是彻底等同的:
fun check(name: String?) {
if (name != null) {
println(name.toUpperCase())
} else {
println(null)
}
}
fun check(name: String?) {
println(name?.toUpperCase())
}
复制代码
Elvis 运算符:?:
用于替代 ?.
直接返回默认值 null 的状况,Elvis 运算符接收两个运算数,若是第一个运算数不为 null ,运算结果就是其运算结果值,若是第一个运算数为 null ,运算结果就是第二个运算数
例如,如下两种写法是彻底等同的:
fun check(name: String?) {
if (name != null) {
println(name)
} else {
println("default")
}
}
fun check(name: String?) {
println(name ?: "default")
}
复制代码
安全转换运算符:as?
用于把值转换为指定的类型,若是值不适合该类型则返回 null
fun check(any: Any?) {
val result = any as? String
println(result ?: println("is not String"))
}
复制代码
非空断言用于把任何值转换为非空类型,若是对 null 值作非空断言,则会抛出异常
fun main() {
var name: String? = "leavesC"
check(name) //7
name = null
check(name) //kotlinNullPointerException
}
fun check(name: String?) {
println(name!!.length)
}
复制代码
为可空类型定义扩展函数是一种更强大的处理 null 值的方式,能够容许接收者为 null 的调用,并在该函数中处理 null ,而不是在确保变量不为 null 以后再调用它的方法
例如,以下方法能够被正常调用而不会发生空指针异常
val name: String? = null
println(name.isNullOrEmpty()) //true
复制代码
isNullOrEmpty()
的方法签名以下所示,能够看到这是为可空类型 CharSequence? 定义的扩展函数,方法中已经处理了方法调用者为 null 的状况
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || this.length == 0
}
复制代码
平台类型是 kotlin 对 java 所做的一种平衡性设计。kotlin 将对象的类型分为了可空类型和不可空类型两种,但 java 平台的一切对象类型均为可空的,当在 kotlin 中引用 java 变量时,若是将全部变量均归为可空类型,最终将多出许多 null 检查;若是均当作不可空类型,那么就很容易就写出忽略了NPE 风险的代码。为了平衡二者,kotlin 引入了平台类型,即当在 kotlin 中引用 java 变量值时,既能够将之当作可空类型,也能够将之当作不可空类型,由开发者本身来决定是否进行 null 检查
is 与 !is 操做符用于在运行时检查对象是否符合给定类型:
fun main() {
val strValue = "leavesC"
parserType(strValue) //value is String , length : 7
val intValue = 100
parserType(intValue) //value is Int , toLong : 100
val doubleValue = 100.22
parserType(doubleValue) //value !is Long
val longValue = 200L
parserType(longValue) //unknown
}
fun parserType(value: Any) {
when (value) {
is String -> println("value is String , length : ${value.length}")
is Int -> println("value is Int , toLong : ${value.toLong()}")
!is Long -> println("value !is Long")
else -> println("unknown")
}
}
复制代码
在许多状况下,不须要在 kotlin 中使用显式转换操做符,由于编译器跟踪不可变值的 is 检查以及显式转换,并在须要时自动插入安全的转换
例如,对于如下例子来讲,当判断 value 为 String 类型经过时,就能够直接将 value 当作 String 类型变量并调用其内部属性
fun main() {
val strValue = "leavesC"
parserType(strValue) //value is String , length : 7
val intValue = 100
parserType(intValue) //value is Int , toLong : 100
val doubleValue = 100.22
parserType(doubleValue) //value !is Long
val longValue = 200L
parserType(longValue) //unknown
}
fun parserType(value: Any) {
when (value) {
is String -> println("value is String , length : ${value.length}")
is Int -> println("value is Int , toLong : ${value.toLong()}")
!is Long -> println("value !is Long")
else -> println("unknown")
}
}
复制代码
编译器会指定根据上下文环境,将变量智能转换为合适的类型
if (value !is String) return
//若是 value 非 String 类型时直接被 return 了,因此此处能够直接访问其 length 属性
println(value.length)
// || 右侧的 value 被自动隐式转换为字符串,因此能够直接访问其 length 属性
if (value !is String || value.length > 0) {
}
// && 右侧的 value 被自动隐式转换为字符串,因此能够直接访问其 length 属性
if (value is String && value.length > 0) {
}
复制代码
若是转换是不可能的,转换操做符 as
会抛出一个异常。所以,咱们称之为不安全的转换操做符
fun main() {
parserType("leavesC") //value is String , length is 7
parserType(10) //会抛出异常 ClassCastException
}
fun parserType(value: Any) {
val tempValue = value as String
println("value is String , length is ${tempValue.length}")
}
复制代码
须要注意的是,null 不能转换为 String 变量,由于该类型不是可空的
所以以下转换会抛出异常
val x = null
val y: String = x as String //会抛出异常 TypeCastException
复制代码
为了匹配安全,能够转换的类型声明为可空类型
val x = null
val y: String? = x as String?
复制代码
可使用安全转换操做符 as? 来避免在转换时抛出异常,它在失败时返回 null
val x = null
val y: String? = x as? String
复制代码
尽管以上例子 as? 的右边是一个非空类型的 String,可是其转换的结果是可空的
类的概念就是把数据和处理数据的代码封装成一个单一的实体。在 Java 中,数据存储在一个私有字段中,经过提供访问器方法:getter 和 setter 来访问或者修改数据
在 Java 中如下的示例代码是很常见的,Point 类包含不少重复的代码:经过构造函数把参数赋值给有着相同名称的字段,经过 getter 来获取属性值
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
}
复制代码
使用 kotlin 来声明 Point 类则只须要一行代码,二者彻底等同
class Point(val x: Int, val y: Int)
复制代码
kotlin 也使用关键字 class 来声明类,类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成,类头与类体都是可选的,若是一个类没有类体,能够省略花括号。此外,kotlin 中类默认是 publish(公有的) 且 final (不可继承)的
kotlin 区分了主构造方法(在类体外部声明)和次构造方法(在类体内部声明),一个类能够有一个主构造函数和多个次构造函数,此外也容许在初始化代码块中 init
添加额外的初始化逻辑
主构造函数是类头的一部分,跟在类名(和可选的类型参数)后,主构造函数的参数能够是可变的(var)或只读的(val)
class Point constructor(val x: Int, val y: Int) {
}
复制代码
若是主构造函数没有任何注解或者可见性修饰符,能够省略 constructor 关键字
class Point(val x: Int, val y: Int) {
}
//若是不包含类体,则能够省略花括号
class Point(val x: Int, val y: Int)
复制代码
若是构造函数有注解或可见性修饰符,则 constructor 关键字是必需的,而且这些修饰符在它前面
class Point public @Inject constructor(val x: Int, val y: Int) {
}
复制代码
主构造函数不能包含任何的代码,初始化的代码能够放到以 init 关键字做为前缀的初始化块(initializer blocks)中,初始化块包含了在类被建立时执行的代码,主构造函数的参数能够在初始化块中使用。若是须要的话,也能够在一个类中声明多个初始化语句块。须要注意的是,构造函数的参数若是用 val/var 进行修饰,则至关于在类内部声明了一个同名的全局属性。若是不加 val/var 进行修饰,则构造函数只能在 init 函数块和全局属性初始化时进行引用
此外,要建立一个类的实例不须要使用 Java 中的 new 关键字,像普通函数同样调用构造函数便可
class Point(val x: Int, val y: Int) {
init {
println("initializer blocks , x value is: $x , y value is: $y")
}
}
fun main() {
Point(1, 2) // initializer blocks , x value is: 1 , y value is: 2
}
复制代码
主构造函数的参数也能够在类体内声明的属性初始化器中使用
class Point(val x: Int, val y: Int) {
private val localX = x + 1
private val localY = y + 1
init {
println("initializer blocks , x value is: $x , y value is: $y")
println("initializer blocks , localX value is: $localX , localY value is: $localY")
}
}
fun main() {
Point(1, 2)
//initializer blocks , x value is: 1 , y value is: 2
//initializer blocks , localX value is: 2 , localY value is: 3
}
复制代码
类也能够声明包含前缀 constructor 的次构造函数。若是类有一个主构造函数,每一个次构造函数都须要直接委托给主构造函数或者委托给另外一个次构造函数以此进行间接委托,用 this 关键字来进行指定便可
class Point(val x: Int, val y: Int) {
private val localX = x + 1
private val localY = y + 1
init {
println("initializer blocks , x value is: $x , y value is: $y")
println("initializer blocks , localX value is: $localX , localY value is: $localY")
}
constructor(base: Int) : this(base + 1, base + 1) {
println("constructor(base: Int)")
}
constructor(base: Long) : this(base.toInt()) {
println("constructor(base: Long)")
}
}
fun main() {
Point(100)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
Point(100L)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
//constructor(base: Long)
}
复制代码
初始化块中的代码实际上会成为主构造函数的一部分,委托给主构造函数会做为次构造函数的第一条语句,所以全部初始化块中的代码都会在次构造函数体以前执行。即便该类没有主构造函数,这种委托仍会隐式发生,而且仍会执行初始化块。若是一个非抽象类没有声明任何(主或次)构造函数,会默认生成一个不带参数的公有主构造函数
在 Java 中,字段和其访问器的组合被称做属性。在 kotlin 中,属性是头等的语言特性,彻底替代了字段和访问器方法。在类中声明一个属性和声明一个变量同样是使用 val 和 var 关键字。val 变量只有一个 getter ,var 变量既有 getter 也有 setter
fun main() {
val user = User()
println(user.name)
user.age = 200
}
class User() {
val name: String = "leavesC"
var age: Int = 25
}
复制代码
访问器的默认实现逻辑很简单:建立一个存储值的字段,以及返回属性值的 getter 和更新属性值的 setter。若是须要的话,也能够自定义访问器
例如,如下就声明了三个带自定义访问器的属性
class Point(val x: Int, val y: Int) {
val isEquals1: Boolean
get() {
return x == y
}
val isEquals2
get() = x == y
var isEquals3 = false
get() = x > y
set(value) {
field = !value
}
}
复制代码
若是仅须要改变一个访问器的可见性或者为其添加注解,那么能够定义访问器而不定义其实现
fun main() {
val point = Point(10, 10)
println(point.isEquals1)
//如下代码会报错
//point.isEquals1 = true
}
class Point(val x: Int, val y: Int) {
var isEquals1: Boolean = false
get() {
return x == y
}
private set
}
复制代码
通常地,非空类型的属性必须在构造函数中初始化,但像使用了 Dagger2 这种依赖注入框架的项目来讲就十分的不方便了,为了应对这种状况,能够用 lateinit 修饰符来标记该属性,用于告诉编译器该属性会在稍后的时间被初始化
用 lateinit 修饰的属性或变量必须为非空类型,而且不能是原生类型
class Point(val x: Int, val y: Int)
class Example {
lateinit var point: Point
var point2: Point
constructor() {
point2 = Point(10, 20)
}
}
复制代码
若是访问了一个未通过初始化的 lateinit 变量,则会抛出一个包含具体缘由(该变量未初始化)的异常信息
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property point has not been initialized
复制代码
声明为 abstract 的类内部能够包含没有实现体的成员方法,且该成员方法也用 abstract 标记,这种类称为抽象类,包含的没有实现体的方法称为抽象方法
此外,咱们并不须要用 open 标注一个抽象类或者抽象方法,由于这是默认声明的
abstract class BaseClass {
abstract fun fun1()
}
class Derived : BaseClass() {
override fun fun1() {
}
}
复制代码
数据类是一种很是强大的类,能够避免重复建立 Java 中的用于保存状态但又操做很是简单的 POJO 的模版代码,它们一般只提供了用于访问它们属性的简单的 getter 和 setter
定义一个新的数据类很是简单,例如
data class Point(val x: Int, val y: Int)
复制代码
数据类默认地为主构造函数中声明的全部属性生成了以下几个方法
为了确保生成的代码的一致性以及有意义的行为,数据类必须知足如下要求:
能够利用 IDEA 来反编译查看 Point 类的 Java 实现,了解其内部实现
public final class Point {
private final int x;
private final int y;
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public final int component1() {
return this.x;
}
public final int component2() {
return this.y;
}
@NotNull
public final Point copy(int x, int y) {
return new Point(x, y);
}
// $FF: synthetic method
// $FF: bridge method
@NotNull
public static Point copy$default(Point var0, int var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.x;
}
if ((var3 & 2) != 0) {
var2 = var0.y;
}
return var0.copy(var1, var2);
}
public String toString() {
return "Point(x=" + this.x + ", y=" + this.y + ")";
}
public int hashCode() {
return this.x * 31 + this.y;
}
public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof Point) {
Point var2 = (Point)var1;
if (this.x == var2.x && this.y == var2.y) {
return true;
}
}
return false;
} else {
return true;
}
}
}
复制代码
经过数据类能够简化不少的通用操做,能够很方便地进行:格式化输出变量值、映射对象到变量、对比变量之间的相等性、复制变量等操做
fun main() {
val point1 = Point(10, 20)
val point2 = Point(10, 20)
println("point1 toString() : $point1") //point1 toString() : Point(x=10, y=20)
println("point2 toString() : $point2") //point2 toString() : Point(x=10, y=20)
val (x, y) = point1
println("point1 x is $x,point1 y is $y") //point1 x is 10,point1 y is 20
//在 kotlin 中,“ == ” 至关于 Java 的 equals 方法
//而 “ === ” 至关于 Java 的 “ == ” 方法
println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false
val point3 = point1.copy(y = 30)
println("point3 toString() : $point3") //point3 toString() : Point(x=10, y=30)
}
复制代码
须要注意的是,数据类的 toString()、equals()、hashCode()、copy()
等方法只考虑主构造函数中声明的属性,所以在比较两个数据类对象的时候可能会有一些意想不到的结果
data class Point(val x: Int) {
var y: Int = 0
}
fun main() {
val point1 = Point(10)
point1.y = 10
val point2 = Point(10)
point2.y = 20
println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false
}
复制代码
Sealed 类(密封类)用于对类可能建立的子类进行限制,用 Sealed 修饰的类的直接子类只容许被定义在 Sealed 类所在的文件中(密封类的间接继承者能够定义在其余文件中),这有助于帮助开发者掌握父类与子类之间的变更关系,避免因为代码更迭致使的潜在 bug,且密封类的构造函数只能是 private 的
例如,对于 View 类,其子类只能定义在与之同一个文件里,Sealed 修饰符修饰的类也隐含表示该类为 open 类,所以无需再显式地添加 open 修饰符
sealed class View {
fun click() {
}
}
class Button : View() {
}
class TextView : View() {
}
复制代码
由于 Sealed 类的子类对于编译器来讲是可控的,因此若是在 when 表达式中处理了全部 Sealed 类的子类,那就不须要再提供 else 默认分支。即便之后因为业务变更又新增了 View 子类,编译器也会检测到 check 方法缺乏分支检查后报错,因此说 check 方法是类型安全的
fun check(view: View): Boolean {
when (view) {
is Button -> {
println("is Button")
return true
}
is TextView -> {
println("is TextView")
return true
}
}
}
复制代码
kotlin 也提供了枚举的实现,相比 Java 须要多使用 class 关键字来声明枚举
enum class Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
复制代码
枚举能够声明一些参数
enum class Day(val index: Int) {
SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6)
}
复制代码
此外,枚举也能够实现接口
interface OnChangedListener {
fun onChanged()
}
enum class Day(val index: Int) : OnChangedListener {
SUNDAY(0) {
override fun onChanged() {
}
},
MONDAY(1) {
override fun onChanged() {
}
}
}
复制代码
枚举也包含有一些共有函数
fun main() {
val day = Day.FRIDAY
//获取值
val value = day.index //5
//经过 String 获取相应的枚举值
val value1 = Day.valueOf("SUNDAY") //SUNDAY
//获取包含全部枚举值的数组
val value2 = Day.values()
//获取枚举名
val value3 = Day.SUNDAY.name //SUNDAY
//获取枚举声明的位置
val value4 = Day.TUESDAY.ordinal //2
}
复制代码
使用对象表达式来建立匿名内部类实例
interface OnClickListener {
fun onClick()
}
class View {
fun setClickListener(clickListener: OnClickListener) {
}
}
fun main() {
val view = View()
view.setClickListener(object : OnClickListener {
override fun onClick() {
}
})
}
复制代码
在 kotlin 中在类里面再定义的类默认是嵌套类,此时嵌套类不会包含对外部类的隐式引用
class Outer {
private val bar = 1
class Nested {
fun foo1() = 2
//错误
//fun foo2() = bar
}
}
fun main() {
val demo = Outer.Nested().foo1()
}
复制代码
以上代码经过 IDEA 反编译后能够看到其内部的 Java 实现方式
能够看到 Nested 其实就是一个静态类,所以 foo2() 不能访问外部类的非静态成员,也不用先声明 Outer 变量再指向 Nested 类,而是直接经过 Outer 类指向 Nested 类
public final class Outer {
private final int bar = 1;
public static final class Nested {
public final int foo1() {
return 2;
}
}
}
public final class MainkotlinKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
int demo = (new Outer.Nested()).foo1();
}
}
复制代码
若是须要去访问外部类的成员,须要用 inner 修饰符来标注被嵌套的类,这称为内部类。内部类会隐式持有对外部类的引用
class Outer {
private val bar = 1
inner class Nested {
fun foo1() = 2
fun foo2() = bar
}
}
fun main() {
val demo = Outer().Nested().foo2()
}
复制代码
再来看其内部的 Java 实现方式
使用 inner 来声明 Nested 类后,就至关于将之声明为非静态内部类,所以 foo2() 能访问其外部类的非静态成员,在声明 Nested 变量前也须要经过 Outer 变量来指向其内部的 Nested 类
public final class Outer {
private final int bar = 1;
public final class Nested {
public final int foo1() {
return 2;
}
public final int foo2() {
return Outer.this.bar;
}
}
}
public final class MainkotlinKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
int demo = (new Outer().new Nested()).foo2();
}
}
复制代码
类A在类B中声明 | 在Java中 | 在kotlin中 |
---|---|---|
嵌套类(不存储外部类的引用) | static class A | class A |
内部类(存储外部类的引用) | class A | inner class A |
kotlin 中的接口与 Java 8 中的相似,能够包含抽象方法的定义以及非抽象方法的实现,不须要使用 default 关键字来标注有默认实现的非抽象方法,但在实现接口的抽象方法时须要使用 override 进行标注
fun main() {
val view = View()
view.click()
view.longClick()
}
class View : Clickable {
override fun click() {
println("clicked")
}
}
interface Clickable {
fun click()
fun longClick() = println("longClicked")
}
复制代码
若是一个类实现了多个接口,而接口包含带有默认实现且签名相同的方法,此时编译器就会要求开发者必须显式地实现该方法,能够选择在该方法中调用不一样接口的相应实现
class View : Clickable, Clickable2 {
override fun click() {
println("clicked")
}
override fun longClick() {
super<Clickable>.longClick()
super<Clickable2>.longClick()
}
}
interface Clickable {
fun click()
fun longClick() = println("longClicked")
}
interface Clickable2 {
fun click()
fun longClick() = println("longClicked2")
}
复制代码
接口中能够包含抽象属性声明,接口不定义该抽象属性是应该存储到一个支持字段仍是经过 getter 来获取,接口自己并不包含任何状态,所以只有实现这个接口的类在须要的状况下会存储这个值
看如下例子,Button 类和 TextView 类都实现了 Clickable 接口,并都提供了取得 statusValue 值的方式
Button 类提供了一个自定义的 getter 用于在每次访问时从新获取 statusValue 值,所以在屡次获取属性值时其值可能都会不一致,由于每次 getRandom() 方法都会被调用
TextView 类中的 statusValue 属性有一个支持字段来存储在类初始化时获得的数据,所以其值在初始化后是不会再次获取值,即 TextView 类中的 getRandom() 只会被调用一次
fun main() {
val button = Button()
println(button.statusValue)
val textView = TextView()
println(textView.statusValue)
}
class Button : Clickable {
override val statusValue: Int
get() = getRandom()
private fun getRandom() = Random().nextInt(10)
}
class TextView : Clickable {
override val statusValue: Int = getRandom()
private fun getRandom() = Random().nextInt(10)
}
interface Clickable {
val statusValue: Int
}
复制代码
除了能够声明抽象属性外,接口还能够包含具备 getter 和 setter 的属性,只要它们没有引用一个支持字段(支持字段须要在接口中存储状态,而这是不容许的)
interface Clickable {
val statusValue: Int
val check: Boolean
get() = statusValue > 10
}
复制代码
在 kotlin 中全部类都有一个共同的超类 Any ,对于没有超类声明的类来讲它就是默认超类。须要注意的是, Any 并非 java.lang.Object ,它除了 equals() 、 hashCode() 与 toString() 外没有其余属性或者函数
要声明一个显式的超类,须要把父类名放到类头的冒号以后
open class Base()
class SubClass() : Base()
复制代码
当中,类上的 open 标注与 Java 中的 final 含义相反,用于容许其它类从这个类继承。默认状况下,kotlin 中全部的类都是 final
若是派生类有一个主构造函数,其基类型必须直接或间接调用基类的主构造函数
open class Base(val str: String)
class SubClass(val strValue: String) : Base(strValue)
class SubClass2 : Base {
constructor(strValue: String) : super(strValue)
constructor(intValue: Int) : super(intValue.toString())
constructor(doubValue: Double) : this(doubValue.toString())
}
复制代码
与 Java 不一样,kotlin 须要显式标注可覆盖的成员和覆盖后的成员:
open class Base() {
open fun fun1() {
}
fun fun2() {
}
}
class SubClass() : Base() {
override fun fun1() {
super.fun1()
}
}
复制代码
用 open 标注的函数才能够被子类重载,子类用 override 表示该函数是要对父类的同签名函数进行覆盖。标记为 override 的成员自己也是开放的,也就是说,它能够被子类覆盖。若是想禁止再次覆盖,可使用 final 关键字标记 若是父类没有使用 open 对函数进行标注,则子类不容许定义相同签名的函数。对于一个 final 类(没有用 open 标注的类)来讲,使用 open 标记属性和方法是无心义的
属性覆盖与方法覆盖相似。在超类中声明为 open 的属性,若是要进行覆盖则必须在派生类中从新声明且以 override 开头,而且它们必须具备兼容的类型
每一个声明的属性能够由具备初始化器的属性或者具备 getter 方法的属性覆盖
open class Base {
open val x = 10
open val y: Int
get() {
return 100
}
}
class SubClass : Base() {
override val x = 100
override var y = 200
}
fun main() {
val base = Base()
println(base.x) //10
println(base.y) //100
val base1: Base = SubClass()
println(base1.x) //100
println(base1.y) //200
val subClass = SubClass()
println(subClass.x) //100
println(subClass.y) //200
}
复制代码
此外,也能够用一个 var 属性覆盖一个 val 属性,但反之则不行。由于一个 val 属性本质上声明了一个 getter 方法,而将其覆盖为 var 只是在子类中额外声明一个 setter 方法
能够在主构造函数中使用 override 关键字做为属性声明的一部分
open class Base {
open val str: String = "Base"
}
class SubClass(override val str: String) : Base()
fun main() {
val base = Base()
println(base.str) //Base
val subClass = SubClass("leavesC")
println(subClass.str) //leavesC
}
复制代码
派生类能够经过 super 关键字调用其超类的函数与属性访问器的实现
open class BaseClass {
open fun fun1() {
println("BaseClass fun1")
}
}
class SubClass : BaseClass() {
override fun fun1() {
super.fun1()
}
}
复制代码
对于内部类来讲,其自己就能够直接调用调用外部类的函数
open class BaseClass2 {
private fun fun1() {
println("BaseClass fun1")
}
inner class InnerClass {
fun fun2() {
fun1()
}
}
}
复制代码
但若是想要在一个内部类中访问外部类的超类,则须要经过由外部类名限定的 super 关键字来实现
open class BaseClass {
open fun fun1() {
println("BaseClass fun1")
}
}
class SubClass : BaseClass() {
override fun fun1() {
println("SubClass fun1")
}
inner class InnerClass {
fun fun2() {
super@SubClass.fun1()
}
}
}
fun main() {
val subClass = SubClass()
val innerClass = subClass.InnerClass()
//BaseClass fun1
innerClass.fun2()
}
复制代码
若是一个类从它的直接超类和实现的接口中继承了相同成员的多个实现, 则必须覆盖这个成员并提供其本身的实现来消除歧义
为了表示采用从哪一个超类型继承的实现,使用由尖括号中超类型名限定的 super 来指定,如 super< BaseClass >
open class BaseClass {
open fun fun1() {
println("BaseClass fun1")
}
}
interface BaseInterface {
//接口成员默认就是 open 的
fun fun1() {
println("BaseInterface fun1")
}
}
class SubClass() : BaseClass(), BaseInterface {
override fun fun1() {
//调用 SubClass 的 fun1() 函数
super<BaseClass>.fun1()
//调用 BaseInterface 的 fun1() 函数
super<BaseInterface>.fun1()
}
}
复制代码
kotlin 的集合设计和 Java 不一样的另外一项特性是:kotlin 把访问数据的接口和修改集合数据的接口分开了,kotlin.collections.Collection
接口提供了遍历集合元素、获取集合大小、判断集合是否包含某元素等操做,但这个接口没有提供添加和移除元素的方法。kotlin.collections.MutableCollection
接口继承于 kotlin.collections.Collection
接口,扩展出了用于添加、移除、清空元素的方法
就像 kotlin 对 val
和 var
的区分同样,只读集合接口与可变集合接口的分离能提升对代码的可控性,若是函数接收 Collection
做为形参,那么就能够知道该函数不会修改集合,而只是对数据进行读取
如下是用来建立不一样类型集合的函数
集合元素 | 只读 | 可变 |
---|---|---|
List | listOf | mutableListOf、arrayListOf |
Set | setOf | mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf |
Map | mapOf | mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf |
val list = listOf(10, 20, 30, 40)
//不包含 add 方法
//list.add(100)
println(list.size)
println(list.contains(20))
val mutableList = mutableListOf("leavesC", "叶应是叶", "叶")
mutableList.add("Ye")
println(mutableList.size)
println(mutableList.contains("leavesC"))
复制代码
由于 Java 并不会区分只读集合与可变集合,即便 kotlin 中把集合声明为只读的, Java 代码也能够修改这个集合,而 Java 代码中的集合对 kotlin 来讲也是可变性未知的,kotlin 代码能够将之视为只读的或者可变的,包含的元素也是能够为 null 或者不为 null 的
例如,在 Java 代码中 names 这么一个 List< String > 类型的变量
public class JavaMain {
public static List<String> names = new ArrayList<>();
static {
names.add("leavesC");
names.add("Ye");
}
}
复制代码
在 kotlin 中能够用如下四种方式来引用变量 names
val list1: List<String?> = JavaMain.names
val list2: List<String> = JavaMain.names
val list3: MutableList<String> = JavaMain.names
val list4: MutableList<String?> = JavaMain.names
复制代码
只读集合不必定就是不可变的。例如,假设存在一个拥有只读类型接口的对象,该对象存在两个不一样的引用,一个只读,一个可变,当可变引用修改了该对象后,这对只读引用来讲就至关于“只读集合被修改了”,所以只读集合并不老是线程安全的。若是须要在多线程环境下处理数据,须要保证正确地同步了对数据的访问,或者使用支持并发访问的数据结构
例如,list1 和 list1 引用到同一个集合对象, list3 对集合的修改同时会影响到 list1
val list1: List<String> = JavaMain.names
val list3: MutableList<String> = JavaMain.names
list1.forEach { it -> println(it) } //leavesC Ye
list3.forEach { it -> println(it) } //leavesC Ye
for (index in list3.indices) {
list3[index] = list3[index].toUpperCase()
}
list1.forEach { it -> println(it) } //LEAVESC YE
复制代码
集合的可空性能够分为三种:
例如,intList1 能够包含为 null 的集合元素,但集合自己不能指向 null;intList2 不能够包含为 null 的集合元素,但集合自己能够指向 null;intList3 能够包含为 null 的集合元素,且集合自己能指向 null
//List<Int?> 是能持有 Int? 类型值的列表
val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
//List<Int>? 是能够为 null 的列表
var intList2: List<Int>? = listOf(10, 20, 30, 40)
intList2 = null
//List<Int?>? 是能够为 null 的列表,且能持有 Int? 类型值
var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
intList3 = null
复制代码