java8新特性学习

标签: 学习笔记


stream的简单使用

使用map,filter,forEach,collect,distinct

//Trade.java
public class Trader {

    public String getName() {
        return name;
    }

    public String getCity() {
        return city;
    }

    private final String name;
    private final String city;

    public Trader(String name, String city) {
        this.name = name;
        this.city = city;
    }
}

//Transaction.java
public class Transaction {
    public Trader getTrader() {
        return trader;
    }

    public int getYear() {
        return year;
    }

    public int getValue() {
        return value;
    }

    private final Trader trader;
    private final int year;
    private final int value;

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }
}

//main函数测试
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class StreamApplication {
    public static void main(String[] args) {
        Trader raoul = new Trader("Raoul", "剑桥");
        Trader mario = new Trader("Mario", "米兰");
        Trader alan = new Trader("Alan", "剑桥");
        Trader brian = new Trader("Brian", "剑桥");

        List<Transaction> transactions = Arrays.asList(
                new Transaction(raoul, 2011, 400),
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
        System.out.println("1. 找出2011年发生的所有交易,并按交易额排序(低到高)");
        transactions.stream()
                .filter(item->item.getYear() == 2011)
                .sorted((a,b)->((Integer)a.getValue()).compareTo(b.getValue()))
                .map(Transaction::getTrader)
                .map(Trader::getName)
                .collect(Collectors.toList()).forEach(System.out::println);

        System.out.println("2. 交易员都在那些不同的城市工作过?");
        transactions.stream()
                .map(Transaction::getTrader)
                .map(Trader::getCity)
                .distinct()
                .collect(Collectors.toList())
                .forEach(System.out::println);

        System.out.println("3. 所有来自剑桥的交易员,按姓名排序");
        transactions.stream()
                .filter(item->item.getTrader().getCity().equals("剑桥"))
                .map(Transaction::getTrader)
                .distinct()
                .sorted(Comparator.comparing(Trader::getName))
                .map(Trader::getName)
                .collect(Collectors.toList())
                .forEach(System.out::println);

        System.out.println("4. 返回所有交易员姓名的字符串");
        transactions.stream()
                .map(item->item.getTrader().getName())
                .distinct()
                .collect(Collectors.toList())
                .forEach(System.out::println);

        System.out.println("5. 有没有交易员是再米兰工作的");
        Optional<Transaction> optional = transactions.stream()
                .filter(item->"米兰".equals(item.getTrader().getCity()))
                .findAny();
        if (optional.isPresent())
            System.out.println("有");
        else
            System.out.println("无");

        System.out.println("6. 生活在剑桥的交易员所有交易额");
        System.out.println(transactions.stream()
                .filter(item->item.getTrader().getCity().equals("剑桥"))
                .map(Transaction::getValue)
                .reduce(0,(a,b)->a+b));

        System.out.println("7. 最高的交易额");
        System.out.println(transactions.stream()
                .map(Transaction::getValue)
                .reduce(Integer.MIN_VALUE,Integer::max));

        System.out.println("8. 最低的交易额");
        System.out.println(transactions.stream()
                .map(Transaction::getValue)
                .reduce(Integer.MAX_VALUE,Integer::min));
    }
}
  • mapflatMap的区别:map是将一种类型的值转换为另外一种类型的值,flatMap将多个Stream连接成一个Stream
  • flatMap不存在笛卡尔积的情况

如何获得笛卡尔积的情况

给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1,2,3]和列表[3,4],应该返回[(1,3),(1,4),(2,3),(2,4),(3,3),(3,4)]。

List<Integer> list1 = Arrays.asList(1,2,3);
List<Integer> list2 = Arrays.asList(3,4);

List<int[]> pairs = list1.stream().flatMap(i->list2.stream().map(j->new int[]{i,j})).collect(Collectors.toList());
pairs.stream().map(Arrays::toString).forEach(System.out::println);

/*结果:
[1, 3]
[1, 4]
[2, 3]
[2, 4]
[3, 3]
[3, 4]
*/

使用归约、汇总和分组

  • 根据某个属性进行分类groupingby
  • 使用reducing
    对于计算菜单中菜品总热量的需求,可以使用一下几种方法来实现:
  • 使用reducing工厂方法
  • 使用reduce
  • 使用mapToInt之后,使用Integersum方法
//使用reducing
int totalCalories = menu.stream().collect(reducing(0,Dish::getCalories,(i,j)->i+j));
int totalCalories = menu.stream().collect(reducing(0,Dish::getCalories,Integer::sum));

//使用reducing
int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();

//int totalCalories = menu.stream().mapToInt(Dish::geCalories).sum();

使用reduce之后,返回的不是int类型,而是Optional<Integer>,这样可以在空流的情况下安全执行归约操作。因此可以使用get来提取值即可。

函数式接口

java8中新增的函数式接口的包是java.util.function,其中包含了很多函数式接口,下面介绍几种常用的:

  • Predicate<T>: T->boolean,接受一个输入参数,返回一个布尔值结果。原始类型特化的函数式接口有:IntPredicate,LongPredicate,DoublePredicate,其中定义的是test方法。
  • Consumer<T>: T->(),代表了接受一个输入参数并且无返回的操作。原始类型特化的函数式接口有:IntConsumer,LongConsumer,DoubleConsumer。其中定义的是accept方法。
    -Function<T,R>T->R,接受一个输入参数,返回一个结果。他有很多的原始类型特化。其中定义的是apply方法。
  • Supplier<T>()->T,无参数,返回一个结果。

collection.toArray(new String[0])中new String[0]的语法解释

new String[0]只是用来创建一个String类型的数组对象,但是里面没有任何东西。

String[] testString = new String[0];
        System.out.println(testString);
        System.out.println(testString[0]);
//结果
//[Ljava.lang.String;@3f91beef
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
//  at Demo.main(Demo.java:59)

Collection的公有方法中,toArray()是比较重要的一个。
但是使用无参数的toArray()有一个缺点,就是转换后的数组类型是Object[]。 虽然Object数组也不是不能用,但当你真的想用一个具体类型的数组,比如String[]时,问题就来了。而把Object[]给cast成String[]还是很麻烦的,需要用到这个:

String[] stringArray = Arrays.copyOf(objectArray, objectArray.length, String[].class);

不管是从哪方面看还是一开始就弄成String[]比较好。

具体怎么办呢?其实用带参数的toArray就好了。官方是这样给出的例子:
String[] a = c.toArray(new String[0]);

如何使用最少的代码来完成一个对象到另一个对象到映射

ObjectMapper的使用

ObjectMapper有很多强大的功能,这里只介绍其中的convertValue方法。它可以将某个对象的属性赋给另一个对象

//定义一个Teacher类和一个Person类
//Teacher.java
public class Teacher {
    private int id;
    private String name;
    private String time;

    public Teacher(int id, String name, String time) {
        this.id = id;
        this.name = name;
        this.time = time;
    }

    public Teacher() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    @Override
    public String toString() {
        return this.id + " " + this.name + " " + this.time + " ";
    }
}

//Person.java
public class Person {
    private int id;
    private String name;
    private long time;

    public Person(int id, String name, long time) {
        this.id = id;
        this.name = name;
        this.time = time;
    }

    public Person() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    @Override
    public long toString() {
        return this.id + " " + this.name + " " + this.time;
    }
}
  • 可以看到,time变量的变量名相同,但是类型不同,一个是long类型,一个是String类型。尝试使用ObjectMapper中的convertValue来进行两个对象之间的变量传递。
import com.fasterxml.jackson.databind.ObjectMapper;

public class Demo {
    public static void main(String[] args) {
        Person person = new Person(1,"xiaoming",12l);
        //从Person到Teacher, 时间参数从long到string
        Teacher teacher = new ObjectMapper().convertValue(person,Teacher.class);
        System.out.println("从Person到Teacher, 时间参数从long到string");
        System.out.println(teacher);

        //从Teacher到Person, 时间参数从string到long
        Teacher teacher1 = new Teacher(1,"xiaoming","12");
        Person person1 = new ObjectMapper().convertValue(teacher1,Person.class);
        System.out.println("从Teacher到Person, 时间参数从string到long");
        System.out.println(person1);
    }
}

运行结果如下:

从Person到Teacher, 时间参数从long到string
1 xiaoming 12 
从Teacher到Person, 时间参数从string到long
1 xiaoming 12
  • 当两个类中的time属性只有命名不同,其他包括类型都相同的情况下,使用ObjectMapper会报错。
public class Teacher {
    private int id;
    private String name;
    private String time1;

    public Teacher(int id, String name, String time1) {
        this.id = id;
        this.name = name;
        this.time1 = time1;
    }

    public Teacher() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTime1() {
        return time1;
    }

    public void setTime1(String time1) {
        this.time1 = time1;
    }

    @Override
    public String toString() {
        return this.id + " " + this.name + " " + this.time1 + " ";
    }
}
/*
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51595:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/tools.jar:/Users/xiaoyaoma/Study/ObjectMapperDemoq/target/classes:/Users/xiaoyaoma/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.5.3/jackson-core-2.5.3.jar:/Users/xiaoyaoma/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.5.3/jackson-annotations-2.5.3.jar:/Users/xiaoyaoma/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.5.3/jackson-databind-2.5.3.jar Demo
Exception in thread "main" java.lang.IllegalArgumentException: Unrecognized field "time" (class Teacher), not marked as ignorable (3 known properties: "id", "name", "time1"])
 at [Source: N/A; line: -1, column: -1] (through reference chain: Teacher["time"])
    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3286)
    at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3212)
    at Demo.main(Demo.java:7)
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "time" (class Teacher), not marked as ignorable (3 known properties: "id", "name", "time1"])
 at [Source: N/A; line: -1, column: -1] (through reference chain: Teacher["time"])
    at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)
    at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:817)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:958)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1324)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1302)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:249)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:136)
    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3281)
    ... 2 more

Process finished with exit code 1
*/

BeanUtils的使用

问题引入

在我们进行程序开发的过程中,常常会将某个对象map到另一个对象,两个对象拥有类似的属性,但是可能属性数量和类型不同。

解决方法

可以使用ObjectMapper来对其进行对应,但是需要属性名字和变量一一对应。这里介绍使用BeanUtilscopyProperties方法完成。spring和apache中都有BeanUtils,两者都需要Bean中拥有setget,且变量名称相同。

辨析

  • spring中的BeanUtils
BeanUtils.copyProperties(origin,destination);
//copyProperties destination.setName(origin.getName());

也可以忽略某些字段:

org.springframework.beans.BeanUtils.copyProperties(ba, bb, "name1");

不存在类型转换

  • apache中的BeanUtils
BeanUtils.copyProperties(destination, origin);//把origin的值给destination

存在类型转换

设计模式

代理模式

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用,比如我们生活中到邮局,快递公司,婚介所等等。代理模式可以分为静态代理模式和动态代理模式。

  • 静态代理:是由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行之前,代理类.class文件就已经被创建了
  • 动态代理:是在程序运行时通过java反射机制动态创建对。

代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

静态代理模式

我们以一个例子来介绍。在古代,某家的公子看上了别家的姑娘,一般都是家里的大人去姑娘的家里提亲,双方父母同意了,然后就拜堂成婚,后面要宴请亲朋好友。这里这个公子只需要拜堂成婚就行了,至于提亲和宴请亲友都是父母操办的。我们用代码来模拟一下这个场景。
首先,建立一个Person接口:

public interface Person {
    /**
     * 人有很对行为,这里我们用到的是结婚
     */
    void marry();
}

然后这家公子要成亲,我们建个Son类实现Person接口:

public class Son implements Person {
    @Override
    public void marry() {
        System.out.println("我终于结婚了");
    }
}

父亲帮儿子提亲,建个Father类:

public class Father {
    private Son son;
    public Father(Son son){
        this.son = son;
    }
    public void marry(){
        System.out.println("父亲上门提亲");
        this.son.marry();
        System.out.println("父亲宴请亲友");
    }
}

最后是测试代码:

public class Test {
    public static void main(String[] args) {
        Father father = new Father(new Son());
        father.marry();
    }
}

输出:

父亲上门提亲
我终于结婚了
父亲宴请亲友

静态代理对缺点:单一,一个类只能代理一个目标对象。

动态代理模式

动态代理可以分为JDK动态代理和cglib动态代理两种。这里先介绍JDK动态代理对方式来模拟一个通过婚介所找朋友的场景。
Person`接口如下:

public interface Person {
    void findFriend();
}

然后是婚介所类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKMarimonialAgency implements InvocationHandler {
    private Object target;
    public Object getInstance(Object target) {
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object obj = method.invoke(this.target,args);
        after();
        return obj;
    }

    private void after() {
        System.out.println("已经找到合适的,尽快安排你相亲");
    }

    private void before() {
        System.out.println("这是婚介所,请提供你的需求");
    }
}

JDK动态代理主要是实现InvocationHandler接口,并实现invoke方法。
然后创建Customer类:

public class Constumer implements Person {
    @Override
    public void findFriend() {
        System.out.println("我想找个善良男");
    }
}

然后是测试类:

public class Test {
    public static void main(String[] args) {
        Person person = (Person) new JDKMarimonialAgency().getInstance(new Constumer());
        person.findFriend();
    }
}

输出如下:

这是婚介所,请提供你的需求
我想找个善良男
已经找到合适的,尽快安排你相亲

JDK动态代理-Proxy.newProxyInstance
现在要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5之后提供了一个"java.lang.reflect.Proxy"类,通过"Proxy"类提供的一个newProxyInstance方法用来创建一个对象的代理对象,

  • java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口、目标接口的类加载器以及InvocationHandler便可为目标接口生成代理类及代理对象。
 // 方法 1: 该方法用于获取指定代理对象所关联的InvocationHandler
static InvocationHandler getInvocationHandler(Object proxy) 

// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 

// 方法 3:该方法用于判断指定类是否是一个动态代理类
static boolean isProxyClass(Class cl) 

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

推荐阅读更多精彩内容