kotlin的类型系统相比Java,引入了一些新特性,它们是提升代码的可读性的基本要素之一,比如对可空类型和只读集合的支持。与此同时,kotlin去除了Java类型系统中不必要的或者有问题的特性,比如把数组作为头等公民来支持。
可空性
kotlin对可空类型是显示的支持。这意味着 必须在程序中指出哪些属性和变量允许为null。如果一个变量为null,对变量的调用就是不安全的,kotlin不允许这样做,这样就能阻止很多不必要的异常。
fun strLen(s:String)=s.length
//伪代码,kotlin里null必须显示声明
strLen(null)
伪正确姿势
//?显示声明可以为空,可用于任何类型
fun strLen(s:String?)=s.length //s.length这里会抛异常
为什么说是伪正确姿势?因为s.length在这里是不允许被调用的,因为一旦声明为可空类型,那么能对他的操作也被进行了限制(比如不能调用它的方法,赋值给其它非空变量,也不能把它传给拥有非空的参数的函数)。
不雅的正确姿势
//增加null检查
fun strLen(s:String?)= if (s!=null) s.length else 0
println(strLen(null)) //0
值得一提的是,作null检查后,编译器会自动记住在比较的作用域里当作非空来对待。但是为什么不雅?因为这样代码很容易变得冗长,我们并不是在写Java。
那么优雅是怎么优雅的呢?
安全调用运算符:?.
安全调用运算符是kotlin里最有效的工具之一。它允许把一次null检查和方法合并成一个操作。
优雅的正确姿势
//安全调用
fun strLen(s:String?)= s?.length
val test = "kotlin"
println(strLen(test)) //6
//安全调用值如果是null,调用不会发生,并认定整个表达式为null
println(strLen(null)) // null
需要注意的是,安全调用的结果类型也是可空的,上面代码如果不为空调用结果类型是Int,但是如果为空结果就是Int?。
安全调用不光可以调用方法,也可以用来访问属性。
class Emoloyere(val namee:String,val manager:Emoloyere?) {
}
fun managerName(emoloyere: Emoloyere):String?=emoloyere.manager?.namee
fun main(args: Array<String>) {
val ceo = Emoloyere("ceo",null)
val boss = Emoloyere("boss",ceo)
println(managerName(ceo))//null
println(managerName(boss))//ceo
}
如果对象中有多个可空类型,通常可以在一个表达式中方便的使用多个?.。
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.countName():String{
//多个安全调用链接一起
return company?.address?.country ?: "Sorry"
}
fun main(args: Array<String>) {
val person = Person("czz", null)
println(person.countName())//Sorry
}
Elivs运算符 ?:
上面代码里笔者已经用上了?:,它被称作Elivs运算符,又称null合并运算符。它用来提供代替null的默认值。
?:经常和?.一起使用。
fun main(args: Array<String>) {
println(strLenSafe("cbc")) //3
println(null)//null
}
fun strLenSafe(s: String?): Int = s?.length ?: 0
当然,?:也可以配合rtrun,throw这样的表达式使用。这里就不再举例子。
安全转换:as?
顾名思义,即可以把值转成指定类型,如果不是适合类型返回为null。
举个例子不细说
class People(val firstName: String, val lastName: String) {
override fun equals(other: Any?): Boolean {
//检查类型,匹配返回转换类型,不匹配就返回false
val otherPeople = other as? People ?: return false
//检查类型确定后,会智能转换
return otherPeople.firstName == firstName && otherPeople.lastName == lastName
}
override fun hashCode(): Int {
return firstName.hashCode() * 37 + lastName.hashCode()
}
}
fun main(args: Array<String>) {
val people1 = People("JoJo","BoBo")
val people2 = People("BoBo","JoJo")
println(people1==people2) //false
}
非空断言:!!
非空断言不细说,也是简单一提,只会应用在特定情况下,比如在一个函数里检查值是否为null,然后在另一个函数里使用这个值,编译器无法识别是否安全。笔者个人认为能尽量避免使用就避免使用。
fun String.getLen( string: String?){
//伪代码,!!非空断言强制把可空转为非空
val s = string!!.length
}
可空值实参给非空函数:let函数
可空参数最常见的用法是被传递给非空函数。let函数配合?:让处理可空表达式函数更加容易。
fun main(args: Array<String>) {
val email: String? = null;
//let只会在非空情况下调用,即let调用后,内部一定非空
email?.let { sendEmailTo(email) }
}
fun sendEmailTo(email: String) {
}
值得一提的是,检查多个null值使用let嵌套会显得难以理解甚至啰嗦,用if表达式判断反而更简单直观。
延迟初始化属性的修饰符:lateinit
Kotlin通常要求你在构造方法中初始化所有属性,如果某个属性是非空类型,必须提供非空初始化值,否则必须使用可空类型。可空类型就表明每次访问属性的时候必须要做null检查或者!!非空断言。
lateinit修饰符属性声明可以延迟初始化,正是用来解决这样的问题。
class MyTest{
//使用lateinit来延迟初始化
private lateinit var myService: MyService
fun setUp(){
//初始化属性
myService=MyService()
}
}
延迟初始化的属性都是var,因为需要在构造方法外修改它的值,而val声明是会被编译成final字段。尽管这个属性是非空类型,但是lateinit使你并不需要在构造方法中就去初始化它。如果在属性未被初始化之前访问它,编译器会像你抛出异常。
可空类型的扩展
为可空类型定义扩展函数是一种更强大处理null值的方式。可以允许接收者为null的扩展函数调用,并在该函数中去处理null。只有扩展函数能做到这一点,普通成员方法是通过对象实例来触发的,而实例为null时则永远也不会执行。
kotlin中String的两个扩展函数isEmpty(判断是否是个空字符串"")和isBlank(null或者空白字符)就是如此。通常这些函数简洁方便又有价值。而isEmptyOrNull和isNullOrBlank也可以由String?类型的接收者调用。
fun main(args: Array<String>) {
verifyUserInput("") //please fill in the required fields
verifyUserInput(null)//please fill in the required fields
}
fun verifyUserInput(input:String?){
//不需要安全调用
if(input.isNullOrBlank()){
println("please fill in the required fields")
}
}
不需要安全访问,可以直接调用可空接收者声明的扩展函数,这个函数会处理可能的null值。至于isBlank只能在非空String上使用。
fun String?.isNullBlank():Boolean=
this==null||this.isBlank()
当为一个可空类型(以?)结尾定义扩展函数时,这意味着可以对可空值调用这个函数,并且函数体中this可能为null,所以必须显示检查。java中this永远是非空的,而在kotlin中,这并不成立,在可空类型扩展函数里,this可以为null。
注意,之前提到的let函数也是可以被可空接收者使用,但是它并不检查值是否null。如果你在一个可空类型上直接调用let函数,而没有使用安全调用运算符,lambda实参是可能为空的。
val person:Person?=null
//会抛异常
person.let { it.sendEmailTo(it)}
//正确姿势
person?.let { it.sendEmailTo(it) }
一般定义扩展函数时候,应该首选非空,如果发现大部分使用情况需要可空,在进行安全修改。
类型参数的可空性
Kotlin中所有泛型和泛型函数的类型参数默认都是可空的。任何类型包括可空类型在内,都可以替换类型参数。这种情况下,使用类型参数作为类型声明都允许为null,尽管类型参数T并没有?结尾。
fun main(args: Array<String>) {
println(printHashCode(null))//null
}
//这里T被推导为Any?,可能为null,必须安全调用
fun <T> printHashCode(t:T){
println(t?.hashCode())
}
为类型参数声明非空上界
fun main(args: Array<String>) {
//异常,不能传递null值
//Error:(3, 13) Kotlin: Type parameter bound for T in fun <T : Any> printHashCode(t: T): Unit
// is not satisfied: inferred type Nothing? is not a subtype of Any
println(printHashCode(null))
//正确姿势
println(printHashCode(42))//42
}
//这里T不允许为空
fun <T:Any> printHashCode(t:T){
println(t.hashCode())
}
必须使用问好结尾来标记类型为可空,没有问好就是非空。类型参数是这个规则的唯一列外。
可空性和Java
当kotlin和Java交互时候,因为Java是不支持可空类型的,所以需要用注解来表达。@Nullable String被kotlin理解为String?.。而@NotNull String就是kotlin的String(非空)。
而当注解不存在的时候,Java类型就会变成Kotlin的平台类型。
平台类型本质上就是kotlin并不知道的类型。既可以看作可空类型也可以看作非空类型。这意味着必须像在Java里一样在此类型上负责。编译器会允许所有操作,这需要像在Java里使用它那样去使用,否则就会遇到NullPointerException。
//java类型在kotlin中既可以当作可空类型也可以当作非空类型。
type(java) = type? or type
举个例子
//一般的java class 没有可空性注解
class Person {
private final String name;
public Person(String name) {
this.name = name;
}
//kotlin编译器判断不出来是否为空
//即自己要小心处理
public String getName() {
return name;
}
}
正确姿势,kotlin调用java使用null检查
fun main(args: Array<String>) {
val person:Person =Person(null)
yellAtSafe(person)//ISNULL
}
fun yellAtSafe(person: Person){
println((person.name?:"isNull").toUpperCase())
}
个人建议,kotlin在与javaApi互动中最好像在java中那样进行繁琐的null检查,尽管kotlin是null安全的。
平台类型在kotlin表示为type!(如String!),但是在kotlin中是禁止声明平台类型的变量的,平台类型只能来自java,如果要表示平台类型只有两个选择
//可空
val name:String? = person.name
//非空
val name1:String =person.name
kotlin与java混合的陷阱(继承)
因为平台类型的介入,导致问题复杂了起来。混合的kotlin与java类层级关系存在陷阱。当在kotlin中重写java方法时候,可以选择把参数和返回类型定义成可空,也可以定义成非空。
//java接口
public interface StringProcessor {
void process(String value);
}
//kotlin可空调用
class StringPrinter:StringProcessor {
override fun process(value: String?) {
println(value)
}
}
//kotlin非空调用
class StringPrinter:StringProcessor {
override fun process(value: String?) {
if(value!=null){
println(value)
}
}
}
kotlin在实现java类或者接口方法时候一定要清楚它的可控性。因为方法的实现可以在非kotlin代码中调用,kotlin编译器会为非空声明生成非空断言,如果java传入的是空,非空断言触发异常,即时从来没有去访问过这个值。
类型系统总结
关于kotlin的可空性和非空性处理可使用安全调用运算符?.,Elvis运算符?:,安全转换运算符as?
,也有粗暴的非安全运算符非空断言!!。let函数能更进一步简化非空检查。基于平台类型需要谨慎处理。
kotlin基本数据类型和其他基本类型
Kotlin的基本类型如Int,Boolean和Any,它并不会像java一样去区分基本数据类型和他们的包装类。
基本数据类型
Java把基本数据类型和引用类型做了区分(基本类型直接存储值,引用类型为内存地址的引用)。基本数据类型值能更高效的存储和传递,但是不能调用方法和存放集合。而在kotlin里,你使用的永远是同一个类型。
//永远是同一种类型
val i :Int =1;
val list:List<Int> = listOf(i);
也可以直接对数字类型值调用方法。
fun showProgress(progress:Int){
//Int的函数coerceIn把值限定在特定范围
val percent = progress.coerceIn(0,100);
println("Ok!$percent%")
}
showProgress(146);//Ok!100%
基本数据如果与引用类型是一样的,意味着值必须使用对象来表示,这非常低效,所以kotlin并非是这种处理方式。在运行时,数字类型会尽可能使用最高效的方式表示。大多数情况下,kotlin的基本类型都会被编译成Java的基本数据类型(如Int直接编译为int),唯一不行的是泛型类(比如集合),泛型类的基本数据类型会被编译成java的包装类型(List<Int>编译成List<Integer>)。
整数类型 ——Byte,Short,Int,Long
浮点数类型 ——Float,Double
字符类型——Char
布尔类型——Boolean
Kotlin类型在底层可以轻易地编译成对应Java数据类型(都不能存储null引用)。反之,在Kotlin中使用java声明(java基本数据类型同样变成非空,非平台类型)。
可空的基本数据类型
Kotlin中的可空类型不能用Java的基本数据类型表示(null存在java引用类型中),这意味着可空的数据类型会被编译成对应的包装类型。
//年龄未知(可空)
data class Person(val name:String,val age:Int?=null) {
//比较年龄大小
fun isOlderThan(other:Person):Boolean?{
//不能直接比较age(会被认为是Java的Integer类型),必须进行null检查
if (age==null||other.age==null)return null
return age>other.age
}
}
//true
println(Person("Bob",27).isOlderThan(Person("Joj",26)))
//null
println(Person("Bob",27).isOlderThan(Person("Joj")))
上述代码中,如果age并非来自Java类时,不用去区分是否包装类型。只需要在kotlin中选出正确类型(null是否可能是它们可能的值)。
但是,如前文所提,泛型类是包装类型应用的另一种情况。如果使用基本数据类型作为泛型类型参数,kotlin会使用该类型的包装形式。
//虽然没有声明可空和使用null,但是默认Integer包装
val list = listOf(1,2,3,4)
造成这样的原因是因为java虚拟机实现泛型的方式决定的(不支持基本数据类型)。因此如果要高效的存储基本数据类型元素的大型集合,建议使用数组。
数字转换
kotlin与java在处理数字转换上有很大不同。kotlin不会自动把数字从一种类型转成另外一种,即时是转换成范围更大的类型。
伪代码
val i =1;
val l:Long = i;
正确姿势
//必须显示转换
val i = 1
val l:Long=i.toLong()
每一种基本数据类型(Boolean除外)都定义有转换函数,这些函数同时也支持双向转换:既可以把小范围的类型扩展到大范围(如Int.toLong()),也可以把大范围的类型截取到小范围,比如Long.toInt()。
为了避免意外情况,kotlin要求转换必须是显式的,尤其是在比较装箱值的时候。比较两个装箱值的equase方法不仅会检查它们存储的值,还要比较装箱类型(如new Integer(42).equals(new Long(42))返回false)。
val i = 1
//如果同时用到不同类型,必须显示地转换。
println(i.toLong() in listOf(1L,2L,3L))//true
kotlin除了简单的十进制数字外,同时也是支持书写数字字面值(同Java的表现形式)的方式。当书写数字字面值的时候,一般不需要使用转换函数。因为当使用数字字面值去初始化一个类型已知变量的时候又或者把字面值作为实参传给函数时,必要的转换会自动发生。此外,算术运算符也被重载了,它们可以接收所有适当的数字类型。
fun foo(l: Long) = println(l)
//常量有正常的类型
val b: Byte = 1
//+可以进行字节类型和长整形参数的计算
val s = b + 1L
foo(s)//2
注意的是,Kotlin算术运算符关于数值范围溢出行为和Java完全一致:Kotlin并没有引入由溢出检查带来的额外开销。
kotlin标准库也提供了一套相似的扩展方法,用来把字符串转换成基本数据类型(toInt(),toByte(),toBoolean()等)。
Any和Any?:根类型
Any类型是Kotlin所有非空类型的超类型(非空类型的根),Java的是Object。java的基本数据类型不包含在其中,不属于类层级结构的一部分,而在Kotlin中,Any是所有类型的超类型(所有类型的根),包括基本数据类型。
//Any是引用类型,所以值42会被装箱
val answer:Any = 42
注意的是Any是非空类型,所以Any类型的变量不可以持有null值。如果需要可以持有任何可能值的变量,包括null在内,必须使用Any?类型。
在底层,Any类型对应java的Object。kotlin把Java方法参数会和返回参数类型中用到的Object类型看作Any(平台类型,可空性未知),即kotlin的Any类型会被编译成Java字节码的Object。
Any并不能调用Object的方法。所以Kotlin里的所有类包含的toString,equase,hashCode方法都继承自Any。如需调用需要手动转换。
Unit类型
Kotlin中的Unit类型与Java里void一样功能。Unit在函数式编程语言中习惯上被用来表示"只有一个实例"。
//显式
fun say():Unit{
println("say")
}
//隐式
fun say(){
println("say")
}
kotlin在底层会被编译成java的void,反之在java中使用带Unit的函数就必须要声明void了。不同的区别在于Unit在kotlin里是一个完备的类型,可以作为类型参数。只存在一个值是Unit类型,这个值也叫Unit,并被隐式返回。
这在重写返回泛型参数函数时非常漂亮。
interface Processor<T> {
fun process():T
}
class NoResultProcessor:Processor<Unit>{ //返回Unit类型可以被省略
override fun process() {
//不需要显示return,如果是Java则需要强制return null
}
}
Nothing:永不返回
"返回类型"对kotlin来说毫无意义,因为它们从来不会成功结束。所以Kotlin使用Nothing来表示这种特殊的返回类型。
/**
* Nothing类型没有任何值,只能被当作函数返回值使用
* 或者被当作泛型函数返回值的类型参数使用才有意义。
*/
fun fail(message:String):Nothing{
throw IllegalStateException(message)
}
fail("Error") //Exception in thread "main" java.lang.IllegalStateException: Error
当然,返回Nothing的函数可以放在Elvis(?:)运算符右边做先决条件检查。
val address = company.address ?: fail("No")
编译器会知道Nothing这样的返回类型从不会正常终止,所以会果断推断成非空,因为为null就会抛出异常。
可空性和集合
kotlin的集合可以持有null元素,即支持可空性,表现与变量表现形式相同,如List<Int?>。
fun readNumbers(reader: BufferedReader):List<Int?>{
//创建包含可空Int值的列表
val result = ArrayList<Int?>()
for (line in reader.lineSequence()){
try {
val number =line.toInt()
//像列表添加非空整数值
result.add(number)
}
catch (e:NumberFormatException){
//像列表添加null,null不能被解析成整数
result.add(null)
}
}
return result;
}
上述代码可以使用String的扩展函数String.toIntOrNull来简化。
注意的是,变量自己类型可空性和用作类型参数的类型的可空性是有区别的。List<Int?>是能持有Int?类型值的列表(可以持有Int或者null),集合本身不为空,而List<Int>?表示集合本身可空(包含空引用),但是持有的元素不能为空(持有Int,不能持有null)。如果既需要本身可空和持有元素为空的时候,应该这样声明:List<Int?>?。
fun main(args: Array<String>) {
val rangeModel = BufferedReader(StringReader("1\nabc\n42"))
val numbers = readNumbers(rangeModel)
addValidNumbers(numbers)
}
fun addValidNumbers(numbers:List<Int?>){
var sumOfValidNumbers=0
var invalidNumbers=0
//从列表中读取可空值
for (number in numbers){
//检查值是否为null
if (number!=null){
sumOfValidNumbers+=number
}else{
invalidNumbers++
}
}
println("Sum of void numbers:$sumOfValidNumbers")
println("Invalid numbers:$invalidNumbers")
}
fun readNumbers(reader: BufferedReader):List<Int?>{
//创建包含可空Int值的列表
val result = ArrayList<Int?>()
for (line in reader.lineSequence()){
try {
val number =line.toInt()
//像列表添加非空整数值
result.add(number)
}
catch (e:NumberFormatException){
//像列表添加null,null不能被解析成整数
result.add(null)
}
}
return result;
}
如上代码所示,如果持有可空元素时,使用前必须进行null检查。
遍历一个包含可空值的集合并过滤掉null是一个非常常见的操作,因此kotlin提供了一个标准库函数filterNotNull函数。
//简化
fun addValidNumbers(numbers:List<Int?>){
//此行为影响了集合类型,由可空变为非空(空被过滤了)
val validNumbers = numbers.filterNotNull()
println("Sum of void numbers:${validNumbers.sum()}")
println("Invalid numbers:${numbers.size-validNumbers.size}")
}
只读集合与可变集合
kotlin的集合与java不同的另一项重要特质是把访问集合数据接口和修改数据集合的数据接口分开。
kotlin.collections.Collection是最基础的集合接口,可以遍历,获取集合大小,判断集合是否包含元素以及执行其它从该集合中读取数据操作,然而并不支持修改(添加和删除)。
kotlin.collections.MutableCollection接口继承Collection,可以修改(添加,删除,清空)。
一般在任何地方都应该使用只读接口,只有在修改的地方使用可变体。这样分离的好处在于会更容易理解程序。知道它在何时是可以修改何时是不变的。如果用了这种组合方式,需要把集合先拷贝一份在传递(防御式拷贝)。
fun main(args: Array<String>) {
val source:Collection<Int> = arrayListOf(3,5,7)
val target :MutableCollection<Int > = arrayListOf(1)
copyElements(source,target)
println(target)//[1, 3, 5, 7]
}
fun <T> copyElements(source:Collection<T>,target: MutableCollection<T>){
//遍历不可变集合
for (item in source){
//像可变集合添加元素
target.add(item)
}
}
不能把只读集合类型变量作为可变集合参数传递。
//伪代码
val target :Collection<Int> = arrayListOf(1) // 异常
需要注意的是,使用集合接口有一个关键点是只读集合不一定是不可变的。如果使用的变量是一只读接口类型,它可能是同一个集合众多引用中的一个。任何其它引用都可能有一个可变接口类型。即只读集合并不总是线程安全的。如果在多线程环境下处理数据,需要保证正确同步了对数据的访问或使用支持并发的数据结构,否则做好接收异常准备。
kotlin集合和Java
kotlin接口仍是对应Java集合接口的一个实例。不过每个Java集合接口在kotlin中都有两种表示:只读和可变。
kotlin集合创建函数表
集合类型 | 只读 | 可变 |
---|---|---|
List | listOf | mutableListOf,arrayListOf |
Set | setOf | mutableSetOf,hashSetOf,linkedSetOf,sortedSetOf |
Map | mapOf | mutableMapOf,hashMapOf,linkedMapOf,sortedMapOf |
当调用Java方法并把集合作为实参传给它时候,不需要额外步骤。因为Java并不会去区分只读集合和可变集合。所以在kotlin声明里为只读在Java里也是可以修改的。
//java类
class CollectionUtils {
public static List<String> uppercaseAll(List<String> items){
for (int i=0;i<items.size();i++){
items.set(i,items.get(i).toUpperCase());
}
return items;
}
}
//kotlin函数
fun printInUppercase(list: List<String>){ //参数声明为只读
//这里却调用java函数可以修改集合
println(CollectionUtils.uppercaseAll(list))
println(list.first())
}
fun main(args: Array<String>) {
val list = listOf("1","2","3")
println(printInUppercase(list))//[1, 2, 3],1
}
上述代码是跨语言兼容的kotlinJava代码,可以看出,kotlin函数使用集合并传递给java,必须正确使用参数类型,取决于是否在java代码里去修改集合。空类型和非空类型同理。
平台类型的集合
平台类型的集合指kotlin里使用的Java集合类型,需要应对的集合是否可空?集合中的元素是否可空,方法会不会修改集合?只有考虑到这些问题才能确保类型正确保证程序健壮。
/**
* kotlin实现这个接口需要考虑
* 1.集合可能为空
* 2.集合元素非空,因为文件每一行都不会null
* 3.集合是只读的,因为文件内容不会允许被修改
*/
interface FileContentProcessor {
void processContents(File path, byte[]binaryContents, List<String> textContents);
}
class FIleIndexer:FileContentProcessor {
//idea的实现
override fun processContents(path: File?, binaryContents: ByteArray?, textContents: MutableList<String>?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
//具体情况具体分析,可以手动更改来达到更好的效果(这里按上述分析改动)
override fun processContents(path: File, binaryContents: ByteArray?, textContents: List<String>?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
当然,可以如上所示默认平台类型,简单粗暴。也可以具体分析做出更改,不过这要求Java接口或类必须遵守确切契约。
对象和基本数据类型的数组
fun main(args: Array<String>) {
for (i in args.indices){ //使扩展属性array.indices在下班范围内迭代
println("Argument $i is : ${args[i]}") //通过下标使用array[index]访问元素
}
}
kotlin中数组是一个带有类型参数的类,其元素类型被指定为相应的类型参数
val strs = arrayOf("1","2","3") //创建一个数组,包含元素是指定该函数的实参
//创建一个给定大小的数组,包含null元素(只能创建可空性数组)
val str:Array<String?> = arrayOfNulls(3)
//Array构造接收数组大小和一个lambda表达式。
//lambda表达式创建每一个数组元素,非空元素类型来初始化数组,不用显示传递每个元素
val str1 = Array<String>(26){i -> ('a'+i).toString() }
println(str1.joinToString(""))//abcdefghijklmnopqrstuvwxyz
lambda接收数组元素下标并返回放在数组下标的位置的值。编译器能推导出类型,所以类型可省略。
kotlin最常见创建数组情况之一是需要调用参数为数组的java方法时候,或调用vararg参数的kotlin函数时,通常将数据存储在集合中,在转换成数组,toTypeedArray可以简单快速实现。
val string= listOf("a","b","c")
//期望vararg参数时使用展开运算符*传递数组
println("%s/%s/%s".format(*string.toTypedArray()))//a/b/c
和其它类型相同,数组类型的类型参数始终会变成对象类型(如Array<Int>在Java类型是Integer)。如果需要创建没有包装过的基本数据类型,必须使用基本数据类型数组的特殊类。
kotlin也因此创建了若干独立类,每一种基本数据类型对应一个(如IntArray)它们都会被编译成java的基本数据类型数组。如果基本类型反推包装过类型只许调用toxxArray(如toIntArray)。
//IntArray创建数组
val fiveZeros =IntArray(5)
//工厂函数创建
val fiveZerosTo = intArrayOf(0,0,0,0,0)
//lambda构造
val fiveZeroL =IntArray(5){i ->(i+1) }
println(fiveZeroL.joinToString(""))//12345
数组除了基本操作(获取长度,或者获取元素)外,kotlin通过标准库支持一套和集合相同的扩展函数用于数组(注意返回类型是集合而不是数组)
fun main(args: Array<String>) {
//对数组使用forEachIndexed
args.forEachIndexed { index, s -> println("Argument $index is: $s") }
}
如上,kotlin中可以像使用集合一样使用数组。
总结,略。