0%

前言

近段时间在使用easyopen时,发现定义的请求体与实际参数不符时会出现参数无法正常传递的现象,于是就把easyopen的源码 clone 下来研究了一波。

easyopen 测试版本

1
1.16.6.1

场景复现

  • 请求体定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
public class GoodsParam {

@ApiDocField(description = "商品名称", required = true, example = "iphoneX", name = "goods")
private String goodsName;

public String getGoodsName() {
return goodsName;
}

public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
}
  • 接口定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ApiService
@ApiDoc("商品模块")
public class GoodsApi {

private static final Logger log = LoggerFactory.getLogger(GoodsApi.class);

@Api(name = "goods.get")
@ApiDocMethod(description = "获取商品")
public String getGoods(GoodsParam param) {
log.info("incoming param: {}", JSONObject.toJSONString(param));
return param.getGoodsName();
}

}
上述代码使用[官方示例](https://gitee.com/durcframework/easyopen/tree/master/easyopen-demo/easyopen-server-normal)修改。

因为我是要复现出错的场景,所以我对请求体属性 goodsName 进行修改,并修改原有接口逻辑,使得接口打印请求体数据并直接返回请求体 goodsName 属性。

启动项目,请求接口 [goods.get](http://localhost:8081/api/doc#201),日志与结果截图如下

接口并未如文档定义一般拿到请求体的参数,场景复现。

寻找原因

代码的好处是,0 即是 0,1 就是 1,如果 0 变成 1,那就是你写的 bug 或者你有意为之,这个场景必定有代码作为支撑。

  因为研读源码的经验较浅,所以一开始从框架打印日志入手。

 程序在启动时总会打这几行日志,所以就全局搜索了一波,定位到 src\main\java\com\gitee\easyopen\register\ApiRegister.java 的 doWith 方法中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
checkTransactionalAnnotation(method);
ReflectionUtils.makeAccessible(method);
Api api = AnnotationUtils.findAnnotation(method, Api.class);
boolean ignoreSign = api.ignoreSign() ? true : this.apiServiceAnno.ignoreSign();
boolean ignoreValidate = api.ignoreValidate() ? true : this.apiServiceAnno.ignoreValidate();
boolean isWrapResult = this.apiServiceAnno.wrapResult() ? api.wrapResult() : false;
ApiDefinition apiDefinition = new ApiDefinition();
//apiDefinition setter....
Parameter[] parameters = method.getParameters();
Class<?> paramClass = null;
if (parameters != null && parameters.length > 0) {
Parameter parameter = parameters[0];
paramClass = parameter.getType();
boolean isNumberOrStringType = FieldUtil.isNumberOrStringType(paramClass);
apiDefinition.setSingleParameter(isNumberOrStringType);
apiDefinition.setMethodArguClass(paramClass);
if (isNumberOrStringType) {
SingleParameterContext.add(handler, method, parameter, api);
}
}
logger.debug("注册接口name={},version={},method={} {}({})", api.name(), api.version(),
method.getReturnType().getName(), method.getName(), paramClass == null ? "" : paramClass.getName());
try {
DefinitionHolder.addApiDefinition(apiDefinition);
apiConfig.getApiRegistEvent().onSuccess(apiDefinition);
} catch (DuplicateApiNameException e) {
logger.error(e.getMessage(), e);
System.exit(0);
}
apiCount++;
}

public static void addApiDefinition(ApiDefinition apiDefinition) throws DuplicateApiNameException {
String key = getKey(apiDefinition);
boolean hasApi = apiDefinitionMap.containsKey(key);
if (hasApi) {
throw new DuplicateApiNameException("重复申明接口,name:" + apiDefinition.getName() + " ,version:"+ apiDefinition.getVersion() + ",method:" + apiDefinition.getMethod().getName());
}
apiDefinitionMap.put(key, apiDefinition);
}
阅读代码发现,这一段代码只负责定义接口的解析并形成一个 String to ApiDefinition 的 Map,并没有请求体参数转换逻辑,所以这不是目的地。

但是这么大段代码只形成一个 Map,这个 Map 必定有其作用。于是寻找调用这个 Map 的 getter 方法,最后定位到 src\main\java\com\gitee\easyopen\register\ApiInvoker.java 的 doInvoke 方法中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
protected Object doInvoke(ApiParam param, HttpServletRequest request, HttpServletResponse response) throws Throwable {
ApiDefinition apiDefinition = this.getApiDefinition(param);
ApiContext.setApiMeta(apiDefinition);
if (!apiDefinition.isIgnoreJWT()) {
this.initJwtInfo(request, param);
}
// 方法参数
Object methodArgu = null;
// 返回结果
Object invokeResult = null;
Validator validator = ApiContext.getApiConfig().getValidator();
param.setIgnoreSign(apiDefinition.isIgnoreSign());
param.setIgnoreValidate(apiDefinition.isIgnoreValidate());
// 验证操作,这里有负责验证签名参数
validator.validate(param);
// 业务参数json格式
String busiJsonData = ApiContext.getApiConfig().getDataDecoder().decode(param);
// 业务参数Class
Class<?> arguClass = apiDefinition.getMethodArguClass();
boolean isSingleParameter = apiDefinition.isSingleParameter();
Object singleParamProxy = null;
int interceptorIndex = 0;
try {
// 将参数绑定到业务方法参数上,业务方法参数可以定义的类型:JSONObject,Map<String,Object>,String,业务参数类
if (arguClass != null) {
if(arguClass == JSONObject.class) {
methodArgu = JSON.parseObject(busiJsonData);
} else if(arguClass == Map.class) {
methodArgu = new HashMap<String,Object>(JSON.parseObject(busiJsonData));
} else if(isSingleParameter) {
SingleParameterContext.SingleParameterContextValue value = SingleParameterContext.get(param.fatchName(), param.fatchVersion());
if (value != null) {
JSONObject jsonObj = JSON.parseObject(busiJsonData);
methodArgu = jsonObj.getObject(value.getParamName(), arguClass);
singleParamProxy = jsonObj.toJavaObject(value.getWrapClass());
}
} else {
methodArgu = JSON.parseObject(busiJsonData, arguClass);
}
this.bindUploadFile(methodArgu);
}
// 拦截器
ApiInterceptor[] interceptors = ApiContext.getApiConfig().getInterceptors();
if(interceptors == null) {
interceptors = EMPTY_INTERCEPTOR_ARRAY;
}
//1. 调用preHandle
for (int i = 0; i < interceptors.length; i++) {
ApiInterceptor interceptor = interceptors[i];
if (interceptor.match(apiDefinition) && !interceptor.preHandle(request, response, apiDefinition.getHandler(),methodArgu)) {
//1.1、失败时触发afterCompletion的调用
triggerAfterCompletion(apiDefinition, interceptorIndex, request, response, methodArgu, null,null);
return null;
}
//1.2、记录当前预处理成功的索引
interceptorIndex = i;
}
// 验证业务参数JSR-303
this.validateBizArgu(validator, methodArgu, singleParamProxy);
/* *** 调用业务方法,被@Api标记的方法 ***/
MethodCaller methodCaller = apiDefinition.getMethodCaller();
if (methodCaller != null) {
invokeResult = methodCaller.call(new ApiInvocation(apiDefinition, methodArgu));
} else {
invokeResult = Callers.call(apiDefinition, methodArgu);
}
//3、调用postHandle,业务方法调用后处理(逆序)
for (int i = interceptors.length - 1; i >= 0; i--) {
ApiInterceptor interceptor = interceptors[i];
if(interceptor.match(apiDefinition)) {
interceptor.postHandle(request, response, apiDefinition.getHandler(), methodArgu, invokeResult);
}
}
if(invokeResult == null) {
invokeResult = EMPTY_OBJECT;
}
// 最终返回的对象
Object finalReturn = this.wrapResult(apiDefinition, invokeResult);
setMsg(finalReturn);
//4、触发整个请求处理完毕回调方法afterCompletion
triggerAfterCompletion(apiDefinition, interceptorIndex, request, response, methodArgu, finalReturn, null);
return finalReturn;
} catch (Exception e) {
this.triggerAfterCompletion(apiDefinition, interceptorIndex, request, response, methodArgu, invokeResult, e);
throw e;
}
}
 阅读代码发现,此段代码主要做了四个事情:解析网络请求数据,从  apiDefinitionMap 获取接口对应配置,反射调用对应接口,包装调用接口返回结果并返回。参数解析必定在此段代码中处理!
 
 通过断点 debug 查看数据得知,参数解析集中于以下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (arguClass != null) {
if (arguClass == JSONObject.class) {
methodArgu = JSON.parseObject(busiJsonData);
} else if (arguClass == Map.class) {
methodArgu = new HashMap(JSON.parseObject(busiJsonData));
} else if (isSingleParameter) {
SingleParameterContextValue value = SingleParameterContext.get(apiDefinition.getMethod());
if (value != null) {
JSONObject jsonObj = JSON.parseObject(busiJsonData);
methodArgu = jsonObj.getObject(value.getParamName(), arguClass);
singleParamProxy = jsonObj.toJavaObject(value.getWrapClass());
}
} else {
methodArgu = JSON.parseObject(busiJsonData, arguClass);
}

this.bindUploadFile(methodArgu);
}

step into断点得知,easyopen 依赖 fastjson 的序列化机制进行参数的序列化,原因得知:easyopen 使用 fastjson 序列化参数成为参数类获取参数,所以当 @ApiDocField 的属性 name 与参数类对应的属性名不一致时,属性值获取失败。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于 cartoon的博客
转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/source-code-unscramble/easyopen参数无法正常传递现象解析

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;众所周知,Linux与Win之间的区别是Linux需要大量的命令行操作,而有些配置文件也是在命令行中输入的。

&nbsp;&nbsp;&nbsp;&nbsp;这些操作就需要编辑器的帮助了。

&nbsp;&nbsp;&nbsp;&nbsp;我接触Ubuntu的时间不长,碰到过三个编辑器

1
2
3
gedit  //好像是叫这个名字
vi //Linux和Unix上最基本的文本编辑器
vim //比vi更好用的文本编辑器
  • 安装vim

&nbsp;&nbsp;&nbsp;&nbsp;终端内输入

1
sudo apt-get install vim
  • 常用命令
    1
    2
    3
    4
    5
    6
    vim fileName  //vim打开文件,文件需带有绝对路径
    i //编辑文件
    esc //退出编辑状态
    :q //不保存直接退出
    :wq //保存并退出
    ! //覆盖原文件

&nbsp;&nbsp;&nbsp;&nbsp;后三个命令必须与esc配合使用,可以组合使用

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间把自己电脑(win)、虚拟机(Ubuntu)以及阿里云(ubuntu)都重置了一遍,其中本机以及阿里云都有用到MySQL,不想之后找教程找的那么麻烦。所以就自己总结一遍,一次性把轮子造好。

&nbsp;&nbsp;&nbsp;&nbsp;环境

1
2
Win10 1803 Home
Ubuntu 16.04.3

Windows环境下安装

  1. 下载MySQL压缩包
    1
    https://dev.mysql.com/downloads/mysql/
  2. 解压到本地目录,并添加一个配置文件,命名为my.ini,内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [mysqld] 
    # 设置为自己MYSQL的安装目录
    # 注意是双斜杆,我就入过坑
    basedir=D:\\MySQL
    # 设置为MYSQL的数据目录
    # 文件夹需手动创建
    datadir=D:\\MySQL\\data
    default-storage-engine=INNODB
    character-set-server=utf8
    [mysql]
    # 设置mysql客户端默认字符集
    default-character-set=utf8
    [client]
    # 设置mysql客户端连接服务端时默认使用的端口
    port=3306
    default-character-set=utf8
  3. 以管理员权限进入MySQL的bin目录
    1
    2
    d: //MySQL的安装目录盘符
    cd MySQL/bin
  4. 安装MySQL服务
    1
    2
    mysqld --install
    //出现Service successfully installed即安装成功
  5. 初始化日志文件
    1
    mysqld --initialize --console
    &nbsp;&nbsp;&nbsp;&nbsp;记住mysql反馈语句中最后一条:a temporary password is….为第八步出现的问题作铺垫。

  1. 配置环境变量
    1
    2
    ;D:\MySQL\bin;
    //分号分隔前属性,bin目录的路径换成自己电脑的路径
  2. 启动服务
    1
    net start mysql
  3. 登录服务器
    1
    mysql -uroot -p
    &nbsp;&nbsp;&nbsp;&nbsp;输入密码提示Access denied for user ‘root‘@’localhost’ (using password: YES)

    &nbsp;&nbsp;&nbsp;&nbsp;解决方法及后续步骤:解决MySQL 8.0登录Access denied for user ‘root‘@’localhost’ (using password: YES)

Ubuntu环境下安装

  1. 打开终端

  2. 输入命令

    1
    2
    3
    sudo apt-get install mysql-server
    sudo apt-get isntall mysql-client
    sudo apt-get install libmysqlclient-dev

    &nbsp;&nbsp;&nbsp;&nbsp;在安装过程中会让你输入密码以及确认密码,不要忘掉,那是mysql的root用户密码

  3. 检查是否安装成功

    1
    sudo netstat -tap | grep mysql

    &nbsp;&nbsp;&nbsp;&nbsp;若出现截图内的语句即说明安装成功

&nbsp;&nbsp;&nbsp;&nbsp;存在的一点点问题

&nbsp;&nbsp;&nbsp;&nbsp;原本我是想在ubuntu下像在win一样通过配置文件进行安装的,但是试了很久都不行最后再次重置了服务器。

&nbsp;&nbsp;&nbsp;&nbsp;如果你们有什么方法是可以的可以在评论告诉我。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间在ubuntu中搭建jdk并在jdk的基础上建设Oracle数据库,遇到一个问题:常规GUI不能把文件移到usr目录下,提示permission denied。

&nbsp;&nbsp;&nbsp;&nbsp;取了一个巧,利用终端获取管理员权限后移动文件。

&nbsp;&nbsp;&nbsp;&nbsp;方法:终端输入

1
sudo nautilus

&nbsp;&nbsp;&nbsp;&nbsp;然后新创建的窗口默认带有管理员权限,问题就解决了。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间一直在学java三大框架,遇到了一个问题:eclipse中jsp默认编码格式不是UTF-8,导致页面显示中文出现乱码,每次单独修改过于麻烦。

&nbsp;&nbsp;&nbsp;&nbsp;解决方案:window–Preferences–Web–JSP File–在encoding的下拉列表选择UTF-8。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;这个坑我遇到了两次了,所以就写下来以防自己再遇到。

&nbsp;&nbsp;&nbsp;&nbsp;第一次没看conscole,所以就不知道具体原因是什么。

&nbsp;&nbsp;&nbsp;&nbsp;第二次留意了conscole

1
2
3
Djava.endorsed.dirs=D:\Tomcat 9.0\endorsed is not supported. 

Endorsed standards and standalone APIsin modular form will be supported via the concept of upgradeable modules.

&nbsp;&nbsp;&nbsp;&nbsp;第一次整整弄了一个下午,最后通过降级jdk版本到1.8解决掉。
&nbsp;&nbsp;&nbsp;&nbsp;第二次就在想每一次新建工作空间都像第一次这样过于麻烦。因为命令行启动tomcat是没有问题的,所以猜测应该是eclipse的问题。解决方法:

  1. eclipse–Window–Preferences–Install/update–Available Software Sites,将The Eclipse Web Tools Platform的location后缀的neon改成oxygen。
  2. help–check for update,等待重启。
  3. Run–Run Configurations–Apache Tomcat–Tomcat v9.0 Server–Arguments–VM arguments,将参数最后的-Djava.endorsed.dirs=”D:\java\tomcat\apache-tomcat-9.0.10\endorsed删除掉,Apply然后再Start Tomcat就好了。

&nbsp;&nbsp;&nbsp;&nbsp;第二次的解决方法部分来自https://bbs.csdn.net/topics/392277991中的12楼的层主Ice2017,谢谢了。

&nbsp;&nbsp;&nbsp;&nbsp;为什么说部分呢,因为我是在更新WTP之后再修改设置的(因为练习的项目涉及到JSTL库,所以之前更新了),Ice是修改设置之后再更新WTP的。

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;上一篇文章中我在集合元素的遍历中已经有涉及到Iterator的普遍使用方法,但是并没有对此进行解释。
&nbsp;&nbsp;&nbsp;&nbsp;其实,Iterator来源于java.util包,也是属于Java集合框架中的一份子,不同于Collection(存放单一数据)和Map(存放具有映射关系的数据),Iterator主要用于集合元素的迭代输出,所以它的对象又被称为迭代器。

&nbsp;&nbsp;&nbsp;&nbsp;Iterator的方法包括:

成员方法 作用
boolean hasNext(); 判断迭代器是否还有未遍历的元素
E next(); 返回迭代器中下一未遍历元素
void remove(); 移除迭代器上一遍历的元素
void forEachRemaining(Consumer action) 以特定的Lambda表达式遍历元素

注:void forEachRemaining(Consumer action)为Java 8新增的默认方法。

&nbsp;&nbsp;&nbsp;&nbsp;下面是Iterator的实例化以及各方法的使用方法

  • Iterator的实例化

    1
    Iterator it=c5.iterator();

    &nbsp;&nbsp;&nbsp;&nbsp;Iterator本身不存在容纳对象的能力,它的对象必须依附于Collection对象。
    同时,一个Iterator对象只能使用一次,复用会导致java.util.ConcurrentModificationException。我还没搞懂为什么会这样,应该是关于设计模式的内容。

  • boolean hasNext();以及next();

    1
    2
    3
    while(it.hasNext()) {
    System.out.println(it.next());
    }
    1
    输出:5
  • void remove();

    1
    2
    3
    4
    while(it.hasNext()) {
    System.out.println(it.next());
    it.remove();
    }
    1
    输出:5

    &nbsp;&nbsp;&nbsp;&nbsp;remove方法要在next方法之后调用,次序调换会导致java.lang.IllegalStateException

  • void forEachRemaining(Consumer action);

    1
    2
    Iterator it=c5.iterator();
    it.forEachRemaining(obj->System.out.println(obj));
    1
    输出:5

    &nbsp;&nbsp;&nbsp;&nbsp;通过特定的Lambda表达式格式输出元素。

&nbsp;&nbsp;&nbsp;&nbsp; Iterator接口就写到这里了。通常我都是用它作为测试Collection对象的,好处就是不用通过循环遍历,代码量会少一点。但是正式使用我还是会使用循环遍历Collection对象,虽则代码量多,但是可以省掉一个对象的内存空间,能省一点是一点,哈哈!

&nbsp;&nbsp;&nbsp;&nbsp;如果你还想了解关于java集合的内容,欢迎点击JAVA集合概述

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;在概述里面也说过:Collection是java集合两大接口之一,旗下有三大子接口:Set(元素不能重复,且无序)、Queue、List(元素可重复,且有序)。

&nbsp;&nbsp;&nbsp;&nbsp;Collection来源于java.util包,主要方法包括:

Iterator的方法包括:

成员方法 作用
boolean hasNext(); 将传入的Object对象添加到容器中,添加后方法返回true
boolean addAll(Collection c) 将传入的集合c中的所有对象添加到容器中,添加后方法返回true
void clear() 清空集合,集合长度变为0
boolean contains(Object o) 检查集合是否存在对象o,若存在返回true
boolean containsAll(Collection c) 检查集合是否存在集合c的所有对象,若存在返回true
boolean isEmpty() 返回集合的size是否为0
Iterator iterator() 返回Iterator对象
boolean remove(Object o) 删除集合中第一个符合条件的元素,若集合存在对象o,删除并返回true
boolean removeAll(Collection c) 删除集合中所有与集合c重合的元素,若删除后对象进行了改变返回true
boolean retainAll(Collection c) 删除集合中所有与集合c不重合的元素,若删除后对象进行了改变返回true
int size() 返回集合元素的个数
Object[] toArray() 把集合转变成数组,集合的元素变成对应的数组元素

&nbsp;&nbsp;&nbsp;&nbsp;下面是Collection的实例化以及各成员方法的使用方法

  • 实例化
1
2
3
4
5
6
Collection c1=new TreeSet();   
Collection c2=new HashSet();
Collection c3=new LinkedHashSet();
Collection c4=new ArrayDeque();
Collection c5=new ArrayList();
Collection c6=new LinkedList();
  • boolean isEmpty();
1
2
boolean isEmpty=c5.isEmpty();
System.out.print(isEmpty?"c5为空":"c5不为空");
1
输出: c5为空

&nbsp;&nbsp;&nbsp;&nbsp;因为c5在此之前并没有添加任何元素,所以为空。

  • Iterator iterator();
1
2
3
4
Iterator it5=c5.iterator();
while(it5.hasNext()) {
System.out.println("集合5元素:"+it5.next());
}
1
输出:集合5元素:5

&nbsp;&nbsp;&nbsp;&nbsp;注:每个Iterator对象只能使用一次, 复用会导致java.util.ConcurrentModificationException。

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,新建了一个局部变量it5保存c5的迭代器,再通过迭代器的成员方法hasNext判断是否存在下一元素,若true,输出此元素。

&nbsp;&nbsp;&nbsp;&nbsp;java8为Collection的遍历新增了一个来源于Collection的父接口iterable的方法:forEach(Consumer action)。

1
c5.forEach(obj->System.out.println("集合元素为"+obj));
1
输出:集合元素为5
  • boolean add(Object o);
1
2
3
4
5
c5.add(5);
Iterator it=c5.iterator();
while(it.hasNext()) {
System.out.println("集合元素:"+it.next());
}
1
输出:集合元素:5

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,c5被添加了一个元素5,所以经过迭代器输出只能得到5。

  • boolean addAll(Collection c);
1
2
3
4
5
c6.addAll(c5);
Iterator it6=c6.iterator();
while(it6.hasNext()) {
System.out.println("集合6元素:"+it6.next());
}
1
输出:集合6元素:5

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,c5中的所有元素(5)被整体添加到c6中,所以c6迭代输出5。

  • void clear();
1
2
3
4
5
c6.clear();
Iterator it6=c6.iterator();
while(it6.hasNext()) {
System.out.println("集合6元素:"+it6.next());
}
1
无输出

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,c6被清空,所以迭代输出并没有结果。

  • boolean contains(Object o);
1
2
3
4
boolean isContainsFive=c5.contains(5);
boolean isContainsSix=c5.contains(6);
System.out.println("c5是否存在元素5:"+isContainsFive);
System.out.println("c5是否存在元素6:"+isContainsSix);
1
2
输出:c5是否存在元素5:true
c5是否存在元素6:false

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,isContainsFive的值取决于c5是否含有元素5(isContainsSix亦是如此)。而c5只有一个元素5,所以isContainsFive的值为true,isContainsSix的值为false。

  • boolean containsAll(Collection c);
1
2
3
4
c4.addAll(c5);
System.out.println("c5是否包括c6的全部元素:"+c5.containsAll(c4));
c4.add(3);
System.out.println("c5是否包括c6的全部元素:"+c5.containsAll(c4));
1
2
输出:c5是否包括c6的全部元素:true
c5是否包括c6的全部元素:false

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,首先将c5的所有元素(5)添加到c4中,所以第一个输出语句必定为true。但是在第一个语句后c4添加了一个新的元素3,所以第二个输出语句为false。

  • boolean remove(Object o);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    c5.add(6);
    Iterator it5=c5.iterator();
    while(it5.hasNext()) {
    System.out.println("集合元素:"+it5.next());
    }
    c5.remove(6);
    Iterator aIt5=c5.iterator();
    while(aIt5.hasNext()) {
    System.out.println("集合5元素:"+aIt5.next());
    }
    1
    2
    3
    输出:集合5元素:5
    集合5元素:6
    集合5元素:5

    &nbsp;&nbsp;&nbsp;&nbsp;在本代码中,c5首先添加了一个新元素6,迭代输出得到5、6。然后调用c5的成员方法remove,迭代输出得到5。

  • boolean removeAll(Collection c);

1
2
3
4
5
c5.removeAll(c4);
Iterator aIt5=c5.iterator();
while(aIt5.hasNext()) {
System.out.println("集合5元素:"+aIt5.next());
}
1
无输出

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,c5移除掉c4中的所有元素(3、5),可以理解为移除掉两个集合的交集,所以迭代输出没有结果。

  • boolean retainAll(Collection c);
1
2
3
4
5
6
c5.add(5);
c4.retainAll(c5);
Iterator it4=c4.iterator();
while(it4.hasNext()) {
System.out.println("集合4元素:"+it4.next());
}
1
输出:集合4元素:5

&nbsp;&nbsp;&nbsp;&nbsp;注:因为在方法9中集合5已经变成空集合,所以在此方法中将5添加到集合5中以做演示。
&nbsp;&nbsp;&nbsp;&nbsp;在本方法中,c5添加了一个元素5,然后调用c4的成员方法retainAll删除掉不属于c5的所有元素,迭代输出5。

  • int size();
1
2
int size=c4.size();
System.out.println("集合4的元素个数为:"+size);
1
输出:集合4的元素个数为:1

&nbsp;&nbsp;&nbsp;&nbsp;在本代码中,新建一个局部变量size保存c4的成员方法size返回的值,输出得到1(因为c4只有一个元素5)。

  • Object[] toArray();
1
System.out.println(c4.toArray());
1
输出:[Ljava.lang.Object;@71e7a66b

&nbsp;&nbsp;&nbsp;&nbsp;作为对比,我尝试调用父类Object的String toString()方法,结果得到了[5],与直接输出对象的结果是一致的。

&nbsp;&nbsp;&nbsp;&nbsp;注:若遍历集合的同时改变集合,将引发ConcurrentModificationException异常。

&nbsp;&nbsp;&nbsp;&nbsp;Collection接口就写到这里了,看起来简单,但是某些成员方法需要查看源码才知道如何去用,基本上写完这篇博文,我对Collection已经有了比较深入的了解,忽然觉得写作跟编程是相辅相成的。

&nbsp;&nbsp;&nbsp;&nbsp;一点题外话:这是我第一次系统地写博文,懂得了一个道理:世界上没有简单的事。写的不好请多多见谅。之后要狠狠提高一下我的写作水平,之前写公众号的文笔都不知道丢在哪里了。
&nbsp;&nbsp;&nbsp;&nbsp;如果你还想了解关于java集合的内容,欢迎点击JAVA集合概述

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间在学习java集合,原本也没想着写笔记或者博客的。但是慢慢看下去之后发现java集合的内容繁多,但是各集合之间具有很大的共通性。所以就动了写博客的念头。
&nbsp;&nbsp;&nbsp;&nbsp;在安卓开发的时候已经有在用集合了,具体是用ArrayList向上转型到list对象。

&nbsp;&nbsp;&nbsp;&nbsp;我觉得集合比数组方便的地方主要有4个:

  • 所有操作都是基于对象进行,符合java的核心思想。
  • 不用担心数据项的增加导致的数组溢出的情况。
  • 在插入、删除等等操作的时间开销较少。
  • Map接口的子接口以及实现类可以存放具有映射关系的数据。

&nbsp;&nbsp;&nbsp;&nbsp;缺点也不是没有,可能是我学的不够深,暂时只发现了一个:Collection接口的子接口以及实现类大部分都是基于一个长度为16的数组实现,所以当插入的数据项少于16时可能会造成资源的浪费。

&nbsp;&nbsp;&nbsp;&nbsp;java集合主要由两个接口派生而来:Collection以及Map。

&nbsp;&nbsp;&nbsp;&nbsp;下面是两个接口的概述图

&nbsp;&nbsp;&nbsp;&nbsp;图的来源为李刚老师的《疯狂java讲义》,如有侵权请联系删除。

&nbsp;&nbsp;&nbsp;&nbsp;其中Collection接口的Vector实现类是不建议使用的,一是年代有点久远,二是已经有更先进更好用的ArrayList可以替换使用。

&nbsp;&nbsp;&nbsp;&nbsp;下面是Collection、Map接口以及各实现类、子接口的用法(持续更新)。

&nbsp;&nbsp;&nbsp;&nbsp;Collection接口

&nbsp;&nbsp;&nbsp;&nbsp;Iterator接口

&nbsp;&nbsp;&nbsp;&nbsp;本文首发于cartoon的博客

&nbsp;&nbsp;&nbsp;&nbsp;转载请注明出处:https://cartoonyu.github.io/cartoon-blog

&nbsp;&nbsp;&nbsp;&nbsp;近段时间有一个需求:在线获取图片并且显示在界面上。因为在线获取图片必定会有延迟,如果在主线程中获取会有很大几率造成主线程阻塞。解决的办法是开辟子线程进行网络访问。新的问题又出现了:android的UI更新必须在主线程中完成。但是子线程的值脱离线程会无效,造成java.lang.NullPointerException。所以思考了一下决定用android自带的异步处理机制:Handler,进行消息的回调。

  1. 线程的开辟。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void downloadImage() {
new Thread(new Runnable() {
@Override
public void run() {
final Bitmap bitmap= ServerImage.downloadFile(); //在线获取图片
Looper.prepare();
Message message=new Message();
message.what=0;
message.obj=bitmap;
handler.sendMessage(message);
Looper.loop();
}
}).start();

}
  1. 自定义Handler。
1
2
3
4
5
6
7
8
9
10
11
class ImageHandler extends Handler{
@Override
public void handleMessage(Message msg){
//编写自定义逻辑
switch (msg.what){
case 0:{

}
}
}
}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;其中switch中的case与调用handler时定义的msg.what要严格一致。

  1. handler对象的定义

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;定义的时候注意一点:要在子线程外定义。

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我尝试过在子线程内定义,结果在把handler里面得到的图片赋值给UI组件时发生java.lang.NullPointerException。不清楚什么原因,但是确实挺有趣的,有时间会探求一下里面的原理。