先扯两句
转眼之间距离上次发类、抽象类、接口已经过去了不知道多少个日夜了,原以为在博客中已经暗示的不能再明显了。可结果呢,几篇博客发出去了,别说找女朋友了,就连赞都没得到几个,在孤寂的夜自己的眼泪还是得自己擦啊。还能咋办,接着写呗,万一啥时候被我真命天女看到了呢(虽然看技术博客的真命天女还是让我瑟瑟发抖)!
之前说了,之所以写《java基础系列》并不是因为自己闲着没事来复习知识点来了,单纯的是因为想要写《设计模式》,自以为是的想要将一些内容解释出来让不太清楚的与我一样的菜鸟们了解,所以专门“封装”的博客,那么设计模式中,个人感觉泛型还是挺重要的,就写着玩了,为了女朋友!!!
咳咳,就想着女朋友了,差点忘了鸣谢这位老哥Java3y 的泛型就这么简单,大家如果想了解泛型正经一些的介绍可以去看看。
正文
之前为了找女朋友,专门学习了一下怎么做清蒸多宝鱼,既然留不住妹子的心,就只能迂回一下,想方设法留住妹子的胃了胖菜鸟说接口。
@Test
public void girlfriendDate() {
judge(new Lina(), new Tina(), new Shawn());
}
/**
* 判断女孩是都满足做我女朋友的条件
*
* @param girls 相亲的女孩们
*/
private void judge(IGirl... girls) {
for (IGirl girl : girls) {
if (girl instanceof ILikeSteamedTurbot) {
System.out.println(MessageFormat.format("{0}喜欢吃清蒸多宝鱼,我要追她"
, girl.getClass().getSimpleName()));
} else if (girl instanceof IDislikeSteamedTurbot) {
System.out.println(MessageFormat.format("{0}讨厌吃清蒸多宝鱼,没机会了"
, girl.getClass().getSimpleName()));
}
}
}
虽然到现在还是没找到妹子,但是为了不让自己的努力白费,因此找女朋友的时候,必须添加一条要求,那就是必须喜欢吃清蒸多宝鱼。而之前的验证方式中,无论是否满足的条件女孩,我都要抽出时间按个去相亲,即便相亲第一句就问对方喜欢不喜欢吃清蒸多宝鱼,如果不喜欢就转身出门,还是要白白浪费掉大量的时间,有这大好青春,我舒舒服服的睡个回笼觉不好吗?
因此,如果能够在约见相亲对象之前就能对于“是否喜欢吃清蒸多宝鱼”这一项做一个预判断,不喜欢吃的直接就不用去相亲岂不是美滋滋!说干就干!
@Test
public void girlfriendDate() {
IGirl lina = new Lina();
IGirl tina = new Tina();
IGirl shawn = new Shawn();
judge(new Lina());
judge(new Tina());
judge(new Shawn());
}
/**
* 判断女孩是都满足做我女朋友的条件
*
* @param girls 相亲的女孩
*/
private void judge(SuitableGirl girl) {
if (girl instanceof ILikeSteamedTurbot) {
System.out.println(MessageFormat.format("{0}喜欢吃清蒸多宝鱼,我要追她"
, girl.getClass().getSimpleName()));
} else if (girl instanceof IDislikeSteamedTurbot) {
System.out.println(MessageFormat.format("{0}讨厌吃清蒸多宝鱼,没机会了"
, girl.getClass().getSimpleName()));
}
}
上面就是调整好的代码,当天,这么看基本是看不出来什么的,我们来看一下IDE的截图:
可以看到预判断给出的提示信息为:“Lina类不能被应用到judge方法中”。而Shawn却没有报错,这是为什么呢?
/*
* Lina
*
* @author 半寿翁
* @date 2020-4-6
*/
public class Lina implements IDislikeSteamedTurbot {
}
/*
* Shawn
*
* @author 半寿翁
* @date 2020-4-6
*/
public class Shawn implements ILikeSteamedTurbot {
}
可以看到,这里Shawn有实现前面泛型规定的“SuitableGirl extends ILikeSteamedTurbot”限制条件,而Lina却没有实现ILikeSteamedTurbot接口,所以自然会报错了。而这个验证的条件,正是使用的泛型。
泛型不是 一个T
别打人啊!虽然我这里使用的是SuitableGirl指代泛型,不是传统博客中使用的“ T ”,或者是通配符“ ? ”,但可没有规定只有T才能指代泛型的,这里最好是依据实际的应用环境,大家能给出一个更加易于理解的名字,不然整改代码架构中满满的都是泛型的时候,看到这些“ T ”,在遇到几个不爱写注解的主,你会哭的!好吧,非要举一个官方泛型不用 T 的例子的话,请看:
显而易见,大家常用的 Map就是不用 T 做泛型的典范,而这里的“ K ”就是“key”,而“V”则是“value”,所以官方都这么用了,你还在等什么!
什么?为什么Map会有两个泛型?当然人家充钱了呗!
信你个鬼!!!
诶诶诶,别打脸,别打脸!我说,其实Java类中并没有限制每一个封装的类或者接口中具体使用的泛型的数量,用多少完全看你的心情,只要你以后能看得懂。
啊啊啊,别打脸啊!
啥是泛型
定义:
把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
设计原则:
只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常
还记得之前我们写的女朋友验证的方法吗?忘了?没关系,我再贴出来一份不就好了吗(顺便凑字数,虽然博客不按字数给打赏 T-T)
/**
* 判断女孩是都满足做我女朋友的条件
*
* @param girls 相亲的女孩
*/
private void judge(SuitableGirl girl) {
if (girl instanceof ILikeSteamedTurbot) {
System.out.println(MessageFormat.format("{0}喜欢吃清蒸多宝鱼,我要追她"
, girl.getClass().getSimpleName()));
} else if (girl instanceof IDislikeSteamedTurbot) {
System.out.println(MessageFormat.format("{0}讨厌吃清蒸多宝鱼,没机会了"
, girl.getClass().getSimpleName()));
}
}
前面贴出来了女生判断方法调用时的截图,那么这个验证方法中截图后会有什么好玩的事发生吗?
两张图显示的警告信息分别为:
- “girl instanceof ILikeSteamedTurbot”的判断条件是多余的,可以不做验证
- “girl instanceof IDislikeSteamedTurbot”的验证条件永远都不会满足
然后连续两次Alt + Enter
PS:针对我用的Android Studio,不知道这套快捷键是否适用于所有的idea IDE,如果是用的eclipse快捷键体系的这里就只能自力更生了。
好吧,系统就是严谨,我一直都没发现自己竟然忘记了判空...好吧,这段掐掉别播,我们接着说正题。可以看到,经过了泛型的验证后,我们的代码简化了好多,这也就是泛型的好处之一。
为什么要使用泛型
前面说了这么多,都是直接上来就使用了泛型,并没有明确说明我们为什么要使用泛型。其实从泛型的定义就可以看出泛型的最重要的好处了:
- 代码更加简洁【不用强制转换】
- 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
- 可读性和稳定性【在编写集合的时候,就限定了类型】
代码更加简洁【不用强制转换】
为了验证这一点,让我们来看看没有泛型的世界:
在实际的开发中,我们难免会遇到这样的工具类,他们的返回类型会随着传入参数的不同而改变,为了通用性,我们势必无法做到重载(返回值不同并不算重载),因此,就只能使用最简单的玩法,那就是使用最基本的父类Object来接收,而随之而来的,就是我们需要在获取结果的时候进行强转。
而使用了泛型会有什么不同呢?
显而易见,我们限制了返回值与传入的参数都必须是泛型“VALUE_TYPE”后,当传入的是String后,IDE会自动帮我们进行验证,要求返回值也是String,因此第一行直接使用String接收返回结果的,就是正确的。
而当我们的传入参数变成String后,可以看到IDE就开始提示我们这么写是错误的了,接收方(result2)是String,而我们方法返回的是个Integer,那反过来呢,如果把接收和传参的类型换一下会怎么样?
可以看到,提示的接收方(result3)是int,而方法返回值是String,同样会报错,也就是说只要传参与接收方不是相同的VALUE_TYPE,就会提示报错。这样我们既省略了强转的步骤,也能避免类型不同导致的异常(这个下一条会进行分析)
PS:这里需要额外解释说明两点
1. 前面提到过,虽然我们看到的大多数博客泛型使用的都是 T,但是并不是只有 T 可以指代泛型,我这里就是为了方便这个方法被理解,而使用了“VALUE_TYPE”,也就是“值的类型”,因此大家在使用中,千万别被万能的 T 坑了,反正我个人如果接手了一个泛型全是 T 的项目,绝对会哭死的
2. 有前面的IED提示我们可以发现,第一张截图中异常行是输入的Integer,result是String,IDE提示我们的是“Change variable ‘result2’ type to ‘Integer’”(请将变量result2的类型改为Integer);而第二张截图中异常行是输入的String,result是int,IDE提示我们的是“Change variable ‘result3’ type to ‘String’”(请将变量result2的类型改为String)。显而易见,他们有一个相同点,就是都是以传入参数为基准来提示我们如何修改的。而我们在实际编程的时候,需求是千奇百怪的,绝对不可能每次都是返回值这里出了问题,所以一定要具体问题具体分析,而不要图个便利,直接使用了IDE提供的便捷调整方案。
程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
同样,我们还是先看看没有泛型的世界:
可以看到我们成功使用addAll方法将一个String的集合添加到了Integer集合中,从而导致了我们取Integer值时出现了类转换异常(ClassCastException)。而再看看,我们为String集合添加上泛型之后呢?
显而易见,IDE会直接提示你,人家要的是一个Integer集合,不能应用String集合,这也就是为什么前面的女朋友验证类中能够进行“女孩是否喜欢清蒸多宝鱼”的验证。并且,验证后在judge方法中的执行操作可以得以简化的原因。
可读性和稳定性【在编写集合的时候,就限定了类型】
这条其实是基于前面的两条说的内容,例如集合的例子,我们已经明确了自己要使用的是String的集合(List
泛型的应用环境
前面说了那么多还是偏理论性质的,那具体我们的泛型应该怎么用呢?
泛型方法
比如我实在靠自己找不到女朋友了,就只能去相亲,相亲就得找人给我介绍,我就得提要求啊:
public void wanted(GirlFriend gf){
}
interface Girl{
}
别问我为什么用的“public”,能自产自销女朋友的就不用相亲了!!!
而介绍人一看,那就赶快给介绍吧:
public GirlFriend introduce(GirlFriend gf){
return gf;
}
interface Girl{
}
或者是介绍人也找人帮忙介绍的:
public GirlFriend introduce(){
GfDelegate delegate = new GfDelegate<>();
// 省略代理找女朋友的步骤
return delegate.introduce();
}
interface Girl {
}
如上这三种情况都是泛型方法,也就是说泛型是在方法中进行设置的“
/**
* 判断数组的长度
*
* @param array 索要获取长度的数组
* @return 该数组的长度
*/
public static - int getArrayLength(ITEM[] array) {
if (array == null) {
return -99;
} else {
return array.length;
}
}
泛型类(接口)
前面说了泛型方法,可是其中却有这么一段代码:
GfDelegate delegate = new GfDelegate<>();
其实现是这样的:
class GfDelegate {
private GirlFriend girlFriend;
private LookForSingleGirl lookForOne;
public void setLookForOne(LookForSingleGirl lookForOne) {
this.lookForOne = lookForOne;
}
public GirlFriend introduce() {
if (null == girlFriend) {
girlFriend = lookForOne.lookForOne();
}
return girlFriend;
}
}
interface LookForSingleGirl {
GirlFriend lookForOne();
}
GfDelegate就是泛型类,而LookForSingleGirl则是代理接口,两者的区别就在于一个是类,一个是接口而已,除此之外,没有太大的差别,而我们需要做的就是如何利用他们来搞事情。
对于泛型类与泛型接口的应用,其实主要遵从的还是类和接口的应用场景,只是添加了一个对于特定参数类型的限制而已,比如说,通用工具类封装和通用父类的封装。
通用工具类封装——网络请求
一般而言我们在使用网络请求的时候,接收的结构都是这样的:
{
"code":"0",
"data":{
},
"message":"请求成功"
}
这个时候就可以封装通用的网络请求接收Bean(或者pojo或者其他的,看你们公司怎么要求了,也可以看看这篇文章,明确这哥几个都是干什么的:POJO,JAVABEAN,Entity区别)
我做的就是封装一个结果接收的类,然后封装一个接收结果回调的接口:
/**
* @author 半寿翁
*/
public class ResultBean implements Parcelable {
/**
* code : 0
* message : 请求成功
*/
private String code;
private String message;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
public interface NetRequestCallBack{
public abstract void success(RESULT_BEAN baseBean);
}
通用父类的封装——刚刚找女朋友的代理
class MotherInLawDelegate extends GfDelegate{
@Override
public Girl introduce() {
return new Girl() {};
}
}
如果我找女朋友的代理正好找到了我未来岳母,然后她把自己的女儿直接嫁给我了,那样就是已经很明确了,不需要从外部寻找对应的单身女孩了,这个时候,就可以直接明确对象就是Girl,然后返回的时候,直接返回她构造的女儿就可以了(咳咳,肯定不是现构造,大家领会精神就可以啊)。
PS:这里new Girl() {}后面有个大括号是因为Girl是接口,实现接口或者抽象类的时候需要实现其中的抽象方法,虽然这里没有抽象方法,但是需要保留{}这个结构
通配符
前面说的那么多,都是找女朋友的事,到这里我们意淫一下,找到女朋友,结婚,生了个孩子,孩子上学要带饭(好吧,现在的年轻人都是外面买着吃,大家就领会精神吧)
@Test
public void packageFoodTest() {
packageFood(new Box<>(new Fish()));
packageFood(new Box<>(new Chicken()));
}
interface Food {
}
class Fish implements Food {
}
class Chicken implements Food {
}
/**
* 打包盒
*/
class Box {
private F f;
public Box(F f) {
this.f = f;
}
public void printFoodName() {
System.out.println(f.getClass().getSimpleName() + "打包好了");
}
}
private void packageFood(Box> box) {
box.printFoodName();
}
可以看到,我们的打包盒是使用的泛型,按照正常的使用,我们的packageFood只能提前设定好我们想要打包的是什么,比如鸡肉或者是鱼肉,但是无奈,小孩太贪嘴,那就得两样都给他(她)带着。而我们基本的泛型对于这种情况就只能束手无策了。
而我们封装的packageFood方法中却有了这么一个东西,让不可能变成了可能,那就是“ ? ”,它存在的意思就是通配符,那就是来者不拒,只要你敢给,我就敢收,并且能够保证正常运行不报错。同样也是用来方便我们在使用泛型时候的一些适配问题,就比如前面说到测量数组的长度,那么我们如果测量集合的长度呢:
/**
* 判断集合的长度
*
* @param list 索要获取长度的集合
* @return 该集合的长度
*/
public static int getListSize(List> list) {
if (list == null) {
return -99;
} else {
return list.size();
}
}
这样添加了通配符以后,任何泛型的集合传入,就都可以测量出size了,而不用担心报错或者异常的情况发生。
PS:
- 如果上面的方法,一不小心忘记了输入>,只是在传参处写了List,运行的时候发现了,抱着忐忑的心情等着程序崩溃,但是很遗憾,你会看到程序正常执行了,而且关键我写的时候竟然也没有异常警告,气不气人。作为一个菜鸟,暂时还不知道有没有>究竟有什么区别,强行解释的话,就只能引用了说这样写不优雅了。
/**
* 判断集合的长度
*
* @param list 索要获取长度的集合
* @return 该集合的长度
*/
public static int getListSize(List list) {
if (list == null) {
return -99;
} else {
return list.size();
}
}
- 这条原本是想加到上一条结尾的,但是奈何本身写的就已经很长了,这里就别再凑热闹了。使用“ ? ”通配符需要注意是(不添加泛型同样需要注意),在这个方法里面,可以对执行属性的查看操作和删除操作,但是尽量不要做编辑或者删除操作,因为在通配符的作用下,我们的泛型是无法进行严格的限制的,这个时候进行增加编辑很可能会添加错误的类型,而IDE没有泛型验证,也不会报错,为程序留下隐患
- 通配符的使用对象必须是对泛型类或者是泛型接口进行操作的时候,是无法直接通过泛型方法去定义,也无法在创建泛型类和泛型接口的时候使用的。
泛型通配符上限
这个通配符的上限的意思是从继承的角度出发的,大家都知道父类和子类的关系吧,那就是子类是继承自父类的。所以从这个角度来说,父类就是上,而子类就是下。
既然是从继承的角度说明,那就显而易见了,我们之前写的打包食物的部分,如果我们在食物的基础上,再创建一个子类——Meat:
可以看到当我们设置了上限Meat后,继承自Meat的fish是符合条件的,可以正常调用。而由于egg还是继承自Food,并不是Meat的子类,因此不符合上限的要求,因此会报错。
在开发中,如果A是B的父类,B是C的父类,而某个方法是在B类中创建并可被继承的时候,我们就可以通过这种设置上限的方式,来实现约束,或者是没有必要查过深的层级的时候,减少资源消耗的时候,可以做上限限制。
泛型通配符下限
既然提到了上限,那么相对的,自然也会有下限限制,其使用的关键词就是super,例子如下:
可以看到Meat自然不用说了,Food是Meat的父类,因此这里都可以正常调用,Fish是Meat的子类,Egg更是跟Meat一点关系都没有(叔伯关系就别来捣乱了),所以看到报错。
PS: 之所以将Food和Meat的创建提到上面进行,而不是直接在Box的构造方法中直接new,是因为Food与Meat是接口,实例化的时候需要实现里面的抽象方法,即便没有也要保留结构(好像前面说过),并不会影响到结果,但是如果直接放到构造方法中,确实报错的内容与下面的Fish和Egg的一样,这个我这里没有找到对应的解释,如果有哪位大神知道欢迎一起讨论一下**
使用环境的话,我这里能想到的就是对于父类的方法,我们总会有重写的时候,如果哪个子类将父类中的方法重写到父类都不认识它了,比如删掉了super,这个时候我们就需要通过设置通配符下限的方式,限制到这个重写了方法的子类前的所有父类可以调用,避免异常。
例如:A是B的父类,B是C的父类,C将B中的父类复写到与原来完全相反的效果了(当然,规范是不允许的,但是规矩是死的人是活的,谁知道能干出来什么事),这个时候再使用这类通用方法的时候,就需要限制成 super B>。
通配符区间(同时设置上限和下限)——无法实现(个人的几种尝试均没有实现,如果的大家发现可以实现的方法,可以一起分享一下)
直接写
首先最容易想到的,那就是直接写到一起嘛:
错误提示是“意外绑定”,就是IDE都没想到的意思。那既然这种方式行不通,我们换种方法嵌套的方式呢?
方法嵌套
- 先验证下限,再验证上限
可以看到父类->子类的顺序是:Food->Meat->Fish->TurbotFish,packageFood的限制是设置的下限(传入的参数必须是TurbotFish的父类或TurbotFish自身),而packageFood2的限制是设置的上限(传入的参数必须是Meat的子类或Meat自身),从上面的父类到子类的关系中很显然可以看到,有符合条件的类的。但是验证的时候根本就没有考虑这个,而是验证下限了后,上限就是不行,别问我什么道理,我就是道理!
- 先验证上限,再验证下限
好吧,我承认自己就是为了凑字数,不说了,直接上图吧,都是泪啊!
泛型擦除
前面说的内容基本就是足够泛型的使用了,后面的这部分其实原本我也是不知道的,不过在部门培训中讲了一遍后,有个同事让我讲讲泛型擦除,很显然我被无情的挂在了讲台上,任由冷冷的冰雨在脸上胡乱的拍。
其实对于泛型擦除,我现在能想到的也就是了解概念,在正常工作中,除非专门为了写bug,不太有机会用到,但是毕竟我也是个菜鸟,这里就不多哔哔了,反正多学点东西总是没有坏处的。
前面说过了,我就是个小菜鸟,对于泛型擦除的理解还是比较浅显的,这里给大家推荐frank909大神的Java 泛型,你了解类型擦除吗?,有较深刻的分析。我这里只提取三点用来避坑的(也防止大神博客万一不小心404了,还有地方查看)
泛型擦除的证明
List l1 = new ArrayList();
List l2 = new ArrayList();
System.out.println(l1.getClass() == l2.getClass());
感官上,一个是String的集合,一个是Integer的集合,很显然,两个Class应该是不同的,但是其结果竟然是true
都不用运行,IDE就直接告诉我们答案了!!!
泛型擦除后变成了什么?
说到擦除,擦除后肯定不能直接就没有了啊,毕竟泛型不仅仅是用来做验证,很可能有一些私有变量就是泛型,不能直接把这些变量擦没了。
通过反射可以得出,泛型擦除后,会变成他的上限,如果没有设置上限,则会变成Object:
@Test
public void packageFoodTest() {
Box box = new Box<>(new Fish());
Field[] fs = box.getClass().getDeclaredFields();
for (Field f : fs) {
System.out.println("Field name " + f.getName() + " type:" + f.getType().getName());
}
}
interface Food {
}
interface Meat extends Food {
}
class Fish implements Meat {
}
class Box {
private F food;
public Box(F f) {
this.food = f;
}
public void printFoodName() {
System.out.println(food.getClass().getSimpleName() + "打包好了");
}
}
可以看到,我们约束了
@Test
public void packageFoodTest() {
Box box = new Box<>(new Fish());
Field[] fs = box.getClass().getDeclaredFields();
for (Field f : fs) {
System.out.println("Field name " + f.getName() + " type:" + f.getType().getName());
}
}
interface Food {
}
interface Meat extends Food {
}
class Fish implements Meat {
}
class Box {
private F food;
public Box(F f) {
this.food = f;
}
public void printFoodName() {
System.out.println(food.getClass().getSimpleName() + "打包好了");
}
}
果然,将Box类的泛型换成
泛型擦除了还有限制作用吗?
这里厚着脸皮套用一下frank909大神的代码,可以看到,我们的List泛型是Integer,很显然正常通过add写入“test”这一项的时候肯定会报错,但是用反射直达泛型被擦除成为Object之后操作呢(List的泛型是没有设置上限的)
@Test
public void packageFoodTest() {
List ls = new ArrayList<>();
try {
Method method = ls.getClass().getDeclaredMethod("add", Object.class);
method.invoke(ls, "test");
method.invoke(ls, 42.9f);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (Object o : ls) {
System.out.println(o);
}
}
好吧,结果也很显然,反射确实成功的规避了泛型的验证,将String格式的“test”写入了泛型为Integer的集合中。这个例子除了提示我们java确实会将泛型擦除外,也提示了我们在写程序时需要注意的事,那就是慎用反射,尤其在有泛型的时候,尽量避免通过反射直接向泛型类中写入数据,不然会有悲剧发生的。
List ls = new ArrayList<>();
try {
Method method = ls.getClass().getDeclaredMethod("add", Object.class);
method.invoke(ls, "test");
method.invoke(ls, 42.9f);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int sum = 0;
for (Integer integer : ls) {
sum += integer;
}
System.out.println(sum);
由于反射插入错误类型导致的崩溃
鸣谢(素材来自网络,如有疑问请联系博主)
- 女朋友:图片取自Pixabay的Free-Photos
- 充钱:图片取自中国首富换人啦!然而不到半天又被他夺回!看完评论我就放心了…
- 糟老头子坏的很:图片取自你这个糟老头子坏得很表情包 v1.0
- 贱贱的笑:图片取自 欢乐喜剧人
- 我做错了什么:图片取自微信鼻青脸肿恶搞表情包
- 有奇怪的东西混进去了:图片取自有哪些[混进去一个奇怪的东西]的图片?
- 搞事情:图片取自《爱情公寓》又双叒叕回来搞事情了,没错这就是标题
- 听说有人要跟我讲道理:图片取自收入姿势摆不对,月入十万也心累
- 冷冷的冰雨在脸上胡乱的拍:图片取自社会人的消暑秘笈拯救你的小确丧!