唐宋八大家之一欧阳修在《卖油翁》中写道:
翁取一葫芦置于地,以钱覆其口,徐以杓酌油沥之,自钱孔入,而钱不湿。因曰:“我亦无他,唯手熟尔。”
编写代码的”老司机”也是如此,”老司机”之所以被称为”老司机”,原因也是”无他,唯手熟尔”。编码过程中踩过的坑多了,获得的编码经验也就多了,总结的编码技巧也就更多了。总结的编码技巧多了,凡事又能够举一反三,编码的速度自然就上来了。笔者从数据结构的角度,整理了一些 java 编程技巧,以供大家学习参考。
使用hasht判断主键是否存在
hasht 实现 t 接口,由哈希表(实际上是 hashmap )实现,但不保证 t 的迭代顺序,并允许使用 元素。hasht 的时间复杂度跟 hashmap 一致,如果没有哈希冲突则时间复杂度为 o(1) ,如果存在哈希冲突则时间复杂度不超过 o(n) 。所以,在日常编码中,可以使用 hasht 判断主键是否存在。
案例:给定一个字符串(不一定全为字母),请返回第一个重复出现的字符。
/** 查找第一个重复字符 */
public static char findfirstrepeatedchar(string string) {
// 检查空字符串if (objects.is(string) || string.impty) {
return ;
}// 查找重复字符char chararray = string.tochararray;
t chart = new hasht<>(chararray.length);for (char ch : chararray) {
if (chart.contains(ch)) {
return ch;
}chart.add(ch);}// 默认返回为空return ;
}
其中,由于 t 的 add 函数有个特性——如果添加的元素已经再集合中存在,则返回 fal 。可以简化代码为:
if (!chart.add(ch)) {
return ch;
}
使用hashmap存取键值映射关系
简单来说,hashmap 由数组和链表组成的,数组是 hashmap 的主体,链表则是主要为了解决哈希冲突而存在的。如果定位到的数组位置不含链表,那么查找、添加等操作很快,仅需一次寻址即可,其时间复杂度为 o(1) ;如果定位到的数组包含链表,对于添加操作,其时间复杂度为 o(n) ——首先遍历链表,存在即覆盖,不存在则新增;对于查找操作来讲,仍需要遍历链表,然后通过key对象的 equals 方法逐一对比查找。从性能上考虑, hashmap 中的链表出现越少,即哈希冲突越少,性能也就越好。所以,在日常编码中,可以使用 hashmap 存取键值映射关系。
案例:给定菜单记录列表,每条菜单记录中包含父菜单标识(根菜单的父菜单标识为 ),构建出整个菜单树。
/** 菜单do类 */
@tter
@getter
@tostring
public static class menudo {
/** 菜单标识 */
private long id;
/** 菜单父标识 */
private long parentid;
/** 菜单名称 */
private string name;
/** 菜单链接 */
private string url;
}/** 菜单vo类 */
@tter
@getter
@tostring
public static class menuvo {
/** 菜单标识 */
private long id;
/** 菜单名称 */
private string name;
/** 菜单链接 */
private string url;
/** 子菜单列表 */
private list<menuvo> childlist;
}/** 构建菜单树函数 */
public static list<menuvo> buildmenutree(list<menudo> menulist) {
// 检查列表为空
if (collectionutils.impty(menulist)) {
return collections.emptylist;
}
// 依次处理菜单
int menusize = menulist.size;
list<menuvo> rootlist = new arraylist<>(menusize);
map<long, menuvo> menumap = new hashmap<>(menusize);
for (menudo menudo : menulist) {
// 赋值菜单对象
long menuid = menudo.getid;
menuvo menu = menumap.get(menuid);
if (objects.is(menu)) {
menu = new menuvo;
menu.tchildlist(new arraylist<>);
menumap.put(menuid, menu);
}
menu.tid(menudo.getid);
menu.tname(menudo.getname);
menu.turl(menudo.geturl);
// 根据父标识处理
long parentid = menudo.getparentid;
if (objects.non(parentid)) {
// 构建父菜单对象
menuvo parentmenu = menumap.get(parentid);
if (objects.is(parentmenu)) {
parentmenu = new menuvo;
parentmenu.tid(parentid);
parentmenu.tchildlist(new arraylist<>);
menumap.put(parentid, parentmenu);
}
// 添加子菜单对象
parentmenu.getchildlist.add(menu);
} el {
// 添加根菜单对象
rootlist.add(menu);
}
}
// 返回根菜单列表
return rootlist;
}
使用 threadlocal 存储线程专有对象
threadlocal 提供了线程专有对象,可以在整个线程生命周期中随时取用,极大地方便了一些逻辑的实现。
常见的 threadlocal 用法主要有两种:
1、保存线程上下文对象,避免多层级参数传递;
2、保存非线程安全对象,避免多线程并发调用。
保存线程上下文对象,避免多层级参数传递
这里,以 pagehelper 插件的源代码中的分页参数设置与使用为例说明。
设置分页参数代码:
/** 分页方法类 */
public abstract class pagemethod {
/** 本地分页 */
protected static final threadlocal<page> local_page = new threadlocal<page>;
/** 设置分页参数 */
protected static void tlocalpage(page page) {
local_page.t(page);
}/** 获取分页参数 */
public static <t> page<t> getlocalpage {
return local_page.get;
}/** 开始分页 */
public static <e> page<e> startpage(int pagenum, int pagesize, boolean count, boolean reasonable, boolean pagesizezero) {
page<e> page = new page<e>(pagenum, pagesize, count);
page.treasonable(reasonable);page.tpagesizezero(pagesizezero);page<e> oldpage = getlocalpage;if (oldpage != && oldpage.isorderbyonly) {
page.torderby(oldpage.getorderby);}tlocalpage(page);return page;
}}
使用分页参数代码:
/** 虚辅助方言类 */
public abstract class abstracthelperdialect extends abstractdialect implements constant {
/** 获取本地分页 */
public <t> page<t> getlocalpage {
return pagehelper.getlocalpage;
}/** 获取分页sql */
@override
public string getpagesql(mappedstatement ms, boundsql boundsql, object parameterobject, rowbounds rowbounds, cachekey pagekey) {
string sql = boundsql.getsql;
page page = getlocalpage;string orderby = page.getorderby;
if (stringutil.isnotempty(orderby)) {
pagekey.update(orderby);sql = orderbyparr.convertoorderbysql(sql, orderby);}if (page.isorderbyonly) {
return sql;
}return getpagesql(sql, page, pagekey);
}...}
使用分页插件代码:
/** 查询用户函数 */
public pageinfo<urdo> queryur(urquery urquery, int pagenum, int pagesize) {
pagehelper.startpage(pagenum, pagesize);list<urdo> urlist = urdao.queryur(urquery);pageinfo<urdo> pageinfo = new pageinfo<>(urlist);
return pageinfo;
}
如果要把分页参数通过函数参数逐级传给查询语句,除非修改 mybatis 相关接口函数,否则是不可能实现的。
保存非线程安全对象,避免多线程并发调用
在写日期格式化工具函数时,首先想到的写法如下:
/** 日期模式 */
private static final string date_pattern = "yyyy-mm-dd";
/** 格式化日期函数 */
public static string formatdate(date date) {
return new simpledateformat(date_pattern).format(date);
}
其中,每次调用都要初始化 dateformat 导致性能较低,把 dateformat 定义成常量后的写法如下:
/** 日期格式 */
private static final dateformat date_format = new simpledateformat("yyyy-mm-dd");
/** 格式化日期函数 */
public static string formatdate(date date) {
return date_format.format(date);
}
由于 simpledateformat 是非线程安全的,当多线程同时调用 formatdate 函数时,会导致返回结果与预期不一致。如果采用 threadlocal 定义线程专有对象,优化后的代码如下:
/** 本地日期格式 */
private static final threadlocal<dateformat> local_date_format = new threadlocal<dateformat> {
@override
protected dateformat initialvalue {
return new simpledateformat("yyyy-mm-dd");
}};/** 格式化日期函数 */
public static string formatdate(date date) {
return local_date_format.get.format(date);
}
这是在没有线程安全的日期格式化工具类之前的实现方法。在 jdk8 以后,建议使用 datetimeformatter 代替 simpledateformat ,因为 simpledateformat 是线程不安全的,而 datetimeformatter 是线程安全的。当然,也可以采用第三方提供的线程安全日期格式化函数,比如 apache 的 dateformatutils 工具类。
注意:threadlocal 有一定的内存泄露的风险,尽量在业务代码结束前调用 remove 函数进行数据清除。
使用 pair 实现成对结果的返回
在 c/c++ 语言中, pair (对)是将两个数据类型组成一个数据类型的容器,比如 std::pair 。
pair 主要有两种用途:
1、把 key 和 value 放在一起成对处理,主要用于 map 中返回名值对,比如 议论文800字作文高中map 中的 entry 类;
2、当一个函数需要返回两个结果时,可以使用 pair 来避免定义过多的数据模型类。
第一种用途比较常见,这里主要说明第二种用途。
定义模型类实现成对结果的返回
函数实现代码:
/** 点和距离类 */
@tter
@getter
@tostring
@allargsconstructor
public static class pointanddistance {
/** 点 */
private point point;
/** 距离 */
private double distance;
}/** 获取最近点和距离 */
public static pointanddistance getnearestpointanddistance(point point, point[] points) {
// 检查点数组为空
if (arrayutils.impty(points)) {
return ;
}
// 获取最近点和距离
point nearestpoint = points[0];
double nearestdistance = getdistance(point, points[0]);
for (int i = 1; i < points.length; i++) {
double distance = getdistance(point, point[i]);
if (distance < nearestdistance) {
nearestdistance = distance;
nearestpoint = point[i];
}
}
// 返回最近点和距离
return new pointanddistance(nearestpoint, nearestdistance);
}
函数使用案例:
point point = ...;
point points = ...;pointanddistance pointanddistance = getnearestpointanddistance(point, points);if (objects.non(pointanddistance)) {
point point = pointanddistance.getpoint;double distance = pointanddistance.getdistance;
...}
使用 pair 类实现成对结果的返回
在 jdk 中,没有提供原生的 pair 数据结构,也可以使用 map::entry 代替。不过, apache 的 commons-lang3 包中的 pair 类更为好用,下面便以 pair 类进行举例说明。
函数实现代码:
/** 获取最近点和距离 */
public static pair<point, double> getnearestpointanddistance(point point, point[] points) {
// 检查点数组为空
if美术生高考分数线 (arrayutils.impty(points)) {
return ;
}
// 获取最近点和距离
point nearestpoint = points[0];
double nearestdistance = getdistance(point, points[0]);
for (int i = 1; i < points.length; i++) {
double distance = getdistance(point, point[i]);
if (distance < nearestdistance) {
nearestdistance = distance;
nearestpoint = point[i];
}
}
// 返回最近点和距离
return pair.of(nearestpoint, nearestdistance);
}
函数使用案例:
point point = ...;
point points = ...;pair<point, double> pair = getnearestpointanddistance(point, points);
if (objects.non(pair)) {
point point = pair.getleft;double distance = pair.getright;
...}
定义 enum 类实现取值和描述
在 c++、java 等计算机编程语言中,枚举类型(enum)是一种特殊数据类型,能够为一个变量定义一组预定义的常量。在使用枚举类型的时候,枚举类型变量取值必须为其预定义的取值之一。
用 class 关键字实现的枚举类型
在 jdk5 之前, java 语言不支持枚举类型,只能用类(class)来模拟实现枚举类型。
/** 订单状态枚举 */
public final class orderstatus {
/** 属性相关 */
/** 状态取值 */
private final int value;
/** 状态描述 */
private final string description;
/** 常量相关 */
/** 已创建(1) */
public static final orderstatus created = new orderstatus(1, "已创建");
/** 进行中(2) */
public static final orderstatus processing = new orderstatus(2, "进行中");
/** 已完成(3) */
public static final orderstatus finished = new orderstatus(3, "已完成");
/** 构造函数 */
private orderstatus(int value, string description) {
this.value = value;
this.description = description;
}/** 获取状态取值 */
public int getvalue {
return value;
}/** 获取状态描述 */
public string getdescription {
return description;
}}
用 enum 关键字实现的枚举类型
jdk5 提供了一种新的类型—— java 的枚举类型,关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常量使用,这是一种非常有用的功能。
/** 订单状态枚举 */
public enum orderstatus {
/** 常量相关 */
/** 已创建(1) */
created(1, "已创建"),
/** 进行中(2) */
processing(2, "进行中"),
/** 已完成(3) */
finished(3, "已完成");
/** 属性相关 */
/** 状态取值 */
private final int value;
/** 状态描述 */
private final string description;
/** 构造函数 */
private orderstatus(int value, string description) {
this.value = value;
this.description = description;
}/** 获取状态取值 */
public int getvalue {
return value;
}/** 获取状态描述 */
public string getdescription {
return description;
}}
其实,enum 类型就是一个语法糖,编译器帮我们做了语法的解析和编译。通过反编译,可以看到 java 枚举编译后实际上是生成了一个类,该类继承了 java.lang.enum<e> ,并添加了 values、valueof 等枚举类型通用方法。
定义 holder 类实现参数的输出
在很多语言中,函数的参数都有输入(in)、输出(out)和输入输出(inout)之分。在 c/c++ 语言中,可以用对象的引用(&)来实现函数参数的输出(out)和输入输出(inout)。但在 java 语言中,虽然没有提供对象引用类似的功能,但是可以通过修改参数的字段值来实现函数参数的输出(out)和输入输出(inout)。这里,我们叫这种输出参数对应的数据结构为holder(支撑)类。
holder 类实现代码:
/** 长整型支撑类 */
@getter@tter@tostringpublic class longholder {
/** 长整型取值 */
private long value;
/** 构造函数 */
public longholder {}
/** 构造函数 */
public longholder(long value) {
this.value = value;
}}
holder 类使用案例:
/** 静态常量 */
/** 页面数量 */
private static final int page_count = 100;
/** 最大数量 */
private static final int max_count = 1000;
/** 处理过期订单 */
public void handleexpiredorder {
longholder minidholder = new longholder(0l);
for (int pageindex = 0; pageindex < page_count; pageindex++) {
if (!handleexpiredorder(pageindex, minidholder)) {
break;
}}}/** 处理过期订单 */
private boolean handleexpiredorder(int pageindex, longholder minidholder) {
// 获取最小标识
long minid = minidholder.getvalue;
// 查询过期订单(按id从小到大排序)
list<orderdo> orderlist = orderdao.queryexpired(minid, max_count);
if (collectionutils.impty(tasktaglist)) {
return fal;
}
// 设置最小标识
int ordersize = orderlist.size;
minid = orderlist.get(ordersize - 1).getid;
minidholder.tvalue(minid);
// 依次处理订单
for (orderdo order : orderlist) {
...
}
// 判断还有订单
return ordersize >= page_size;
}
其实,可以实现一个泛型支撑类,适用于更多的数据类型。
定义 union 类实现数据体的共存
在 c/c++ 语言中,联合体(union),又称共用体,类似结构体(struct)的一种数据结构。联合体(union)和结构体(struct)一样,可以包含很多种数据类型和变量,两者区别如下:
1、结构体(struct)中所有变量是“共存”的,同时所有变量都生效,各个变量占据不同的内存空间;
2、联合体(union)中是各变量是“互斥”的,同时只有一个变量生效,所有变量占据同一块内存空间。
当多个数据需要共享内存或者多个数据每次只取其一时,可以采用联合体(union)。
在java语言中,没有联合体(union)和结构体(struct)概念,只有类(class)的概念。众所众知,结构体(struct)可以用类(class)来实现。其实,联合体(union)也可以用类(class)来实现。但是,这个类不具备“多个数据需要共享内存”的功能,只具备“多个数据每次只取其一”的功能。
这里,以微信协议的客户消息为例说明。根据我多年来的接口协议封装经验,主要有以下两种实现方式。
使用函数方式实现 union
union 类实现:
/** 客户消息类 */
@tostring
public class customermessage {
/** 属性相关 */
/** 消息类型 */
private string msgtype;
/** 目标用户 */
private string tour;
/** 共用体相关 */
/** 新闻内容 */
private news news;
.../** 常量相关 */
/** 新闻消息 */
public static final string msg_type_news = "news";
.../** 构造函数 */
public customermessage {}
/** 构造函数 */
public customermessage(string tour) {
this.tour = tour;
}/** 构造函数 */
public customermessage(string tour, news news) {
this.tour = tour;
this.msgtype = msg_type_news;
this.news = news;
}/** 清除消息内容 */
private void removemsgcontent {
// 检查消息类型
if (objects.is(msgtype)) {
return;
}
// 清除消息内容
if (msg_type_news.equals(msgtype)) {
news = ;
} el if (...) {
...
}
msgtype = ;
}
/** 检查消息类型 */
private void checkmsgtype(string msgtype) {
// 检查消息类型
if (objects.is(msgtype)) {
throw new illegalargumentexception("消息类型为空");
}
// 比较消息类型
if (!objects.equals(msgtype, this.msgtype)) {
throw new illegalargumentexception("消息类型不匹配");
}
}
/** 设置消息类型函数 */
public void tmsgtype(string msgtype) {
// 清除消息内容
removemsgcontent;
// 检查消息类型
if (objects.is(msgtype)) {
throw new illegalargumentexception("消息类型为空");
}
// 赋值消息内容
this.msgtype = msgtype;
if (msg_type_news.equals(msgtype)) {
news = new news;
} el if (...) {
...
} el {
throw new illegalargumentexception("消息类型不支持");
}
}
/** 获取消息类型 */
public string getmsgtype {
// 检查消息类型
if (objects.is(msgtype)) {
throw new illegalargumentexception("消息类型无效");
}
// 返回消息类型
return this.msgtype;
}
/** 设置新闻 */
public void tnews(news news) {
// 清除消息内容
removemsgcontent;
// 赋值消息内容
this.msgtype = msg_type_news;
this.news = news;
}
/** 获取新闻 */
public news getnews {
// 检查消息类型
checkmsgtype(msg_type_news);
// 返回消息内容
return this.news;
}
...
}
union 类使用:
string accesstoken = ...;
string tour = ...;
list<article> articlelist = ...;news news = new news(articlelist);
customermessage customermessage = new customermessage(tour, news);
wechatapi.ndcustomermessage(accesstoken, customermessage);
主要优缺点:
优点:更贴近 c/c++ 语言的联合体(union);缺点:实现逻辑较为复杂,参数类型验证较多。使用继承方式实现 union
union 类实现:
/** 客户消息类 */
@getter
@tter
@tostring
public abstract class customermessage {
/** 属性相关 */
/** 消息类型 */
private string msgtype;
/** 目标用户 */
private string tour;
/** 常量相关 */
/** 新闻消息 */
public static final string msg_type_news = "news";
.../** 构造函数 */
public customermessage(string msgtype) {
this.msgtype = msgtype;
}/** 构造函数 */
public customermessage(string msgtype, string tour) {
this.msgtype = msgtype;
this.tour = tour;
}}/** 新闻客户消息类 */
@getter
@tter
@tostring(callsuper = true)
public class newscustomermessage extends customermessage {
/** 属性相关 */
/** 新闻内容 */
private news news;
/** 构造函数 */
public newscustomermessage {
super(msg_type_news);
}/** 构造函数 */
public newscustomermessage(string tour, news news) {
super(msg_type_news, tour);
this.news = news;
}}
union 类使用:
string accesstoken = ...;
string tour = ...;
list<article> articlelist = ...;news news = new news(articlelist);
customermessage customermessage = new newscustomermessage(tour, news);
wechatapi.ndcustomermessage(accesstoken, customermessage);
主要优缺点:
优点:使用虚基类和子类进行拆分,各个子类对象的概念明确;缺点:与 c/c++ 语言的联合体(union)差别大,但是功能上大体一致。在 c/c外阴++ 语言中,联合体并不包括联合体当前的数据类型。但在上面实现的 java 联合体中,已经包含了联合体对应的数据类型。所以,从严格意义上说, java 联合体并不是真正的联合体,只是一个具备“多个数据每次只取其一”功能的类。
使用泛型屏蔽类型的差异性
在 c++ 语言中,有个很好用的模板(template)功能,可以编写带有参数化类型的通用版本,让编译器自动生成针对不同类型的具体版本。而在 java 语言中,也有一个类似的功能叫泛型(generic)。在编写类和方法的时候,一般使用的是具体的类型,而用泛型可以使类型参数化,这样就可以编写更通用的代码。
许多人都认为, c++ 模板(template)学英语从零开始和 java 泛型(generic)两个概念是等价的,其实实现机制是完全不同的。 c++ 模板是一套宏指令集,编译器会针对每一种类型创建一份模板代码副本; java 泛型的实现基于”类型擦除”概念,本质上是一种进行类型限制的语法糖。
泛型类
以支撑类为例,定义泛型的通用支撑类:
/** 通用支撑类 */
@getter@tter@tostringpublic class genericholder<t> {
/** 通用取值 */
private t value;
/** 构造函数 */
public genericholder {}
/** 构造函数 */
public genericholder(t value) {
this.value = value;
}}
泛型接口
定义泛型的数据提供者接口:
/** 数据提供者接口 */
public interface dataprovider<t> {
/** 获取数据函数 */
public t getdata;
}
泛型方法
定义泛型的浅拷贝函数:
/** 浅拷贝函数 */
public static <t> t shallowcopy(object source, class<t> clazz) throws beanxception {
// 判断源对象if (objects.is(source)) {
return ;
}// 新建目标对象t target;try {target = clazz.newinstance;} catch (exception e) {throw new beanxception("新建类实例异常", e);
}// 拷贝对象属性beanutils.copyproperties(source, target);
// 返回目标对象return target;
}
泛型通配符
泛型通配符一般是使用”?”代替具体的类型实参,可以把”?”看成所有类型的父类。当具体类型不确定的时候,可以使用泛型通配符 “?”;当不需要使用类型的具体功能,只使用o北方立秋吃什么bject类中的功能时,可以使用泛型通配符 “?”。
/** 打印取值函数 */
public static void printvalue(genericholder<?> holder) {
system.out.println(holder.getvalue);
}/** 主函数 */
public static void main(string[] args) {
printvalue(new genericholder<>(12345));
printvalue(new genericholder<>("abcde"));
}
在 java 规范中,不建议使用泛型通配符”?”,上面函数可以改为:
/** 打印取值函数 */
public static <t> void printvalue(genericholder<t> holder) {
system.out.println(holder.getvalue);
}
泛型上下界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。泛型上下界的声明,必须与泛型的声明放在一起 。
上界通配符(extends):
上界通配符为 ”extends ”,可以接受其指定类型或其子类作为泛参。其还有一种特殊的形式,可以指定其不仅要是指定类型的子类,而且还要实现某些接口。例如: list<? extends a> 表明这是 a 某个具体子类的 list ,保存的对象必须是a或a的子类。对于 list<? extends a> 列表,不能添加 a 或 a 的子类对象,只能获取a的对象。
下界通配符(super):
下界通配符为”super”,可以接受其指定类型或其父类作为泛参。例如:list<? super a> 表明这是 a 某个具体父类的 list ,保存的对象必须是 a 或 a 的超类。对于 list<? super a> 列表,能够添加 a 或 a 的子类对象,但只能获取 object 的对象。
pecs(producer extends consumer super)原则:作为生产者提供数据(往外读取)时,适合用上界通配符(extends);作为消费者消费数据(往里写入)时,适合用下界通配符(super)。
在日常编码中,比较常用的是上界通配符(extends),用于限定泛型类型的父类。例子代码如下:
/** 数字支撑类 */
@getter
@tter
@tostring
public class numberholder<t extends number> {
/** 通用取值 */
private t value;
/** 构造函数 */
public numberholder {}
/** 构造函数 */
public numberholder(t value) {
this.value = value;
}}/** 打印取值函数 */
public static <t extends number> void printvalue(genericholder<t> holder) {
system.out.println(holder.getvalue);}
后记
笔者曾在通信行业从业十余年,接入了各类网管和设备的北向接口协议上百余种,涉及到传输、交换、接入、电源、环境等专业,接触了 corba、http/https、webrvice、socket tcp/udp、串口 rs232/485 等接口,总结出一套接口协议封装的”方法论”。其中,把接口协议文档中的数据格式转化为 java 的枚举、结构体、联合体等数据结构,是接口协议封装中极其重要的一步。
作者:陈昌毅,花名常意,高德地图技术专家,2018年加入阿里巴巴,一直从事地图数据采集的相关工作。
声明:本文为作者投稿,版权归作者所有。
本文发布于:2023-04-05 02:46:19,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/79ffef5bc5c9086c915874341805eee2.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:java判断数组是否为空的方法(数组未赋值是空还是0).doc
本文 PDF 下载地址:java判断数组是否为空的方法(数组未赋值是空还是0).pdf
留言与评论(共有 0 条评论) |