Kotlin中的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数。例如let这个标准函数,他的主要作用就是配合?.操作符来进行辅助判空处理。
标准函数with、run和apply
with函数
with函数接收两个参数:第一个参数可以是任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。
示例代码如下:
val result=with(obj){
//这里是obj的上下文
"value"//with函数的返回值
}
那么这个函数有什么作用呢?它可以在连续调用同一个对象的多个方法时让代码变得更加精简,下面我们来看一个具体的例子。
比如有一个水果列表,现在我们想吃完所有的水果,并将结果打印出来,可以这样写
val list= listOf("Apple","Banana","Orange","Pear","Grape")
val builder=StringBuilder()
builder.append("Start eating fruits.\n")
for(fruit in list){
builder.append(fruit).append("\n")
}
builder.append("Ate all fruits.")
val result=builder.toString()
println(result)
这段代码的逻辑很简单,就是使用StringBuilder来构建吃水果的字符串,最后将结果打印出来
打印结果为:
观察上述代码,你会发现我们连续调用了很多次builder对象的方法。其实这个时候就可以考虑使用with函数来让代码变得更加精简,如下所示:
val list= listOf("Apple","Banana","Orange","Pear","Grape")
val result= with(StringBuilder()){
append("Start eating fruits.\n")
for(fruit in list){
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
println(result)
首先我们给with函数的第一个参数传入一个StringBuilder对象,那么接下来整个Lambda表达式的上下文就会是这个StringBuilder对象。于是我们在Lambda表达式中就不用再像刚才那样调用builder.append()和builder.toString()方法了,而是可以直接调用append()和toString()方法。Lambda表达式最后一行代码会作为with函数的返回值返回,最终我们将结果打印出来。
虽然两段代码结果一样,但是明显第二段代码的写法更加简洁一些,这就是with函数的作用。
run函数
run函数的用法和使用场景其实和with函数时非常类似的,只是稍微做了一些语法改动而已。首先run函数是不能直接调用的,而是一定要调用某个对象的run函数才行;其次run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。其他方面和with函数一样,包括也会使用Lambda表达式最后一行作为返回值返回。示例代码如下:
val result=obj.run{
//这里是obj的上下文
"value"//run函数的返回值
}
那么现在我们就可以使用run函数来修改一下吃水果的这段代码,如下所示
val list= listOf("Apple","Banana","Orange","Pear","Grape")
val result=StringBuilder().run{
append("Start eating fruits.\n")
for(fruit in list){
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
println(result)
总体来说变化很小,只是将调用with函数并传入StringBuilder对象改成了调用StringBuilder对象的run方法,其他没什么区别,这两段代码最终的执行结果是完全相同的。
apply函数
apply函数和run函数也是极其相似的,都是在某个对象上调用,并且接收一个Lambda参数,也会在Lambda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身。示例如下:
val result=obj.apply{
//这里是obj的上下文
}
//result==obj
那么现在我们再使用apply函数来修改一下吃水果的这段代码
val list= listOf("Apple","Banana","Orange","Pear","Grape")
val result=StringBuilder().apply{
append("Start eating fruits.\n")
for(fruit in list){
append(fruit).append("\n")
}
append("Ate all fruits.")
}
println(result.toString())
由于apply函数无法指定返回值,只能返回调用对象本身,因此result实际上是一个StringBuilder对象,所以我们在最后打印的时候还要调用它的toString()方法才行。这段代码的执行结果和前面两段仍然完全相同的。
其实with、run和apply这几个函数的用法和使用场景是非常类似的。在大多数情况下,可以相互转换。
例如我们启动Activity的时候
val intent=Intent(this,SecondActivity::class.java)
intent.putExtra("param1","data1")
intent.putExtra("param2","data2")
startActivity(intent)
这里每传递一个参数就会调用一次intent.putExtra()方法,如果要传递10个参数,那就得调用10次。对于这种情况,我们就可以使用标准函数来对代码进行精简,如下所示
val intent=Intent(this,SecondActivity::class.java).apply{
intent.putExtra("param1","data1")
intent.putExtra("param2","data2")
}
startActivity(intent)
可以看到,由于Lambda表达式中的上下文就是Intent对象,所以我们不需要调用intent.putExtra()方法,而是直接调用putExtra()方法就可以了。传递的参数越多,这种写法优势也就越明显。
定义静态方法
静态方法再某些编程语言里面又叫做类方法,指的就是那种不需要创建实例就能调用的方法,所有主流的编程语言都会支持静态方法这个特性。
在java中定义一个静态方法,只需要在方法上声明一个static关键字就可以了,如下所示:
public class Util{
public static void doAction(){
System.out.printlin("do action")
}
}
上述代码中的doAction()方法就是一个静态方法。调用静态方法并不需要创建类的实例,而是可以直接以Util.doAction()这种写法调用。因而静态方法非常适合用于编写一些工具类的功能,因为工具类通常没有创建实例的必要,基本上是全局通用的。
和大多数主流编程语言不同的是,kotlin却极度弱化了静态方法这个概念,想要在Kotlin中定义一个静态方法反倒不是那么容易。
因为Kotlin提供了比静态方法更好用的语法特性,那就是单例类
object Util{
fun doAction(){
println("do action")
}
}
虽然这里的doAction不是静态方法,但是我们仍然可以使用Util.doAction()方式来调用,这就是单例类所带来的便利。
不过,使用单例类的写法会将整个类中所有方法全部变成类似于静态方法的调用方式,而如果我们只是希望让类中的某一个方法变成静态方法的调用方式该怎么办呢?这个时候就可以使用companion object了,示例如下:
class Util{
fun doAction1(){
println("do action1")
}
companion object{
fun doAction2(){
println("do action2")
}
}
}
首先我们将Util从单例类改成一个普通类,然后在类中直接定义一个doAction1()方法,又在companion object中定义了一个doAction2()方法。现在两个方法就有了本质的区别,因为doAction1()方法是一定要先创建Util类的实例才能调用的,而doAction2()方法可以直接使用Util.doAction2()的方式调用。
不过,doAction2()方法其实也不是静态方法,companion object这个关键字实际上会在Util类的内部创建一个伴生类,而doAction2()方法就是定义在这个伴生类里面的实例方法。只是Kotlin会保证Util类始终只会存在一个伴生类对象,因此调用Util.doAction2()方法实际上就是调用了Util类中伴生对象的doAction2()方法。
编译成字节码结果如下:
public final class Util {
public static final Util.Companion Companion = new Util.Companion((DefaultConstructorMarker)null);
public final void doAction1() {
String var1 = "do action1";
boolean var2 = false;
System.out.println(var1);
}
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
d2 = {"Ltest/Util$Companion;", "", "()V", "doAction2", "", "kotlin01"}
)
public static final class Companion {
public final void doAction2() {
String var1 = "do action2";
boolean var2 = false;
System.out.println(var1);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
由此看出,Kotlin中确实没有直接定义静态方法的关键字,但是提供了一些语法特性来支撑类似于静态方法调用的写法,这些语法特性基本上可以 满足我们平时的开发需求了。
如果需要真正的静态方法,Kotlin提供了两种实现方式:注解和顶层方法。
先来看注解,前面的单例类和companion object都只是在语法的形式上模仿了静态方法的调用方式,实际上他们都不是真正的静态方法。因此如果你在Java中以静态方法的形式去调用的话,你会发现这些方法并不存在。而如果我们给单例类或companion object中的方法加上@JvmStatic注解,那么Kotlin编译器就会将这些方法编译成真正的静态方法,如下所示:
class Util{
fun doAction1(){
println("do action1")
}
companion object{
@JvmStatic
fun doAction2(){
println("do action2")
}
}
}
编译成字节码,此时doAction2()方法为静态方法
public final class Util {
public static final Util.Companion Companion = new Util.Companion((DefaultConstructorMarker)null);
public final void doAction1() {
String var1 = "do action1";
boolean var2 = false;
System.out.println(var1);
}
@JvmStatic
public static final void doAction2() {
Companion.doAction2();
}
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0007¨\u0006\u0005"},
d2 = {"Ltest/Util$Companion;", "", "()V", "doAction2", "", "kotlin01"}
)
public static final class Companion {
@JvmStatic
public final void doAction2() {
String var1 = "do action2";
boolean var2 = false;
System.out.println(var1);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
注意@JvmStatic注解只能加在单例类或Companion object中的方法上,如果你尝试加在一个普通方法上,会直接提示语法错误。
由于doAction2()方法已经成为真正的静态方法,那么现在不管是在Kotlin中还是Java中,都可以使用Util.doAction2()的写法来调用。
再来看顶层方法,顶层方法指的是那些没有定义在任何类中的方法,比如我们编写的main()方法。Kotlin编译器会将所有的顶层方法全部编译成静态方法,因此只要你定义了一个顶层方法,那么它就一定是静态方法。
想要定义一个顶层方法,首先要创建一个Kotlin文件。对着任意包名右击→New→Kotlin File/Class,在弹出的对话框中输入文件名即可。注意创建类型要选择File,如图所示
点击“ok”完成创建,这样刚刚的包名路径下就会出现一个Helper.kt文件。现在我们再这个文件中定义的任何方法都会是顶层方法,比如这里我就定义一个doSomething()方法吧,如下所示:
fun doSomething(){
println("do something")
}
Kotlin会将所有的顶层方法全部编译成静态方法,那么我们要怎么调用这个doSomething()方法呢?
如果是在Kotlin中调用的话,所有的顶层方法都可以在任何位置被直接调用,不用管包名路径,也不用创建实例,直接输入doSomething()即可。
但如果在Java代码中调用,你会发现找不到doSomething()这个方法,因为Java中没有顶层方法这个概念,所有的方法必须定义在类中。那么这个doSomething()被藏在哪里呢?我们刚才创建的Kotlin文件名叫做Helper.kt,于是Kotlin编译器会自动创建一个叫作Helperkt的Java类,doSomething()方法就是以静态方法的形式定义在HelperKt类里面的,因此在Java中使用Helperkt.doSomething()的写法来调用就可以了。