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

转载

不要用Java的语法思维来写Kotlin,不要让kotlin的优雅埋没。如果你没有Java开发经验,下面的内容也对你会有帮助。。。

kotlin list 常用处理

.also 同时处理
list.fist 取list 第一条记录
list.last 取list 最后一条记录
list.map 将list中对象转换成另一个对象
list.sortBy根据list中对象排序
list.find查找满足条件的第一条数据

@JvmStatic
    fun main(args: Array<String>) {
      val list = ArrayList<User>()
    //**.also** 同时处理
      list.add(User().also {
        it.id = 1
        it.account = "aaa"
      })
      list.add(User().also {
        it.id = 2
        it.account = "bac"
      })
      list.add(User().also {
        it.id = 3
        it.account = "vada"
      })
      //list.find查找满足条件的第一条数据
      list.find {
     //条件
        "aaa".equals(it.account)
      }.let { usr ->
        println(usr)
      }
      // list.fist 取list 第一条记录
     // list.last 取list 最后一条记录
      println(list.first())
      //list.map 将list中对象转换成另一个对象
      //list.sortBy根据list中对象排序
      list.map { user ->
        UserVo(id = user.id ?: 0, account = user.account ?: "")
      }.sortedByDescending {
        it.account
      }.let {
        println(it)
        println(it.first())
      }
    }

1.尽可能的少用 !!

个人感觉对于Null的检查是Koltin最语法糖的东西了,强制在编码过程中考虑空指针,因此《十亿美元的错误》,也许你不会再有这个机会犯错了(也许可以说成,你赚了十亿美金^_^)。

首先需要介绍是!!操作符。

!! 操作符:这是为空指针爱好者准备的,非空断言运算符(!!)将任何值转换为非空类型,若该值为空则抛出异常。我们可以写 a!! ,这会返回一个非空的 a 值 (例如:在我们例子中的 String)或者如果 a 为空,就会抛出一个 空指针 异常:

val b = a!!.length

所以,我们能不用!!操作符就不要用。。。

下面介绍几种方式避免使用!!操作符

1).多用 val 而不是 var

在 Kotlin 中 val代表只读,var代表可变。建议尽可能多的使用valval是线程安全的,并且必须在定义时初始化,所以不需要担心 null 的问题。只需要注意 val 在某些情况下也是可变的就行了。

val 和 var 是用于表示属性是否有 getter/setter:

  • var:同时有 getter 和 setter
  • val:只有 getter

所以,强烈推荐能用val的地方就用val

2).使用 lateinit

有些时候并不能用val,比如在spring boot接口测试中就不能使用val,对于这种情况,可以使用 lateinit 关键字。。

依赖倒转,对象的创建是通过spring完成的,而val要求定义的时候初始化

/**

*/

@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")

}

}

注意:lateinit很好用,但也有坑

  • 访问未初始化的 lateinit 属性会导致 UninitializedPropertyAccessException。
  • lateinit 不支持基础数据类型,比如 Int。对于基础数据类型,我们可以这样:
> private var mNumber: Int by Delegates.notNull<Int>()

3).Elvis 操作符

当b为可空引用时,我们可以使用if表达式处理

val l: Int = if (b != null) b.length else -1

但更加优雅的方式是使用Elvis 操作符?:

val l = b?.length ?: -1

如果 ?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。

注意:当且仅当左侧为空时,才会对右侧表达式求值。

4).也许可以尝试一下let函数

let函数一般与安全调用操作符一起使用,我们首先介绍安全调用操作?.

b?.length
如果 b 非空,就返回 b.length,否则返回 null,这个表达式的类型是 Int?。

安全调用在链式调用中很有用。例如,如果一个员工Quanke可能会(或者不会)分配给一个部门, 并且可能有另外一个员工是该部门的负责人,那么获取 Quanke 所在部门负责人(如果有的话)的名字,我们写作:

quanke?.department?.head?.name

如果任意一个属性(环节)为空,这个链式调用就会返回 null。

如果要只对非空值执行某个操作,安全调用操作符可以与 let 一起使用:

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } // 输出 A 并忽略 null
}
还有一种常见的错误(放ide里面试试就知道什么错误了):

private var a: String? = null
fun aLetDemo() {
   if (a != null) {
        test(a)
    }
}

我们可以这样:

private var a: String? = null
fun aLetDemo() {
    if (a != null) {
        test(a!!)
    }
}

但是这样的后果就是你还是需要在test函数里处理空指针。

我们充分利用?.let的特点,更加优雅的解决这个编译错误,如下

private var a: String? = null
fun aLetDemo() {
    if (a != null) {
        a?.let {
            test(it)
        }
    }
}

2.少写点Util类和继承

很多时候框架提供给我的方法是比较原子,或者某些常用的方法框架并没有提供,Java一般是写一个工具类:

public final class StringUtil {

/**

 * 删除所有的标点符号

 *

 * @param str 处理的字符串

 */

public  static String trimPunct(String str) {

    if(isEmpty(str)){

        return "";

    }

    return str.replaceAll("[\\pP\\p{Punct}]", "");

}
}

Kotlin可以通过扩展函数的形式实现:

/**

- 删除所有的标点符号

 *

- @param str 处理的字符串

 */
fun String.trimPunct(): String {
    return if (this.isEmpty()) {
        ""
    } else this.replace("[\\pP\\p{Punct}]".toRegex(), "")
}

调用:

fun main(args: Array<String>) {
    val a = "把我的标点符号去掉吧,全科。"
    print(a.trimPunct())
}

打印:

把我的标点符号去掉吧全科

Process finished with exit code 0

3.别再用+号拼接字符串

无论是Java还是Android开发,我们都会用到字符串拼接,比如进行日志输出等等。在Kotlin中,支持字符串模板,我们可以很轻松的完成一个字符串数的拼接,当然你可能会说使用StringBuilder性能更好,比如:

val site = "http://woquanke.com"

val sb: StringBuilder = StringBuilder()

sb.append("我的博客名字叫《我全科》,我的博客地址是:")

sb.append(site)

println(sb.toString())

但kotlin的字符串模版可以优雅的做这个事情:

val site = "http://woquanke.com"

println("我的博客名字叫《我全科》,我的博客地址是:$site")

4.也许可以忘记getters/setters了

我们经常创建一些只保存数据的类。在这些类中,一些标准函数往往是操作一下ide生成的。在 Kotlin 中,这叫做 数据类 并标记为 data:

data class User(val name: String, val age: Int)

data class 自动生成getter,setting,hashcode和equals等方法

5.请忘记三元运算符

在 Kotlin 中,if是一个表达式,即它会返回一个值。因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if 就能胜任这个角色。

// 作为表达式

val max = if (a > b) a else b

6.哪里还有switch

when 取代了类java 语言的 switch 操作符。其最简单的形式如下:

when (x) {

    1 -> print("x == 1")
    
    2 -> print("x == 2")
    
    else -> { // 注意这个块
    
        print("x is neither 1 nor 2")
    
    }

}

如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

when (x) {
0, 1 -> print("x == 0 or x == 1")

else -> print("otherwise")
}

可以用任意表达式(而不只是常量)作为分支条件

when (x) {

    parseInt(s) -> print("s encodes x")
    
    else -> print("s does not encode x")

}

也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:

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")

}

另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需任何额外的检测。

fun hasPrefix(x: Any) = when(x) {

    is String -> x.startsWith("prefix")
    
    else -> false

}

when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:

when {

    x.isOdd() -> print("x is odd")
    
    x.isEven() -> print("x is even")
    
    else -> print("x is funny")

}

7.去你的ClassCastException

Kotlin智能转换(Smart Casts)

对于不可变的值,Kotlin一般不需要显式转换对象类型,编译器能跟踪is检查类型,在需要时会自动插入类型转换代码(安全):

fun classCast(a: Any) {

    if (a is String) {
    
        print(a.length) //编译器自动把a转换为String类型
    
    }

}

Kotlin编译器很聪明,能识别反向检查类型!is操作符,会自动插入类型转换代码:

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())

}

如果不能保证变量在类型检查is/!is操作符和变量使用之间不可改变时,智能转换不能用。智能转换的适用条件或规则:

  • val局部变量-总是适用!
  • val属性-适用于private或internal,或者类型检查is/!is在声明属性的同一模块中执行;
  • 不适用于open的属性,或者具有自定义getter的属性!
  • var局部变量—适用于变量在类型检查和使用之间没有修改,且不在修改它的lambda中捕获!
  • var属性-不适用(因为该变量可随时被修改)

安全(可空)转换-操作符as?

为避免抛出异常,可用安全转换操作符as?,在失败时返回null

val a: String? = b as? String

尽管as?右边是一个非空类型String,但是as?转换失败时返回可空(null),换句话说就是,as?函数参数String不能为null,但是as?函数的返回值可以是null

8.真的要习惯Koltin的for循环,太强大了

Kotlin没有Java中的for(初始值;条件;增减步长)这个规则。但是Kotlin中对于for循环语句新增了其他的规则,来满足刚提到的规则。

  • for循环提供迭代器用来遍历任何东西
  • for循环数组被编译为一个基于索引的循环,它不会创建一个迭代器对象

新增的规则,去满足for(初始值;条件;增减步长)这个规则

递增

关键字:until
范围:until[n,m) => 即大于等于n,小于m

例:

// 循环5次,且步长为1的递增

for (i in 0 until 5){

  print("i => $i \t")

}

输出结果为

i => 0 i => 1 i => 2 i => 3 i => 4

递减

关键字:downTo
范围:downTo[n,m] => 即小于等于n,大于等于m ,n > m
例:

// 循环5次,且步长为1的递减

for (i in 15 downTo 11){

    print("i => $i \t")

}

输出结果为:

i => 15 i => 14 i => 13 i => 12 i => 11

符号(’ .. ‘) 表示递增的循环的另外一种操作

使用符号( ‘..’).
范围:..[n,m]=> 即大于等于n,小于等于m
和until的区别,一是简便性。二是范围的不同。
例:

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")

}

输出结果为:
使用 符号..的打印结果

i => 20 i => 21 i => 22 i => 23 i => 24 i => 25
使用until的打印结果
i => 20 i => 21 i => 22 i => 23 i => 24

设置步长

关键字:step

例:

for (i in 10 until 16 step 2){
    print("i => $i \t")
}

输出结果为:

i => 10 i => 12 i => 14

迭代

for循环提供一个迭代器用来遍历任何东西。
for循环数组被编译为一个基于索引的循环,它不会创建一个迭代器对象

遍历字符串

此用法在数据类型章节中的字符串类型中用到过。还不甚清楚的可以查看 Kotlin——最详细的数据类型介绍。

例:

for (i in "abcdefg"){

    print("i => $i \t")

}

输出结果为:

i => a i => b i => c i => d i => e i => f i => g

遍历数组

此用法在数据类型章节中的数组类型中用到过。还不甚清楚的可以查看 Kotlin——最详细的数据类型介绍。

例:

var arrayListOne = arrayOf(10,20,30,40,50)

for (i in arrayListOne){

    print("i => $i \t")

}

输出结果为:

i => 10 i => 20 i => 30 i => 40 i => 50

使用数组的indices属性遍历

例:

var arrayListTwo = arrayOf(1,3,5,7,9)

for (i in arrayListTwo.indices){

    println("arrayListTwo[$i] => " + arrayListTwo[i])

}

输出结果为:

arrayListTwo[0] => 1
arrayListTwo[1] => 3
arrayListTwo[2] => 5
arrayListTwo[3] => 7
arrayListTwo[4] => 9

使用数组的withIndex()方法遍历

例:

var arrayListTwo = arrayOf(1,3,5,7,9)

for ((index,value) in arrayListTwo.withIndex()){

    println("index => $index \t value => $value")

}

输出结果为:

index => 0 value => 1
index => 1 value => 3
index => 2 value => 5
index => 3 value => 7
index => 4 value => 9

使用列表或数组的扩展函数遍历

数组或列表有一个成员或扩展函数iterator()实现了Iterator<t style="box-sizing: border-box;">接口,且该接口提供了next()与hasNext()两个成员或扩展函数
其一般和while循环一起使用
可以查看Array.kt这个类。可以看见其中的iterator()函数,而这个函数实现了Iterator接口。</t>

/**

- Creates an iterator for iterating over the elements of the array.

  */

public operator fun iterator(): Iterator<T>



//查看Iterator.kt这个接口类,这个接口提供了hasNext()函数和next()函数。



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

}

例:

var arrayListThree = arrayOf(2,'a',3,false,9)

var iterator: Iterator<Any> = arrayListThree.iterator()

while (iterator.hasNext()){

    println(iterator.next())

}

输出结果为:

2
a
3
false
9

关于for循环的内容来自《Kotlin——最详细的控制语句详解》

9.kotlin stream 真心可以

流式处理给我们的集合操作带来了很大的方便,其实Java 8 一样支持流式处理,我只是想在这里推广一下 stream。

下面举例:

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) })

关于更多流式处理,请自行搜索Java stream

10.少写点方法重载

因为kotlin支持默认参数,所以在封装方法时会少很多的方法重载的。

如果没有默认参数的需要实现下面的日志打印,需要写多个方法:

fun log(tag: String, content: String) {
    println("tag:$tag-->$content")
}
fun log( content: String) {
    log("quanke","")
}

使用默认参数只需要一个方法:

fun log(tag: String="quanke", content: String) {
    println("tag:$tag-->$content")
}

原文链接:http://woquanke.com/posts/39073
作者:woquanke.com
转载请注明出处!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容