函数
函数定义与调用
//定义函数
fun < T > joinToString ( collection : Collection < T > , separator : String ,
prefix : String , postfix : String ): String {
val result = StringBuilder ( prefix )
for (( index , element ) in collection . withIndex ()){
if ( index > 0 ) result . append ( separator )
result . append ( element )
}
result . append ( postfix )
return result . toString ()
}
val list = listOf ( 1 , 2 , 3 )
println ( joinToString ( list , ";" , "(" , ")" ))
参数
Kotlin参数是val类型的不可修改。
命名参数
当调用一个kotlin
定义的函数时,可以显式地标明一些参数的名称。如果在调用一个函数时,指明一个参数的名称,为了避免混淆,那它之后的所有参数都要标明名称。当调用Java的函数时,不能采用命名参数。
println ( joinToString ( list , separator = ";" , prefix = "(" , postfix = ")" ))
默认参数值
在Kotlin
中,可以在声明函数的时候,指定参数的默认值,这样就可以避免创建重载的函数。
fun < T > joinToString ( collection : Collection < T > ,
separator : String = "," ,
prefix : String = "" ,
postfix : String = "" ): String {
//...
}
println ( joinToString ( list )) // 1,2,3
println ( joinToString ( list , ";" )) //1;2;3
println ( joinToString ( list , ";" , "(" , ")" )) //(1;2;3)
println ( joinToString ( list , prefix = "(" )) //(1,2,3
当使用常规的调用语法时,必须按照函数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数。如果使用命名参数,可以省略中间的一些参数,也可以以你想要的任意顺序只给定你需要的参数:
当你从Java
中调用Kotlin
函数的时候必须显式地指定所有参数值。如果需要从Java代码中做频繁的调用,而且希望它能对Java
的调用者更简便,可以用@JvmOverloads
注解它。这个指示编译器生成如下重载函数。
扩展函数需要进行导入才能使用它。
可变参数
fun < T > asList ( vararg ts : T ): List < T > {
val result = ArrayList < T > ()
for ( t in ts ) // ts is an Array
result . add ( t )
return result
}
val list = asList ( 1 , 2 , 3 )
调用 vararg
函数时,可以单独传递参数,例如 asList(1, 2, 3)
。如果您已经有一个数组并希望将其内容传递给函数,请使用 spread 运算符(在数组前面加上 *
):
val a = intArrayOf ( 1 , 2 , 3 ) // IntArray is a primitive type array
val list = asList ( - 1 , 0 , * a . toTypedArray (), 4 )
中缀表示法
//定义
public infix fun < A , B > A . to ( that : B ): Pair < A , B > = Pair ( this , that )
//调用
val pair = "a" to "b"
顶层函数和属性
package strings
fun joinToString (...): String {...}
这里它会编译成如下的Java代码:
package strings ;
public class JoinKt {
public static String joinToString (...){...}
}
可以看到Kotlin编译生成的类的名称,对应于包含函数的文件的名称。这个文件中的所有顶层函数编译为这个类的静态函数。
要改变包含Kotlin顶层函数的生成的类的名称,需要为这个文件添加@JvmName的注解,将其放到这个文件的开头,位于包名的前面:
@file : JvmName ( "StringFunctions" )
package strings
顶层属性
const val UNIX_LINE_SEPARATOR = "\n"
扩展函数和属性
添加扩展函数就是把你要扩展的类或者接口的名称,放到即将添加的函数前面。这个类的名称被称为接收者类型;用来调用这个扩展函数的那个对象,叫做接收者对象。
fun String . lastChar (): Char = this . get ( this . length - 1 )
println ( "Kotlin" . lastChar ()) // n
在扩展函数中,可以像其他成员函数一样用this。而且也可以像普通的成员函数一样,省略它。
fun String . lastChar (): Char = get ( length - 1 )
在扩展函数中,可以直接访问被扩展的类的其他方法和属性,就好像是在这个类自己的方法中访问它们一样。和在类内部定义的方法不同的是,扩展函数不能访问私有的或者受保护的成员。
3.3.1 导入和扩展函数
import strings.lastChar
//import strings.* //也可以用*来导入
println ( "Kotlin" . lastChar ()) // n
可以使用关键字as来修改导入的类或者函数名称,可以避免重名函数的冲突。
import strings.lastChar as last
println ( "Kotlin" . last ()) // n
3.3.2 从Java中调用扩展函数
和顶层函数一样,包含这个函数的Java类的名称,是由这个函数声明的文件名称决定的。假设它声明在一个叫做StringUtil.kt的文件中
char c = StringUtilKt . lastChar ( "Java" );
3.3.3 作为扩展函数的工具函数
fun < T > Collection < T > . joinToString (
separator : String = "," ,
prefix : String = "" ,
postfix : String = "" ): String {
val result = StringBuilder ( prefix )
for (( index , element ) in withIndex ()){
if ( index > 0 ) result . append ( separator )
result . append ( element )
}
result . append ( postfix )
return result . toString ()
}
val list = listOf ( 1 , 2 , 3 )
println ( list . joinToString ( separator = ";" , prefix = "(" , postfix = ")" )) //(1;2;3)
扩展函数的静态性质也决定了扩展函数不能被子类重写。
3.3.4 不可重写的扩展函数
open class View {
open fun click () = println ( "View clicked" )
}
open class Button : View () {
override fun click () = println ( "Button clicked" )
}
val view : View = Button ()
view . click () //Button clicked
fun View . showoff () = println ( "I'm a view!" )
fun Button . showoff () = println ( "I'm a button!" )
val view : View = Button ()
view . showoff () //I'm a view!
3.3.4 扩展属性
val String . lastChar : Char
get () = get ( length - 1 )
println ( "Kotlin" . lastChar ) //n
val sb = StringBuilder ( "Kotlin?" )
sb . lastChar = '!'
println ( sb ) //Kotlin!
3.4 处理集合:可变参数、中缀调用和库的支持
3.4.1 扩展Java集合的API
3.4.2 可变参数:让函数支持任意数量的参数
Kotlin可变参数是在参数上使用vararg修饰符。
public fun < T > listOf ( vararg elements : T ): List < T > {...}
在Java
中可以按原样传递数组,而kotlin
则要求你显式地解包数组,以便每个数组元素在函数中能作为单独的参数来调用。从技术的角度来讲,这个功能被称为展开运算符
。
fun main ( args : Array < String > ) {
val list = listOf ( "args:" , * args )
println ( list ) //[args:, one, two, three]
}
3.4.3 键值对的处理:中缀调用和解构声明
val map = hashMapOf ( 1 to "one" , 7 to "seven" , 53 to "fifty-three" )
这行代码中的单词to不是内置的结构,而是一种特殊的函数调用,被称为中缀调用。
在中缀调用中,没有添加额外的分隔符,函数名称是直接放在目标对象名称和参数之间的。以下两种调用方式是等价的:
中缀调用可以与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。要允许使用中缀符号调用函数,需要使用infix修饰符来标记它。
public infix fun < A , B > A . to ( that : B ): Pair < A , B > = Pair ( this , that )
可以直接用Pair的内容来初始化两个变量:
val ( number , name ) = 1 to "one"
这个功能称为解构声明。用to函数创建一个pair,然后用解构声明来展开。
3.5 字符串和正则表达式的处理
3.5.1 分割字符串
String [] strings = "12.345-6.A" . split ( "." );
System . out . println ( strings . length ); //0
Java的split方法将一个正则表达式作为参数,并根据表达式将字符串分割成多个字符串。这里点(.)是表示任何字符的正则表达式。
Kotlin把这个令人费解的函数隐藏了,作为替换,提供了一些名为split的,具有不同参数的重载的扩展函数。用来承载正则表达式的值需要一个Regex类型,而不是String,这样确保了当有一个字符串传递给这些函数的时候,不会被当做正则表达式。
println ( "12.345-6.A" . split ( "\\.|-" . toRegex ())) //[12, 345, 6, A]
Kotlin中的split扩展函数的其他重载支持任意数量的纯文本字符串分隔符:
println ( "12.345-6.A" . split ( "." , "-" )) //[12, 345, 6, A]
3.5.2 正则表达式和三重引号的字符串
fun parsePath ( path : String ) {
val directory = path . substringBeforeLast ( "/" )
val fullName = path . substringAfterLast ( "/" )
val fileName = fullName . substringBeforeLast ( "." )
val extension = fullName . substringAfterLast ( "." )
println ( "Dir: $ directory , name: $ fileName , ext: $ extension " )
}
3.5.3 多行三重引号的字符串
3.6 让你的代码更简洁:局部函数和扩展
class User ( val id : Int , val name : String , val address : String )
fun saveUser ( user : User ){
//重复的字段检查
if ( user . name . isEmpty ()){
throw IllegalArgumentException (
"Can't save user ${ user . id } :empty Name" )
}
if ( user . address . isEmpty ()){
throw IllegalArgumentException (
"Can't save user ${ user . id } :empty Address" )
}
//保存user到数据库
}
fun saveUser ( user : User ) {
fun validate ( user : User , value : String , fieldName : String ){
if ( value . isEmpty ()) {
throw IllegalArgumentException (
"Can't save user ${ user . id } :empty $ fieldName " )
}
}
validate ( user , user . name , "Name" )
validate ( user , user . address , "Address" )
//保存user到数据库
}
因为局部函数可以访问所在函数中的所有参数和变量。我们可以利用这一点,去冗余的User参数。
fun saveUser ( user : User ) {
fun validate ( value : String , fieldName : String ){
if ( value . isEmpty ()) {
throw IllegalArgumentException (
"Can't save user ${ user . id } :empty $ fieldName " )
}
}
validate ( user . name , "Name" )
validate ( user . address , "Address" )
//保存user到数据库
}
fun User . validateBeforeSave (){
fun validate ( value : String , fieldName : String ){
if ( value . isEmpty ()) {
throw IllegalArgumentException (
"Can't save user $ id :empty $ fieldName " )
}
}
validate ( name , "Name" )
validate ( address , "Address" )
}
fun saveUser ( user : User ) {
user . validateBeforeSave ()
//保存user到数据库
}
May 23, 2023
October 12, 2018