本章内容包括:
- 处理 null 的可空类型和语法
- 基本数据类型和它们对应的Java类型
- Kotlin 的集合,以及它们和Java的关系
6.1 可空性
代码语言:javascript复制 /**-------------------- 6.1.1 可空类型 ? ----------------------*/
// Kotlin和Java最重要的区别:对可空类型的显式的支持。
/*java*/
// int strLen(String s){
// return s.length()
// }
fun strLen(s: String) = s.length
// 在编译器会标记成错误(这个函数中的参数被声明成String类型,在Kotlin中这表示它必须包含一个String实例)
// strLen(null)
// 如果允许调用这个方法的时候传给它所有的可能的实参,包括null,需要显示地在类型名称后面加上问号来标记它:
fun strLenSafe(s: String?) = {}
// Type? = Type or null
val e: String? = null
// val f: String = null // 错误
// strLen(e)// 错误
// 代码清单6.1 使用if检查处理null
fun strLenSafe2(s: String?): Int = if (s != null) s.length else 0
// fun strLenSafe2(s: String?): Int = s?.length ?: 0
strLenSafe2(e)// 0
strLenSafe2("sss")// 3
/**-------------------- 6.1.2 类型的含义 ----------------------*/
/*
* 类型就是数据的分类......决定了该类型可能的值,以及该类型的值上可以完成的操作
* 可空和非可空的对象在运行时没有什么区别;
* 可空类型并不是非空类型的包装。
* 所有的检查都发生在编译期。这意味着使用Kotlin的可空类型并不会在运行时带来额外的开销
*/
/**-------------------- 6.1.3 安全调用运算符: ?. ----------------------*/
// ?. 它允许你把一次null检查和一次方法调用合并成一个操作
val ok: String? = null
println(ok?.toUpperCase())// null
println(
if (ok != null) {
ok.toUpperCase()
} else null
)
// 这次调用的结果类型也是可空的。
fun printAllCaps(s: String?) {
// allCaps可能也是null
val allCaps: String? = s?.toLowerCase()
println(allCaps)
}
// 安全调用不光可以调用方法,也能访问属性。
// 代码清单6.2 使用安全调用处理可空属性
class Employee(val name: String, val manager: Employee?)
fun managerName(employee: Employee): String? = employee.manager?.name
val ceo = Employee("Da Boss", null)
val developer = Employee("jingbin", ceo)
managerName(ceo)// null
managerName(developer)// Da Boss
// 代码清单6.3 链接多个安全调用
class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)
class Company(val name: String, val address: Address?)
class Person(val name: String, val company: Company?)
fun Person.countryName(): String {
val country = this.company?.address?.country
return if (country != null) country else "unKnow"
// return country ?: "unKnow"
}
val person = Person("beijing", null)
println(person.countryName())// unKnow
/**-------------------- 6.1.4 Elvis运算符 ?: ----------------------*/
// kotlin中有方便的运算符来提供null的默认值。
fun foo(s: String?) {
// 如果 s 为null,结果是一个空的字符串
val t: String = s ?: ""
}
// 代码清单6.4 使用Elvis运算符处理null值
fun strLenSafe3(s: String?): Int = s?.length ?: 0
println(strLenSafe3("aaa"))// 3
println(strLenSafe3(null))// 0
// 代码清单6.5 同时使用throw和Elvis运算符
// class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)
fun printShipping(person: Person) {
// 缺少address就抛出异常
val address = person.company?.address ?: throw IllegalArgumentException("No address")
// 使用with函数避免在这一行中重复使用四次address
with(address) {
println(streetAddress)
println("$zipCode $city $country")
}
}
val address = Address("高新区", 1000, "武汉", "中国")
val company = Company("keji", address)
val person1 = Person("jingbin", company)
printShipping(person1)
printShipping(Person("jingbin", null))// java.lang.IllegalArgumentException:No address
/**-------------------- 6.1.5 安全转换 as? ----------------------*/
// as? 运算符尝试把值转换成指定的类型,如果值不是合适的类型就返回null
// foo as? Type
// foo is Type --- foo as Type
// foo !is Type --- null
// 代码清单6.6 使用安全转换实现equals
class Person2(val firstName: String, val lastName: String) {
override fun equals(o: Any?): Boolean {
// 检查类型,如果不匹配就返回false
val otherPerson = o as? Person2 ?: return false
// 在安全转换后,变量otherPerson被智能转换为Person类型
return otherPerson.firstName == o.firstName && otherPerson.lastName == o.lastName
}
override fun hashCode(): Int = firstName.hashCode() * 37 lastName.hashCode()
}
val p1 = Person2("jing", "bin")
val p2 = Person2("jing", "bin")
// == 运算符会调用equals方法
println(p1 == p2)// true
println(p1.equals(12))// false
/**-------------------- 6.1.6 非空断言 !! ----------------------*/
// 有时候你并不需要Kotlin的这些支持来处理null值,你只需要直接告诉编译器这个值实际上并不是null。
// 非空断言是Kotlin提供最简单直率的处理可空类型值的工具。
// foo!!
// foo != null --- foo
// foo == null --- NullPointerException
// 代码清单6.7 使用非空断言
fun ignoreNulls(s: String?) {
// 异常在这一行:告诉编译器 我知道这个值不为null,如果我错了我准备好了接收这个异常
val sNotNull: String = s!!
println(sNotNull.length)
}
// ignoreNulls(null)
// 代码清单6.8 在Swing action中使用非空断言
// class CopyRowAction(val list: JList<String>) : AbstractAction {
// override fun isEnabled(): Boolean = list.selectedValue != null
// 只会在isEnabled返回 true 时调用
// override fun actionPerformed(e: ActionEvent) {
// val selectedValue = list.selectedValue!!
// }
// }
// 不要写这样的代码,因为不知道 跟踪信息只表明异常发生在哪一行代码
person1.company!!.address!!.country
/**-------------------- 6.1.7 let 函数 ----------------------*/
/*
* let函数让处理可空表达式变得更容易,和安全调用运算符一起,它允许你对表达式求值,检查求值结果是否为null,并把结果保存为一个变量。
* 所有这些变动都在同一个简洁的表达式中。
* let 函数做的所有事情就是把一个调用它的对象编程lambda表达式的参数。
*/
// foo?.let {
// ...it...
// }
// foo !=null // 在lambda内部it是非空的
// foo ==null // 什么都不会发生
// 代码清单6.9 使用let调用一个接受非空参数的函数
fun sendEmailTo(email: String) {
println("Sending email to $email")
}
val email: String? = "jingbin@qq.com"
// let函数只在email的值非空时才会被调用
email?.let { sendEmailTo(it) }
val email2: String? = null
// 不会被调用
email2?.let { sendEmailTo(it) }
// val person3 : Person? = getTheBestInWord()
// if (person3!=null) sendEmailTo(person3.email)
// 等同于
// getTheBestInWord()?.let{sendEmailTo(it.email)}
/**-------------------- 6.1.8 延迟初始化的属性 ----------------------*/
//代码清单6.10 使用非空断言访问可空属性
// open class MyService {
//// fun performAction(): String = "foo"
//// }
//// class MyTest {
////
//// // 声明一个可空类型的属性并初始化为null
//// private var myService: TypeSystem1Activity.MyService? = null
////
//// @Before
//// fun setUp() {
//// // 在setUp方法中提供真正的初始化器
//// myService = TypeSystem1Activity.MyService()
//// }
////
//// @Test
//// fun testAction() {
//// // 必须注意可空性:要么用!!,要么用?.
//// Assert.assertEquals("foo", myService!!.performAction())
//// }
//// }
// 代码清单6.11 使用延迟初始化属性 lateinit
// class MyTest2 {
//
// // 声明一个不需要初始化器的非空类型的属性
// private lateinit var myService: TypeSystem1Activity.MyService
//
// @Before
// fun setUp() {
// // 像之前的例子一样在setUp方法中初始化属性
// myService = TypeSystem1Activity.MyService()
// }
//
// @Test
// fun testAction() {
// // 不需要null检查直接访问属性
// Assert.assertEquals("foo", myService.performAction())
// }
// }
// 延迟初始化的属性都是var的。
/**-------------------- 6.1.9 可空类性的扩展 ----------------------*/
// isEmpty 是否是"" isBlank是否是Null或""
//代码清单6.12 用可空接收者调用扩展函数
fun verifyUserInput(input: String?) {
// 这里不需要安全调用
if (input.isNullOrBlank()) {
println("isNullOrBlank....")
}
}
// 接收者调用isNullOrBlank并不会导致任何异常
verifyUserInput(null)
/*
* input 可空类型的值
* isNullOrBlank() 可空类型的拓展
* . 不需要安全调用
*/
// 可空字符串的扩展
fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank()
// 当你为一个可空类型(以?结尾)定义扩展函数时,这意味着你可以对可空的值调用这个函数;
// 并且函数体中的this可能为null,所以你必须显示的检查。在可空类型的扩展函数中,this可能为null
val person5: String? = null
// 没有安全调用,所以 it 是可空类型
// person5.let { sendEmailTo(it) }
person5?.let { sendEmailTo(it) }
/**-------------------- 6.1.10 类型参数的可空性 ----------------------*/
// Kotlin中所有泛型类和泛型函数的类型参数默认都是可空的。
// 代码清单6.13 处理可空的类型参数
fun <T> printHashCode(t: T) {
// 因为 t 可能为null,所以必须使用安全调用
println(t?.hashCode())
}
// T 被推导成 Any?
printHashCode(null)
// 要是类型参数非空,必须要为它指定一个非空的上界,那样泛型会拒绝可空类型作为实参。
// 代码清单6.14 为类型参数声明非空上界
fun <T : Any> printHashCode2(t: T) {
// T 不是可空的
println(t.hashCode())
}
// printHashCode2(null)
/**-------------------- 6.1.11 可空性和Java ----------------------*/
// Java中的 @Nullable String 被Kotlin当做 String?,而@NotNull String就是String
// @Nullable Type = Type?
// @NotNull Type = Type
/*
* 平台类型
* 平台类型本质上就是Kotlin不知道可空性信息的类型。
* Type(Java) = Type? or Type (Kotlin)
*/
//代码清单6.15 没有可空性注解的 Java 类
/*
* public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
*/
// getName()能不能返回null? Kotlin编译器不知道可空性,需要自己处理它
//代码清单6.16 不使用null检查访问Java类
fun yellAt(person: JavaCode.PersonJava) {
println(person.name.toUpperCase() "!!!")
}
// yellAt(JavaCode.PersonJava(null)) // 会有异常
// 代码清单6.17 使用null 检查来访问Java类
fun yellAtSafe(person: JavaCode.PersonJava) {
println((person.name ?: "Anyone").toUpperCase() "!!!")
}
yellAtSafe(JavaCode.PersonJava(null))// Anyone!!!
// 这两种都是可以的
val person6 = JavaCode.PersonJava("222")
val s: String? = person6.name
val s1: String = person6.name
/**继承*/
// 代码清单6.18 使用String参数的Java接口
/*Java*/
// public interface StringProcess {
// void process(String value);
// }
// 代码清单6.19 实现Java接口时使用不同的参数可空性
class StringPrint : JavaCode.StringProcess {
override fun process(value: String) {
}
}
class StringPrint2 : JavaCode.StringProcess {
override fun process(value: String?) {
if (value != null) {
println(value)
}
}
}
6.2 基本数据类型和其他基本类型
代码语言:javascript复制 /**-------------------- 6.2.1 基本数据类型:Int、Boolean及其他 ----------------------*/
// Kotlin并不区分基本数据类型和包装类型,使用的永远是同一类型:(如:Int)
val i: Int = 1
val listOf: List<Int> = listOf(1, 2, 3)
fun showProgress(progress: Int) {
val coerceIn = progress.coerceIn(0, 100)
println("we are ${coerceIn}% done!")
}
// 只在泛型类的时候会被编译成Integer,如集合类,其他是int
// 对应到Java基本数据类型的类型完整列表如下:
/*
* 整数类型:Byte、Short、Int、Long
* 浮点型类型:Float、Double
* 字符类型:Char
* 布尔类型:Boolean
*/
/**-------------------- 6.2.2 可空的基本数据类型:Int?、Boolean? 及其他 ----------------------*/
data class Person(val name: String, val age: Int? = null) {
fun isOlderThan(other: Person): Boolean? {
if (age == null || other.age == null) {
return null
}
return age > other.age
}
}
/**-------------------- 6.2.3 数字转换 ----------------------*/
// Kotlin和Java之间一条重要的区别就是处理数字转换的方式。
val i2 = 1
// 必须显示的转换
val l: Long = i2.toLong()
println(i2.toLong() in listOf(1L, 2L, 3L))
/*
* 基本数据类型字面值
* 使用后缀 L 表示Long 字面值:123L
* 标准浮点数表示 Double 字面值:0.12 2.0 1.2e10
* 后缀 F 表示Float类型 字面值:123.4f、.456f
* 使用前缀 0x或0X 表示十六进制字面值:0xCAFEBABE 或者 0xbdcL
* 使用前缀 0b或0B 表示二进制字面值:0b000000101
*
* */
// 初始化一个类型已知和变量时,或者把字面值作为实参传给函数时,必要的转换会自动的发生。
fun foo(l: Long) = println(l)
foo(42)
val b: Byte = 1
val l2 = b 1L
/**-------------------- 6.2.4 Any 和 Any? 根类型 ----------------------*/
/*
* 和 Object作为Java类层级结构的根差不多,Any类型是Kotlin所有非空类型的超类型(非空类型的根)。
*/
// Any是引用类型,所以值42会被装箱
val answer: Any = 42
/**-------------------- 6.2.5 Unit类型 Kotlin的 void ----------------------*/
// Kotlin中的Unit类型完成了Java中的void一样的功能。当函数没什么有意义的结果返回时,他可以用作函数的返回类型
fun f(): Unit {}
// 显式的Unit声明被省略了
fun f2() {}
class NoResultProcess : Processor2<Unit> {
// 返回Unit,但可以省略类型说明
override fun process(): Unit {
// 这里不需要显示的return
// return Unit
}
}
/**-------------------- 6.2.6 Nothing类型: 这个函数永不返回 ----------------------*/
// 对某些 Kotlin 函数来说,"返回类型”的概念没有任何意义,因为它们从来不会成功地结束。
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
// 注意 返回Nothing的函数可以放在Elvis运算符的右边来做先决条件检查:
// val address: String? = null
val address: String? = "wuhan"
val s = address ?: fail("No address")
6.3 数组与集合
代码语言:javascript复制 /**-------------------- 6.3.1 可空性和集合 ----------------------*/
// 代码清单6.21 创建一个包含可空值的集合
// 从一个文件中读取文本行的列表,并尝试把每一行文本解析成一个数字
fun readNumbers(reader: BufferedReader): List<Int?> {
// 创建包含可空Int值的列表
val arrayList = ArrayList<Int?>()
for (line in reader.readLine()) {
try {
val toInt = line.toInt()
// 向列表添加整数(非空值)
arrayList.add(toInt)
} catch (e: NumberFormatException) {
// 向列表添加null
arrayList.add(null)
}
}
return arrayList
}
// 代码清单6.22 使用可空值的集合
fun addValidNumbers(numbers: List<Int?>) {
var sumOfValidNumbers = 0
var invalidNumbers = 0
for (number in numbers) {
if (number != null) {
sumOfValidNumbers = number
} else {
invalidNumbers
}
}
println("sumOfValidNumbers: $sumOfValidNumbers")
println("invalidNumbers: $invalidNumbers")
}
val bufferedReader = BufferedReader(StringReader("1nabcn42"))
// [1,null,42]
val readNumbers = readNumbers(bufferedReader)
addValidNumbers(readNumbers)
// sumOfValidNumbers: 43
// invalidNumbers: 1
// 代码清单6.23 对包含可空值的集合使用 filterNotNull
fun addValidNumbers2(numbers: List<Int?>) {
// 类型为List<Int>,因为过滤保证了不会出现null
val filterNotNull: List<Int> = numbers.filterNotNull()
println("sum of valid numbers:: ${filterNotNull.sum()}")
println("Invalid numbers: ${numbers.size - filterNotNull.size}")
}
/**-------------------- 6.3.2 只读集合和可变集合 ----------------------*/
/*
* Kotlin的集合设计和Java不同的另一项重要特质是,它把访问集合数据的接口和修改集合数据的接口分开了。
* 一般的规则是在代码的任何地方都应该使用只读接口,只在代码需要修改集合的地方使用可变接口的变体。
*
* Collection
* - size
* - iterator()
* - contains()
* MutableCollection 继承Collection
* - add()
* - remove()
* - clear()
*/
// 代码清单6.24 使用只读集合接口与可变集合接口
fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>) {
// 在source集合中的所有元素中循环
for (item in source) {
// 想可变的target集合中添加元素
target.add(item)
}
}
val source: Collection<Int> = arrayListOf(3, 5, 7)
val target: MutableCollection<Int> = arrayListOf(1)
copyElements(source, target)
println(target)// [1,3,5,7]
// 只读集合不一定是不可变的,只读集合并不总是线程安全的。
/**-------------------- 6.3.3 Kotlin集合和Java ----------------------*/
/*
* 集合创建函数
* 集合类型 只读 可变
* List listOf mutableListOf、arrayListOf
* Set setOf mutableSetOf、hashSet、linkedSetOf、sortedSetOf
* Map mapOf mutableMapOf、hashMapOf、linkedMapOf、sortMapOf
*
* Java并不会区分只读集合与可变集合,即使Kotlin中把集合声明成只读的,Java代码页能够修改这个集合。
*/
/*Java*/
// public static class CollectionUtils {
// public static List<String> uppercaseAll(List<String> items) {
// for (int i = 0; i < items.size(); i ) {
// items.set(i, items.get(i).toLowerCase());
// }
// return items;
// }
// }
// 声明只读的参数
fun printInUppercase(list: List<String>) {
// 调用可以修改集合的Java函数
println(JavaCode.CollectionUtils.uppercaseAll(list))
// 打印被修改过的函数
println(list.first())
}
val listOf = listOf("a", "b", "c")
printInUppercase(listOf)// [A,B,C] A
/**-------------------- 6.3.4 作为平台类型的集合 ----------------------*/
/*
* 集合是否可空?
* 集合中的元素是否可空?
* 你的方法会不会修改集合?
*/
// 代码清单6.25 使用集合参数的Java接口r
// public interface FileContentProcessor {
// void processContents(File path, byte[] binaryContents, List<String> textContents);
// }
// 代码清单6.26 FileContentProcessor的kotlin实现
class FileIndexer : JavaCode.FileContentProcessor {
override fun processContents(path: File, binaryContents: ByteArray?, textContents: MutableList<String>?) {
}
}
// 代码清单6.27 另一个使用集合参数的Java接口
// public interface DataParser<T> {
// void parseData(String input, List<T> output, List<String> errors);
// }
// 代码清单6.28 DataParser的kotlin实现
class PersonParser : JavaCode.DataParser<Object4Activity.Person> {
override fun parseData(input: String, output: MutableList<Object4Activity.Person>, errors: MutableList<String?>?) {
// 默认是都会为空、集合元素不为空、方法会修改集合。
// 需要根据具体场景配置设置
}
}
/**-------------------- 6.3.5 对象和基本数据类型的数组 ----------------------*/
fun main(args: Array<String>) {
// 使扩展属性 array.indices 在下标的范围内迭代
for (i in args.indices) {
// 通过下标使用array[index]访问元素
println("Argument $i is: ${args[i]}")
}
}
// 代码清单6.30 创建字符数组
val array = Array<String>(26) { i -> ('a' i).toString() }
println(array.joinToString(""))// abcd...
// 代码清单6.31 向vararg方法传递集合
val listOf1 = listOf("a", "b", "c")
// 期望vararg参数时使用展开运算符 (*) 传递数组
println("%s%s%s".format(*listOf1.toTypedArray()))
// 创建存储了5个0的整型数组的两种选择:
val fiveZeros = IntArray(5)
val intArrayOf = intArrayOf(0, 0, 0, 0, 0)
// 接收lambda的构造方法的例子
val intArray = IntArray(5) { i -> (i 1) * (i 1) }
println(intArray.joinToString())// 1,4,9,16,25
// 代码清单6.32 对数组使用 forEachIndexed
fun main2(args: Array<String>) {
args.forEachIndexed { index, element -> println("Argument $index is: $element !!!!") }
}
总结
- Kotlin 对可空类型的支持,可以帮助我们在编译期,检测出潜在的NullPointerException错误。
- Kotlin 提供了像安全调用(?.)、 Elvis 运算符(?:)、非 言( !!)及let 函数这样的工具来简洁地处理可空类型。
- as ?运算符提供了 种简单的方式来把值转换成 个类型,以及处理当它拥有不同类型时的情况。
- Java 中的类型在 Kotlin 中被解释成平台类型,允许开发者把它们当作可空或非空来对待。
- 表示基本数字的类型(如 Int )看起来用起来都像普通的类,但通常会被编译成 Java 基本数据类型。
- 可空的基本数据类型(如 Int ?)对应着 Java 中的装箱基本数据类型(如java.lang.Integer )。
- Any 类型是所有其他类型的超类型,类 Java Object 。而 Unit 类比于void
- 不会正常终止的函数使用 Nothing 类型作为返回类型。
- Kotlin 使用标准 Java 集合类,并通过区分只读和可变集合来增强它们。
- 当你在 Kotlin 中继承 Java 类或者实现 Java 接口时,你需要仔细考虑参数的可空性和可变性。
- Kotlin的Array 类就像普通的泛型类 但它会被编译成 Java 数组。
- 基本数据类型的数组使用像 IntArray 这样的特殊类来表示。