重学泛型

什么是泛型?

  • Java泛型(generics)是JDK5中引入的一种参数化类型特性

  • Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了
    编译时类型安全检测机制,
    该机制允许程序员在编译时检测到非法的类型
    泛型的本质是参数类型,也就是说所操作的数据类型被指定为一个参数
    泛型不存在于JVM虚拟机

什么是参数化类型?

把类型当参数一样传递

数据类型只能是引用类型(泛型的副作用)

举个例子:

  • List<T>中的”T”称为类型参数
  • List<Person>中的"Person"称为实际类型参数
  • "List<T>"整个成为泛型类型
  • "List<Person>"整个称为参数化的类型ParameterizedType

为什么使用泛型,使用泛型的好处?

  1. 代码更健壮(只要编译期没有警告,那么运行期就不会出现ClassCastException)
//不使用泛型,运行期报错
List list = new ArrayList(); 
list.add("hello");
Integer s = (String) list.get(0);// Causes a ClassCastException to be thrown.
//使用泛型,编译期就检查
List<String> list = new ArrayList(); 
list.add("hello");
Integer s = (String) list.get(0);//编译器就会不通过
  1. 代码更简洁,不用强转
//不使用泛型,需要强转
List list = new ArrayList(); 
list.add("hello");
String s = (String) list.get(0);
//使用泛型,不需要强转
List<String> list = new ArrayList<String>();
list.add("hello"); 
String s = list.get(0); // no cast
  1. 代码更灵活,复用
 // java.util.List中的排序方法sort,只要实现了Comparator接口的都可以使用这个方法
 default void sort(@Nullable Comparator<? super E> c) {
        throw new RuntimeException("Stub!");
    }

Java是如何处理泛型的

  1. 通过运行时获取的类信息是完全一样的。泛型类型被擦除了,擦除后只剩下原始类型,如下面所示的只剩下ArrayList类型。
 ArrayList<String> strings = new ArrayList<>();
 ArrayList<Integer> integers = new ArrayList<>();
 System.out.println(strings.getClass() == integers.getClass());
 //result true

泛型擦除

  • 功能:保证了泛型不在运行时出现
  • 类型消除应用的场合:
    编译器会把泛型类型中所有的类型参数替换为它们的上(下)限,如果没有对类型参数做出限制,那么就替换为Object类型。因此,编译出的字节码仅仅包含了常规类,接口和方法。
    在必要时插入类型转换以保持类型安全。
    生成桥方法以在扩展泛型时保持多态性
  • Bridge Methods 桥方法
    当编译一个扩展参数化类的类,或一个实现了参数化接口的接口时,编译器有可能因此要创建一个合成方法,名为桥方法。它是类型擦除过程中的一部分

用一个简单的例子看一下Java是怎么处理泛型的

  1. 定义一个泛型接口
public interface Box<T> {
    void set(T t);
    T get();
}
  1. 利用javac命令获取字节码文件
public interface Box<T> {
    void set(T var1);

    T get();
}
  1. 利用javap -c命令查看生成的字节码,我们的T变成了Object类型。
public abstract interface test3/Box {
  public abstract set(Ljava/lang/Object;)V
  public abstract get()Ljava/lang/Object;
}

  1. 我们定义一个类去实现这个接口
public class ConditionalBox<T> implements Box<T> {

    private List<T> items = new ArrayList<T>(10);

    public ConditionalBox() {
    }

    @Override
    public void set(T t) {
        items.add(t);
    }

    @Override
    public T get() {
        int index = items.size() - 1;
        if (index >= 0) {
            return items.get(index);
        } else {
            return null;
        }
    }
}

5.用javap -c命令查看生成的字节码。可以看到我们的set和get还是构造方法,T变成了Object类型。

public class test3/ConditionalBox implements test3/Box {

  // compiled from: ConditionalBox.java

  // access flags 0x2
  // signature Ljava/util/List<TT;>;
  // declaration: items extends java.util.List<T>
  private Ljava/util/List; items

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 10 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 8 L1
    ALOAD 0
    NEW java/util/ArrayList
    DUP
    BIPUSH 10
    INVOKESPECIAL java/util/ArrayList.<init> (I)V
    PUTFIELD test3/ConditionalBox.items : Ljava/util/List;
   L2
    LINENUMBER 11 L2
    RETURN
   L3
    LOCALVARIABLE this Ltest3/ConditionalBox; L0 L3 0
    // signature Ltest3/ConditionalBox<TT;>;
    // declaration: this extends test3.ConditionalBox<T>
    MAXSTACK = 4
    MAXLOCALS = 1

  // access flags 0x1
  // signature (TT;)V
  // declaration: void set(T)
  public set(Ljava/lang/Object;)V
   L0
    LINENUMBER 15 L0
    ALOAD 0
    GETFIELD test3/ConditionalBox.items : Ljava/util/List;
    ALOAD 1
    INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z (itf)
    POP
   L1
    LINENUMBER 16 L1
    RETURN
   L2
    LOCALVARIABLE this Ltest3/ConditionalBox; L0 L2 0
    // signature Ltest3/ConditionalBox<TT;>;
    // declaration: this extends test3.ConditionalBox<T>
    LOCALVARIABLE t Ljava/lang/Object; L0 L2 1
    // signature TT;
    // declaration: t extends T
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1
  // signature ()TT;
  // declaration: T get()
  public get()Ljava/lang/Object;
   L0
    LINENUMBER 20 L0
    ALOAD 0
    GETFIELD test3/ConditionalBox.items : Ljava/util/List;
    INVOKEINTERFACE java/util/List.size ()I (itf)
    ICONST_1
    ISUB
    ISTORE 1
   L1
    LINENUMBER 21 L1
    ILOAD 1
    IFLT L2
   L3
    LINENUMBER 22 L3
    ALOAD 0
    GETFIELD test3/ConditionalBox.items : Ljava/util/List;
    ILOAD 1
    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf)
    ARETURN
   L2
    LINENUMBER 24 L2
   FRAME APPEND [I]
    ACONST_NULL
    ARETURN
   L4
    LOCALVARIABLE this Ltest3/ConditionalBox; L0 L4 0
    // signature Ltest3/ConditionalBox<TT;>;
    // declaration: this extends test3.ConditionalBox<T>
    LOCALVARIABLE index I L1 L4 1
    MAXSTACK = 2
    MAXLOCALS = 2
}
  1. IntelligentBox<T extends Comparable<T>>实现Box接口。代码如下
public class IntelligentBox<T extends Comparable<T>> implements Box<T> {

    private List<T> items = new ArrayList<T>(10);

    @Override
    public void set(T t) {
        items.add(t);
        Collections.sort(items);
    }

    @Override
    public T get() {
        int index = items.size() - 1;
        if (index >= 0) {
            return items.get(index);
        } else {
            return null;
        }
    }
}

  1. 用javap -c命令查看IntelligentBox生成的字节码
public class test3/IntelligentBox implements test3/Box {

  // compiled from: IntelligentBox.java

  // access flags 0x2
  // signature Ljava/util/List<TT;>;
  // declaration: items extends java.util.List<T>
  private Ljava/util/List; items

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 9 L1
    ALOAD 0
    NEW java/util/ArrayList
    DUP
    BIPUSH 10
    INVOKESPECIAL java/util/ArrayList.<init> (I)V
    PUTFIELD test3/IntelligentBox.items : Ljava/util/List;
    RETURN
   L2
    LOCALVARIABLE this Ltest3/IntelligentBox; L0 L2 0
    // signature Ltest3/IntelligentBox<TT;>;
    // declaration: this extends test3.IntelligentBox<T>
    MAXSTACK = 4
    MAXLOCALS = 1

  // access flags 0x1
  // signature (TT;)V
  // declaration: void set(T)
  public set(Ljava/lang/Comparable;)V
   L0
    LINENUMBER 13 L0
    ALOAD 0
    GETFIELD test3/IntelligentBox.items : Ljava/util/List;
    ALOAD 1
    INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z (itf)
    POP
   L1
    LINENUMBER 14 L1
    ALOAD 0
    GETFIELD test3/IntelligentBox.items : Ljava/util/List;
    INVOKESTATIC java/util/Collections.sort (Ljava/util/List;)V
   L2
    LINENUMBER 15 L2
    RETURN
   L3
    LOCALVARIABLE this Ltest3/IntelligentBox; L0 L3 0
    // signature Ltest3/IntelligentBox<TT;>;
    // declaration: this extends test3.IntelligentBox<T>
    LOCALVARIABLE t Ljava/lang/Comparable; L0 L3 1
    // signature TT;
    // declaration: t extends T
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1
  // signature ()TT;
  // declaration: T get()
  public get()Ljava/lang/Comparable;
   L0
    LINENUMBER 19 L0
    ALOAD 0
    GETFIELD test3/IntelligentBox.items : Ljava/util/List;
    INVOKEINTERFACE java/util/List.size ()I (itf)
    ICONST_1
    ISUB
    ISTORE 1
   L1
    LINENUMBER 20 L1
    ILOAD 1
    IFLT L2
   L3
    LINENUMBER 21 L3
    ALOAD 0
    GETFIELD test3/IntelligentBox.items : Ljava/util/List;
    ILOAD 1
    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf)
    CHECKCAST java/lang/Comparable
    ARETURN
   L2
    LINENUMBER 23 L2
   FRAME APPEND [I]
    ACONST_NULL
    ARETURN
   L4
    LOCALVARIABLE this Ltest3/IntelligentBox; L0 L4 0
    // signature Ltest3/IntelligentBox<TT;>;
    // declaration: this extends test3.IntelligentBox<T>
    LOCALVARIABLE index I L1 L4 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1041
  public synthetic bridge get()Ljava/lang/Object;
   L0
    LINENUMBER 7 L0
    ALOAD 0
    INVOKEVIRTUAL test3/IntelligentBox.get ()Ljava/lang/Comparable;
    ARETURN
   L1
    LOCALVARIABLE this Ltest3/IntelligentBox; L0 L1 0
    // signature Ltest3/IntelligentBox<TT;>;
    // declaration: this extends test3.IntelligentBox<T>
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1041
  public synthetic bridge set(Ljava/lang/Object;)V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    ALOAD 1
    CHECKCAST java/lang/Comparable
    INVOKEVIRTUAL test3/IntelligentBox.set (Ljava/lang/Comparable;)V
    RETURN
   L1
    LOCALVARIABLE this Ltest3/IntelligentBox; L0 L1 0
    // signature Ltest3/IntelligentBox<TT;>;
    // declaration: this extends test3.IntelligentBox<T>
    MAXSTACK = 2
    MAXLOCALS = 2
}

  • 可以看到,有两处地方进行强制类型转换,分别是get和set方法。
 INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf)
CHECKCAST java/lang/Comparable
CHECKCAST java/lang/Comparable
INVOKEVIRTUAL test3/IntelligentBox.set (Ljava/lang/Comparable;)V
  • 可以看到有两个桥方法
  public synthetic bridge set(Ljava/lang/Object;)V
  public synthetic bridge get()Ljava/lang/Object;
  • 用伪代码来表示IntelligentBox的过程
public class test3/IntelligentBox implements test3/Box {
    public void set(Comparable t) { /* compiled code */ }

    public Comparable get() { /* compiled code */ }
    
    @Overide
    public synthetic bridge get(){
    }
    
    @Overide
    public synthetic bridge set(Object t){
        set((Comparable)t)
    }
}

泛型擦除的残留

看一下Box的字节码文件Box.class和查看生成的字节码

public interface Box<T> {
    void set(T var1);
    T get();
}

  • 疑问:不是类型擦除之后变成Object了吗?怎么这里字节码文件还是T类型?其实这里看到的其实是签名而已,还保留定义的格式,对于分析字节码有好处。并不是真的擦除了,保存在类的常量池中。
/**
 * ParameterizedType
 * 具体的范型类型, 如Map<String, String>
 * 有如下方法:
 *
 * Type getRawType(): 返回承载该泛型信息的对象, 如上面那个Map<String, String>承载范型信息的对象是Map
 * Type[] getActualTypeArguments(): 返回实际泛型类型列表, 如上面那个Map<String, String>实际范型列表中有两个元素, 都是String
 * Type getOwnerType(): 返回是谁的member.(上面那两个最常用)
 */
public class TestType {
    Map<String, String> map;
    //擦除 其实在类常量池里面保留了泛型信息
    public static void main(String[] args) throws Exception {
        Field f = TestType.class.getDeclaredField("map");
        System.out.println(f.getGenericType());                               // java.util.Map<java.lang.String, java.lang.String>
        System.out.println(f.getGenericType() instanceof ParameterizedType);  // true
        ParameterizedType pType = (ParameterizedType) f.getGenericType();
        System.out.println(pType.getRawType());                               // interface java.util.Map
        for (Type type : pType.getActualTypeArguments()) {
            System.out.println(type);                                         // 打印两遍: class java.lang.String
        }
        System.out.println(pType.getOwnerType());                             // null
    }
}

  • java虚拟机规范中为了响应在泛型类中如何获取传入的参数化类型等问题,引入了signature,LocalVariableTypeTable等新的属性来记录泛型信息,所以所谓的泛型类型擦除,仅仅是对方法的code属性中的字节码进行擦除,而原数据中还是保留了泛型信息的,这些信息被保存在class字节码的常量池中,使用了泛型的代码调用处会生成一个signature签名字段,signature指明了这个常量在常量池的地址,这样我们就找到了参数化类型。这样我们也知道 现在就明白了泛型擦除不是擦除全部

总结

  • QUESTION:Java泛型的原理?什么是泛型擦除机制?

  • ANSWER:Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。

  • QUESTION:Java编译器具体是如何擦除泛型的

  • ANSWER:

    1. 检查泛型类型,获取目标类型
    2. 擦除类型变量,并替换为限定类型
      如果泛型类型的类型变量没有限定(<T>),则用Object作为原始类型。如果有限定(<T extends XClass>),则用XClass作为原始类型如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
    3. 在必要时插入类型转换以保持类型安全
    4. 生成桥方法以在扩展时保持多态性

使用泛型以及泛型擦除带来的影响(副作用)

泛型类型变量不能使用基本数据类型

比如没有ArrayList<int>,只有ArrayList<Integer>.当类型擦除后,ArrayList的原始类中的类型变量(T)替换成Object,但Object类型不能存放int值

//error报错,因为擦除后变成了Object,而Object是无法存放int
ArrayList<int> ints = new ArrayList<int>();
ArrayList<Integer> integerArrayList = new ArrayList<Integer>();

不能使用instanceof 运算符

因为擦除后,ArrayList<String>只剩下原始类型,泛型信息String不存在了,所有没法使用instanceof

ArrayList<String> stringArrayList = new ArrayList<String>();
//使用ArrayList<?>可以
if (stringArrayList instanceof ArrayList<?>){
            
}
//因为擦除ArrayList<String>后String丢失了
if (stringArrayList instanceof ArrayList<String>){

}

泛型在静态方法和静态类中的问题

因为泛型类中的泛型参数的实例化是在定义泛型类型对象
(比如ArrayList<Integer>)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,如何确定这个泛型参数是什么

//下面两个会报错,因为泛型参数是要创建对象时确定
public static T a;
public static T test1(T t) {
}

//这里不报错,因为这是一个泛型方法,此T非彼T test2(T t)的T
public static <T> T test2(T t) {
    return t;
}

泛型类型中的方法冲突

因为擦除后两个equals方法变成一样的了

//方法冲突,因为擦除后变一样了
@Override
public boolean equals(T obj) {
    return super.equals(obj);
}

@Override
public boolean equals(Object obj) {
    return super.equals(obj);
}

没法创建泛型实例

因为类型不确定

class Test02 {
   //无法创建一个类型参数的实例。下面会报错
   public static <E> void append(List<E> list) {
      //  E elem = new E();  // compile-time error
      //  list.add(elem);
   }
   //通过反射创建一个参数化类型的实例
   public static <E> void append(List<E> list, Class<E> cls) throws Exception {
       E elem = cls.newInstance();   // OK
       list.add(elem);
   }
}

没有泛型数组

因为数组是协变,擦除后就没法满足数组协变的原则

//        Plate<Apple>[] applePlates = new Plate<Apple>[10];//不允许
//        T[] arr = new T[10];//不允许
       Apple[] apples = new Apple[10];
       Fruit[] fruits = new Fruit[10];
        System.out.println(apples.getClass());
        //class [Lcom.zero.genericsdemo02.demo02.Apple;
        System.out.println(fruits.getClass());
        //class [Lcom.zero.genericsdemo02.demo02.Fruit;
       fruits = apples;
       // fruits里面原本是放什么类型的? Fruit or Apple
        // Apple[]
       fruits[0] = new Banana();//编译通过,运行报ArrayStoreException
        //Fruit是Apple的父类,Fruit[]是Apple[]的父类,这就是数组的协变
        //如果加入泛型后,由于擦除机制,运行时将无法知道数组的类型
        Plate<?>[] plates = new Plate<?>[10];//这是可以的

泛型,继承和子类型

给定两种具体的类型A和B(例如Fruit和Apple),
无论A和B是否相关,
MyClass<A>与MyClass<B>都没半毛钱关系,
它们的公共父对象是Object

泛型PECS原则

  • 如果你只需要从集合中获得类型T , 使用<? extends T>通配符
  • 如果你只需要将类型T放到集合中, 使用<? super T>通配符
  • 如果你既要获取又要放置元素,则不使用任何通配符。例如List<Apple>
  • PECS即 Producer extends Consumer super, 为了便于记忆。
  • 为何要PECS原则?提升了API的灵活性
  • <?> 既不能存也不能取

在泛型编程时,使用部分限定的形参时,<? super T>和<? extends T>的使用场景容易混淆, PECS原则可以帮助我们很好记住它们:提供者(Provider)使用extends,消费者(Consumer) 使用super。通俗地说, Provider指的就是该容器从自己的容器里提供T类型或T的子类型的对象供别人使用; Consumer指的就是该容器把从别处拿到的T类型或T的子类型的对象放到自己的容器。

Kotlin的泛型

  • 使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends。
  • 使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super。
var textViews: List<out TextView>
var textViews: List<in TextView>

声明处的 out 和 in

Kotlin 提供了另外一种写法:可以在声明类的时候,给泛型符号加上 out 关键字,表明泛型参数 T 只会用来输出,在使用的时候就不用额外加 out 了。

class Producer<out T> {
    fun produce(): T {
        ...
    }
}

val producer: Producer<TextView> = Producer<Button>() // 👈 这里不写 out 也不会报错
val producer: Producer<out TextView> = Producer<Button>() // 👈 out 可以但没必要

where关键字

Java 中声明类或接口的时候,可以使用 extends 来设置边界,将泛型类型参数限制为某个类型的子集,同时这个边界是可以设置多个,用 & 符号连接:

//T 的类型必须同时是 B 和 C 的子类型
class A<T extends B & C>{ 
}

在Kotlin中

//T 的类型必须同时是 B 和 C 的子类型
class A<T> where T : B, T : C

reified关键字

inline fun <reified T> printIfTypeMatch(item: Any) {
    if (item is T) { // 👈 这里就不会在提示错误了
        println(item)
    }
}

Kotlin 泛型与 Java 泛型不一致的地方

  1. Java 里的数组是支持协变的,而 Kotlin 中的数组 Array 不支持协变。

这是因为在 Kotlin 中数组是用 Array 类来表示的,这个 Array 类使用泛型就和集合类一样,所以不支持协变。

  1. Java 中的 List 接口不支持协变,而 Kotlin 中的 List 接口支持协变。

Java 中的 List 不支持协变,原因在上文已经讲过了,需要使用泛型通配符来解决。

在 Kotlin 中,实际上 MutableList 接口才相当于 Java 的 List。Kotlin 中的 List 接口实现了只读操作,没有写操作,所以不会有类型安全上的问题,自然可以支持协变。

面试常问

  1. Array中可以用泛型吗?

答:不能

  1. 泛型类型引用传递问题

问:你可以把List<String>传递给一个接受List<Object>参数的方法吗?

ArrayList<String> arrayList1=new ArrayList<Object>();
ArrayList<Object> arrayList1=new ArrayList<String>();

答:不能。没有半毛钱关系

  1. Java中List<?>和List<Object>之间的区别是什么?
    答:
  • List :完全没有类型限制和赋值限定。
  • List<Object> :看似用法与List一样,但是在接受其他泛型赋值时会出现编译错误。
  • List<?>:是一个泛型,在没有赋值前,表示可以接受任何类型的集合赋值,但赋值之后不能往里面随便添加元素,但可以remove和clear,并非immutable(不可变)集合。List<?>一般作为参数来接收外部集合,或者返回一个具体元素类型的集合,也称为通配符集合。
  1. 什么是泛型中的限定通配符和非限定通配符 ?

答:

  • 限定通配符<? extends T> <? super T>
  • 非限定通配符<?>

5.泛型类型变量不能是基本数据类型

//error
ArrayList<double> arr1 = new ArrayList<>();
ArrayList<Double> arr2 = new ArrayList<>();
  1. 运行时类型查询
ArrayList<String> arrayList=new ArrayList<String>();
if( arrayList instanceof ArrayList<String>) //擦除
if( arrayList instanceof ArrayList<?>)  

  1. Java 的泛型本身是不支持协变和逆变的
  • 可以使用泛型通配符 ? extends 来使泛型支持协变,但是「只能读取不能修改」,这里的修改仅指对泛型集合添加元素,如果是 remove(int index) 以及 clear 当然是可以的。
  • 可以使用泛型通配符 ? super 来使泛型支持逆变,但是「只能修改不能读取」,这里说的不能读取是指不能按照泛型类型读取,你如果按照 Object 读出来再强转当然也是可以的。
  1. Java中数组是协变的
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容