日常工作中,我们总是希望提高效率,比如快捷键、自动化等,其实开发语言本身在兼容的情况下,也是可以提高效率的。比如Java的lambda表达式。Kotlin,并不是为了介绍Kotlin语言的入门或者使用方式,而是和Java对比,说明Kotlin提高效率的方面,下面会用一些小例子,说明一下日常开发中的小提升。
兼容性 版本 Kotlin会被编译为Java字节码,并且可以指定Java版本,这样一来,对于需要运行指定版本Java代码的环境来说,就非常友好了。
双向 在Kotlin的代码中是可以直接调用Java类的方法的,因此对于老项目改造是很友好的。Java中也可以直接使用Kotlin的类以及方法,虽然使用的时候看起来有点奇怪,但是还是可以直接兼容的,因此对于老项目的升级,是没有问题的。
类型处理 类型推导 在Kotlin中,申明变量是这样的:
1 2 val  sdf = SimpleDateFormat("" )var  sdf2 = SimpleDateFormat("" )
可以看到,前面就一个val或者var,看起来像是JavaScript的弱类型啊,其实并不是。当申明变量并且直接初始化为明确类型的值时,可以自动推导类型,不用指定类型。 
1 2 3 4 5 6 7 8 9 var  index = 0 var  time = 0L var  temp = null var  temp: String? = null 
你可能会说,这个好像也没多大区别吧,但是对于下面这两种场景呢?
1 2 3 4 5 SimpleDateFormat sdf = new  SimpleDateFormat("" ); MainFragment.LoadCallback loadCallback = new  MainFragment.LoadCallback() { ... }; OutputStream out = xxx.getOutputStream(); 
这时候就很不友好了,每次接受一个对象需要写一遍类型再写名字。如果同样的内容换成kotlin呢?
1 2 3 4 5 val  sdf = SimpleDateFormat("" );val  loadCallback = object : MainFragment.LoadCallback() { ... };val  out  = xxx.getOutputStream();
这样是不是就友好很多?.var快捷命令,可以帮助我们申明类型,但是它代码本身就在那里,还是会影响视觉。
val及var的区别请前往中文官网自行查看。Java高版本也支持了类型推导,但是个人认为没有Kotlin做得好,而且有版本兼容问题。
 
非空判断 Java中写的最多的,可能就是判空吧。
1 2 3 4 5 6 7 8 9 if  (a != null ) {    a.b(); } String b = "" ; if  (c != null ) {    b = c.d(); } 
现在好消息是,Kotlin里面可以大幅度的省略这部分代码了!
类型非空 上一个小节里面,指定变量类型的时候,在类型名称后面加上了一个?,就像这样:
1 var  temp: String? = null 
这就是类型非空了,当一个变量申明时,如果变量类型后面携带了?,或者类型推导时发现内容可能为null,那么就会在调用时直接报错或者发出警告。提示你对象可能为null,怎么调用这些可能为null的对象,是下一个标题的内容。?,那么它就认为这个变量是不能赋值为null的,如果你在后续的赋值中,赋值了null,就会自己报错,连编译都过不了,就算侥幸骗过了编译,也会在运行时赋值为null的时候直接报错。
空判定 上面说的是类型指定,那么使用呢?如果一个可能为null的对象怎么使用?加if?不用,可以直接这样写:(沿用小节开头的Java例子)
1 2 3 4 5 6 7 8 9 a?.b(); val  b = c?.d()?:"" ;fun  test (a: A ?)     a?:return      ... } 
上面列举了三种常见的场景。?即可,等价于上面的if中调用。?:。return操作。
Getter & Setter Kotlin帮我们做了很多的默认实现,这让我们的代码写起来更加轻松。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public  class  Bean      private  String a;     private  String b;     private  int  c;     public  Bean (String a, String b, String c)           this .a = a;         this .b = b;         this .c = c;     }          public  Bean ()           this ("" , "" , 0 );     }          public  String getA ()           return  a;     }          public  String getB ()           return  b;     }          public  int  getC ()           return  c;     }          public  void  setA (String a)           this .a = a;     }          public  void  setC (int  c)           this .c = c;     }      } 
可以看到,尽管只有三个成员变量,但是一个类还是非常的长,尽管有编译器的一键生成,但是代码仍然很多,不是很友好,而且全都是非常机械非常模板化的代码。那么在Kotlin中会是什么效果,简化会达到什么程度呢?
1 data  class  Bean var  a: String = "" , val  b: String = "" , var  c: Int  = 0 )
是不是感觉有点离谱?这里可以告诉你的是:确实已经写完了一个Bean。那么它符合Java规范吗?它是符合的,只是简化了这个过程,class的前面加上了data,表示这是一个数据类,Kotlin会自动为我们根据成员变量生成toString和equals等方法,我们也可以选择重写它,如果不需要特别重写,那么我们连类的大括号都不用写。Java代码中,b是不能被赋值的,这里只要把var改成val就好了。而getter和setter呢?Kotlin中的每个变量都默认有个getter和setter的实现,也就是都帮我们写好了默认实现。private,让这个变量只能内部可见。Java上的做法就只有提供足够多的重载了,但是Kotlin里面可以这样写:
1 2 3 fun  test ()     val  bean = Bean(a = "hello" , c = 2 ) } 
是的,还可以直接使用变量名指定传值,这样连重载也不用了。
另一个方面,上面说了,Kotlin帮我们写好了getter和setter,那么我们能重写吗?请看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class  A      private  val  colorArray = intArrayOf(Color.RED, Color.GREEN, Color.BLUE)     var  index = 0          private  set           var  color: Int          get () {             return  colorArray[index]         }         set (value) {             colorArray[index] = value         }     fun  next ()          index++         index %= colorArray.size     } } fun  test (a: A )          val  index = a.index                         val  color = a.color          a.color = Color.PINK } 
可以看到,我们的A中维护了一个颜色数组,提供了一个方法来移动下标。color的变量来获取和设置当前下标对应的颜色。但是我们其实没有color这个成员变量,我们只是通过完全重写getter和setter来模拟了一个变量。
扩展方法 都说Kotlin是语法糖,但是这糖也是真的甜。比如这里介绍的扩展方法。Java的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private  String formatInt (int  i)      if  (i < 10 ) {         return  "0"  + i;     }     return  ""  + i; } private  void  funA ()      String str = formatInt(             object1.funB(                 object2.funC())             .funD()); } 
这是一个很简单的例子,但是看起来就不是很友好了,业务场景可能还会有更麻烦的结构。你可能会说多申明几个变量,然后分开写,但是那样又会显得很臃肿,写起来没有这种链条一样调用的爽快感觉。
1 2 3 4 5 6 7 8 9 10 11 12 13 private  fun Int.format(): String {    if  (i < 10 ) {         return  "0$i"      }     return  "$i"  } private  fun funA ()      val str = object1.funB(object2.funC())                         .funD()                         .format() } 
这个扩展方法就像它原本就有的方法一样,可以直接调用,完美的混入了链条中。
函数式编程 Java中是先有Class再有方法,也就是必须要有对象。而在Kotlin中,也是面向对象,但是它可以有顶级函数,也就是不依托于Class的函数,当然,这也是语法糖而已。但是使用效果非常不错,我们先举例子再说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public  class  TaskUtil      private  static  final  Executor threadPool = Executors.newCachedThreadPool();     private  static  final  Handler mainHandler = new  Handler(Looper.getMainLooper());          public  static  void  doAsync (ErrorCallback err, RunCallback run)           threadPool.execute(new  Task(err, run));     }          public  static  void  onUI (ErrorCallback err, RunCallback run)           mainHandler.post(new  Task(err, run));     }          private  static  class  Task  extends  Runnable                private  ErrorCallback errorCallback;         private  RunCallback runCallback;              public  Task (ErrorCallback e, RunCallback r)               errorCallback = e;             runCallback = r;         }              @Override          public  void  run ()               try  {                 runCallback.run();             } catch  (e: Throwable) {                 if  (errorCallback != null ) {                     errorCallback.onError(e);                 }             }         }     }          interface  ErrorCallback           void  onError (Throwable e)      }          interface  RunCallback           void  run ()      }      } class  Test      public  void  test ()                    final  String str = "" ;                          TaskUtil.doAsync(null , new  TaskUtil.RunCallback() {             @Override              public  void  run ()                                    ...                 Test.this .testB(str);                 TaskUtil.onUI(null , new  TaskUtil.RunCallback() {                     @Override                      public  void  run ()                           ...                                                  Test.this .testB(str);                     }                 });             }         });     }          public  void  testB (String str)        } 
上面是一个简单的线程同步的工具类以及调用示例,可以看到,尽管简化了一部分代码,但是使用时的调用仍然非常繁琐,而且会存在上下文问题(回调函数中的this到底是哪个this?。那么Kotlin里面呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 val  threadPool: Executor by  lazy {    Executors.newCachedThreadPool() } val  mainHandler: Handler by  lazy {    Handler(Looper.getMainLooper()) } inline  fun  <reified  T: Any>  T.doAsync (         noinline  err: ((Throwable ) -> Unit )? = null ,         noinline  run: T .() -> Unit )     threadPool.execute {          try  {             run.invoke(this )         } catch  (e: Throwable) {             err?.invoke(e)         }     } } inline  fun  <reified  T: Any>  T.onUI (         noinline  err: ((Throwable ) -> Unit )? = null ,         noinline  run: T .() -> Unit )     mainHandler.post {          try  {             run.invoke(this )         } catch  (e: Throwable) {             err?.invoke(e)         }     } } class  Test      fun  test ()          val  str = ""          doAsync {                          testB(str)             onUI {                                  testB(str)             }         }     }          fun  testB (str: String )       } 
可以看到,对比效果非常明显。调用简单 而且结构清晰 。
顶级函数 顶级函数,也就是前面方法申明的时候,外面没有套一层Class,减少了类的约束,成为了全局的方法。Kotlin中的Math类也是这样的,相比Java中的Math,调用时可以直接写作min(a, b),不用加上类名。
内联 inline 它的作用类似于我们日常中的封装,但是又和我们的封装有些不一样。我们遇到多次出现但是内容一样的代码时,一般都是抽取并且包装成一个方法,如果是多个类共用,那么可能还要再抽取一个类出来,尽管方法实现还是那样的,但是毕竟还是抽了个方法出来,可能还跨了类调用,方法寻址还是需要时间的。
reified 这个关键一般是配合inline使用的,从含义上来讲,是使它更真实,我的理解就是推导泛型。
函数引用 在Kotlin一切函数皆可lambda,可以直接拿到函数的引用,然后在必要时调用,就像上面的run.invoke()。new对象。::来获取对象中的函数引用,只要函数,不要对象,使用和传递过程也少了很多限制。
懒加载 就是上面申明线程池的那个关键字by lazy,它要求变量必须是val,其实就是帮我们做了在get的时候调用后面的表达式实例化对象,然后后面一直使用这个对象,就像帮我们做了个小单例。
糖衣内的Java 上面的糖衣看起来是真的很甜啊,可以省略很多代码,大大的提高开发的效率,那么糖衣里面是什么呢?IntelliJ IDEA中,打开一个kt结尾的Kotlin代码文件,然后依次打开菜单中的Tool - Kotlin - Show Kotlin Bytecode。Decompile按钮,那么就会出现一个将字节码反编译为Java的窗口了。语法糖帮我们做了什么?
顶级函数 首先,我们先写一个文件,名字就叫做Test.kt,里面放一个顶级函数,就像这样
1 2 3 4 5 package  lollipop.testfun  sayHello ()     print("Hello" ) } 
然后我们按照上面的方法,反编译一下。然后就得到了这样的一个Java类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  lollipop.test;import  kotlin.Metadata;@Metadata (   mv = {1 , 1 , 16 },    bv = {1 , 0 , 3 },    k = 2 ,    d1 = {"\u0000\b\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001¨\u0006\u0002" },    d2 = {"sayHello" , "" , "HelloWorld" } ) public  final  class  TestKt     public  static  final  void  sayHello ()         String var0 = "Hello" ;       boolean  var1 = false ;       System.out.print(var0);    } } 
然后我们找一下不同,看看它帮我们做了什么。
它帮我们声明了一个类,类名就是文件名然后加上Kt后缀,我们都知道,Java要求每一个Java文件都需要有一个与文件名同名的且为public的类,显然它帮我们做了这件事。 
类名和方法自动加上了final,这也是Kotlin在不加open时不能被继承和重写的原因。 
sayHello方法本来是写在外面的,现在它帮我们放在了类中,同时加上了static,说明它是一个可以直接使用的方法。 
那么顶级函数就是一个静态方法吗?我们再去使用的地方验证一下。Test.java,另一个是Test2.kt。Java中的调用.
1 2 3 4 5 6 7 8 9 10 11 12 13 package  lollipop.test;class  Test      private  Test (String value)       public  static  void  with (int  value)           TestKt.sayHello();     } } 
经过一番尝试,发现需要按照我们刚刚反编译的方式调用,Kotlin里面的调用呢?
1 2 3 4 5 6 7 8 9 10 11 package  lollipop.testclass  Test2      fun  world ()               sayHello()              }      } 
这里很简单,就像本来就有这个方法一样,直接使用,看起来很舒服,那么它实际上是什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  lollipop.test;import  kotlin.Metadata;@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\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005" },    d2 = {"Llollipop/test/Test2;" , "" , "()V" , "world" , "" , "HelloWorld" } ) public  final  class  Test2     public  final  void  world ()         TestKt.sayHello();    } } 
实际上和我们手写的Java类是一样的调用。
伴生对象 我们都知道,在写kotlin时,要在一个类中写一些静态方法时,一般都是写在companion object中,中文翻译就是伴随对象。那么它和我们的静态方法有什么关系和区别呢?Test2.kt加一点代码看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package  lollipop.testclass  Test2      companion  object  {         const  val  NAME = "Test2"          val  pre = "I am"          var  id = 0          private  var  id2 = 1          private  var  id3 = 2          fun  name (n: String )              return  "Hey $n , $pre  $NAME , id is $id -$id2 "          }     }     fun  world ()          id = 1          sayHello()     } } 
现在是这样,为了做对比,我加了三个变量一个方法,分别用不同的关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package  lollipop.test;import  kotlin.Metadata;import  kotlin.jvm.internal.DefaultConstructorMarker;import  kotlin.jvm.internal.Intrinsics;import  org.jetbrains.annotations.NotNull;@Metadata (   mv = {1 , 1 , 16 },    bv = {1 , 0 , 3 },    k = 1 ,    d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0002\u0018\u0000 \u00052\u00020\u0001:\u0001\u0005B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0006" },    d2 = {"Llollipop/test/Test2;" , "" , "()V" , "world" , "" , "Companion" , "HelloWorld" } ) public  final  class  Test2     @NotNull     public  static  final  String NAME = "Test2" ;    @NotNull     private  static  final  String pre = "I am" ;    private  static  int  id;    private  static  int  id2 = 1 ;    private  static  int  id3 = 2 ;    public  static  final  Test2.Companion Companion = new  Test2.Companion((DefaultConstructorMarker)null );    public  final  void  world ()         id = 1 ;       TestKt.sayHello();    }        public  static  final  void  access$setId2$cp(int  var0) {       id2 = var0;    }    @Metadata (       mv = {1 , 1 , 16 },       bv = {1 , 0 , 3 },       k = 1 ,       d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\f\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u000e\u0010\u0010\u001a\u00020\u00042\u0006\u0010\u0011\u001a\u00020\u0004R\u000e\u0010\u0003\u001a\u00020\u0004X\u0086T¢\u0006\u0002\n\u0000R\u001a\u0010\u0005\u001a\u00020\u0006X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\u0007\u0010\b\"\u0004\b\t\u0010\nR\u000e\u0010\u000b\u001a\u00020\u0006X\u0082\u000e¢\u0006\u0002\n\u0000R\u000e\u0010\f\u001a\u00020\u0006X\u0082\u000e¢\u0006\u0002\n\u0000R\u0014\u0010\r\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u000e\u0010\u000f¨\u0006\u0012" },       d2 = {"Llollipop/test/Test2$Companion;" , "" , "()V" , "NAME" , "" , "id" , "" , "getId" , "()I" , "setId" , "(I)V" , "id2" , "id3" , "pre" , "getPre" , "()Ljava/lang/String;" , "name" , "n" , "HelloWorld" }    )    public  static  final  class  Companion         @NotNull        public  final  String getPre ()            return  Test2.pre;       }       public  final  int  getId ()            return  Test2.id;       }       public  final  void  setId (int  var1)            Test2.id = var1;       }       @NotNull        public  final  String name (@NotNull String n)            Intrinsics.checkParameterIsNotNull(n, "n" );          return  "Hey "  + n + ", "  + ((Test2.Companion)this ).getPre() + " Test2, id is "  + ((Test2.Companion)this ).getId() + '-'  + Test2.id2;       }       private  Companion ()         }              public  Companion (DefaultConstructorMarker $constructor_marker)            this ();       }    } } 
我们一个个的看。
生成了一个叫做Companion的内部类,它是静态的,并且提供了一系列方法。它应该就是我们的伴生对象了。 
Companion类在主类中有一个实例,它是同时被static final修饰的,Kotlin中没有静态修饰符,因此它一定程度上就代表了Kotlin的静态方法区和静态变量区。const修饰的NAME变成了一个常量,前面是public,是一个标准的常量。而只是少了个const的pre却是private,但是在伴生对象中提供了一个getPre方法,说明const关键字是用来声明真·常量。 
id初始化为0,但是在反编译代码中没有看到初始化为0的代码。被private修饰的其他几个变量,没有提供getter方法。 
所有声明在伴生对象中的变量对外接口几乎都是通过伴生对象,但是实际都是在主类中保存为静态的。 
伴生对象中的方法,最后会在生成的伴生类中,并且是静态方法。 
字符串最后生成结果仍然是用+拼接的,而变量的使用,对于有提供getter方法的,会使用getter方法。 
 
函数引用 函数式编程是Kotlin的一大特色,这次我们看看它怎么包装的。
1 2 3 4 5 6 7 8 9 package  lollipop.test;class  Test      public  static  void  say ()           TestKt.sayHello();     } } 
Test.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 package  lollipop.testfun  sayHello ()     print("Hello" ) } fun  add (num: () -> Int ) Int  {    return  num() + num.invoke() } fun  textSay (run: () -> Unit )     run.invoke() } 
Test2.kt
1 2 3 4 5 6 7 8 9 10 11 12 package  lollipop.testimport  java.util.*class  Test2      fun  world ()          val  random = Random()         val  value = add { random.nextInt() }         textSay(Test::say)     } } 
Java类没什么好看的,我们直接看后面的,Test.kt。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package  lollipop.test;import  kotlin.Metadata;import  kotlin.jvm.functions.Function0;import  kotlin.jvm.internal.Intrinsics;import  org.jetbrains.annotations.NotNull;@Metadata (   mv = {1 , 1 , 16 },    bv = {1 , 0 , 3 },    k = 2 ,    d1 = {"\u0000\u0016\n\u0000\n\u0002\u0010\b\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0003\u001a\u0014\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00010\u0003\u001a\u0006\u0010\u0004\u001a\u00020\u0005\u001a\u0014\u0010\u0006\u001a\u00020\u00052\f\u0010\u0007\u001a\b\u0012\u0004\u0012\u00020\u00050\u0003¨\u0006\b" },    d2 = {"add" , "" , "num" , "Lkotlin/Function0;" , "sayHello" , "" , "textSay" , "run" , "HelloWorld" } ) public  final  class  TestKt     public  static  final  void  sayHello ()         String var0 = "Hello" ;       boolean  var1 = false ;       System.out.print(var0);    }    public  static  final  int  add (@NotNull Function0 num)         Intrinsics.checkParameterIsNotNull(num, "num" );       return  ((Number)num.invoke()).intValue() + ((Number)num.invoke()).intValue();    }    public  static  final  void  textSay (@NotNull Function0 run)         Intrinsics.checkParameterIsNotNull(run, "run" );       run.invoke();    } } 
可以看到,我们声明的函数,现在变成了一个叫做Function0的类,其他的都没变,方法内使用的是Function0的invoke方法,让我们看看这个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package  kotlin.jvm.functionspublic interface Function0<out R> : Function<R> {          public  operator fun invoke () : R } public  interface Function1<in P1, out R> : Function<R>          public  operator fun invoke (p1: P1) : R } public  interface Function2<in P1, in P2, out R> : Function<R>          public  operator fun invoke (p1: P1, p2: P2) : R }   public  interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R>          public  operator fun invoke (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22) : R } 
可以看到,这是跳转到了Koltin标准库中的一个文件,它声明了非常多的方法,最多是到了22个参数,每个都有自己的范型,而刚刚的Function0就是无参数的接口。所以其实就是它帮我们写好了很多通用接口,然后回调触发?
1 2 3 4 5 6 7 8 9 10 fun  args (num: (Int , String , Float ,                Int , String , Float ,                Int , String , Float ,                Int , String , Float ,                Int , String , Float ,                Int , String , Float ,                Int , String , Float ,                Int , String , Float ) -> Int ) Int  {    return  0  } 
于是我加了上面的这个方法,24个参数,看看它做了啥。
1 2 3 4 public  static  final  int  args (@NotNull FunctionN num)     Intrinsics.checkParameterIsNotNull(num, "num" );    return  0 ; } 
结果是这样的,(///▽///),所以它是知道有我们这种流氓,所以干脆弄个N的出来呗?FunctionN里面是啥呢?
1 2 3 4 interface FunctionN<out R> : Function<R>, FunctionBase<R> {     operator fun invoke (vararg args: Any?) : R      override val arity: Int } 
emmm,构建者给出长度,然后参数全放数组里面,一了百了了。
行,那么我们再看看Test2.kt,看看调用的地方怎么样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package  lollipop.test;import  java.util.Random;import  kotlin.Metadata;import  kotlin.jvm.functions.Function0;@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\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005" },    d2 = {"Llollipop/test/Test2;" , "" , "()V" , "world" , "" , "HelloWorld" } ) public  final  class  Test2     public  final  void  world ()         final  Random random = new  Random();       int  value = TestKt.add((Function0)(new  Function0() {                              public  Object invoke ()               return  this .invoke();          }          public  final  int  invoke ()               return  random.nextInt();          }       }));       TestKt.textSay((Function0)null .INSTANCE);    } } 
首先是add方法,不出所料的是创建了一个匿名内部类,但是里面的写法很有意思,它先是实现一个final的方法,返回值是int的,来包裹我们的业务代码,然后另一个再去调用它。所以推测返回Object的方法才是接口的真正方法,这样做的原因推测是跟范型擦除有点关系吧。(瞎猜的)
而另一个方法就很奇怪了,不明白使用Java的函数引用,怎么就反编译成了这样。为了验证,我又改成了Kotlin的方法传了进去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  lollipop.testimport  java.util.*class  Test2      fun  world ()          val  random = Random()         val  value = add { random.nextInt() }         textSay(this ::test)     }          fun  test ()          sayHello()     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package  lollipop.test;import  java.util.Random;import  kotlin.Metadata;import  kotlin.Unit;import  kotlin.jvm.functions.Function0;import  kotlin.jvm.internal.Reflection;import  kotlin.reflect.KDeclarationContainer;@Metadata (   mv = {1 , 1 , 16 },    bv = {1 , 0 , 3 },    k = 1 ,    d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004J\u0006\u0010\u0005\u001a\u00020\u0004¨\u0006\u0006" },    d2 = {"Llollipop/test/Test2;" , "" , "()V" , "test" , "" , "world" , "HelloWorld" } ) public  final  class  Test2     public  final  void  world ()         final  Random random = new  Random();       int  value = TestKt.add((Function0)(new  Function0() {                              public  Object invoke ()               return  this .invoke();          }          public  final  int  invoke ()               return  random.nextInt();          }       }));       TestKt.textSay((Function0)(new  Function0((Test2)this ) {                              public  Object invoke ()               this .invoke();             return  Unit.INSTANCE;          }          public  final  void  invoke ()               ((Test2)this .receiver).test();          }          public  final  KDeclarationContainer getOwner ()               return  Reflection.getOrCreateKotlinClass(Test2.class ) ;          }          public  final  String getName ()               return  "test" ;          }          public  final  String getSignature ()               return  "test()V" ;          }       }));    }    public  final  void  test ()         TestKt.sayHello();    } } 
这个结果看起来更加奇怪了,看样子它是做了反射,所以上面我们直接拿Java的方法,应该也是反射了,中间也许出了点问题,不过感觉问题不大。(也许)
扩展函数 终于说到了扩展函数,Kotlin的标准库中包含了大量的扩展函数,比如最常用的apply和let。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @kotlin .internal .InlineOnlypublic  inline  fun  <T, R>  T.let (block: (T ) -> R )     contract {         callsInPlace(block, InvocationKind.EXACTLY_ONCE)     }     return  block(this ) } @kotlin .internal .InlineOnlypublic  inline  fun  <T>  T.apply (block: T .() -> Unit )     contract {         callsInPlace(block, InvocationKind.EXACTLY_ONCE)     }     block()     return  this  } 
这个没啥说的,只是返回值有点区别而已,我们看书看重点,看看扩展函数会变成什么吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package  lollipop.testclass  Test2      fun  test ()          "hello" .let {             print(""  + it.length)         }         "world" .apply {             print(""  + this .length)         }     } } 
反编译后是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package  lollipop.test;import  kotlin.Metadata;@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\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005" },    d2 = {"Llollipop/test/Test2;" , "" , "()V" , "test" , "" , "HelloWorld" } ) public  final  class  Test2     public  final  void  test ()         String var1 = "hello" ;       boolean  var2 = false ;       boolean  var3 = false ;       int  var5 = false ;       String var6 = ""  + var1.length();       boolean  var7 = false ;       System.out.print(var6);       var1 = "world" ;       var2 = false ;       var3 = false ;       var5 = false ;       var6 = ""  + var1.length();       var7 = false ;       System.out.print(var6);    } } 
尽管代码被inline优化过了,但是还是可以看出,它就是把被扩展的对象当作参数使用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 package  lollipop.testclass  Test2      fun  test ()          val  result = "hello" .link("world" )     }     private  fun  String.link (value: String )          return  this  + value     } } 
可以看到,我们自己声明了一个扩展函数,而且没有inline。那么反编译结果呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  lollipop.test;import  kotlin.Metadata;import  org.jetbrains.annotations.NotNull;@Metadata (   mv = {1 , 1 , 16 },    bv = {1 , 0 , 3 },    k = 1 ,    d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004J\u0014\u0010\u0005\u001a\u00020\u0006*\u00020\u00062\u0006\u0010\u0007\u001a\u00020\u0006H\u0002¨\u0006\b" },    d2 = {"Llollipop/test/Test2;" , "" , "()V" , "test" , "" , "link" , "" , "value" , "HelloWorld" } ) public  final  class  Test2     public  final  void  test ()         String result = this .link("hello" , "world" );    }    private  final  String link (@NotNull String $this $link, String value)         return  $this $link + value;    } } 
emmm,这次是真的非常明显了,它就是把被扩展的对象作为第一个参数,传了进去,这样一看,瞬间觉得不神奇了,索然无味啊。
懒加载 接着我们来看看懒加载,懒加载可以一定程度上减少了软件初始化时的爆发式内存消耗,不过它真的是这样吗?它又是怎么实现的呢?我们试试看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  lollipop.testimport  java.util.*class  Test2      private  val  myRandom: Random by  lazy {         Random()     }     fun  test ()          myRandom.nextInt()     } } 
可以看到,我们懒加载了一个随机数对象,它会在我们使用的时候再初始化。真的是这样吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package  lollipop.test;import  java.util.Random;import  kotlin.Lazy;import  kotlin.LazyKt;import  kotlin.Metadata;import  kotlin.jvm.functions.Function0;@Metadata (   mv = {1 , 1 , 16 },    bv = {1 , 0 , 3 },    k = 1 ,    d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0005\n\u0002\u0010\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\t\u001a\u00020\nR\u001b\u0010\u0003\u001a\u00020\u00048BX\u0082\u0084\u0002¢\u0006\f\n\u0004\b\u0007\u0010\b\u001a\u0004\b\u0005\u0010\u0006¨\u0006\u000b" },    d2 = {"Llollipop/test/Test2;" , "" , "()V" , "myRandom" , "Ljava/util/Random;" , "getMyRandom" , "()Ljava/util/Random;" , "myRandom$delegate" , "Lkotlin/Lazy;" , "test" , "" , "HelloWorld" } ) public  final  class  Test2     private  final  Lazy myRandom$delegate;    private  final  Random getMyRandom ()         Lazy var1 = this .myRandom$delegate;       Object var3 = null ;       boolean  var4 = false ;       return  (Random)var1.getValue();    }    public  final  void  test ()         this .getMyRandom().nextInt();    }    public  Test2 ()         this .myRandom$delegate = LazyKt.lazy((Function0)null .INSTANCE);    } } 
可以看到,最后的类型,声明的是一个叫做Lazy的类型,而获取的时候,是从它那里拿的,而它的初始化,是在构造器中。我们好像猜中了什么。有点似曾相识的感觉啊。Lazy类。
1 2 3 4 5 6 public  interface  Lazy <out T >          public  val  value: T     public  fun  isInitialized () Boolean  } 
emmm,从方法名就有点感觉了,似乎就是那么回事啊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private  class  SynchronizedLazyImpl <out T >null ) : Lazy<T>, Serializable {    private  var  initializer: (() -> T)? = initializer     @Volatile  private  var  _value: Any? = UNINITIALIZED_VALUE     private  val  lock = lock ?: this      override  val  value: T         get () {             val  _v1 = _value             if  (_v1 !== UNINITIALIZED_VALUE) {                 @Suppress("UNCHECKED_CAST" )                  return  _v1 as  T             }             return  synchronized(lock) {                 val  _v2 = _value                 if  (_v2 !== UNINITIALIZED_VALUE) {                     @Suppress("UNCHECKED_CAST" )  (_v2 as  T)                 } else  {                     val  typedValue = initializer!!()                     _value = typedValue                     initializer = null                      typedValue                 }             }         }     override  fun  isInitialized () Boolean  = _value !== UNINITIALIZED_VALUE     override  fun  toString () if  (isInitialized()) value.toString() else  "Lazy value not initialized yet."      private  fun  writeReplace ()  } 
没跑了,就是这个了,这不就是我们写单例模式的代码吗?只不过这里用的是懒汉模式。
总结 这里简单的看了下常用的Kotlin的基础使用和基础的Java实现,看起来确实是语法糖,只要合理使用设计模式,一样可以做到这样的效果,可是它有编译器支持,比我们土制的好看啊。Kotlin用起来,可以减少一大部分的低级Bug。