kotlin
的函数使用fun关键字声明,如下所示:
fun double(x: Int): Int {
return 2 * x
}
double(2).tostring()
和java
不一样,kotlin
的函数参数采用pascal
表示法定义,即 name: type。
fun powerOf(number: Int, exponent: Int) { /*……*/ }
kotlin可以设置默认参数:
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*……*/ }
kotlin可以使用具名称的函数参数。使得代码可读性更强:
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
reformat(str, wordSeparator = '_')
具名参数和位置参数混用的时候,位置参数应该放在具名参数的前面,允许调用 f(1, y = 2)
但不允许 f(x = 1, 2)
。
可以通过使用星号操作符将可变数量参数(vararg) 以具名形式传入:
fun foo(vararg strings: String) { /*……*/ }
foo(strings = *arrayOf("a", "b", "c"))
当覆盖一个带有默认值的方法时,应该省略默认参数值:
open class A {
open fun foo(i: Int = 10) { /*……*/ }
}
class B : A() {
override fun foo(i: Int) { /*……*/ } // 不能有默认值
}
如果要指定某个参数的值,那么可以使用具名参数:
fun foo(bar: Int = 0, baz: Int) { /*……*/ }
foo(baz = 1) // 使用默认值 bar = 0
如果lambda表达式作为参数的最后一个值,那么他可以放在括号外面:
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*……*/ }
foo { println("hello") } // 使用两个默认值 bar = 0 与 baz = 1
Unit相当于java
中的void,即不返回任何值。
fun printHello(name: String?): Unit{ …… }
fun printHello(name: String?) { …… }//代码等同于上面
返回单个表达式的时候,可以直接把表达式写在等号后面。并且如果返回值类型可以由编译器推断的时候,那么返回值类型可以忽略。
fun double(x: Int): Int = x * 2
fun double(x: Int) = x * 2//Int类型可以省略
具有块代码体的函数必须始终显式指定返回类型。 Kotlin
不推断具有块代码体的函数的返回类型,因为这样的函数在代码体中可能有复杂的控制流,并且返回类型对于读者(有时甚至对于编译器)是不明显的。
中缀表示法有点像C++中的运算符重载。中缀表示法的标有infix关键字,它三个必要条件:
infix fun Int.add(x: Int): Int {
return this + x
}
fun main(args: Array<String>) {
println("Result = "+(1 add 2))
}
Kotlin
的泛型kotlin
泛型的写法和java
类似,都是将泛型写在尖括号内,并放在类后面。如下所示:
class Box<T>(t: T) {
var value = t
}
val box: Box<Int> = Box<Int>(1)
val box = Box(1)//kotlin会自动判断类型
还有一种就是泛型函数,写法类似:
fun <T> singletonList(item: T): List<T> {
// ……
}
val l = singletonList<Int>(1)
val l = singletonList(1)//kotlin会自动判断类型
对应于java
泛型中的extend关键字
fun <T:Int> printTest(value:T){
println("result = "+(value+3))
}
fun main(args: Array<String>) {
printTest(1)
// printTest("nothing") //报错:inferred type String is not a subtype of Int
}
如果是需要多个上界的话,那么可以用where来实现
interface A{
fun printA(infor:String)
}
interface B{
fun printB(infor:String)
}
fun <T> printTest2(value:T)
where
T:A,
T:B
{
value.printA("打印A")
value.printB("打印B")
}
fun main(args: Array<String>) {
printTest2(object:A,B{
override fun printA(infor:String){
println("printA = "+infor)
}
override fun printB(infor:String){
println("printB = "+infor)
}
})
}
类型擦除指的是泛型声明用法执行的类型安全检测仅在编译期进行。在运行期间不保留实参的任何信息,即无法用as来判断类型。然而有一种例外即内联函数的具体化类型参数。
型变分为两种:协变和逆变。用法分别是在参数前加上out和in。
协变的时候对象是作为生产者,也就是说它只负责产出。逆变的时候对象是作为消费者,它只负责消费。具体代码如下所示:
interface Operation<out T>{
fun eat():T
}
interface Operation2<in E>{
fun print(e:E)
}
fun main(args: Array<String>) {
val A:Operation<Number> = object:Operation<Int>{
override fun eat():Int{
return 17
}
}
val B:Operation2<Int> = object:Operation2<Number>{
override fun print(e:Number){
println("Operation2")
}
}
}
使用处型变也称为类型投影,他是在不变类型上动态做协变或者逆变。
val list1:MutableList<String> = mutableListOf()
list1.add("hello")
list1.add("world")
val list2:MutableList<out String> = mutableListOf()
list2.add("hello") // compile error
list2.add("world") // compile error
val list3:MutableList<in String> = mutableListOf()
list3.add("hello")
list3.add("world")
lateinit var list4:MutableList<String>
list4 = list3; // compile error
如上所示MutableList
是一个不变对象。当使用out
时,list2
即变成协变的对象,他不能作为消费者而只能是生产者。对应的当使用in时候,list3
就变成了逆变的对象。
有种场景是参数类型未知,那么星投影的功能就是声明对象的只读特性。
Foo
,那么Foo<*>
表示的意义就是该对象作为生产者,可以安全的读取读取TUpper
的值。
fun main(args: Array<String>) {
val star6: Star3<*> = Star3<String>("hello word")
println(star6.getValue())
}
class Star3<T:CharSequence>(private var t: T) {
fun getValue(): T {
return this.t
}
}
打印出的是"hello word",那么可以理解为out修饰参数的时候,星投影会则输出的是Sting的类。
Foo
,那么Foo<*>
表示的意义就是该对象作为消费者,不能写入任何数据。这个好理解,既然类型都不知道,输入什么都是不安全的。也就是只读不能写了。
Foo
,那么Foo<*>
表示的意义就是结合了上面两种场景。写入时候不能安全写入任何数据,读取时只能安全读取 TUpper
的值。
总体上感觉kotlin
的函数是对java
的方法做了几点处理:
优化:比如说弥补了java
泛型在只产出或者只消费的场景下无法型变的问题
智能:会在编译的时候就自动判别类型,比如说 val key = 1,key就自动判断为Int类型。这点在java
做不到
扩展:引入了内联,以及类似运算符重载的中缀表示法。
接下来继续学习内联函数,扩展函数等其他类型的函数。