异常

异常的体系

exception

异常:是在运行时期发生的不正常情况。 在java中用类的形式对不正常情况进行了描述和封装对象。描述不正常的情况的类,就称为异常类。

  1. 以前正常流程代码和问题处理代码相结合,现在将正常流程代码和问题处理代码分离,提高阅读性。
  2. 其实异常就是java通过面向对象的思想将问题封装成了对象,用异常类对其进行描述。
  3. 不同的问题用不同的类进行具体的描述。比如角标越界、空指针异常等等。
  4. 问题很多,意味着描述的类也很多,将其共性进行向上抽取,形成了异常体系。

不正常情况分成了两大类:

Throwable:无论是error,还是异常、问题,问题发生就应该可以抛出,让调用者知道并处理。

该体系的特点就在于Throwable及其所有的子类都具有可抛性。

可抛性到底指的是什么呢?怎么体现可抛性呢?

其实是通过两个关键字来体现的:throws、throw,凡是可以被这两个关键字所操作的类和对象都具备可抛性。

  1. 一般不可处理的:Error

    特点:是由jvm抛出的严重性问题。

    这种问题发生,一般不针对性处理,直接修改程序。

  2. 可以处理的:Exception

    该体系的特点:子类的后缀名都是用其父类名作为后缀,阅读性很强。

常见异常

异常 说明
IndexOutOfBoundsException 角标越界异常
NullPointerException 空指针异常
ConcurrentModificationException 并发修改异常
ClassCastException 类型转换异常
UnsupportedOperationException 不支持操作异常
NullPointerException 没有元素异常
IllegalArgumentException 非法参数异常
IllegalAccessException 非法的访问异常

Throwable常用方法

方法声明 功能描述
getMessage() 获取异常信息,返回字符串。
toString() 获取异常类名和异常信息,返回字符串。
printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置,返回值void。
printStackTrace(PrintStreams) 通常用该方法将异常内容保存在日志文件中,以便查阅。

示例:

class Demo{
   public static int method(int[] arr, int index){
       if(arr == null){
           throw new NullPointerException("数组的引用不能为空!");
       }
       if(index >= arr.length ){
           throw new ArrayIndexOutOfBoundsException("数组的角标越界:" +
                   index);
       }
       return arr[index];
   }
}
class ExceptionDemo{
   public static void main(String[] args){
       int[] arr = new int[3];
       Demo.method(arr,30);
   }
}

运行结果:

exception

自定义异常

可以自定义出现的问题称为自定义异常。
对于角标为负数的情况,可以用负数角标异常来表示,负数角标这种异常在java中并没有定义过。
那就按照java异常的创建思想,面向对象,将负数角标进行自定义描述,并封装成对象。
这种自定义的问题描述称为自定义异常。

注意事项:

如果让一个类成为异常类,必须要继承异常体系,因为只有成为异常体系的子类才有资格具备可抛性,才可以被两个关键字所操作:throws、throw。

自定义类继承Exception或者其子类,通过构造函数定义异常信息。

示例:

Class DemoException extends Exception

{
    DemoException(Stringmessage)
    {
        super(message);
    }
}

通过throw将自定义异常抛出。

自定义类继承Exception,作为基类

public class HMException extends Exception {

    public HMException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public HMException(String detailMessage, Throwable throwable) {
        super(detailMessage, throwable);
        // TODO Auto-generated constructor stub
    }

    public HMException(String detailMessage) {
        super(detailMessage);
        // TODO Auto-generated constructor stub
    }

    public HMException(Throwable throwable) {
        super(throwable);
        // TODO Auto-generated constructor stub
    }

}

子类

public class HMAException extends HMException {

}

public class HMBException extends HMException {

}

public class HMCException extends HMException {

}

public class HMDException extends HMException {

}

ExceptionHandler异常处理器

public class ExceptionHandler {
    /**
     * 根据不同的HMException给用户具体的提示
     * @param e
     */
    public static void toastByHMException(Context context, HMException e) {
        int errCode = 0;
        // errCode 具体化
        if (e instanceof HMAException) {
            errCode = 1;
        } else if (e instanceof HMBException) {
            errCode = 2;
        } else if (e instanceof HMCException) {
            errCode = 3;

        } else if (e instanceof HMDException) {
            errCode = 4;
        }
        // 根据不同的errcode给用户做提示
        toastByErrCode(context, errCode);
    }

    private static void toastByErrCode(Context context, int errCode) {
        String content = "";
        switch (errCode) {
        case 1:
            content = "程序出现了HMAException";
            break;
        case 2:
            content = "程序出现了HMBException";
            break;
        case 3:
            content = "程序出现了HMCException";
            break;
        case 4:
            content = "程序出现了HMDException";
            break;

        default:
            break;
        }
        Toast.makeText(context, content, 0).show();
    }
}

HMApi

public class HMApi {
    public void method1() throws HMException {
        // 模拟,某一个时刻出现了HMAException
        throw new HMAException();
    }

    public void method2() throws HMException {
        // 模拟,某一个时刻出现了HMBException
        throw new HMBException();
    }

    public void method3() throws HMException {
        // 模拟,某一个时刻出现了HMCException
        throw new HMCException();
    }

    public void method4() throws HMException {
        // 模拟,某一个时刻出现了HMDException
        throw new HMDException();
    }
}

MainActivity.class,使用自定义异常

public class MainActivity extends Activity {

    private HMApi   mApi;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mApi = new HMApi();
    }

    public void method1(View v) {
        try {
            mApi.method1();
        } catch (HMException e) {
            e.printStackTrace();
            ExceptionHandler.toastByHMException(MainActivity.this, e);
        }
    }

    public void method2(View v) {
        try {
            mApi.method2();
        } catch (HMException e) {
            e.printStackTrace();
            ExceptionHandler.toastByHMException(MainActivity.this, e);
        }
    }

    public void method3(View v) {
        try {
            mApi.method3();
        } catch (HMException e) {
            e.printStackTrace();
            ExceptionHandler.toastByHMException(MainActivity.this, e);
        }
    }

    public void method4(View v) {
        try {
            mApi.method4();
        } catch (HMException e) {
            e.printStackTrace();
            ExceptionHandler.toastByHMException(MainActivity.this, e);
        }
    }

}

throws和throw的区别

  1. throws用于标识函数暴露出的异常类,并且可以抛出多个,用逗号分隔。throw用于抛出异常对象

  2. thorws用在函数上,后面跟异常类名。throw用在函数内,后面跟异常对象。

定义功能方法时,需要把出现的问题暴露出来让调用者去处理,那么就通过throws在函数上标识。

在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw把异常对象抛出。

示例:

class FuShuIndexException extends Exception {
    FuShuIndexException() {
    }

    FuShuIndexException(String msg) {
        super(msg);
    }
}

class Demo {
    public static int method(int[] arr, int index) throws FuShuIndexException {
        if (index < 0) {
            throw new FuShuIndexException ("数组的角标是负数啦!");
        }
        return arr[index];
    }
}

class ExceptionDemo {
    public static void main(String[] args) throws FuShuIndexException {
        int[] arr = new int[3];
        Demo.method(arr, -30);
    }
}

运行结果:

exception

异常的分类:

编译时被检测异常

只要是Exception和其子类都是,除了特殊子类RuntimeException体系。这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式。这样的问题都可以针对性的处理。

编译时不检测异常(运行时异常)

就是Exception中的RuntimeException和其子类。这种问题的发生,无法让功能继续,运算无法运行,更多是因为调用的原因导致的或者引发了内部状态的改变导致的。

那么这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序强制停止,让调用者对代码进行调整。

所以自定义异常时,要么继承Exception,要么继承RuntimeException。

示例:

class FuShuIndexException extends RuntimeException {
    FuShuIndexException() {
    }

    FuShuIndexException(String msg) {
        super(msg);
    }
}

class Demo {
    public static int method(int[] arr, int index) {
        // RuntimeException没有必要用throws抛出,并不是必须要处理
        if (index < 0) {
            throw new FuShuIndexException("数组的角标是负数啦!");
        }
        return arr[index];
    }
}

运行结果:

exception

注意事项:

RuntimeException是那些可能在Java虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的RuntimeException的任何子类都无需在throws子句中进行声明。

异常处理的捕捉形式

可以对异常进行针对性处理的方式。具体格式是:

    try{
         //需要被检测异常的代码。
    }
    catch(异常类 变量) //该变量用于接收发生的异常对象
    {
         //处理异常的代码。
    }
    finally{
         //一定会执行的代码;
    }

注意事项:

finally代码块只有一种情况不会被执行,就是在之前执行了System.exit(0)。

处理过程:

try 中检测到异常会将异常对象传递给catch,catch捕获到异常进行处理。
finally 里通常用来关闭资源。比如:数据库资源,IO资源等。

需要注意:try是一个独立的代码块,在其中定义的变量只在该变量块中有效。如果在try以外继续使用,需要在try外建立引用,在try中对其进行初始化。IO,Socket就会遇到。

示例:

class FuShuIndexException extends RuntimeException{
    FuShuIndexException(){}

    FuShuIndexException(String msg){
        super(msg);
    }
}

class Demo{
    public static int method(int[] arr, int index) throws
            NullPointerException,FuShuIndexException{
        if(arr == null)
            throw new NullPointerException("没有任何数组实体");
        if(index < 0){
            throw new FuShuIndexException("数组的角标是负数啦!");
        }
        return arr[index];
    }
}

class ExceptionDemo{
    public static void main(String[] args){
        int[] arr = new int[3];
        try{
            int num = Demo.method(arr,-30);
            System.out.println("num:" + num);
        } catch(NullPointerException e){
            System.out.println(e);
        } catch(FuShuIndexException e){
            System. out.println("message:" + e.getMessage());
            System.out.println("string:" + e);
            e.printStackTrace(); //jvm 默认的异常处理机制就是调用异常对象的这个方法。
            System.out.println("负数角标异常!!!");
        } catch(Exception e){//Exception的catch放在最下面,先处理有针对性的异常
            System.out.println(e);
        }
        System.out.println("over" );
    }
}

运行结果:

exception

异常处理的原则

  1. 函数内容如果抛出需要检测的异常,那么函数上必须要声明。否则,必须在函数内用try/catch捕捉,否则编译失败。

  2. 如果调用到了声明异常的函数,要么try/catch,要么throws,否则编译失败。

  3. 什么时候catch,什么时候throws呢?

    功能内容可以解决,用catch。解决不了,用throws告诉调用者,由调用者解决。

  4. 一个功能如果抛出了多个异常,那么调用时,必须有对应多个catch进行针对性处理。内部有几个需要检测的异常,就抛几个异常,抛出几个,就catch几个。

示例:

 class Demo{
        public int show(int index) throws ArrayIndexOutOfBoundsException{
              if(index < 0)
                    throw new ArrayIndexOutOfBoundsException("越界啦!");
              int[] arr = new int[3];
              return arr[index];
        }
 }

 class ExceptionDemo{
        public static void main(String[] args){
             Demo d = new Demo();
             try{
                   int num = d.show(-3);
                   System.out.println("num = " + num);
              } catch(ArrayIndexOutOfBoundsException e){
                   System.out.println(e.toString());
                   System.exit(0);//退出jvm
              } finally{//通常用于关闭(释放)资源
                   System.out.println("finally");//由于前面执行了System.exit(0);,故不会执行此语句。
              }
             System.out.println("over");
       }
 }

运行结果:

exception

try catch finally 代码块组合特点:

  1. try catch finally
  2. try catch(多个):当没有资源需要释放时,可以不用定义finally。
  3. try finally:异常无法直接catch处理,但是资源必须关闭。

示例:

 void show() throws Exception{
      try{
           //开启资源
           throw new Exception();
       }finally{
           //关闭资源
       }
 }

异常综合案例

 /*
 毕老师用电脑上课。

 问题领域中涉及两个对象。
 毕老师,电脑。

 分析其中的问题。
 比如电脑蓝屏,冒烟等。
 */
 class LanPingException extends Exception{
       LanPingException(String msg){
              super(msg);
       }
 }

 class MaoYanException extends Exception{
       MaoYanException(String msg){
              super(msg);
       }
 }

 class NoPlanException extends Exception{
       NoPlanException(String msg){
              super(msg);
       }
 }

 class Computer{
        private int state = 1;//0 2
        public void run() throws LanPingException,MaoYanException{
              if(state == 1)
                    throw new LanPingException("电脑蓝屏啦!");
              if(state == 2)
                    throw new MaoYanException("电脑冒烟啦!");
              System. out.println("电脑运行");
        }
        public void reset(){
             state = 0;
             System.out.println("电脑重启");
        }
 }

 class Teacher{
        private String name ;
        private Computer comp ;

        Teacher(String name){
              this.name = name;
              comp = new Computer();
        }

        public void prelect() throws NoPlanException{
              try{
                   comp.run();
                   System. out.println(name + "讲课");
              } catch(LanPingException e){
                   System.out.println(e.toString());
                   comp.reset();
                   prelect();
              } catch(MaoYanException e){
                   System. out.println(e.toString());
                   test();
                    //可以对电脑进行维修
                    throw new NoPlanException("课时进度无法完成,原因:" + e.getMessage());
              }
        }
        public void test(){
             System.out.println("大家练习");
        }
 }

 class ExceptionDemo{
        public static void main(String[] args){
             Teacher t = new Teacher("毕老师");
              try{
                   t.prelect();
              } catch(NoPlanException e){
                   System.out.println(e.toString() + "......." );
                   System.out.println("换人");
              }
        }
 }

运行结果:

exception

异常的注意事项

  1. RuntimeException以及其子类如果在函数中被throw抛出,可以不用在函数上声明。
  2. 子类在覆盖父类方法时,父类的方法如果抛出了异常,那么子类的方法只能抛出父类的异常或者该异常的子类。
  3. 如果父类抛出多个异常,那么子类只能抛出父类异常的子集。

简单说:子类覆盖父类只能抛出父类的异常或者子类的子集。如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛,就只能try。

异常常见问题

1. try{ }里有一个return x语句,那么紧跟在这个try后的finally { }里的code会不会被执行,什么时候被执行,在return前还是后?

在return中间执行,return x其实是先执行了返回值计算,并把计算结果的地址保存在一个临时的局部变量中,然后开始执行finally子句,finally执行完毕后,再从先前的临时变量中取得返回地址,返回方法的最终结果。根据这样的处理方式,当我们试图在finally子句中再行改变x的值时,已经不会对方法的返回结果造成影响。

return并不是让函数马上返回,而是return语句执行后,将把返回结果放置进函数栈中,此时函数并不是马上返回,它要执行finally语句后才真正开始返回

2. 运行时异常runtime exception与一般异常checked exception有何异同?

对于检查异常,编译器强制要求我们去try catch,否则编译不通过;运行时异常我们可以不处理。这样的异常由虚拟机接管。出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常

UncaughtExceptionHandler

UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告,我们可以实现UncaughtExceptionHandler类的uncaughtException方法,该方法的参数为发生异常的线程和异常信息,我们可以在该类中自己处理异常,例如上报服务器或者记录异常等,也可以交由系统的异常处理机制去处理。

示例代码:

/**
* UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告
*/
public class CrashHandler implements UncaughtExceptionHandler { 
//    public static final String TAG = "CrashHandler";
public static final String TAG = "liuyou";  
   //系统默认的UncaughtException处理类 
   private Thread.UncaughtExceptionHandler mDefaultHandler;
   //CrashHandler实例
   private static CrashHandler INSTANCE = new CrashHandler();
   //程序的Context对象
   private Context mContext;
   //用来存储设备信息和异常信息
   private Map<String, String> infos = new HashMap<String, String>();
   //用于格式化日期,作为日志文件名的一部分
   private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
   /** 保证只有一个CrashHandler实例 */
   private CrashHandler() {
   }
   /** 获取CrashHandler实例 ,单例模式 */
   public static CrashHandler getInstance() {
       return INSTANCE;
   }
   /**
    * 初始化
    * @param context
*/
   public void init(Context context) {
       mContext = context;
       //获取系统默认的UncaughtException处理器
       mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
       //设置该CrashHandler为程序的默认处理器
       Thread.setDefaultUncaughtExceptionHandler(this);
   }
   /**
    * 当UncaughtException发生时会转入该函数来处理
*/
   @Override
   public void uncaughtException(Thread thread, Throwable ex) {
       if (!handleException(ex) && mDefaultHandler != null) {
           //如果用户没有处理则让系统默认的异常处理器来处理
           mDefaultHandler.uncaughtException(thread, ex);
       } else {
           try {
               Thread.sleep(3000);
           } catch (InterruptedException e) {
               Log.e(TAG, "error : ", e);
           }
           mDefaultHandler.uncaughtException(thread, ex);
           //退出程序
//            android.os.Process.killProcess(android.os.Process.myPid());
//            System.exit(1);
       }
   }
   /**
    * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
    * @param ex
    * @return true:如果处理了该异常信息;否则返回false.
*/
   private boolean handleException(Throwable ex) {
       if (ex == null) {
           return false;
       }
       //使用Toast来显示异常信息
       new Thread() {
           @Override
           public void run() {
               Looper.prepare();
//                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
               Looper.loop();
           }
       }.start();
       //收集设备参数信息 
       collectDeviceInfo(mContext);
       //保存日志文件 
       Log.i("liuyou", "ex:"+ex.toString()+"--"+ex.getLocalizedMessage());
       saveCrashInfo2File(ex);
       return true;
   }
   /**
    * 收集设备参数信息
    * @param ctx
*/
   public void collectDeviceInfo(Context ctx) {
       try {
           PackageManager pm = ctx.getPackageManager();
           PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
           if (pi != null) {
               String versionName = pi.versionName == null ? "null" : pi.versionName;
               String versionCode = pi.versionCode + "";
               infos.put("versionName", versionName);
               infos.put("versionCode", versionCode);
           }
       } catch (NameNotFoundException e) {
           Log.e(TAG, "an error occured when collect package info", e);
       }
       Field[] fields = Build.class.getDeclaredFields();
       for (Field field : fields) {
           try {
               field.setAccessible(true);
               infos.put(field.getName(), field.get(null).toString());
//                Log.d(TAG, field.getName() + " : " + field.get(null));
           } catch (Exception e) {
               Log.e(TAG, "an error occured when collect crash info", e);
           }
       }
   }
   /**
    * 保存错误信息到文件中
    * @param ex
    * @return    返回文件名称,便于将文件传送到服务器
*/
   private String saveCrashInfo2File(Throwable ex) {       
       StringBuffer sb = new StringBuffer();
//        for (Map.Entry<String, String> entry : infos.entrySet()) {
//            String key = entry.getKey();
//            String value = entry.getValue();
//            sb.append(key + "=" + value + "\n");
//        }      
       Writer writer = new StringWriter();
       PrintWriter printWriter = new PrintWriter(writer);
       ex.printStackTrace(printWriter);
       Throwable cause = ex.getCause();
       while (cause != null) {
       Log.i("liuyou", "cause:"+cause.toString()+"--");
           cause.printStackTrace(printWriter);
           cause = cause.getCause();
       }
       printWriter.close();
       String result = writer.toString();
       Log.i("liuyou", "result:"+result);
       sb.append(result);
       try {
           long timestamp = System.currentTimeMillis();
           String time = formatter.format(new Date());
           String fileName = "crash-" + time + "-" + timestamp + ".log";
           if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
               String path = "/sdcard/crash/";
               File dir = new File(path);
               if (!dir.exists()) {
                   dir.mkdirs();
               }
               FileOutputStream fos = new FileOutputStream(path + fileName);
               fos.write(sb.toString().getBytes());
               fos.close();
           }
           return fileName;
       } catch (Exception e) {
           Log.e(TAG, "an error occured while writing file...", e);
       }
       return null;
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容