java8--函數(shù)式編程

其實在java8就已經(jīng)有java的函數(shù)式編程寫法娩井,只是難度較大,大家都習慣了對象式用法饶号,但在其它語言中都有函數(shù)式的用法,如js,scala姻成,函數(shù)式其實是抽象到極致的思想。

什么是函數(shù)式編程

函數(shù)式編程并不是Java新提出的概念愿棋,其與指令編程相比科展,強調函數(shù)的計算比指令的計算更重要;與過程化編程相比糠雨,其中函數(shù)的計算可以隨時調用才睹。

當然,大家應該都知道面向對象的特性(抽象甘邀、封裝琅攘、繼承多態(tài))松邪。其實在Java8出現(xiàn)之前坞琴,我們關注的往往是某一類對象應該具有什么樣的屬性,當然這也是面向對象的核心--對數(shù)據(jù)進行抽象逗抑。但是java8出現(xiàn)以后剧辐,這一點開始出現(xiàn)變化寒亥,似乎在某種場景下,更加關注某一類共有的行為(這似乎與之前的接口有些類似)荧关,這也就是java8提出函數(shù)式編程的目的护盈。如圖1-1所示,展示了面向對象編程到面向行為編程的變化羞酗。

fun.png

函數(shù)接口

接口 參數(shù) 返回類型 描述
Predicate<T> T boolean 用來比較操作
Consumer<T> T void 沒有返回值的函數(shù)
Function<T, R> T R 有返回值的函數(shù)
Supplier<T> None T 工廠方法-返回一個對象
UnaryOperator<T> T T 入?yún)⒑统鰠⒍际窍嗤瑢ο蟮暮瘮?shù)
BinaryOperator<T> (T,T) T 求兩個對象的操作結果

java8 函數(shù)式編程的入口,每個函數(shù)接口都帶有 @FunctionalInterface 注釋,有且僅有一個未實現(xiàn)的方法紊服,表示接收 Lambda 表達式檀轨,它們存在的意義在于將代碼塊作為數(shù)據(jù)打包起來。
這幾個函數(shù)接口欺嗤,完全可以把它們看成普通的接口参萄,不過他們有且僅有一個抽象方法(因為要接收 Lambda 表達式)。
@FunctionalInterface 該注釋會強制 javac 檢查一個接口是否符合函數(shù)接口的標準煎饼。 如果該注釋添加給一個枚舉類型讹挎、 類或另一個注釋, 或者接口包含不止一個抽象方法吆玖, javac 就會報錯筒溃。

Lambda 表達式

Lambda 表達式,有時候也稱為匿名函數(shù)或箭頭函數(shù)沾乘,幾乎在當前的各種主流的編程語言中都有它的身影怜奖。Java8 中引入 Lambda 表達式,使原本需要用匿名類實現(xiàn)接口來傳遞行為翅阵,現(xiàn)在通過 Lambda 可以更直觀的表達歪玲。

  • Lambda 表達式,也可稱為閉包掷匠。閉包就是一個定義在函數(shù)內部的函數(shù)滥崩,閉包使得變量即使脫離了該函數(shù)的作用域范圍也依然能被訪問到。
  • Lambda 表達式的本質只是一個”語法糖”讹语,由編譯器推斷并幫你轉換包裝為常規(guī)的代碼,因此你可以使用更少的代碼來實現(xiàn)同樣的功能钙皮。
  • Lambda 表達式是一個匿名函數(shù),即沒有函數(shù)名的函數(shù)募强。有些函數(shù)如果只是臨時一用株灸,而且它的業(yè)務邏輯也很簡單時,就沒必要非給它取個名字不可擎值。
  • Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中).

Lambda 表達式語法如下:形參列表=>函數(shù)體(函數(shù)體多于一條語句的可用大括號括起)慌烧。在Java里就是() -> {}:

(parameters) -> expression
(parameters) ->{ statements; }

Lambda表達式的重要特征:

  • Lambda 表達式主要用來定義行內執(zhí)行的方法類型接口,例如鸠儿,一個簡單方法接口屹蚊。
  • Lambda表達式是通過函數(shù)式接口(必須有且僅有一個抽象方法聲明)識別的
  • 可選類型聲明:不需要聲明參數(shù)類型厕氨,編譯器可以統(tǒng)一識別參數(shù)值。
  • 可選的參數(shù)圓括號:一個參數(shù)無需定義圓括號汹粤,但多個參數(shù)需要定義圓括號命斧。
  • 可選的大括號:如果主體包含了一個語句,就不需要使用大括號嘱兼。
  • 可選的返回關鍵字:如果主體只有一個表達式返回值国葬,則編譯器會自動返回值,大括號需要指定表達式返回一個值芹壕。

Lambda表達式中的變量作用域:

  • 訪問權限與匿名對象的方式非常類似汇四。只能夠訪問局部對應的外部區(qū)域的局部final變量,以及成員變量和靜態(tài)變量踢涌。
  • 在Lambda表達式中能訪問域外的局部非final變量通孽、但不能修改Lambda域外的局部非final變量。因為在Lambda表達式中睁壁,Lambda域外的局部非final變量會在編譯的時候背苦,會被隱式地當做final變量來處理。
  • Lambda表達式內部無法訪問接口默認(default)方法.

例子:使用Java 8之前的方法來實現(xiàn)對一個string列表進行排序:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

Java 8 Lambda 表達式:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});
// 只有一條邏輯語句潘明,可以省略大括號
Collections.sort(names, (String a, String b) -> b.compareTo(a));
// 可以省略入?yún)㈩愋?Collections.sort(names, (a, b) -> b.compareTo(a));

FunctionalInterface

Java8的新引入行剂,包含函數(shù)式的設計,接口都有@FunctionalInterface的注解钉疫。
如聲明一個接口

@FunctionalInterface
public interface fun{}
這會編譯錯硼讽,編譯器會告訴你*no target method*。而如果加一個方法:
@FunctionalInterface
public interface fun{
    void run();
}
這就OK了牲阁,一個函數(shù)式接口聲明好了固阁。再加一個呢?
@FunctionalInterface
public interface fun{
    void run();
    void test();
}
不ok城菊,明確說了只有一個抽象方法嘛备燃。但是如果換一種函數(shù)簽名:
@FunctionalInterface
public interface fun{
    Object equals(Object o);
    void test();
}
這就OK了。一個抽象方法凌唬,一個Object的public方法并齐,相安無事。Object還有其他方法客税,clone方法試試會怎么樣况褪?
@FunctionalInterface
public interface fun{
    Object clone();
    void test();
}
這又不行了,因為前面明確說了更耻,要是Object的public方法测垛,而clone是protected的。

小結:函數(shù)式接口秧均,有且僅有一個抽象方法食侮,Object的public方法除外号涯。

因為Java本身支持多接口實現(xiàn),你定義一個Class可以implements多個interface锯七。所以這個限制也沒什么影響链快,如果想約定一個函數(shù)式接口來統(tǒng)一,也可以做一些默認的實現(xiàn)來達到一個接口多個抽象方法的目的眉尸,比如下面這種做法:

一個普通接口NonFunc:

public interface NonFunc {
    void foo();
    void voo();
}
函數(shù)式接口Func:
public interface Func extends NonFunc {
    default void foo();
    default void voo();
    void run();
}
實現(xiàn)的測試類:
Public class FunTest implements Func {
  public static void main(String... args) {
    Func func = new FunTest();
    func.run();
    func.foo();
    func.voo();
  }
  @Override
  public void run() {
    System.out.println("run");
  }
  @Override
  public void foo() {
    System.out.println("foo");
  }
  @Override
  public void voo() {
    System.out.println("voo");
  }
}
函數(shù)式接口的一大特性就是可以被lambda表達式和函數(shù)引用表達式代替域蜗。
public class FunTest {
  public static void main(String... args) {
    FunTest t = new FunTest();
    //lambda
    t.test(10, ()->System.out.println("xxxxx"))
    //method reference
    t.test(100, t::customedFunc);
  }
  public void customedFunc(){
    System.out.println("a customed method reference");
  }
  public void test(int x, Func func) {
    System.out.println(x);
    func.run();
  }
}
上面例子列舉了一個lambda模式和一個方法引用模式,這樣就可以利用函數(shù)式編程強大的能力噪猾,將方法作為參數(shù)了地消。
下面再加一例
public class Main {
    public static void main(String[] args) {
        Action action = System.out :: println;
        action.execute("Hello World!");
        test(System.out :: println, "Hello World!");
    }
    static void test(Action action, String str) {
        action.execute(str);
    }
}
@FunctionalInterface
interface Action<T> {
    public void execute(T t);
}

Function

關于Function接口,其接口聲明是一個函數(shù)式接口畏妖,其抽象表達函數(shù)為

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

函數(shù)意為將參數(shù)T傳遞給一個函數(shù),返回R疼阔。即R=Function(T)

其默認實現(xiàn)了3個default方法戒劫,分別是compose、andThen和identity婆廊,對應的函數(shù)表達為:compose對應V=Function(ParamFunction(T))迅细,體現(xiàn)嵌套關系;andThen對應V=ParamFunction(Function(T))淘邻,轉換了嵌套的順序茵典;還有identity對應了一個傳遞自身的函數(shù)調用對應Function(T)=T。從這里看出來宾舅,compose和andThen對于兩個函數(shù)f和g來說统阿,f.compose(g)等價于g.andThen(f)〕镂遥看個例子:

public static void main(String... args){
        Function<Integer, Integer> incr1 = x->x*2;
        Function<Integer, Integer> multiply = x->x*2;
        int x=2;
        System.out.println("f(x)=x+1,when x="+x+", f(x)="+incr1.apply(x));
        System.out.println("f(x)=x+1,g(x)=2x, when x="+x+",f(g(x))="+incr1.compose(multiply).apply(x));
        System.out.println("f(x)=x+1,g(x)=2x, when x="+x+",g(f(x))="+incr1.andThen(multiply).apply(x));
        System.out.println("compose vs andThen: f(g(x))="+incr1.compose(multiply).apply(x)+","+multiply.andThen(incr1).apply(x));
    }

高階函數(shù)

只是普通的lambda表達式扶平,其能力有限。我們會希望引入更強大的函數(shù)能力——高階函數(shù)蔬蕊,可以定義任意同類計算的函數(shù)结澄。

比如這個函數(shù)定義,參數(shù)是z岸夯,返回值是一個Function麻献,這個Function本身又接受另一個參數(shù)y,返回z+y猜扮。于是我們可以根據(jù)這個函數(shù)勉吻,定義任意加法函數(shù):

//high order function
Function<Integer, Function<Integer, Integer>> makeAdder = z->y->z+y;
x=2;
//define add1
Function<Integer, Integer> add1 = makeAdder.apply(1);
System.out.println("f(x)=x+1,when x="+x+",f(x)="+add1.apply(x));
//define add5
Function<Integer, Integer> add5 = makeAdder.apply(5);
System.out.println("f(x)=x+5, when x="+x+", f(x)="+add5.apply(x));

由于高階函數(shù)接受一個函數(shù)作為參數(shù),結果返回另一個函數(shù)破镰,所以是典型的函數(shù)到函數(shù)的映射餐曼。
BiFunction提供了二元函數(shù)的一個接口聲明压储,舉例來說:

//binary fun
BiFunction<Integer, Integer, Integer> multiply = (a,b)->a*b;
System.out.println("f(z)=x*y, when x=3,y=5 when f(z)="+multiply,apply(3,5));

其輸出結果將是:f(z)=x*y, when x=3,y=5, then f(z)=15。
二元函數(shù)沒有compose能力源譬,只是默認實現(xiàn)了andThen集惋。

有了一元和二元函數(shù),那么可以通過組合擴展出更多的函數(shù)可能踩娘。

Function接口相關的接口包括:

  • BiFunction :R apply(T t, U u);接受兩個參數(shù)刮刑,返回一個值,代表一個二元函數(shù)养渴;
  • DoubleFunction :R apply(double value);只處理double類型的一元函數(shù)雷绢;
  • IntFunction :R apply(int value);只處理int參數(shù)的一元函數(shù);
  • LongFunction :R apply(long value);只處理long參數(shù)的一元函數(shù)理卑;
  • ToDoubleFunction:double applyAsDouble(T value);返回double的一元函數(shù)翘紊;
  • ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函數(shù);
  • ToIntFunction:int applyAsInt(T value);返回int的一元函數(shù)藐唠;
  • ToIntBiFunction:int applyAsInt(T t, U u);返回int的二元函數(shù)帆疟;
  • ToLongFunction:long applyAsLong(T value);返回long的一元函數(shù);
  • ToLongBiFunction:long applyAsLong(T t, U u);返回long的二元函數(shù)宇立;
  • DoubleToIntFunction:int applyAsInt(double value);接受double返回int的一元函數(shù)踪宠;
  • DoubleToLongFunction:long applyAsLong(double value);接受double返回long的一元函數(shù);
  • IntToDoubleFunction:double applyAsDouble(int value);接受int返回double的一元函數(shù)妈嘹;
  • IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函數(shù)柳琢;
  • LongToDoubleFunction:double applyAsDouble(long value);接受long返回double的一元函數(shù);
  • LongToIntFunction:int applyAsInt(long value);接受long返回int的一元函數(shù)润脸;

Operator

Operator其實就是Function柬脸,函數(shù)有時候也叫作算子。算子在Java8中接口描述更像是函數(shù)的補充毙驯,和上面的很多類型映射型函數(shù)類似肖粮。
算子Operator包括:UnaryOperator和BinaryOperator。分別對應單元算子和二元算子尔苦。
算子的接口聲明如下:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
  static <T> UnaryOperator<T> identity(){
    return t->t;
  }
}

二元算子的聲明:
算子就是一個針對同類型輸入輸出的一個映射涩馆。在此接口下,只需聲明一個泛型參數(shù)T即可允坚。對應上面的例子:

public class TestOperator {
  public static void main(String... args) {
    UnaryOperator<Integer> add = x->x+1;
    System.out.println(add.apply(1))
    
    BinaryOperator<Integer> addxy = (x,y)->x+y;
    System.out.println(addxy.apply(3,5));
    
    BinaryOperator<Integer> min = BinaryOperator.minBy((o1,o2)->o1-o2);
    System.out.println(min.apply(100,200));
    BinaryOperator<Integer> max = BinaryOperator.maxBy((o1,o2)->o1-o2);
    System.out.println(max.apply(100,200));
  }
}

例子里補充一點的是魂那,BinaryOperator提供了兩個默認的static快捷實現(xiàn),幫助實現(xiàn)二元函數(shù)min(x,y)和max(x,y)稠项,使用時注意的是排序器可別傳反了:)

其他的Operator接口:(不解釋了)

  • LongUnaryOperator:long applyAsLong(long operand);
  • IntUnaryOperator:int applyAsInt(int operand);
  • DoubleUnaryOperator:double applyAsDouble(double operand);
  • DoubleBinaryOperator:double applyAsDouble(double left, double right);
  • IntBinaryOperator:int applyAsInt(int left, int right);
  • LongBinaryOperator:long applyAsLong(long left, long right);

Predicate

predicate是一個謂詞函數(shù)涯雅,主要作為一個謂詞演算推導真假值存在,其意義在于幫助開發(fā)一些返回bool值的Function展运。本質上也是一個單元函數(shù)接口活逆,其抽象方法test接受一個泛型參數(shù)T精刷,返回一個boolean值。等價于一個Function的boolean型返回值的子集蔗候。

@FunctionalInterface
public interface Predicate<T> {
  boolean test(T t);
  ...
}

其默認方法也封裝了and怒允、or和negate邏輯。寫個小例子看看:

public class TestJ8Predicate {
  public static void main(String... args){
    TestJ8Predicate t = new TestJ8Predicate();
    t.pringBigValue(10, val->val>5);
    t.printBigValueAnd(10, val->val>5);
    t.printBigValueAnd(6, val->val>5);
  }
  public void pringBigValue(int value, Predicate<Integer> predicate) {
    if (predicate.test(value)) {
      System.out.println(value);
    }
  }
  public void printBigValueAnd(int value, Predicate<Integer> predicate) {
    if (predicate.and(v->v<8).test(value)) {
      System.out.println("value < 8:" + value);
    }else{
      System.out.println("value should < 8 at least.");
    }
  }
}

Predicate在Stream中有應用锈遥,Stream的filter方法就是接受Predicate作為入?yún)⒌娜沂隆_@個具體在后面使用Stream的時候再分析深入。

其他Predicate接口:

  • BiPredicate:boolean test(T t, U u);接受兩個參數(shù)的二元謂詞
  • DoublePredicate:boolean test(double value);入?yún)閐ouble的謂詞函數(shù)
  • IntPredicate:boolean test(int value);入?yún)閕nt的謂詞函數(shù)
  • LongPredicate:boolean test(long value);入?yún)閘ong的謂詞函數(shù)

Consumer

看名字就可以想到所灸,這像謂詞函數(shù)接口一樣丽惶,也是一個Function接口的特殊表達——接受一個泛型參數(shù),不需要返回值的函數(shù)接口爬立。

@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
  ...
}

這個接口聲明太重要了钾唬,對于一些純粹consume型的函數(shù),沒有Consumer的定義真無法被Function家族的函數(shù)接口表達侠驯。因為Function一定需要一個泛型參數(shù)作為返回值類型(當然不排除你使用Function來定義知纷,但是一直返回一個無用的值)。比如下面的例子陵霉,如果沒有Consumer,類似的行為使用Function表達就一定需要一個返回值伍绳。

public static void main(String... args) {
  Consumer<Integer> consumer = System.out::println;
  consumer.accept(100);
  //use function, you always need one return value.
  Function<Integer,Integer> function = x->{
    System.out.println(x);
    return x;
  }
  function.apply(100);
}

其他Consumer接口:

  • BiConsumer:void accept(T t, U u);接受兩個參數(shù)
  • DoubleConsumer:void accept(double value);接受一個double參數(shù)
  • IntConsumer:void accept(int value);接受一個int參數(shù)
  • LongConsumer:void accept(long value);接受一個long參數(shù)
  • ObjDoubleConsumer:void accept(T t, double value);接受一個泛型參數(shù)一個double參數(shù)
  • ObjIntConsumer:void accept(T t, int value);接受一個泛型參數(shù)一個int參數(shù)
  • ObjLongConsumer:void accept(T t, long value);接受一個泛型參數(shù)一個long參數(shù)

Supplier

其聲明如下:

@FunctionalInterface
public interface Supplier<T> {
  T get();
  ...
}

其簡潔的聲明踊挠,會讓人以為不是函數(shù)。這個抽象方法的聲明冲杀,同Consumer相反效床,是一個只聲明了返回值,不需要參數(shù)的函數(shù)(這還叫函數(shù)权谁?)剩檀。也就是說Supplier其實表達的不是從一個參數(shù)空間到結果空間的映射能力,而是表達一種生成能力旺芽,因為我們常見的場景中不止是要consume(Consumer)或者是簡單的map(Function)沪猴,還包括了new這個動作。而Supplier就表達了這種能力采章。

比如你要是返回一個常量运嗜,那可以使用類似的做法:

這保證supplier對象輸出的一直是1。

如果是要利用構造函數(shù)的能力呢悯舟?就可以這樣:

Supplier<TestJ8Supplier> anotherSupplier;
for(int i=0; i<10; i++){
  anotherSupplier = TestJ8Supplier::new;
  System.out.println(anotherSupplier.get())
}

這樣的輸出可以看到担租,全部的對象都是new出來的。

這樣的場景在Stream計算中會經(jīng)常用到抵怎,具體在分析Java 8中Stream的時候再深入奋救。

其他Supplier接口:
BooleanSupplier:boolean getAsBoolean();返回boolean
DoubleSupplier:double getAsDouble();返回double
IntSupplier:int getAsInt();返回int
LongSupplier:long getAsLong();返回long

參考:
阮一峰 - 函數(shù)式編程入門教程:http://www.ruanyifeng.com/blog/201
https://blog.csdn.net/lz710117239/article/details/76192629

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末岭参,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子尝艘,更是在濱河造成了極大的恐慌演侯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件利耍,死亡現(xiàn)場離奇詭異蚌本,居然都是意外死亡,警方通過查閱死者的電腦和手機隘梨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門程癌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人轴猎,你說我怎么就攤上這事嵌莉。” “怎么了捻脖?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵锐峭,是天一觀的道長。 經(jīng)常有香客問我可婶,道長沿癞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任矛渴,我火速辦了婚禮椎扬,結果婚禮上,老公的妹妹穿的比我還像新娘具温。我一直安慰自己蚕涤,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布铣猩。 她就那樣靜靜地躺著揖铜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪达皿。 梳的紋絲不亂的頭發(fā)上天吓,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音峦椰,去河邊找鬼失仁。 笑死,一個胖子當著我的面吹牛们何,可吹牛的內容都是我干的萄焦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拂封!你這毒婦竟也來了茬射?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤冒签,失蹤者是張志新(化名)和其女友劉穎在抛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萧恕,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡刚梭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了票唆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朴读。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖走趋,靈堂內的尸體忽然破棺而出衅金,到底是詐尸還是另有隱情,我是刑警寧澤簿煌,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布氮唯,位于F島的核電站,受9級特大地震影響姨伟,放射性物質發(fā)生泄漏惩琉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一夺荒、第九天 我趴在偏房一處隱蔽的房頂上張望瞒渠。 院中可真熱鬧,春花似錦般堆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至始赎,卻和暖如春和橙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背造垛。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工魔招, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人五辽。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓办斑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乡翅,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345