Kotlin "談" "彈" "潭"

Kotlin "談" "彈" "潭"

本篇針對(duì)使用Java的Android開(kāi)發(fā)者,快速入手Kotlin隘庄,期間可能啰啰嗦嗦稀里糊涂緩緩乎乎穿插一些我中過(guò)的坑。這里不講Kotlin異步并發(fā)(協(xié)程)、不講Kotlin反射倦沧,如果你是來(lái)看它們的蔗怠。那我現(xiàn)在也木有墩弯。

image

目錄

一、為什么要學(xué)習(xí)kotlin
二寞射、基本使用
1渔工、Java寫(xiě)法和Kotlin的寫(xiě)法對(duì)比
2、基本類型
  • Kotlin中數(shù)字類型
  • 運(yùn)算符號(hào)
  • 布爾值
  • 數(shù)組
3桥温、基本表達(dá)式
  • 表達(dá)式
  • 流程控制
  • when使你力腕狂瀾
  • 使用表達(dá)式比語(yǔ)句更加安全
  • for循環(huán)的奧義
4引矩、各種類
  • 類、接口基本概念
  • 伴生對(duì)象
  • 單例類
  • 匿名內(nèi)部類
  • 數(shù)據(jù)類
5侵浸、各種函數(shù)
  • lambda表達(dá)式
  • 內(nèi)聯(lián)函數(shù)
  • 擴(kuò)展函數(shù)
  • 方法默認(rèn)參數(shù)
6旺韭、各種集合
  • List
  • Set
  • Map
  • 集合操作
  • 惰性求值和序列
7、函數(shù)方法值類型
8掏觉、高階函數(shù)
9区端、空安全
10、關(guān)于設(shè)計(jì)模式和Kotlin
  • 工廠模式
  • 觀察者和代理模式
11澳腹、快速對(duì)比Java代碼必備技能
三织盼、參考

一、為什么要學(xué)習(xí)kotlin

  • 1遵湖、Google推薦
    現(xiàn)在的新特性文檔以及例子有一些是只有kotlin的了

  • 2悔政、更加簡(jiǎn)潔的語(yǔ)法減少啰嗦代碼

  • 3、都是基于JVM編譯成字節(jié)碼延旧,與Java基本兼容基本沒(méi)有太大問(wèn)題谋国,因此可以用java寫(xiě)的很多庫(kù),生態(tài)強(qiáng)大

  • 4迁沫、強(qiáng)大的語(yǔ)言特性

二芦瘾、基本使用

1捌蚊、Java寫(xiě)法和Kotlin的寫(xiě)法對(duì)比

from-java-to-kotlin

2、基本類型

Kotlin中所有的東西都是對(duì)象近弟,包括基本類型缅糟,一般來(lái)說(shuō)數(shù)字、字符祷愉、布爾值窗宦、數(shù)組與字符串是組成一門(mén)語(yǔ)言的基本數(shù)據(jù)類型。

Kotlin中數(shù)字類型
Type Bit width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

每個(gè)數(shù)字類型支持如下的顯示轉(zhuǎn)換二鳄,比如

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
運(yùn)算符號(hào)
  • shl(bits) – 有符號(hào)左移 (Java 的 <<)
  • shr(bits) – 有符號(hào)右移 (Java 的 >>)
  • ushr(bits) – 無(wú)符號(hào)右移 (Java 的 >>>)
  • and(bits) – 位與
  • or(bits) – 位或
  • xor(bits) – 位異或
  • inv() – 位非
布爾值

Boolean 類型赴涵,有兩個(gè)值true 與 false

數(shù)組

數(shù)組在 Kotlin 中使用 Array 類來(lái)表示,它定義了 get 與 set 函數(shù)(按照運(yùn)算符重載約定這會(huì)轉(zhuǎn)變?yōu)?[])以及 size 屬性

比如說(shuō)

val persons = Array<Person>(3) {
            Person("2")
            Person("3")
            Person("4")
        }
persons.size

當(dāng)然订讼,kotlin也支持簡(jiǎn)單的基本原生基本類型髓窜,無(wú)裝箱開(kāi)箱的開(kāi)銷的ByteArray、 ShortArray欺殿、IntArray 等等

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

3寄纵、基本表達(dá)式

表達(dá)式

什么是表達(dá)式呢,在Java中脖苏,以;結(jié)尾的一段代碼程拭,即為一個(gè)表達(dá)式。

setContentView(R.layout.activity_main)

public static final int CAT_DRIVERS = 20;

Log.e(TAG, "貓司機(jī)的腿數(shù)量: " + catCount * 3);

這個(gè)也是kotlin中表達(dá)式的概念帆阳。

流程控制

Kotlin的流程控制和Java的基本相同哺壶,最大的區(qū)別是Kotlin沒(méi)有switch語(yǔ)句屋吨,kotlin中是更加強(qiáng)大的when語(yǔ)句蜒谤。而且kotlin的if語(yǔ)句和when語(yǔ)句可以當(dāng)做表達(dá)式來(lái)使用,就跟Java中的三目運(yùn)算符一樣至扰。

Java:
String name = isOne ? "是一個(gè)人" : "是半個(gè)人";

可以說(shuō)when是Java中if和switch的一次強(qiáng)大的聯(lián)合鳍徽。比如說(shuō),可以有這種寫(xiě)法

var single3 = when (single) {
        0, 1 -> aLog("single == 0 or single == 1")
        else -> aLog("其他")
    }

if和while都是和java一樣的敢课,區(qū)別在于if可以當(dāng)做表達(dá)式來(lái)使用阶祭,比如

private val single2 = if (single >= 3) {
        aLog("大于等于3")
    } else {
        aLog("小于3")
    }
when使你力腕狂瀾

如果很多分支需要用相同的方式處理,則可以把多個(gè)分支條件放在一起直秆,用逗號(hào)分隔:

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

我們可以用任意表達(dá)式(而不只是常量)作為分支條件

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

我們也可以檢測(cè)一個(gè)值在(in)或者不在(!in)一個(gè)區(qū)間或者集合中:

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

另一種可能性是檢測(cè)一個(gè)值是(is)或者不是(!is)一個(gè)特定類型的值濒募。注意: 由于智能轉(zhuǎn)換,你可以訪問(wèn)該類型的方法與屬性而無(wú)需任何額外的檢測(cè)圾结。

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

when 也可以用來(lái)取代 if-else if鏈瑰剃。 如果不提供參數(shù),所有的分支條件都是簡(jiǎn)單的布爾表達(dá)式筝野,而當(dāng)一個(gè)分支的條件為真時(shí)則執(zhí)行該分支:

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

這些用法晌姚,可以說(shuō)一個(gè)when讓你不再寫(xiě)出混亂的if嵌套粤剧。

使用表達(dá)式比語(yǔ)句更加安全

先看一段Java代碼

private void dididada(boolean needInit) {
    String a = null;
    if(needInit){
        a = "a is Dog that not girlfriend";
    }
    Log.e(TAG, a.toString());
}

這段代碼可能有潛在的問(wèn)題,比如說(shuō)a必須在if語(yǔ)句外部聲明挥唠,它被初始化為null抵恋。這段代碼中,我們忽略了else分支宝磨,如果needInit為false弧关,那么會(huì)導(dǎo)致a.toString()空指針異常問(wèn)題。并且如果needInit的值時(shí)很深的路徑傳遞過(guò)來(lái)的唤锉,那么可能會(huì)導(dǎo)致這個(gè)問(wèn)題更容易被忽略梯醒。

如果,你用了表達(dá)式


image

比如你這樣子寫(xiě)了


fun dididada(boolean needInit) {
    String a = if(needInit){
            "a is Dog that not girlfriend"
    } else{
        ""
    }
    Log.e(TAG, a.toString())
}

實(shí)際上腌紧,可以更簡(jiǎn)化點(diǎn)

fun dididada(boolean needInit) {
    String a = if(needInit) "a is Dog that not girlfriend" else ""
    Log.e(TAG, a.toString())
}

因?yàn)楸磉_(dá)式強(qiáng)制需要你寫(xiě)出else語(yǔ)句茸习,也就不再存在上面說(shuō)的漏掉else的問(wèn)題了。

for循環(huán)的奧義

上面加when的時(shí)候出現(xiàn)的..關(guān)鍵字和in關(guān)鍵字這些壁肋,..這個(gè)叫做區(qū)間号胚,比如說(shuō)1..4代表從1到4的數(shù)列。比如說(shuō)

if (i in 1..4) {  // 等同于 1 <= i && i <= 4
    print(i)
}

for (i in 1..4) {
    print(i)
}
輸出:1浸遗,2,3,4  

如果你要倒序呢猫胁,你可以這樣

for (i in 4 downTo 1) {
    print(i)
}
輸出:4,3,2,1  

downTo關(guān)鍵字代表反向遍歷,如果你想輸出一個(gè)等差數(shù)列跛锌,你可以用step關(guān)鍵字

for (i in 1..8 step 2){
    print(i)
}
輸出:1,3,5,7

用Java寫(xiě)出類似的邏輯弃秆,你需要

for (int i = 1; i <= 8; i += 2) {
  print(i)
}

可見(jiàn)Kotlin相對(duì)于Java的簡(jiǎn)潔

剛才上面所說(shuō)的區(qū)間都是左閉右也閉的,你還可以使用until來(lái)代替..實(shí)現(xiàn)左閉右開(kāi)區(qū)間髓帽,就比如

for (i in 1 until 10) {       // i in [1, 10), 10 is excluded
    print(i)
}

4菠赚、各種類

類、接口基本概念

kotlin中類郑藏、接口的聲明跟Java中是一致的

class Pig { ... }

interface Motion {
    fun run()
}

如果你要繼承一個(gè)類或者實(shí)現(xiàn)一個(gè)接口衡查,都可以使用:符號(hào),另外接口想繼承另外一個(gè)接口必盖,也是可以使用:來(lái)達(dá)到目的拌牲,可以說(shuō)是統(tǒng)一extends和implement關(guān)鍵字,有利有弊歌粥。
比如Pig繼承Animal類和實(shí)現(xiàn)Motion接口


class Animal { 
    var name:String?
}

inteface Motion {
    fun run()
}

class Pig : Animal, Motion{
    override fun run() {
        val pigName = name
    }
}

kotlin的類的聲明有一個(gè)很重要的概念是主構(gòu)造函數(shù)和次構(gòu)造函數(shù)塌忽,主構(gòu)造函數(shù)是類頭的一部分,它跟在類名的后面失驶,比如

class Pig constructor(name: String) { ... }

你甚至可以省略主構(gòu)造器的constructor土居,就像這樣

class Pig(name: String) { ... }

什么情況可以省略呢,比如說(shuō)你不需要給name添加注解或修改可見(jiàn)性的時(shí)候,那比如說(shuō)装盯,如果你想在主構(gòu)造器初始化的時(shí)候在里面寫(xiě)一串初始化邏輯坷虑,要怎么樣呢?用Java的時(shí)候你應(yīng)該是這樣的

class Pig { 
    public Pig(String name) {
        //初始化邏輯
    }
}

但是現(xiàn)在kotlin使用主構(gòu)造器初始化的話埂奈,你在也看不到可愛(ài)的構(gòu)造器方法了


image

莫慌迄损,你還可以這樣,kotlin中提供了一種init代碼塊账磺,它會(huì)在調(diào)用構(gòu)造方法的時(shí)候按照在類中的init塊的順序從上往下執(zhí)行芹敌,比如

class Pig(name: String) {

    //第一個(gè)init塊
    init {
        println("${name}")
    }
   
    //第二個(gè)init塊
    init {
        println("名字長(zhǎng)度:${name.length}")
    }
}

執(zhí)行的時(shí)候

Pig("你是豬豬豬", 5)

輸出:
你是豬豬豬
名字長(zhǎng)度:5

次構(gòu)造器就跟Java中差不多了,但是不同的地方在于必須要有constructor關(guān)鍵字來(lái)聲明

class Pig(name:String) {
    
    constructor(name: String, length:Int):this(name) {
        println("次構(gòu)造器")
    }
    

如果同時(shí)有主構(gòu)造器和次構(gòu)造器以及init塊垮抗,他們的執(zhí)行順序是什么樣的呢氏捞?實(shí)際上,init塊會(huì)作為主構(gòu)造器的一部分冒版。比如

class Pig(name:String) {
     //第一個(gè)init塊
    init {
        println("${name}")
    }

    constructor(name: String, length:Int):this(name) {
        println("次構(gòu)造器")
    }
    
    //第二個(gè)init塊
    init {
        println("名字長(zhǎng)度:${name.length}")
    }
}


執(zhí)行的時(shí)候

Pig("你是豬豬豬")

輸出:
1.你是豬豬豬
2.名字長(zhǎng)度:5
3.次構(gòu)造器

可見(jiàn)液茎,不管你的init塊在哪里,init塊會(huì)先執(zhí)行完畢

伴生對(duì)象

kotlin中沒(méi)有靜態(tài)內(nèi)部類的概念辞嗡,取而代之的是伴生對(duì)象這貨

class Pig {
    companion object Factory {
        fun create(): pig = Pig()
    }
}

該伴生對(duì)象的成員可通過(guò)只使用類名作為限定符來(lái)調(diào)用捆等,比如說(shuō)


kotlin中調(diào)用
val instance = Pig.create()

Java中調(diào)用
Pig pig = Pig.Companion.create()

可見(jiàn),伴生對(duì)象可以讓我們輕松實(shí)現(xiàn)工廠模式续室,造豬運(yùn)動(dòng)

單例類

平時(shí)用到最多的類就是單例類栋烤,kotlin自帶單例特性,比如

object Demo{
    fun dadada(){
        
    }
}

使用的時(shí)候挺狰,只需要寫(xiě)Demo.dadada()就能使用這個(gè)單例類的方法明郭,當(dāng)然不要有誤解,這個(gè)不是靜態(tài)類的靜態(tài)方法丰泊。反編譯kotlin代碼

public final class Demo {
   public static final Demo INSTANCE;

   public final void dadada() {
   }

   static {
      Demo var0 = new Demo();
      INSTANCE = var0;
   }
}

可以看到這個(gè)是Java中靜態(tài)內(nèi)部類版本的單例寫(xiě)法薯定。

當(dāng)然,如果你想自己寫(xiě)單例趁耗,你可以用Java那套自己去擼沉唠。比較高端的用法,由于Kotlin有委托屬性的概念苛败,可以用lazy屬性來(lái)寫(xiě)一個(gè)懶加載的單例類

class Pig private constructor() {
    companion object {
        val instance: Pig by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        Pig() 
        }
    }
}

等同于Java中的雙重校驗(yàn)單例

public class Pig {
    private volatile static Pig instance;
    private Pig(){} 
    public static Pig getInstance(){
        if(instance==null){
            synchronized (Pig.class){
                if(instance==null){
                    instance=new Pig();
                }
            }
        }
        return instance;
    }
}

更多單例的實(shí)現(xiàn)可以去Kotlin下的5種單例模式查看

匿名內(nèi)部類

在Android中,匿名內(nèi)部類是必不可少的径簿,比如button的點(diǎn)擊事件罢屈、各個(gè)監(jiān)聽(tīng)事件的事件回調(diào)等等。

kotlin中的匿名內(nèi)部類是要以object開(kāi)頭的篇亭,比如Java中的

mExpandMenu.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });

kotlin的寫(xiě)法

view.setOnClickListener(object: View.OnClickListener {
            override fun onClick(v: View?) {
                 //onClick代碼塊
            }
        })

你可以更精簡(jiǎn)

view.setOnClickListener {
            //onClick代碼塊
        }
數(shù)據(jù)類

我們經(jīng)常會(huì)使用到一些只保存數(shù)據(jù)的類缠捌,java中需要自己去寫(xiě)屬性和get和set方法,在kotlin中,可以使用數(shù)據(jù)類來(lái)避免模版代碼曼月。

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

編譯器自動(dòng)從主構(gòu)造函數(shù)中聲明的所有屬性導(dǎo)出以下成員:

  • equals()/hashCode() 對(duì)谊却;
  • toString() 格式是 "User(name=John, age=42)";
  • componentN() 函數(shù) 按聲明順序?qū)?yīng)于所有屬性哑芹;
  • copy() 函數(shù)

前三包含了set炎辨、get方法和基本的類方法,copy函數(shù)實(shí)際上是

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

name: String = this.name這種寫(xiě)法是函數(shù)默認(rèn)值寫(xiě)法聪姿,后面會(huì)單獨(dú)介紹碴萧。

又new了一個(gè)對(duì)象出來(lái),達(dá)到復(fù)制的目的末购。

data class數(shù)據(jù)類能讓我們少寫(xiě)bean類很多代碼破喻。

5、各種函數(shù)

lambda表達(dá)式

上面有個(gè)地方其實(shí)有提到過(guò)kotlin的lambda表達(dá)式

view.setOnClickListener(object: View.OnClickListener {
            override fun onClick(v: View?) {
                 //onClick代碼塊
            }
        })

你可以更精簡(jiǎn)

view.setOnClickListener {
            //onClick代碼塊
        }

精簡(jiǎn)后的其實(shí)就是lambda表達(dá)式本身盟榴,下面我們來(lái)看一個(gè)例子

val list = listOf<String>()
val filterList = list.filter { it == "pig" }

創(chuàng)建一個(gè)List然后過(guò)濾出來(lái)數(shù)據(jù)時(shí)pig的元素

來(lái)看一下filter的源碼

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}


public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

可以看見(jiàn)filter接收的是一個(gè)(T) -> Boolean類型的predicate參數(shù)曹质,其實(shí)它就是lambda類型(Kotlin中所有的東西都是對(duì)象,包括這個(gè)玩意)擎场,表示接收一個(gè)T類型的參數(shù)返回一個(gè)Boolean類型lambda表達(dá)式咆繁。這里的predicate會(huì)一直往下傳,直到真正的過(guò)濾函數(shù)filterTo中顶籽,這里寫(xiě)的比較簡(jiǎn)潔玩般,可以嘗試還原,加上大括號(hào)

for (element in this) {
    if (predicate(element)) {
        destination.add(element)
    }
}
return destination

其實(shí)就是遍歷該集合內(nèi)容礼饱,條件是傳進(jìn)來(lái)的lambda表達(dá)式坏为,我們現(xiàn)在外層傳進(jìn)來(lái)的是it == "pig",再度還原镊绪,得到如下代碼匀伏,當(dāng)元素等于pig的時(shí)候,將元素添加到返回的集合中蝴韭,最后返回新的集合够颠。

for (element in this) {
    if (element == "pig") {
        destination.add(element)
    }
}
return destination

Kotlin中提供了簡(jiǎn)潔的語(yǔ)法去定義函數(shù)的類型,大致如下榄鉴,當(dāng)然還有很多變種

() -> Unit//表示無(wú)參數(shù)無(wú)返回值的Lambda表達(dá)式類型

(T) -> Unit//表示接收一個(gè)T類型參數(shù)履磨,無(wú)返回值的Lambda表達(dá)式類型,這種其實(shí)就是上面說(shuō)的filter

(T) -> R//表示接收一個(gè)T類型參數(shù)庆尘,返回一個(gè)R類型值的Lambda表達(dá)式類型

(T, P) -> R//表示接收一個(gè)T類型和P類型的參數(shù)剃诅,返回一個(gè)R類型值的Lambda表達(dá)式類型

(T, (P,Q) -> S) -> R//表示接收一個(gè)T類型參數(shù)和一個(gè)接收P、Q類型兩個(gè)參數(shù)并返回一個(gè)S類型的值的Lambda表達(dá)式類型參數(shù)驶忌,返回一個(gè)R類型值的Lambda表達(dá)式類型

lamda函數(shù)在實(shí)際使用中矛辕,上述列了2種使用場(chǎng)景,很明顯是更加簡(jiǎn)潔易懂,不啰嗦(當(dāng)然聊品,前提是得知道原理飞蹂,不然一臉懵),但是也有可能造成調(diào)試bug成本增加(難以定位問(wèn)題代碼)翻屈。各有利弊

內(nèi)聯(lián)函數(shù)

上面的filter函數(shù)中陈哑,有個(gè)地方用了inline關(guān)鍵字,其實(shí)這個(gè)就是內(nèi)聯(lián)函數(shù)

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

內(nèi)聯(lián)函數(shù)其實(shí)是為了優(yōu)化lambda開(kāi)銷的產(chǎn)生的妖胀,平時(shí)我們制造lambda的函數(shù)盡量加上inline芥颈。具體怎么優(yōu)化的,可以了解下kotlin是怎么實(shí)現(xiàn)lambda函數(shù)(本質(zhì)上是繼承Lambda抽象類并且實(shí)現(xiàn)了FunctionBase生成的類赚抡,比如Function3)(兼容JDK 6的情況下)爬坑,然后怎么對(duì)其做優(yōu)化的。淺談Kotlin語(yǔ)法篇之lambda編譯成字節(jié)碼過(guò)程完全解析(七)

擴(kuò)展函數(shù)

擴(kuò)展函數(shù)我個(gè)人認(rèn)為是kotlin的最精辟的地方涂臣。又回到剛才講的filter函數(shù)盾计,它還有眾多的兄弟姐妹,比如map赁遗、zip等等等等署辉。但是他們本質(zhì)上都是擴(kuò)展函數(shù)


public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}


fun Activity.log(content: String) {
    Log.e("minminaya", content)
}

基于這種類型的(擴(kuò)展的類.擴(kuò)展函數(shù)名字)叫做就是擴(kuò)展函數(shù)。它僅僅限于擴(kuò)展對(duì)象中的方法岩四,擴(kuò)展函數(shù)不能在靜態(tài)類中使用哭尝。我們可以拿這一特性來(lái)做很多事情。比如說(shuō)剖煌,ImageView結(jié)合Gilde的使用材鹦、findViewById、比如工廠方法模式的擴(kuò)展(后面具體講)耕姊。

以我們項(xiàng)目中常用的Glide工具類為準(zhǔn)桶唐,這里先看下ImageView這些怎么拓展。

比如說(shuō)我們之前是將Glide的加載代放到一個(gè)工具類里


    public void displayImage(ImageView imageView, String url, RequestOptions requestOptions) {
        if (!isWithValid(imageView)) {
            return;
        }
        Glide.with(imageView.getContext()).asBitmap().apply(requestOptions).load(parseUrl(url)).into(imageView);
    }

然后使用的時(shí)候是這樣子使用

 GlideLoader.getInstance().displayImage(mIvShareView.getContext(), mIvShareView,
                GlideLoader.wrapFile(imagePath),
                mRequestOptions);

如果將displayImage作為ImageView的擴(kuò)展函數(shù)

public fun ImageView.displayImage(url: String, requestOptions: RequestOptions) {
    if (GlideLoader.isWithValid(this)) {
        return
    }
    Glide.with(context).load(GlideLoader.parseUrl(url)).apply(requestOptions).into(this)
}

使用方式可以變成這樣

val imageView = ImageView(this)
        imageView.displayImage("url", RequestOptions())
然后再看下Activity中怎么樣消除冗長(zhǎng)的findViewById

之前的用法比如像這樣

       mIvLongVideoTimeTip = mRootView.findViewById(R.id.iv_long_record_time);
        mLongVideoEffectOptContainer = mRootView.findViewById(R.id.long_record_opt_container);
        mIvLongVideoArEffect = mRootView.findViewById(R.id.iv_selfie_camera_long_video_ar_effect);

如果用擴(kuò)展函數(shù)茉兰,你可以這樣

fun <T : View> Activity._view(@IdRes id: Int): T {
    return findViewById(id)
}


在Activity中使用:

val tootBar = _view<Toolbar>(R.id.toolbar)

當(dāng)然kotlin為我們提供了更加方便的findId插件尤泽,你只需要在gradle文件中

apply plugin: 'kotlin-android-extensions'

Activity中(比如說(shuō)Activity的布局是activity_main)

import kotlinx.android.synthetic.main.activity_main.*

比如我們現(xiàn)在的布局xml是這樣子寫(xiě)

<androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

那代碼中就可以直接使用這個(gè)id來(lái)對(duì)view進(jìn)行操作

 toolbar.setLogo(R.mipmap.ic_launcher)
方法默認(rèn)參數(shù)

意思就是,方法的參數(shù)可以指定默認(rèn)的值规脸,函數(shù)調(diào)用的時(shí)候坯约,如果沒(méi)有攜帶參數(shù),那么使用函數(shù)中的默認(rèn)值燃辖,以我們的常用的未重構(gòu)未Builder模式之前的GuideViewHelper為例鬼店,看一下要怎么樣用kotlin來(lái)重構(gòu)。

public class GuideViewHelper {

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final int arrowId) {
        return showGuideView(activity, attachView, layoutId, alignView, false, arrowId);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId) {
        return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, 0);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId, final int yOffset) {
        return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, yOffset, null);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId, final int yOffset, final IResetLocationListener resetLocationListener) {
        return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, yOffset, resetLocationListener, null);
    }

    @Nullable
    public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
                                     final boolean below, final int arrowId, final int yOffset,
                                     final IResetLocationListener resetLocationListener, final GuideViewAnimator guideViewAnimator) {
        if (attachView == null || activity == null || activity.isFinishing()) {
            return null;
        }

        final View contentView = getContentView(activity);
        if (contentView instanceof FrameLayout) {
            FrameLayout parent = (FrameLayout) contentView;
            final View guideView = LayoutInflater.from(activity).inflate(layoutId, parent, false);
            if (guideViewAnimator != null) {
                guideView.setTag(R.id.guide_view_animator, guideViewAnimator);
            }
            parent.addView(guideView);
            guideView.setVisibility(View.INVISIBLE);
            guideView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    setGuideViewLocation(alignView, contentView, guideView, attachView, below, arrowId, yOffset, resetLocationListener);
                    if (guideView.getHeight() > 0) {
                        guideView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }
                }
            });
            return guideView;
        }
        return null;
    }
    ....
}

使用kotlin的默認(rèn)參數(shù)黔龟,我們可以少寫(xiě)很多重載方法

class GuideViewHelperFunc {

    companion object {
        fun showGuideView(
            activity: Activity?,
            attachView: View?,
            layoutId: Int,
            @IdRes arrowId: Int,
            alignView: Boolean = false,
            below: Boolean = false,
            yOffset: Int = 0,
            resetLocationListener: IResetLocationListener? = null,
            guideViewAnimator: GuideViewAnimator? = null
        ): View? {
            attachView?.let {
                activity?.let {
                    if (!it.isFinishing) {
                        val contentView = getContentView(activity)
                        if (contentView is FrameLayout) {
                            val parent = contentView as FrameLayout?
                            val guideView =
                                LayoutInflater.from(activity).inflate(layoutId, parent, false)
                            if (guideViewAnimator != null) {
                                guideView.setTag(R.id.guide_view_animator, guideViewAnimator)
                            }
                            parent!!.addView(guideView)
                            guideView.visibility = View.INVISIBLE
                            guideView.viewTreeObserver.addOnGlobalLayoutListener(object :
                                ViewTreeObserver.OnGlobalLayoutListener {
                                override fun onGlobalLayout() {
                                    setGuideViewLocation(
                                        alignView,
                                        contentView,
                                        guideView,
                                        attachView,
                                        below,
                                        arrowId,
                                        yOffset,
                                        resetLocationListener
                                    )
                                    if (guideView.height > 0) {
                                        guideView.viewTreeObserver.removeOnGlobalLayoutListener(this)
                                    }
                                }
                            })
                            return guideView
                        }
                    }
                }
            }
            return null
        }

        fun setGuideViewLocation(
            alignView: Boolean,
            contentView: View?,
            guideView: View,
            attachView: View?,
            below: Boolean,
            arrowId: Int,
            yOffset: Int,
            resetLocationListener: IResetLocationListener?
        ) {
        }

        fun getContentView(activity: Activity?): ViewGroup? {
            return activity?.findViewById(android.R.id.content)
        }
    }

使用的時(shí)候护昧,我們可以使用必填的幾個(gè)參數(shù)或者必傳參數(shù)+選傳參數(shù),必要時(shí)背苦,我們升值可以不按順序?qū)懠瞿恚@里還是推薦,按照順序蛋欣,并且賦值加上參數(shù)名稱航徙,類似第三種寫(xiě)法



 val layoutId = 0x22211
        val arrowId = 0x2223
        val viewGroup: ViewGroup = LinearLayout(this)

        //必傳參數(shù)
        GuideViewHelperFunc.showGuideView(this, viewGroup, layoutId, arrowId)

        //必傳參數(shù)+選傳(按順序)
        GuideViewHelperFunc.showGuideView(
            this,
            viewGroup,
            layoutId,
            arrowId,
            alignView = true,
            yOffset = 20
        )

        //必傳參數(shù)+選傳(順序打亂)
        GuideViewHelperFunc.showGuideView(
            this,
            attachView = viewGroup,
            layoutId = layoutId,
            arrowId = arrowId,
            alignView = true,
            yOffset = 20
        )

        GuideViewHelperFunc.showGuideView(
            this,
            layoutId = layoutId,
            attachView = viewGroup,
            yOffset = 20,
            alignView = true,
            arrowId = arrowId
        )

6、各種集合

Kotlin標(biāo)準(zhǔn)庫(kù)提供了基本類型的實(shí)現(xiàn):Set陷虎、List以及Map到踏。一對(duì)接口代表每種集合類型:

  • 只讀接口:提供訪問(wèn)集合元素的操作【以listOf()創(chuàng)建的集合】
  • 可變接口:具有增刪查改的功能的集合【以mutableListOf<String>()創(chuàng)建的集合】

集合圖譜:


image
List

比如說(shuō)我們來(lái)看一下List集合相關(guān)的內(nèi)容,然后其實(shí)Set和Map都是差不多是如此設(shè)計(jì)的尚猿。

創(chuàng)建一個(gè)只讀的List

val list1= listOf<String>()

然后你看list1的相關(guān)api窝稿,會(huì)發(fā)現(xiàn)竟然沒(méi)有add函數(shù)?凿掂?伴榔?

image
image

再創(chuàng)建一個(gè)可變的List

val list2 = mutableListOf<String>()   

本質(zhì)上其實(shí)是ArrayList

然后你看list2的相關(guān)api,這下正常多了吧庄萎,這才是Java中應(yīng)該有的樣子是吧踪少。

可變集合其實(shí)就是Java中的List最原始的樣子,而只讀集合其實(shí)就是List最原始的樣子去掉了增刪改查的各個(gè)api糠涛。這樣做援奢,算是kotlin另辟蹊徑。在某些情況下只讀集合能保證多線程的穩(wěn)定性忍捡。當(dāng)然只讀集合并不一定是真正的永不改變的集漾,因?yàn)镵otlin設(shè)計(jì)的與Java的互操作性,而Java是沒(méi)有只讀集合的锉罐,那么有可能Kotlin傳入Java的集合會(huì)是可變集合帆竹。

image
Set

set和list一樣也是一對(duì)集合


val set1 = setOf<String>()
val set2 = mutableSetOf<String>()

其中,mutableSetOf本質(zhì)上是LinkedHashSet脓规。如果你想創(chuàng)建別的set集合栽连。你可以看下這個(gè),其實(shí)是和TreeSet侨舆,HashSet等等一一對(duì)應(yīng)的

image
Map

Map和上面的List和set一樣吧秒紧,有只讀和可變的寫(xiě)法。另外更重要的是挨下,map有些寫(xiě)法很特別

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")    
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") // 同上

無(wú)論鍵值對(duì)的順序如何熔恢,包含相同鍵值對(duì)的兩個(gè) Map 是相等的

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)    
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)

println("The maps are equal: ${numbersMap == anotherMap}")

MutableMap 是一個(gè)具有寫(xiě)操作的 Map 接口,可以使用該接口添加一個(gè)新的鍵值對(duì)或更新給定鍵的值

val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11

println(numbersMap)

{one=11, two=2, three=3}

mutableMapOf的默認(rèn)實(shí)現(xiàn)是LinkedHashMap

集合操作

Kotlin中最牛逼的我個(gè)人覺(jué)得就是跟Java Stream一樣的東西臭笆,但是又沒(méi)有版本兼容問(wèn)題叙淌。

//Java 8 Android 7.0以上
 List<String> list = new ArrayList<>();
 list.stream().filter(s -> TextUtils.equals(s, "biubiu"));

Kotlin 無(wú)安卓版本限制
val list = mutableListOf<String>()
list.filter { TextUtils.equals(it, "biubiu") }

甚至可以一條龍操作

 list.filter { TextUtils.equals(it, "biubiu") }.map {
            //map操作
        }.takeWhile {
            //takeWhile操作
        }

再也不用寫(xiě)那么復(fù)雜的for循環(huán)去操作數(shù)據(jù)了秤掌。

惰性求值和序列

如果擔(dān)心產(chǎn)生太多的集合,那可以用asSequence()和toList避免產(chǎn)生太多的中間list對(duì)象


list.asSequence().filter { TextUtils.equals(it, "biubiu") }.map {
            //map操作
        }.takeWhile {
            //takeWhile操作
        }.toList()

這個(gè)操作是先將集合變成序列鹰霍,然后再這個(gè)序列上進(jìn)行相應(yīng)的操作闻鉴,最后通過(guò)toList()轉(zhuǎn)換為集合列表。實(shí)際使用過(guò)程中茂洒,只有調(diào)用了toList()【又叫做末端操作】孟岛,才會(huì)去真正的去求值。


【中間操作】
list.asSequence().filter { TextUtils.equals(it, "biubiu") }.map {
            //map操作
        }.takeWhile {
            //takeWhile操作
        }     

7督勺、函數(shù)方法值類型

kotlin中的方法參數(shù)中聲明的變量是final類型的渠羞,不能去改變的

image

如果非要修改,你需要

fun driveTrain(price: Int) {

        var priceTemp = price
        priceTemp = 33
        Log.e(TAG, "driverTrain一次的價(jià)錢(qián):$priceTemp")
    }

這樣設(shè)計(jì)有利有弊智哀,某種程度上保證了傳遞進(jìn)來(lái)的數(shù)據(jù)不被內(nèi)部"污染"次询,但是有時(shí)候可能會(huì)增加內(nèi)存的使用。

8盏触、高階函數(shù)

Kotlin提供了不少高端的語(yǔ)法特性渗蟹。比如let、with赞辩、run雌芽、apply、also等我們可以用它來(lái)改善項(xiàng)目中的一些寫(xiě)法辨嗽。
比如let函數(shù)世落,定義一個(gè)變量在特定的作用域范圍內(nèi)生效,返回R值糟需。

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

比如這個(gè)是Java中Adapter中常用的操作數(shù)據(jù)的邏輯

if (holder.musicSingerTv == null) {
  return;
}
holder.musicSingerTv.setText(entity.singer);
holder.musicSingerTv.setTextColor(Color.YELLOW);

如果換成kotlin屉佳,你可以這樣

holder.musicSingerTv?.let {
            it.setText(entity.singer);
            it.setTextColor(Color.YELLOW);
        }

其他另外五種,可以去看下Kotlin系列之let洲押、with武花、run、apply杈帐、also函數(shù)的使用

9体箕、空安全

Kotlin中,變量分為可空類型和非空類型挑童。

var str1: String? = null   可空類型
var str2: String = "ddd"   非空類型


str2 = null // 如果這樣寫(xiě)累铅,就會(huì)報(bào)錯(cuò)。要是在java中站叼,會(huì)在運(yùn)行時(shí)候報(bào)錯(cuò)

str1 = null // 如果可控類型這樣寫(xiě)娃兽,就不會(huì)報(bào)錯(cuò)

對(duì)于可空類型,為了防止空指針尽楔,你可以使用安全調(diào)用操作符?.

比如

    fun driveTrain(train: Train?) {
        train?.openDoor() ?: print("train為空了")
    }

?:是Elvis操作符投储,上面的調(diào)用它的意思是說(shuō)第练, train如果為空,那么不執(zhí)行openDoor()轻要,執(zhí)行print("train為空了")

還有一個(gè)操作符是!!

    fun driveTrain(train: Train?) {
        train.openDoor()
    }

比如現(xiàn)在train是可空類型复旬,但是你非要調(diào)用它的openDoor()方法垦缅,你會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Train

其實(shí)是覺(jué)得當(dāng)前你的用法不安全冲泥。可以加上!!壁涎,變成這樣凡恍,程序執(zhí)行的時(shí)候有兩種可能,要么正確返回name怔球,要么拋出空指針異常嚼酝。

 train!!.openDoor()

10、關(guān)于設(shè)計(jì)模式和Kotlin

工廠模式

常用于一個(gè)父類多個(gè)子類的時(shí)候竟坛,通過(guò)其來(lái)創(chuàng)建子類對(duì)象

比如說(shuō)現(xiàn)在有一個(gè)汽車(chē)工廠闽巩,同時(shí)生產(chǎn)奧迪和五菱宏光,我們用熟悉的工廠模式來(lái)描述其業(yè)務(wù)邏輯如下:

interface ICar {
    val name: String
}

class AudiCar(override val name: String) : ICar {
    fun transportPig(count: Int) {
        print("運(yùn)豬頭數(shù):$count")
    }
}

class SGMWCar(override val name: String) : ICar {
    fun transportPig(count: Int) {
        print("運(yùn)火箭枚數(shù):$count")
    }
}


class CarFactory {

    companion object {
        const val CAR_TYPE_AUDI = 0x001
        const val CAR_TYPE_SGMW = 0x002
    }

    fun produceCar(carType: Int): ICar? {
        return when (carType) {
            CAR_TYPE_AUDI -> AudiCar("奧迪")
            CAR_TYPE_SGMW -> SGMWCar("五菱")
            else -> null
        }
    }
}

fun main(args: Array<String>) {
    val audi = CarFactory().produceCar(CarFactory.CAR_TYPE_AUDI)
    audi?.transport(3)
}

這是簡(jiǎn)單的用kotlin模仿java的工廠模式担汤,最后的創(chuàng)建工廠去生產(chǎn)車(chē)輛這里涎跨,kotlin中還可以更簡(jiǎn)化,因?yàn)閗otlin天生支持單例崭歧,只需要將class改為object


object CarFactorySingleton {

    const val CAR_TYPE_AUDI = 0x001
    const val CAR_TYPE_SGMW = 0x002

    fun produceCar(carType: Int):ICar? {
        return when (carType) {
            CAR_TYPE_AUDI -> AudiCar("奧迪")
            CAR_TYPE_SGMW -> SGMWCar("五菱")
            else -> null
        }
    }
}

然后使用的時(shí)候可以變得很簡(jiǎn)潔


fun main(args: Array<String>) {
    val audi = CarFactorySingleton.produceCar(CarFactorySingleton.CAR_TYPE_AUDI)
    audi?.transport(33)
}

kotlin支持一種叫做operator操作符重載的功能【具體看操作符重載

比如上述的代碼還可以修改為


object CarFactorySingletonOperator {

    const val CAR_TYPE_AUDI = 0x001
    const val CAR_TYPE_SGMW = 0x002

    operator fun invoke(carType: Int): Car? {
        return when (carType) {
            CAR_TYPE_AUDI -> AudiCar("奧迪")
            CAR_TYPE_SGMW -> SGMWCar("五菱")
            else -> null
        }
    }
}

調(diào)用的時(shí)候直接


    //運(yùn)算符invoke
    val sgmw = CarFactorySingletonOperator(CarFactorySingletonOperator.CAR_TYPE_SGMW)
    sgmw?.transport(23)

可以看到變得更加簡(jiǎn)潔

這里的操作符的意思其實(shí)是這樣的隅很,比如CarFactorySingletonOperator() ----> CarFactorySingletonOperator.invoke(),所以上面重載運(yùn)算符后可以直接變成后面那樣調(diào)用了率碾,跟直接創(chuàng)建一個(gè)類的實(shí)例沒(méi)什么區(qū)別啦
image

更強(qiáng)大的叔营,你還可以用上kotlin的伴生對(duì)象和操作符重載的特性去更加簡(jiǎn)潔的生成五菱宏光。
現(xiàn)在我們直接把生成的方法直接寫(xiě)到ICar中所宰,如下


interface ICar {
    val name: String
    fun transport(count: Int)

    companion object {
        operator fun invoke(carType: Int): ICar? {
            return when (carType) {
                CarFactorySingletonOperator.CAR_TYPE_AUDI -> AudiCar("奧迪")
                CarFactorySingletonOperator.CAR_TYPE_SGMW -> SGMWCar("五菱")
                else -> null
            }
        }
    }
}


    //伴生對(duì)象直接生產(chǎn)工廠對(duì)象
    val sgmw = ICar(CarFactory.CAR_TYPE_SGMW)
    sgmw?.transport(3)

觀察者和代理模式

觀察者模式和代理模式是kotlin自身支持的特性绒尊,具體可以了解下委托屬性的用法和實(shí)現(xiàn)。

委托屬性

11仔粥、快速對(duì)比Java代碼必備技能

Kotlin工具給我們提供了很好用的反編譯工具婴谱,具體操作如下

  • 1、寫(xiě)一個(gè)kotlin版本的類和方法


    image
  • 2件炉、點(diǎn)擊Android Studio的Tools中的Show Kotlin ByteCode (裝了Kotlin插件的Jertbain全家桶應(yīng)該都是這樣)勘究,這個(gè)時(shí)候你就能看到字節(jié)碼了,

image
  • 3斟冕、點(diǎn)擊字節(jié)碼窗口的Decompile口糕,字節(jié)碼會(huì)轉(zhuǎn)化為java代碼

然后就是熟悉的Java代碼了


image

三、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末超棺,一起剝皮案震驚了整個(gè)濱河市向族,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棠绘,老刑警劉巖件相,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異氧苍,居然都是意外死亡夜矗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén)让虐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)紊撕,“玉大人,你說(shuō)我怎么就攤上這事赡突《苑觯” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵惭缰,是天一觀的道長(zhǎng)浪南。 經(jīng)常有香客問(wèn)我,道長(zhǎng)从媚,這世上最難降的妖魔是什么逞泄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮拜效,結(jié)果婚禮上喷众,老公的妹妹穿的比我還像新娘。我一直安慰自己紧憾,他們只是感情好到千,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赴穗,像睡著了一般憔四。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上般眉,一...
    開(kāi)封第一講書(shū)人閱讀 52,874評(píng)論 1 314
  • 那天了赵,我揣著相機(jī)與錄音,去河邊找鬼甸赃。 笑死柿汛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埠对。 我是一名探鬼主播络断,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼裁替,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了貌笨?” 一聲冷哼從身側(cè)響起弱判,我...
    開(kāi)封第一講書(shū)人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锥惋,沒(méi)想到半個(gè)月后昌腰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡净刮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年剥哑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淹父。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖怎虫,靈堂內(nèi)的尸體忽然破棺而出暑认,到底是詐尸還是另有隱情,我是刑警寧澤大审,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布蘸际,位于F島的核電站,受9級(jí)特大地震影響徒扶,放射性物質(zhì)發(fā)生泄漏粮彤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一姜骡、第九天 我趴在偏房一處隱蔽的房頂上張望导坟。 院中可真熱鬧,春花似錦圈澈、人聲如沸惫周。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)递递。三九已至,卻和暖如春啥么,著一層夾襖步出監(jiān)牢的瞬間登舞,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工悬荣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菠秒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓隅熙,卻偏偏與公主長(zhǎng)得像稽煤,于是被迫代替她去往敵國(guó)和親核芽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容