Java8函數(shù)式編程

2018的JVM生態(tài)報告庶橱,Java8的使用比例達到79%贮勃,Java8也是一個比較有里程碑的版本。在新的版本里提供了強大的語法苏章,能夠?qū)懜逦啙嵉拇a寂嘉。

Java8.png

本文主要是函數(shù)式編程相關(guān),首先是從一個函數(shù)接口開始枫绅,到Java8提供的lambda表達式泉孩,然后到Stream API 和 函數(shù)式編程和原有命令式編程的區(qū)別。

一并淋、Java8一些函數(shù)式相關(guān)的更新

泛型的擴展,優(yōu)化泛型的自動推斷的場景

Java7 增加了菱形語法寓搬,簡化泛型的編寫,原理也是根據(jù)指針的泛型推斷實例的類型县耽,但是Java7也有部分場景不能推斷句喷,Java8對泛型的推斷進行了優(yōu)化。優(yōu)化的原則就是能根據(jù)上下文推斷類型的能力兔毙。

package genericity;

import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.List;

/**
 * 泛型
 * @author liusibo
 * @Title: Genericity
 * @ProjectName java8
 * @date 2019/3/164:39 PM
 */
public class Genericity {

    private List<String> bucket = Lists.newArrayList();

    public void add (List<String> list) {
        bucket.addAll(list);
    }

    public static void main(String[] args) {
        Genericity demo = new Genericity();
        // Java7會報錯
        demo.add(Collections.emptyList());
        demo.add(Collections.<String>emptyList());
    }
}

Map , Collection , List等API的更新

Java8因為接口新增了一個特性唾琼,然后為集合接口和類增加了多個新的方法。

api.png

比如之前Map如果想實現(xiàn)下面的功能澎剥,代碼其實挺很繁瑣锡溯。

Map<String ,String> map = Maps.newHashMap();

// method1 
String result = null;
if(map.containsKey("none")) {
    result = map.get("none");
} else {
    result = "";
}

// method2
result = map.getOrDefault("none", "");

二、函數(shù)接口和lambda表達式

  • 面向?qū)ο缶幊掏ㄟ^封裝不確定因素來使代碼能被人理解,函數(shù)式編程通過盡量減少不確定因素來使代碼能被人理解祭饭。

一芜茵、寫一個函數(shù)式接口

Java8擴展了接口,增加了default修飾的默認方法和靜態(tài)方法。并且新增了一個注解來標(biāo)識和檢查函數(shù)式接口@FunctionalInterface

默認方法的提供甜癞,從函數(shù)上講為函數(shù)式接口擴展做復(fù)合函數(shù)使用夕晓,從定義上講向上兼容,如Collection


package function;

/**
 * @author liusibo
 * @Title: DemoFunction
 * @ProjectName java8
 * @date 2019/3/1010:13 PM
 */
 // 檢查注解悠咱,函數(shù)式接口只能有一個接口類型的方法
@FunctionalInterface
public interface DemoFunction<T , R> {
    /**
     * demo function
     * @param t
     * @return
     */
    R apply(T t);

    default <V> DemoFunction<T , V> then(DemoFunction<R , V> demoFunction) {
        return (T t) -> demoFunction.apply(apply(t));
    }

    static void main(String[] args) {
        DemoFunction<String , Boolean> function = (String t) -> "1".equals(t);
        System.out.println(function.apply("1"));
    }
}

然后怎么去理解函數(shù)接口蒸辆。用傳入的表示接收類型,用傳出的箭頭表示結(jié)果的類型析既。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

function.png

二躬贡、一個demo看函數(shù)式接口的使用

我們現(xiàn)在有如下場景,有一個Person的List集合眼坏,Person對象有名字拂玻,城市,年齡三個屬性

首先做幾種場景的代碼的編寫

1.篩選指定城市的人

在Java8以前實現(xiàn)這個功能宰译,可能就是提供一個篩選的方法檐蚜,然后從一個集合篩選滿足條件的篩選到另外一個集合。


/**
 * 篩選出指定城市的人
 * @param city
 * @return
 */
private List<Person> filterCityPerson(List<Person> people , String city) {
    List<Person> personList = Lists.newArrayList();
    for (Person person :people) {
        if(city.equals(person.getCity())) {
            personList.add(person);
        }
    }
    return personList;
}


2.篩選某個城市然后年齡小于某個值的人

這個時候其實是有點尷尬的沿侈,因為新增一個條件判斷闯第。于是我們又寫了一個方法。


/**
 * 篩選出指定城市金和小于指定年齡的人
 * @param city
 * @return
 */
private List<Person> filterCityAndAgePerson(List<Person> people , String city , Integer age) {
    List<Person> personList = Lists.newArrayList();
    for (Person person :people) {
        if(city.equals(person.getCity()) && age < person.getAge()) {
            personList.add(person);
        }
    }
    return personList;
}

3.接下來考慮在如下場景

篩選城市為上海和杭州的人 缀拭, 篩選年齡小于19的人 咳短, 以及對于各種情況任意組合的人

很明顯上邊兩個方法都不能使用,如果說將年齡比較再封裝一個方法蛛淋,用上面的方法咙好,可以做重復(fù)的迭代完成,但是明顯不太好

或者說傳入一個判斷List集合褐荷,將復(fù)雜度為n的方法變成n^2的勾效,這樣明顯也不太好。

4.接下來分析問題做合理的封裝


@FunctionalInterface
interface PersonPredicate {
    boolean test(Person person);
}

/**
 * 根據(jù)指定條件篩選
 * @param
 * @return
 */
private List<Person> filterCityPerson(List<Person> people , PersonPredicate personPredicate) {
    List<Person> personList = Lists.newArrayList();
    for (Person person :people) {
        if(personPredicate.test(person)) {
            personList.add(person);
        }
    }
    return personList;
}

// 這樣基于內(nèi)部類的方式诚卸,可以在傳入判斷邏輯葵第,對于判斷語句的進行封裝

filterCityPerson(personList , new PersonPredicate() {
        @Override
        public boolean test(Person person) {
            return "上海".equals(person.getCity()) || "杭州".equals(person.getCity());
        }
});

5.接下來就是lambda表達式

我們發(fā)現(xiàn)雖然內(nèi)部類看起來比較簡潔了,但是仔細想上面一個內(nèi)部類50%以上都是模板代碼

如果你的參數(shù)是一個函數(shù)式接口合溺,那么就可以使用lambda

lambda = 參數(shù) + 箭頭 + 主體

然后我們對這個匿名內(nèi)部類根據(jù)lambda表達式的定義一步一步簡化


// 首先這樣卒密,可以省略類的描述信息,直接根據(jù)參數(shù)棠赛,箭頭哮奇,然后方法體的規(guī)則改變方法
filterList(PersonList.personList , (Person person) -> {
    return  "上海".equals(person.getCity());
});

// 第二步膛腐,形參類型省略,類型可以省略依靠上下文判斷參數(shù)具體是什么類型鼎俘,也可以如上明確函數(shù)的類型哲身,便于理解
filterList(PersonList.personList , (person) -> {
    return  "上海".equals(person.getCity());
});

// 第三步如果形參只有一個可以省略參數(shù)體的括號,如果主體只有一行贸伐,也可以省略括號
filterCityPerson(personList , person -> "上海".equals(person.getCity()));

6.到這為止我們發(fā)現(xiàn)語句已經(jīng)很簡單了

我們編寫的自定義判斷的函數(shù)接口是模仿Predicate<T>寫的勘天,接下來寫一個通用的list的篩選


private <T> List<T> filterList(List<T> list , Predicate<T> predicate) {
    List<T> newList = Lists.newArrayList();
    for (T bean : list) {
        if(predicate.test(bean)) {
            newList.add(bean);
        }
    }
    return newList;
}

7.復(fù)合函數(shù)

我們發(fā)現(xiàn)雖然上面是一個通用的方法,也很簡單捉邢,不用寫過多的模板方法脯丝,但是判斷邏輯并沒有完全的解耦口锭,或者說在實際的應(yīng)用場景中可能要寫更加復(fù)雜的判斷表達式斩跌。

如北京大于19歲的人,如果寫這個Predicate接口就是


Predicate<Person> pre = (person) -> "北京".equal(person.getCity()) && 19 < person.getAge();

// 所以現(xiàn)在就是接口的上面默認方法的一個實際應(yīng)用萝风,復(fù)合函數(shù)
Predicate<Person> predicate = x -> "上海".equals(x.getCity());
Predicate<Person> and = predicate.and(x -> 19 < x.getAge());
filterCityPerson(personList , and);

8.最后說一下缺點或者說lambda的一個問題藐翎,過多的簡寫帶來的是語意不明確材蹬,對于最開始語句的復(fù)雜帶來難理解的地方是語句過多,但是用lambda簡化后的語法語句減少吝镣,但是所帶來的理解成本會有所增加堤器,所以Java提供了一些語法糖,如方法形參末贾,和構(gòu)造器形參吼旧。

方法形參(方法引用)

這里是一種,方法引用的思想未舟,他的基本思想是,如果一個lambda代表的是“直接調(diào)用這個方法”掂为,最好還是用名稱來調(diào)用他裕膀,而不是去描述如何調(diào)用它


package function;

import com.google.common.collect.Lists;
import domain.Person;

import java.util.List;
import java.util.function.Predicate;

/**
 * @author liusibo
 * @Title: PersonService2
 * @ProjectName java8
 * @date 2019/3/165:28 PM
 */
public class PersonService2 {

    private static <T> List<T> filterList(List<T> list , Predicate<T> predicate) {
        List<T> newList = Lists.newArrayList();
        for (T bean : list) {
            if(predicate.test(bean)) {
                newList.add(bean);
            }
        }
        return newList;
    }

    public static void main(String[] args) {
        // Predicate<Person> pre = (person) -> "北京".equal(person.getCity()) && 19 < person.getAge();
        filterList(PersonList.personList , PersonService2::filterCityIsBeiJing);
    }

    // 因為這里是static方法,所以上面是類引用勇哗。
    private static boolean filterCityIsBeiJing(Person person) {
        return "北京".equals(person.getCity());
    }

}


// 例二 昼扛,然后在說一個例子
List<Person> personList = Lists.newArrayList();

// 這個是Comparable函數(shù)中的一個靜態(tài)方法
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor);

// List中增加了一個sort(),方法根據(jù)傳入的Comparable接口的實現(xiàn)自動排序
// 首先是lambda的實現(xiàn)版本
personList.sort((x1 , x2) -> x1.getAge().compareTo(x2.getAge()));

// 然后我們根據(jù)上述靜態(tài)方法改造他
personList.sort(Comparator.comparing((Person person) -> person.getAge()));

// 然后 comparing 中接受的是一個 Function接口
personList.sort(Comparator.comparing(Person::getAge).reversed().thenComparing(Person::getCity));


// 例三
public EmployeeDto getEmployeeByUserName(String userName) {
    return Optional.ofNullable(employeeMapper.queryEmpByUserName(userName)).map(this::transformEmployee).orElse(null);
}

/**
 * 對象實體的轉(zhuǎn)換
 */ 
private EmployeeDto transformEmployee(EmployeePo employeePo);

看完上面可能有點暈,因為看起來沒啥規(guī)律,其實方法引用可以歸為三類欲诺,有兩個比較像抄谐,然后例二是一個比較特殊的情況

method.png

構(gòu)造器引用

// 等價于 (String d) -> new String(d);
Function<String , String> function1 = String::new;
String apply = function1.apply("1");

常用的幾個lambda接口

函數(shù)名 含義 用途
Predicate<T> T -> boolean 接受一個參數(shù)返回一個boolean值用于判斷
Function<T , R> T -> R 接受一個T類型參數(shù)創(chuàng)建R類型的實例
Consumer<T> T -> void 消費者
Supplier<T> () -> T 工廠,產(chǎn)生一個T類型的實體
UnaryOperator<T> T -> T 運算操作

Stream API 流式處理

1.什么是流

Stream API 為集合提供的流式處理的迭代操作

有三個關(guān)鍵詞:

聲明性 - 簡介扰法,易讀
可復(fù)合 - 更加靈活
可并行 - 性能更好(有缺陷)

簡短的定義就是蛹含,從支持數(shù)據(jù)處理操作的源生成的元素序列。

幾個關(guān)鍵性詞語:元素序列塞颁,源浦箱,數(shù)據(jù)處理操作吸耿。

然后兩個重要的特征:流水線,內(nèi)部迭代

關(guān)于迭代酷窥,對于流式處理來講應(yīng)該關(guān)心的輸入和輸出

deidai.png

2.首先還是看兩個的例子

場景一:比如我想要人列表里的名字集合咽安,然后通過逗號分隔輸出出來、重名的要去掉


// 可能標(biāo)準(zhǔn)的代碼要這樣寫
List<String> name = Lists.newArrayList();
for (Person person : personList) {
    if(person.getAge() > 19 && !name.contains(person.getName())) {
        name.add(person.getName());
    }
}
String join = String.join(",", name.toArray(new String[1]));

// 首先name這個變量是一個垃圾變量蓬推,只是承接了中間迭代的一個過程妆棒,然后還是上面的一個問題,隨著問題復(fù)雜度的提高沸伏,繁雜的代碼糕珊,難以理解

// 然后是Stream API 提供的寫法 ,升成流 -> 提供元素序列 -> 中間操作(轉(zhuǎn)換馋评,去重) -> 收集結(jié)果
String collect = PersonList.personList.stream()
                .filter(person -> person.getAge() > 19)
                .map(Person::getName)
                .distinct()
                .collect(Collectors.joining(","));

場景二:假如你有一個Person類的List集合放接,用這個list轉(zhuǎn)換成用name屬性為key,List<Person> 為value的Map<String , List<Person>>集合


Map<String ,List<Person>> map = Maps.newHashMap();
for (Person person : personList ) {
    if(map.containsKey(person.getName())) {
        map.get(person.getName()).add(person);
    } else {
        map.put(person.getName() , Lists.newArrayList(person));
    }
}
System.out.println(map);

// 寫完以上這個已經(jīng)是很標(biāo)準(zhǔn)的命令式編程留特,一次循環(huán)纠脾,查詢結(jié)果,但是不得不說這種方式慧脱,還是通病菱鸥,一眼看過去很難理解氮采。
// 而我們希望的是最好有一個類似sql的 group by 的功能

Map<String ,List<Person>> collect = personList.stream().collect(Collectors.groupingBy(Person::getName));

// 這個時候通過Stream API 已經(jīng)很簡潔了,比如說躯概,我要生成map之前先過濾一遍只要北京城市的人呢 娶靡?

此時會發(fā)現(xiàn)固蛾,使用Stream API 可以大量簡化迭代操作

還有另外一個重要的特征 艾凯,比較這兩個場景的兩種代碼就很很清楚的發(fā)現(xiàn),前一個更多側(cè)重的是如何做(命令式編程)蜡感,而后者則是做什么(聲明式編程)郑兴。

場景三:比如現(xiàn)在我們說一個更加復(fù)雜的操作情连,比如場景二的意思是却舀,根據(jù)人名,分組為 key 螃诅, list 。 我想再加一個場景穗椅,根據(jù)人名分完之后,還要根據(jù)地名分組宣鄙,最后獲得的結(jié)果大致是這樣的
Map<String , Map<String , List<Person>>> 默蚌。

如果用傳統(tǒng)的命令式編程冻晤。很復(fù)雜绸吸,就不寫了设江,所以我們換成聲明式編程


// 這個就是還涉及到另外一個東西 collect收集器
Map<String, Map<String, List<Person>>> collect1 = personList.stream()
                .collect(Collectors.groupingBy(Person::getName, Collectors.groupingBy(Person::getCity)));

上面展示了Stream API 在一些場景下的優(yōu)勢攘轩,現(xiàn)在看一下Stream API使用叉存。

首先是看Stream的規(guī)則,三個步驟

  • 首先生成一個流

集合為Collection添加了一個stream的默認方法度帮,用于生成流
Arrays數(shù)組工具類,也提供個stream將數(shù)組轉(zhuǎn)成流
還有Stream類可以創(chuàng)建一個流

  • 然后中間的處理操作,用于過濾率翅,轉(zhuǎn)換,鏈接浴韭,等, 最后是求值操作泉粉。
caozuo.png

3.比較難理解的函數(shù) flatMap

  • flatMap 接受的是函數(shù)式轉(zhuǎn)換是 T -> Stream<R> , 表達式是流式操作中,流的類型的轉(zhuǎn)換的中間操作

List<String> words = Arrays.asList("Hello", "World");
// flatMap
words.stream()
        .flatMap((String word) -> Arrays.stream(word.split("")))
        .distinct()
        .forEach(System.out::print);

  1. 緩求值榴芳,Stream API,或者說函數(shù)式編程嗡靡,真正運行時是在求值操作,之前行為都是函數(shù)式聲明窟感。

Stream<Integer> integerStream = PersonList.personList.stream()
                .peek(System.out::println)
                .map(Person::getAge)
                .filter(age -> age > 19);

integerStream.forEach(System.out::println);

// 注意輸出順序讨彼,方便理解findAny 和 findFrist

collect() 收集器

1.Stream API 提供了collect方法,提供了一系列方便集合求值的操作柿祈。

demo.png

2.toMap

toMap 可以接受三個參數(shù)哈误,生成Map<T , R>
參數(shù)一、key的取值 接受一個Fucntion函數(shù)
參數(shù)二躏嚎、value的取值蜜自,接受一個Function函數(shù)
參數(shù)三、當(dāng)?shù)鷏ist是卢佣,key存在多個時重荠,value的取舍策略,接受一個運算符函數(shù)

3.雖然JDK提供的收集器已經(jīng)滿足絕大多數(shù)收集器的使用虚茶,但是我們也可以自己定義實現(xiàn)一個收集器戈鲁,然后用于集合的求值,以下模擬實現(xiàn)toList();

package stream;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

/**
 * @author liusibo
 * @Title: ToListCollector
 * @ProjectName java8
 * @date 2019/3/178:16 PM
 */
public class ToListCollector<T> implements Collector<T , List<T> , List<T>> {

    // Collector<T , A , R>

    /**
     * 返回一個無參空的容器用于收集數(shù)據(jù)
     * @return
     */
    @Override
    public Supplier<List<T>> supplier() {
        return ArrayList::new;
    }

    /**
     * 執(zhí)行遍歷的歸約操作仇参,當(dāng)函數(shù)執(zhí)行到這里會有兩個參數(shù)一個是保存結(jié)果的累加器,一個是遍歷到這里的元素
     * @return
     */
    @Override
    public BiConsumer<List<T>, T> accumulator() {
//        return (list , t) -> list.add(t);
        return List::add;
    }

    /**
     * 并行流中子任務(wù)如何合并
     * @return
     */
    @Override
    public BinaryOperator<List<T>> combiner() {
        return (list1 , list2) -> {
            list1.addAll(list2);
            return list1;
        };
    }

    /**
     * 對獲取的累加結(jié)果集和最終結(jié)果集之前的轉(zhuǎn)換
     * @return
     */
    @Override
    public Function<List<T>, List<T>> finisher() {
        return Function.identity();
    }

    /**
     * 生成的結(jié)果集的三個枚舉,動作枚舉
     * @return
     */
    @Override
    public Set<Characteristics> characteristics() {
//        Characteristics.UNORDERED 歸約結(jié)果不受流中項目的遍歷和累積順序的影響婆殿,不保證迭代的順序
//        Characteristics.CONCURRENT accumulator支持多線程調(diào)用
//        Characteristics.IDENTITY_FINISH 表示 A 和 R 是一個恒等函數(shù)跳過finisher的執(zhí)行
        return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
    }
}


并行

首先說一下并行的方式 Stream 提供另外一個流 parallelStream(),生成一個并行流诈乒,內(nèi)部機制是ForkJoinPool,Java7新加入的Api鸣皂,采用的是分治法的思想抓谴,但是parallelStream在java8中還有缺陷,原因就是因為不能自定義線程池,而parallelStream默認的ForkJoinPool線程池的機制是有問題寞缝。

然后說一個推薦的并行的方式癌压,也是Java8新加入的API CompletableFuture


ThreadPoolExecutor threadPoolExecutor
                = new ThreadPoolExecutor(
                10, 50, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000),
                (ThreadFactory) Thread::new);

List<CompletableFuture<String>> completableFutureList = words.stream()
                .flatMap(line -> Arrays.stream(line.split("")))
                .map(bean -> CompletableFuture.supplyAsync(bean::toUpperCase, threadPoolExecutor))
                .collect(Collectors.toList());

// 這里一定要分開不能和上一行合成一句,否則會變成單線程荆陆。參考collect原理

List<String> collect = completableFutureList.stream().map(CompletableFuture::join)
        .collect(Collectors.toList());

Optional<T>

綜合起來的一句話就是滩届,為缺失的值建模,有效的避免空指針,當(dāng)處理對象的時候可能為空的情況省略并不是必要的邏輯步驟被啼,這里寫一個簡單的例子來描述下

下面是通過三種方式來獲取 某員工所在部門的部門領(lǐng)導(dǎo)的名字帜消。,看前兩種方式的時候你應(yīng)該想一個問題浓体,你應(yīng)該什么時候關(guān)注null泡挺。


// 方式一
public static String getEmployeeAtOrgLeader(Employee employee) {
    if(employee != null) {
        Organization organization = employee.getOrganization();
        if(organization != null) {
            Employee leader = organization.getLeader();
            if(leader != null) {
                return leader.getName() == null ? "" : leader.getName();
            }
        }
    }
    return "";
}

// 方式二
public static String getEmployeeAtOrgLeader01(Employee employee) {
    if(employee == null) {
        return "";
    }
    Organization organization = employee.getOrganization();
    if(organization == null) {
        return "";
    }
    Employee leader = organization.getLeader();
    if(leader == null) {
        return "";
    }
    return leader.getName() == null ? "" : leader.getName();
}

// 方式三
public static String getEmployeeAtOrgLeader02(Employee employee) {
    return Optional.ofNullable(employee)
            .map(Employee::getOrganization)
            .map(Organization::getLeader)
            .map(Employee::getName)
            .orElse("");
}

Optional提供了,其他的一些列除了中間操作命浴,有關(guān)null值處理的api娄猫。大家可以看源碼詳細了解。

最后

Stream API 和 lambda 可以在對象轉(zhuǎn)換生闲,集合迭代等場景媳溺,用更清晰的語義讓人理解,回到剛開始的那句話碍讯,面向?qū)ο缶幊掏ㄟ^封裝不確定因素來使代碼能被人理解悬蔽,函數(shù)式編程通過盡量減少不確定因素來使代碼能被人理解。
面向?qū)ο缶幊淌峭ㄟ^對數(shù)據(jù)轉(zhuǎn)換的封裝捉兴,函數(shù)式編程是通過對轉(zhuǎn)換行為的封裝蝎困。

大家可以回想下,你熟悉的封裝倍啥,作用域禾乘,可見性等面向?qū)ο缶幊痰臉?gòu)造,這些機制存在的意義逗栽,都是為了精細的控制和讓誰感知和改變狀態(tài)。而當(dāng)涉及多線程操作時這些狀態(tài)的控制就更加復(fù)雜了失暂,這些機制就是不確定因素彼宠,然而與其建立種種機制控制可變的狀態(tài)鳄虱,不知盡可能的消除可變的狀態(tài)這個不確定因素。加入語言不對外暴露那么多出錯的可能性凭峡,那么開發(fā)者就不容易犯錯拙已。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市摧冀,隨后出現(xiàn)的幾起案子倍踪,更是在濱河造成了極大的恐慌,老刑警劉巖索昂,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件建车,死亡現(xiàn)場離奇詭異,居然都是意外死亡椒惨,警方通過查閱死者的電腦和手機缤至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來康谆,“玉大人领斥,你說我怎么就攤上這事∥职担” “怎么了月洛?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長孽锥。 經(jīng)常有香客問我嚼黔,道長,這世上最難降的妖魔是什么忱叭? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任隔崎,我火速辦了婚禮,結(jié)果婚禮上韵丑,老公的妹妹穿的比我還像新娘爵卒。我一直安慰自己,他們只是感情好撵彻,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布钓株。 她就那樣靜靜地躺著,像睡著了一般陌僵。 火紅的嫁衣襯著肌膚如雪轴合。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天碗短,我揣著相機與錄音受葛,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛总滩,可吹牛的內(nèi)容都是我干的纲堵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼闰渔,長吁一口氣:“原來是場噩夢啊……” “哼席函!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起冈涧,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茂附,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后督弓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體营曼,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年咽筋,在試婚紗的時候發(fā)現(xiàn)自己被綠了溶推。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奸攻,死狀恐怖蒜危,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睹耐,我是刑警寧澤辐赞,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站硝训,受9級特大地震影響响委,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窖梁,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一赘风、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纵刘,春花似錦邀窃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舵抹,卻和暖如春肪虎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惧蛹。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工扇救, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刑枝,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓迅腔,卻偏偏與公主長得像仅讽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钾挟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

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