不要用Java的语法思维来写Kotlin

lnbnuaa 6年前
   <p><img src="https://simg.open-open.com/show/c49fd27ae19628322aaa384c2234597e.jpg"></p>    <p>写了多年的Java,直到看到Kotlin,原来代码可以如此优雅!</p>    <p>如果你是像我一样是一名 优秀 的Java开发者 ^_^ ,而且已经想用kotlin来实现你的程序,那么,抱歉!不要用Java的语法思维来写Kotlin,不要让kotlin的优雅埋没。如果你没有Java开发经验,下面的内容也对你会有帮助。。。</p>    <h3>1.尽可能的少用 !!</h3>    <p>个人感觉对于Null的检查是Koltin最语法糖的东西了,强制在编码过程中考虑空指针,因此 <a href="/misc/goto?guid=4959757447581384310" rel="nofollow,noindex">《十亿美元的错误》</a> ,也许你不会再有这个机会犯错了(也许可以说成,你赚了十亿美金 ^_^ )。</p>    <p>首先需要介绍是 !! 操作符。</p>    <p>!! 操作符: 这是为空指针爱好者准备的 ,非空断言运算符(!!)将任何值转换为非空类型,若该值为空则抛出异常。我们可以写 a!! ,这会返回一个非空的 a 值 (例如:在我们例子中的 String)或者如果 a 为空,就会抛出一个 空指针 异常:</p>    <pre>  <code class="language-kotlin">val b = a!!.length</code></pre>    <p>所以,我们能不用 !! 操作符就不要用。。。</p>    <p>下面介绍几种方式避免使用 !! 操作符</p>    <p>1).多用 val 而不是 var</p>    <p>在 Kotlin 中 val 代表只读, var 代表可变。建议尽可能多的使用 val 。 val 是线程安全的,并且必须在定义时初始化,所以不需要担心 null 的问题。只需要注意 val 在某些情况下也是可变的就行了。</p>    <p>val 和 var 是用于表示属性是否有 getter/setter:</p>    <ul>     <li>var:同时有 getter 和 setter</li>     <li>val:只有 getter</li>    </ul>    <p>所以,强烈推荐能用 val 的地方就用 val 。</p>    <p>2).使用 lateinit</p>    <p>有些时候并不能用val,比如在 spring boot 接口测试中就不能使用 val ,对于这种情况,可以使用 lateinit 关键字。。</p>    <p>依赖倒转,对象的创建是通过spring完成的,而val要求定义的时候初始化</p>    <pre>  <code class="language-kotlin">/**   * Created by quanke on 2018/1/9.   * Site:http://woquanke.com .   */  @RunWith(SpringRunner::class)  @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)  class ApplicationTests {      val log = LogFactory.getLog(ApplicationTests::class.java)!!      @Autowired      lateinit var restTemplate: TestRestTemplate            @Test      fun `GET when given quanke then returns "Hello, quanke"`() {          // Given          val name = "quanke"          // When          val body = restTemplate.getForObject("/users/hello/{name}", String::class.java, name)          // Then          assertThat(body).isEqualTo("Hello, $name")      }  }</code></pre>    <p>注意:lateinit很好用,但也有坑</p>    <ul>     <li>访问未初始化的 lateinit 属性会导致 UninitializedPropertyAccessException。</li>     <li>lateinit 不支持基础数据类型,比如 Int。对于基础数据类型,我们可以这样: <pre>  <code class="language-kotlin">> private var mNumber: Int by Delegates.notNull<Int>()  ></code></pre> </li>    </ul>    <p>3).Elvis 操作符</p>    <p>当b为可空引用时,我们可以使用if表达式处理</p>    <pre>  <code class="language-kotlin">val l: Int = if (b != null) b.length else -1</code></pre>    <p>但更加优雅的方式是使用Elvis 操作符 ?:</p>    <pre>  <code class="language-kotlin">val l = b?.length ?: -1</code></pre>    <p>如果 ?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。</p>    <p>注意:当且仅当左侧为空时,才会对右侧表达式求值。</p>    <p>4).也许可以尝试一下let函数</p>    <p>let 函数一般与安全调用操作符一起使用,我们首先介绍安全调用操作 ?.</p>    <pre>  <code class="language-kotlin">b?.length</code></pre>    <p>如果 b 非空,就返回 b.length,否则返回 null,这个表达式的类型是 Int?。</p>    <p>安全调用在链式调用中很有用。例如,如果一个员工Quanke可能会(或者不会)分配给一个部门, 并且可能有另外一个员工是该部门的负责人,那么获取 Quanke 所在部门负责人(如果有的话)的名字,我们写作:</p>    <pre>  <code class="language-kotlin">quanke?.department?.head?.name</code></pre>    <p>如果任意一个属性(环节)为空,这个链式调用就会返回 null。</p>    <p>如果要只对非空值执行某个操作,安全调用操作符可以与 let 一起使用:</p>    <pre>  <code class="language-kotlin">val listWithNulls: List<String?> = listOf("A", null)  for (item in listWithNulls) {       item?.let { println(it) } // 输出 A 并忽略 null  }</code></pre>    <p>还有一种常见的错误(放ide里面试试就知道什么错误了):</p>    <pre>  <code class="language-kotlin">private var a: String? = null  fun aLetDemo() {      if (a != null) {          test(a)      }  }</code></pre>    <p>我们可以这样:</p>    <pre>  <code class="language-kotlin">private var a: String? = null  fun aLetDemo() {      if (a != null) {          test(a!!)      }  }</code></pre>    <p>但是这样的后果就是你还是需要在test函数里处理空指针。</p>    <p>我们充分利用 ?. 加 let 的特点,更加优雅的解决这个编译错误,如下</p>    <pre>  <code class="language-kotlin">private var a: String? = null  fun aLetDemo() {      if (a != null) {          a?.let {              test(it)          }      }  }</code></pre>    <h3>2.少写点Util类和继承</h3>    <p>很多时候框架提供给我的方法是比较原子,或者某些常用的方法框架并没有提供,Java一般是写一个工具类:</p>    <pre>  <code class="language-kotlin">public final class StringUtil {      /**       * 删除所有的标点符号       *       * @param str 处理的字符串       */      public  static String trimPunct(String str) {          if(isEmpty(str)){              return "";          }          return str.replaceAll("[\\pP\\p{Punct}]", "");      }  }</code></pre>    <p>Kotlin可以通过扩展函数的形式实现:</p>    <pre>  <code class="language-kotlin">/**   * 删除所有的标点符号   *   * @param str 处理的字符串   */  fun String.trimPunct(): String {      return if (this.isEmpty()) {          ""      } else this.replace("[\\pP\\p{Punct}]".toRegex(), "")  }</code></pre>    <p>调用:</p>    <pre>  <code class="language-kotlin">fun main(args: Array<String>) {      val a = "把我的标点符号去掉吧,全科。"      print(a.trimPunct())  }</code></pre>    <p>打印:</p>    <pre>  <code class="language-kotlin">把我的标点符号去掉吧全科  Process finished with exit code 0</code></pre>    <h3>3.别再用+号拼接字符串</h3>    <p>无论是Java还是Android开发,我们都会用到字符串拼接,比如进行日志输出等等。在Kotlin中,支持字符串模板,我们可以很轻松的完成一个字符串数的拼接,当然你可能会说使用StringBuilder性能更好,比如:</p>    <pre>  <code class="language-kotlin">val site = "http://woquanke.com"  val sb: StringBuilder = StringBuilder()  sb.append("我的博客名字叫《我全科》,我的博客地址是:")  sb.append(site)  println(sb.toString())</code></pre>    <p>但kotlin的字符串模版可以优雅的做这个事情:</p>    <pre>  <code class="language-kotlin">val site = "http://woquanke.com"  println("我的博客名字叫《我全科》,我的博客地址是:$site")</code></pre>    <h3>4.也许可以忘记getters/setters了</h3>    <p>我们经常创建一些只保存数据的类。在这些类中,一些标准函数往往是操作一下ide生成的。在 Kotlin 中,这叫做 数据类 并标记为 data:</p>    <pre>  <code class="language-kotlin">data class User(val name: String, val age: Int)</code></pre>    <p>data class 自动生成getter,setting,hashcode和equals等方法</p>    <h3>5.请忘记三元运算符</h3>    <p>在 Kotlin 中,if是一个表达式,即它会返回一个值。因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if 就能胜任这个角色。</p>    <pre>  <code class="language-kotlin">// 作为表达式  val max = if (a > b) a else b</code></pre>    <h3>6.哪里还有switch</h3>    <p>when 取代了类java 语言的 switch 操作符。其最简单的形式如下:</p>    <pre>  <code class="language-kotlin">when (x) {      1 -> print("x == 1")      2 -> print("x == 2")      else -> { // 注意这个块          print("x is neither 1 nor 2")      }  }</code></pre>    <p>如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:</p>    <pre>  <code class="language-kotlin">when (x) {      0, 1 -> print("x == 0 or x == 1")      else -> print("otherwise")  }</code></pre>    <p>可以用任意表达式(而不只是常量)作为分支条件</p>    <pre>  <code class="language-kotlin">when (x) {      parseInt(s) -> print("s encodes x")      else -> print("s does not encode x")  }</code></pre>    <p>也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:</p>    <pre>  <code class="language-kotlin">when (x) {      in 1..10 -> print("x is in the range")      in validNumbers -> print("x is valid")      !in 10..20 -> print("x is outside the range")      else -> print("none of the above")  }</code></pre>    <p>另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需任何额外的检测。</p>    <pre>  <code class="language-kotlin">fun hasPrefix(x: Any) = when(x) {      is String -> x.startsWith("prefix")      else -> false  }</code></pre>    <p>when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:</p>    <pre>  <code class="language-kotlin">when {      x.isOdd() -> print("x is odd")      x.isEven() -> print("x is even")      else -> print("x is funny")  }</code></pre>    <h3>7.去你的ClassCastException</h3>    <p>Kotlin智能转换(Smart Casts)</p>    <p>对于不可变的值,Kotlin一般不需要显式转换对象类型,编译器能跟踪is检查类型,在需要时会自动插入类型转换代码(安全):</p>    <pre>  <code class="language-kotlin">fun classCast(a: Any) {      if (a is String) {          print(a.length) //编译器自动把a转换为String类型      }  }</code></pre>    <p>Kotlin编译器很聪明,能识别反向检查类型!is操作符,会自动插入类型转换代码:</p>    <pre>  <code class="language-kotlin">if (a !is String) return      print(a.length) //编译器自动把x转换为String类型:      // ||右侧, a自动转换为String类型      if (a !is String || a.length == 0) return      // &&右侧, a自动转换为String类型      if (a is String && a.length > 0) {          print(a.length) // a 自动转换为字符串      }      //智能转换(smart casts)也用于when表达式和while循环      when (a) {          is Int -> print(a + 1)          is String -> print(a.length + 1)          is IntArray -> print(a.sum())      }</code></pre>    <p> 如果不能保证变量在类型检查 is / !is 操作符和变量使用之间不可改变时,智能转换不能用。智能转换的适用条件或规则:</p>    <ul>     <li>val局部变量-总是适用!</li>     <li>val属性-适用于private或internal,或者类型检查is/!is在声明属性的同一模块中执行;</li>     <li>不适用于open的属性,或者具有自定义getter的属性!</li>     <li>var局部变量—适用于变量在类型检查和使用之间没有修改,且不在修改它的lambda中捕获!</li>     <li>var属性-不适用(因为该变量可随时被修改)</li>    </ul>    <p>安全(可空)转换-操作符as?</p>    <p>为避免抛出异常,可用安全转换操作符 as? ,在失败时返回null</p>    <pre>  <code class="language-kotlin">val a: String? = b as? String</code></pre>    <p>尽管 as? 右边是一个非空类型String,但是 as? 转换失败时返回可空(null),换句话说就是, as? 函数参数String不能为null,但是as?函数的返回值可以是null</p>    <h3>8.真的要习惯Koltin的for循环,太强大了</h3>    <p>Kotlin没有Java中的for(初始值;条件;增减步长)这个规则。但是Kotlin中对于for循环语句新增了其他的规则,来满足刚提到的规则。</p>    <ul>     <li>for循环提供迭代器用来遍历任何东西</li>     <li>for循环数组被编译为一个基于索引的循环,它不会创建一个迭代器对象</li>    </ul>    <p>新增的规则,去满足for(初始值;条件;增减步长)这个规则</p>    <p>递增</p>    <p>关键字:until</p>    <p>范围:until[n,m) => 即大于等于n,小于m</p>    <p>例:</p>    <pre>  <code class="language-kotlin">// 循环5次,且步长为1的递增  for (i in 0 until 5){    print("i => $i \t")  }</code></pre>    <p>输出结果为</p>    <pre>  <code class="language-kotlin">i => 0  i => 1  i => 2  i => 3  i => 4</code></pre>    <p>递减</p>    <p>关键字:downTo</p>    <p>范围:downTo[n,m] => 即小于等于n,大于等于m ,n > m</p>    <p>例:</p>    <pre>  <code class="language-kotlin">// 循环5次,且步长为1的递减  for (i in 15 downTo 11){      print("i => $i \t")  }</code></pre>    <p>输出结果为:</p>    <pre>  <code class="language-kotlin">i => 15     i => 14     i => 13     i => 12     i => 11</code></pre>    <p>符号(’ .. ‘) 表示递增的循环的另外一种操作</p>    <p>使用符号( ‘..’).</p>    <p>范围:..[n,m]=> 即大于等于n,小于等于m</p>    <p>和until的区别,一是简便性。二是范围的不同。</p>    <p>例:</p>    <pre>  <code class="language-kotlin">print("使用 符号`..`的打印结果\n")  for (i in 20 .. 25){      print("i => $i \t")  }  println()  print("使用until的打印结果\n")  for (i in 20 until 25){      print("i => $i \t")  }</code></pre>    <p>输出结果为:</p>    <p>使用 符号 .. 的打印结果</p>    <pre>  <code class="language-kotlin">i => 20     i => 21     i => 22     i => 23     i => 24     i => 25</code></pre>    <p>使用until的打印结果</p>    <pre>  <code class="language-kotlin">i => 20     i => 21     i => 22     i => 23     i => 24</code></pre>    <p>设置步长</p>    <p>关键字:step</p>    <p>例:</p>    <pre>  <code class="language-kotlin">for (i in 10 until 16 step 2){      print("i => $i \t")  }</code></pre>    <p>输出结果为:</p>    <pre>  <code class="language-kotlin">i => 10     i => 12     i => 14</code></pre>    <p>迭代</p>    <p>for循环提供一个迭代器用来遍历任何东西。</p>    <p>for循环数组被编译为一个基于索引的循环,它不会创建一个迭代器对象</p>    <p>遍历字符串</p>    <p>此用法在数据类型章节中的字符串类型中用到过。还不甚清楚的可以查看 Kotlin——最详细的数据类型介绍。</p>    <p>例:</p>    <pre>  <code class="language-kotlin">for (i in "abcdefg"){      print("i => $i \t")  }</code></pre>    <p>输出结果为:</p>    <pre>  <code class="language-kotlin">i => a  i => b  i => c  i => d  i => e  i => f  i => g</code></pre>    <p>遍历数组</p>    <p>此用法在数据类型章节中的数组类型中用到过。还不甚清楚的可以查看 Kotlin——最详细的数据类型介绍。</p>    <p>例:</p>    <pre>  <code class="language-kotlin">var arrayListOne = arrayOf(10,20,30,40,50)  for (i in arrayListOne){      print("i => $i \t")  }</code></pre>    <p>输出结果为:</p>    <pre>  <code class="language-kotlin">i => 10     i => 20     i => 30     i => 40     i => 50</code></pre>    <p>使用数组的indices属性遍历</p>    <p>例:</p>    <pre>  <code class="language-kotlin">var arrayListTwo = arrayOf(1,3,5,7,9)  for (i in arrayListTwo.indices){      println("arrayListTwo[$i] => " + arrayListTwo[i])  }</code></pre>    <p>输出结果为:</p>    <pre>  <code class="language-kotlin">arrayListTwo[0] => 1  arrayListTwo[1] => 3  arrayListTwo[2] => 5  arrayListTwo[3] => 7  arrayListTwo[4] => 9</code></pre>    <p>使用数组的withIndex()方法遍历</p>    <p>例:</p>    <pre>  <code class="language-kotlin">var arrayListTwo = arrayOf(1,3,5,7,9)  for ((index,value) in arrayListTwo.withIndex()){      println("index => $index \t value => $value")  }</code></pre>    <p>输出结果为:</p>    <pre>  <code class="language-kotlin">index => 0   value => 1  index => 1   value => 3  index => 2   value => 5  index => 3   value => 7  index => 4   value => 9</code></pre>    <p>使用列表或数组的扩展函数遍历</p>    <p>数组或列表有一个成员或扩展函数iterator()实现了Iterator接口,且该接口提供了next()与hasNext()两个成员或扩展函数</p>    <p>其一般和while循环一起使用</p>    <p>可以查看Array.kt这个类。可以看见其中的iterator()函数,而这个函数实现了Iterator接口。</p>    <pre>  <code class="language-kotlin">/**    *   Creates an iterator for iterating over the elements of the array.    */  public operator fun iterator(): Iterator<T></code></pre>    <p>查看Iterator.kt这个接口类,这个接口提供了hasNext()函数和next()函数。</p>    <pre>  <code class="language-kotlin">public interface Iterator<out T> {  /**    * Returns the next element in the iteration.    */  public operator fun next(): T  /**    * Returns `true` if the iteration has more elements.    */  public operator fun hasNext(): Boolean  }</code></pre>    <p>例:</p>    <pre>  <code class="language-kotlin">var arrayListThree = arrayOf(2,'a',3,false,9)  var iterator: Iterator<Any> = arrayListThree.iterator()  while (iterator.hasNext()){      println(iterator.next())  }</code></pre>    <p>输出结果为:</p>    <pre>  <code class="language-kotlin">2  a  3  false  9</code></pre>    <p>关于for循环的内容来自 <a href="/misc/goto?guid=4959757447663962683" rel="nofollow,noindex">《Kotlin——最详细的控制语句详解》</a></p>    <h3>9.kotlin stream 真心可以</h3>    <p>流式处理给我们的集合操作带来了很大的方便,其实Java 8 一样支持流式处理,我只是想在这里推广一下 stream。</p>    <p>下面举例:</p>    <pre>  <code class="language-kotlin">val names = arrayOf("Amy", "Alex", "Bob", "Cindy", "Jeff", "Jack", "Sunny", "Sara", "Steven")        //筛选S开头的人名    val sName = names.filter { it.startsWith("S") }.toList()        //按首字母分组并排序    val nameGroup = names.groupBy { it[0] }            .toSortedMap( Comparator { key1, key2 -> key1.compareTo(key2) })</code></pre>    <p>关于更多流式处理,请自行搜索Java stream</p>    <h3>10.少写点方法重载</h3>    <p>因为kotlin支持默认参数,所以在封装方法时会少很多的方法重载的。</p>    <p>如果没有默认参数的需要实现下面的日志打印,需要写多个方法:</p>    <pre>  <code class="language-kotlin">fun log(tag: String, content: String) {      println("tag:$tag-->$content")  }  fun log( content: String) {      log("quanke","")  }</code></pre>    <p>使用默认参数只需要一个方法:</p>    <pre>  <code class="language-kotlin">fun log(tag: String="quanke", content: String) {      println("tag:$tag-->$content")  }</code></pre>    <p>最后我还是想说:抱歉!不要用Java的语法思维来写Kotlin!</p>    <p> </p>    <p>来自:http://www.techug.com/post/java-vs-kotlin.html</p>    <p> </p>