Spring WebFlux 學(xué)習(xí)筆記 - (一) 前傳:學(xué)習(xí)Java 8 Stream Api (3) - Stream的終端操作

Stream API

Java8中有兩大最為重要的改變:第一個(gè)是 Lambda 表達(dá)式;另外一個(gè)則是 Stream API(java.util.stream.*)缕探。

Stream 是 Java8 中處理集合的關(guān)鍵抽象概念肌稻,它可以指定你希望對(duì)集合進(jìn)行的操作维苔,可以執(zhí)行非常復(fù)雜的查找宵荒、過(guò)濾和映射數(shù)據(jù)等操作。使用Stream API 對(duì)集合數(shù)據(jù)進(jìn)行操作苔悦,就類似于使用 SQL 執(zhí)行的數(shù)據(jù)庫(kù)查詢轩褐。也可以使用 Stream API 來(lái)并行執(zhí)行操作。簡(jiǎn)而言之玖详,Stream API 提供了一種高效且易于使用的處理數(shù)據(jù)的方式把介。

流(Stream)是數(shù)據(jù)渠道勤讽,用于操作數(shù)據(jù)源(集合、數(shù)組等)所生成的元素序列拗踢。"集合講的是數(shù)據(jù)脚牍,流講的是計(jì)算! "

集合和流(Stream)巢墅,表面上有一些相似之處诸狭,他們有不同的目標(biāo)诚啃。集合主要關(guān)注其元素的有效管理和訪問(wèn)戏溺,相比之下,流不提供直接訪問(wèn)或操縱元素的手段捎琐,而是關(guān)心聲明性地描述其源和將在該源上進(jìn)行聚合的計(jì)算操作蓄髓。

上篇內(nèi)容我們學(xué)習(xí)了Stream的中間操作叉庐,接下來(lái)我們來(lái)看下Stream數(shù)據(jù)流的結(jié)果消費(fèi),即終端(終止)操作双吆。以下用 終端操作 統(tǒng)稱眨唬。

Stream的終端操作

流管道通過(guò)源生成会前,經(jīng)過(guò)零個(gè)或多個(gè)中間操作后好乐,進(jìn)行最后的終端操作,由此產(chǎn)生結(jié)果或副作用瓦宜,如count()或forEach(Consumer)蔚万。

我們將終端操作的結(jié)果分為如下幾類:

  1. 匹配
  2. 統(tǒng)計(jì)
  3. 消費(fèi)
  4. 轉(zhuǎn)換

以下內(nèi)容提到XxxStream代表IntStream、LongStream临庇、DoubleStream反璃。如無(wú)特殊說(shuō)明Stream也包含IntStream、LongStream假夺、DoubleStream淮蜈。

匹配

匹配類型的終端操作返回值為布爾值,根據(jù)使用語(yǔ)境調(diào)用不同的方法及傳入謂語(yǔ)實(shí)現(xiàn)確定是否匹配已卷。

序號(hào) 支持的類 方法定義 方法說(shuō)明
1 Stream<T> boolean anyMatch(Predicate<? super T> predicate); 部分匹配梧田,返回此流的任何元素是否與提供的謂詞匹配。
2 Stream<T> boolean allMatch(Predicate<? super T> predicate); 全部匹配侧蘸,返回此流的所有元素是否與提供的謂詞匹配裁眯。
3 Stream<T> boolean noneMatch(Predicate<? super T> predicate); 全不匹配,返回此流中是否沒(méi)有元素與提供的謂詞匹配讳癌。

注意:如果流為空時(shí)穿稳,allMatch方法始終返回true;noneMatch方法始終返回true晌坤。

以下代碼見(jiàn) StreamTerminalOperationMatchTest逢艘。

anyMatch的使用

// 是否存在匹配的元素旦袋,true
log.info("[1, 2, 3, 4, 5, 6]存在偶數(shù)否:{}",
        Stream.of(1, 2, 3, 4, 5, 6).anyMatch(n -> n % 2 == 0));

allMatch的使用

// 全部元素是否都匹配,false
log.info("[1, 2, 3, 4, 5, 6]全部都是偶數(shù)否:{}",
        Stream.of(1, 2, 3, 4, 5, 6).allMatch(n -> n % 2 == 0));

noneMatch的使用

// 全部元素是否都不匹配埋虹,false
log.info("[1, 2, 3, 4, 5, 6]全部都不是偶數(shù)否:{}",
        Stream.of(1, 2, 3, 4, 5, 6).noneMatch(n -> n % 2 == 0));

空流驗(yàn)證

// 空流中不存在任何匹配元素猜憎,所以返回false
log.info("空流是否AnyMatch:{}", Stream.empty().anyMatch(Objects::isNull));
// 空流中不存在不匹配的,即全部匹配搔课,所以返回true
log.info("空流是否AllMatch:{}", Stream.empty().allMatch(Objects::isNull));
// 空流中全部都不匹配胰柑,所以返回true
log.info("空流是否NoneMatch:{}", Stream.empty().noneMatch(Objects::isNull));

統(tǒng)計(jì)

統(tǒng)計(jì)類型的終端操作是對(duì)流元素的統(tǒng)計(jì),如元素個(gè)數(shù)爬泥、最大值柬讨、最小值、統(tǒng)計(jì)對(duì)象等袍啡。

序號(hào) 支持的類 方法定義 方法說(shuō)明
1 Stream<T> long count(); 返回此流中的元素?cái)?shù)踩官。
2 Stream<T> Optional<T> min(Comparator<? super T> comparator); 根據(jù)提供的 Comparator返回此流的最小元素。
3 Stream<T> Optional<T> max(Comparator<? super T> comparator); 根據(jù)提供的 Comparator返回此流的最大元素境输。
4 Stream<T> OptionalXxx min(); 返回 OptionalInt此流的最小元素的OptionalInt蔗牡,如果此流為空,則返回一個(gè)空的可選項(xiàng)嗅剖。
5 XxxStream<T> OptionalXxx max(); 返回 OptionalInt此流的最大元素的OptionalInt辩越,如果此流為空,則返回一個(gè)空的可選項(xiàng)信粮。
6 XxxStream<T> OptionalDouble average(); 返回 OptionalDouble此流的元素的算術(shù)平均值的OptionalDouble黔攒,如果此流為空,則返回空的可選項(xiàng)强缘。
7 XxxStream<T> Xxx sum(); 返回此流中元素的總和督惰。
8 XxxStream<T> XxxSummaryStatistics summaryStatistics(); 返回一個(gè) IntSummaryStatistics描述有關(guān)此流的元素的各種摘要數(shù)據(jù)。

XxxSummaryStatistics類型的統(tǒng)計(jì)對(duì)象旅掂,如IntSummaryStatistics赏胚,除了提供最小值、最大值商虐、平均值觉阅、元素個(gè)數(shù)、總和外称龙,還提供了accept留拾、combine兩個(gè)方法,分別支持添加新的數(shù)據(jù)和連接另外的統(tǒng)計(jì)對(duì)象鲫尊,并自動(dòng)重新統(tǒng)計(jì)結(jié)果痴柔。

以下代碼見(jiàn) StreamTerminalOperationStatisticsTest。

count的使用

log.info("[1, 2, 3, 4, 5, 6]元素個(gè)數(shù):{}",
        Stream.of(1, 2, 3, 4, 5, 6).count());

min的使用(使用Comparator比較)

log.info("[1, 2, 3, 4, 5, 6]的最大值:{}",
        Stream.of(1, 2, 3, 4, 5, 6).min(Comparator.comparingInt(n -> n)).get());

max的使用(使用Comparator比較)

log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
        Stream.of(1, 2, 3, 4, 5, 6).max(Comparator.comparingInt(n -> n)).get());

min的使用

log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
        IntStream.of(1, 2, 3, 4, 5, 6).min().getAsInt());

max的使用

log.info("[1, 2, 3, 4, 5, 6]的最大值:{}",
        IntStream.of(1, 2, 3, 4, 5, 6).max().getAsInt());

average的使用

log.info("[1, 2, 3, 4, 5, 6]的平均值:{}",
        IntStream.of(1, 2, 3, 4, 5, 6).average().getAsDouble());

sum的使用

log.info("[1, 2, 3, 4, 5, 6]的求和:{}",
        IntStream.of(1, 2, 3, 4, 5, 6).sum());

summaryStatistics的使用

IntSummaryStatistics summaryStatistics = IntStream.of(1, 2, 3, 4, 5, 6).summaryStatistics();
log.info("[1, 2, 3, 4, 5, 6]的統(tǒng)計(jì)對(duì)象:{}", summaryStatistics);
summaryStatistics.accept(7);
log.info("添加7后疫向,統(tǒng)計(jì)對(duì)象變?yōu)椋簕}", summaryStatistics);
IntSummaryStatistics summaryStatistics2 = IntStream.of(8, 9).summaryStatistics();
summaryStatistics.combine(summaryStatistics2);
log.info("合并[8, 9]后咳蔚,統(tǒng)計(jì)對(duì)象變?yōu)椋簕}", summaryStatistics);

消費(fèi)

消費(fèi)類型的終端操作是對(duì)流內(nèi)元素的獲取或循環(huán)消費(fèi)豪嚎。

序號(hào) 支持的類 方法定義 方法說(shuō)明
1 Stream<T> Optional<T> findFirst(); 返回描述此流的第一個(gè)元素的Optional,如果流為空谈火,則返回一個(gè)空的Optional侈询。
2 Stream<T> Optional<T> findAny(); 返回描述流的一些元素的Optional如果流為空,則返回一個(gè)空的Optional糯耍。
3 Stream<T> void forEach(Consumer<? super T> action); 對(duì)此流的每個(gè)元素執(zhí)行操作扔字。
4 Stream<T> void forEachOrdered(Consumer<? super T> action); 如果流具有定義的順序,則以流的順序?qū)υ摿鞯拿總€(gè)元素執(zhí)行操作温技。

看了forEach和forEachOrdered的Api的說(shuō)明革为,大家可能對(duì)這兩個(gè)還是有點(diǎn)疑問(wèn),這里特別說(shuō)明下舵鳞,forEach在并行流(parallel后面會(huì)講)中并不按照流內(nèi)元素之前定義的順序執(zhí)行操作震檩,是無(wú)序的,而forEachOrdered會(huì)按照流之前定義的順序執(zhí)行操作蜓堕。除非必要抛虏,在并行流中不建議使用forEachOrdered對(duì)其進(jìn)行排序執(zhí)行操作,否則影響性能套才。

流有可能也可能沒(méi)有定義順序迂猴。流是否有順序取決于源和中間操作。某些流源(如List或數(shù)組)本質(zhì)上是有序的霜旧,而其他數(shù)據(jù)源(如HashSet)不是错忱。一些中間操作(例如sorted())可以在其他無(wú)序流上排序儡率,而其他中間操作可以使有序流無(wú)序挂据,例如BaseStream.unordered()。此外儿普,一些終端操作可能會(huì)忽略順序崎逃,如forEach()。

如果一個(gè)流被命令眉孩,大多數(shù)操作被限制為在遇到的順序中對(duì)元素進(jìn)行操作; 如果流的源是List含有[1, 2, 3] 个绍,然后執(zhí)行的結(jié)果map(x -> x*2)必須是[2, 4, 6] 。 然而浪汪,如果源沒(méi)有定義的順序巴柿,則任何[2, 4, 6]排列組合值都將是有效結(jié)果。

對(duì)于順序流死遭,遇到順序的存在或不存在不影響性能广恢,僅影響確定性。 如果流被排序呀潭,在相同的源上重復(fù)執(zhí)行相同的流管線將產(chǎn)生相同的結(jié)果; 如果沒(méi)有排序钉迷,重復(fù)執(zhí)行可能會(huì)產(chǎn)生不同的結(jié)果至非。

對(duì)于并行流,放寬排序約束有時(shí)可以實(shí)現(xiàn)更有效的執(zhí)行糠聪。如果元素的排序不相關(guān)荒椭,某些聚合操作,例如過(guò)濾重復(fù)(distinct())或組合減少(Collectors.groupingBy())可以更有效地實(shí)現(xiàn)舰蟆。 類似地趣惠,本質(zhì)上與遇到順序相關(guān)的操作,如limit()可能需要緩沖以確保正確排序身害,從而破壞并行性的好處信卡。另外,當(dāng)流遇到排序题造,但用戶并不特別在意那次偶遇秩序的情況下傍菇,明確地去操作為無(wú)序流unordered()可以提高某些狀態(tài)或終端操作的并行性能。 然而界赔,大多數(shù)流管線丢习,例如上面的例子的“權(quán)重之和”,仍然在有序的限制下有效地并行化淮悼。

以下代碼見(jiàn) StreamTerminalOperationConsumeTest咐低。

findFirst的使用

log.info("[1, 2, 3, 4, 5, 6]的首個(gè)值:{}",
        Stream.of(1, 2, 3, 4, 5, 6).parallel().findFirst().get());

findAny的使用

log.info("[1, 2, 3, 4, 5, 6]的任意值:{}",
        Stream.of(1, 2, 3, 4, 5, 6).parallel().findAny().get());

forEach的使用

// 并行后順序隨機(jī),輸出不保證順序
log.info("[1, 2, 3, 4, 5, 6]的并行后循環(huán)輸出:");
Stream.of(1, 2, 3, 4, 5, 6).parallel().forEach(System.out::println);

forEachOrdered的使用

// 無(wú)論是否并行袜腥,始終按照流定義的順序或排序后的結(jié)果輸出
log.info("[1, 2, 5, 6, 3, 4]的并行后順序循環(huán)輸出:");
Stream.of(1, 2, 5, 6, 3, 4).sorted().parallel().forEachOrdered(System.out::println);

轉(zhuǎn)換

轉(zhuǎn)換類型的終端操作是將流轉(zhuǎn)換為另一種對(duì)象使用见擦。

序號(hào) 支持的類 方法定義 方法說(shuō)明
1 Stream<T> Optional<T> reduce(BinaryOperator<T> accumulator); 使用associative累積函數(shù)對(duì)此流的元素執(zhí)行reduction,并返回描述減小值(如果有的話)的Optional 羹令。
2 Stream<T> T reduce(T identity, BinaryOperator<T> accumulator); 使用提供的身份值和 associative累積功能對(duì)此流的元素執(zhí)行 reduction 鲤屡,并返回減小的值。
3 Stream<T> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator combiner); 執(zhí)行 reduction在此流中的元素福侈,使用所提供的身份酒来,積累和組合功能。
4 Stream<T> <R, A> R collect(Collector<? super T, A, R> collector); 使用 Collector對(duì)此流的元素執(zhí)行 mutable reduction Collector肪凛。
5 Stream<T> <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); 對(duì)此流的元素執(zhí)行 mutable reduction操作堰汉。

此處我們著重說(shuō)下序號(hào)3,帶有3個(gè)參數(shù)的reduce方法伟墙,該方法支持轉(zhuǎn)換元素(結(jié)果)類型翘鸭,即從類型T轉(zhuǎn)換為類型U。第1個(gè)參數(shù)代表初始值戳葵;第2個(gè)參數(shù)是累加器函數(shù)式接口就乓,輸入類型U和類型T,返回類型U;第3個(gè)參數(shù)是組合器函數(shù)式接口档址,輸入類型U和類型U盹兢,返回類型U。該方法的第3個(gè)參數(shù)在并行執(zhí)行下有效守伸。
同時(shí)需要注意绎秒,此方法有如下要求:

  • u = combiner(identity, u);
  • combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t);

該方法的代碼示例見(jiàn)reduce的使用3reduce的使用4,如果對(duì)該示例不了解尼摹,可以在后面的章節(jié)中講解了并行執(zhí)行之后再回過(guò)頭來(lái)看該示例见芹。

以下代碼見(jiàn) StreamTerminalOperationTransformTest。

reduce的使用1

// 使用reduce方式實(shí)現(xiàn)查找最小值
log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
        Stream.of(1, 2, 3, 4, 5, 6).reduce(Integer::min).get());

reduce的使用2

// 使用reduce方式實(shí)現(xiàn)求和
log.info("[1, 2, 3, 4, 5, 6]的求和:{}",
        Stream.of(1, 2, 3, 4, 5, 6).reduce(0, Integer::sum));

reduce的使用3

// 求單詞長(zhǎng)度之和
Integer lengthSum = Stream.of("I", "love", "you", "too")
        .parallel()
        .reduce(0,// 初始值 // (1)
                (sum, str) -> sum + str.length(), // 累加器 // (2)
                Integer::sum);// 部分和拼接器蠢涝,并行執(zhí)行時(shí)才會(huì)用到 // (3)
// int lengthSum = stream.mapToInt(str -> str.length()).sum();
log.info("ILoveYouToo的長(zhǎng)度為:{}", lengthSum);

reduce的使用4

// 下方方法同步執(zhí)行時(shí)玄呛,能出現(xiàn)正確結(jié)果
// 并行執(zhí)行時(shí),將出現(xiàn)意想不到的結(jié)果
// 多線程執(zhí)行時(shí)和二,append導(dǎo)致初始值identity發(fā)生了變化徘铝,而多線程又導(dǎo)致了數(shù)據(jù)重復(fù)添加
StringBuffer word = Stream.of("I", "love", "you", "too")
        .parallel()                 // 同步執(zhí)行注釋該步驟
        .reduce(new StringBuffer(),// 初始值 // (1)
                StringBuffer::append, // 累加器 // (2)
                StringBuffer::append);// 部分和組合器,并行執(zhí)行時(shí)才會(huì)用到 // (3)
log.info("拼接字符串為:{}", word);

// 此處如果使用字符串concat惯吕,導(dǎo)致性能降低惕它,不停創(chuàng)建字符串常量
String word2 = Stream.of("I", "love", "you", "too")
        .parallel()                 // 同步執(zhí)行注釋該步驟
        .reduce("",// 初始值 // (1)
                String::concat, // 累加器 // (2)
                String::concat);// 部分和組合器,并行執(zhí)行時(shí)才會(huì)用到 // (3)
log.info("拼接字符串為:{}", word2);

// 下面方法并行執(zhí)行時(shí)废登,雖然能達(dá)到正確的結(jié)果淹魄,但是并未滿足reduce的要求
List<Integer> accResult = Stream.of(1, 2, 3, 4)
        .parallel()
        .reduce(Collections.synchronizedList(new ArrayList<>()),
                (acc, item) -> {
                    List<Integer> list = new ArrayList<>();
                    list.add(item);
                    System.out.println("item BiFunction : " + item);
                    System.out.println("acc+ BiFunction: " + list);
                    return list;
                }, (accs, items) -> {
                    accs.addAll(items);
                    System.out.println("item BinaryOperator: " + items);
                    System.out.println("acc+ BinaryOperator: " + accs);
                    return accs;
                });
log.info("accResult: {}", accResult);

由于時(shí)間及版面的緣故,本期就先講到這里堡距,下期在著重將collect甲锡。

源碼詳見(jiàn):https://github.com/crystalxmumu/spring-web-flux-study-note

以上是本期筆記的內(nèi)容,我們下期見(jiàn)羽戒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缤沦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子半醉,更是在濱河造成了極大的恐慌疚俱,老刑警劉巖劝术,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缩多,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡养晋,警方通過(guò)查閱死者的電腦和手機(jī)衬吆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)绳泉,“玉大人逊抡,你說(shuō)我怎么就攤上這事。” “怎么了冒嫡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵拇勃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我孝凌,道長(zhǎng)方咆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任蟀架,我火速辦了婚禮瓣赂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘片拍。我一直安慰自己煌集,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布捌省。 她就那樣靜靜地躺著苫纤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纲缓。 梳的紋絲不亂的頭發(fā)上方面,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音色徘,去河邊找鬼恭金。 笑死,一個(gè)胖子當(dāng)著我的面吹牛褂策,可吹牛的內(nèi)容都是我干的横腿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼斤寂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耿焊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起遍搞,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤罗侯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后溪猿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钩杰,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年诊县,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讲弄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡依痊,死狀恐怖避除,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤瓶摆,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布凉逛,位于F島的核電站,受9級(jí)特大地震影響群井,放射性物質(zhì)發(fā)生泄漏鱼炒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一蝌借、第九天 我趴在偏房一處隱蔽的房頂上張望昔瞧。 院中可真熱鬧,春花似錦菩佑、人聲如沸自晰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酬荞。三九已至,卻和暖如春瞧哟,著一層夾襖步出監(jiān)牢的瞬間混巧,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工勤揩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咧党,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓陨亡,卻偏偏與公主長(zhǎng)得像傍衡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子负蠕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345