您的位置:首页 > 编程语言

高效编程

2015-09-09 08:29 344 查看


聊聊我的高效编程之路

摘要:在程序开发过程中,一些IDE的配置、常用代类的积累以及常用框架的使用。能帮助我们迅速写好代码,快速定位错误。同时,又有利于我们快速构造和部署,以及进行后续的迭代开发。本文主要从IDE的配置、代码的规范、常用代码的积累等方面来聊下自己一些编程方面的见解。

目录:

一、IDE配置篇

二、规范工程篇

三、常用代码篇

四、常用框架篇

五、其它工具


一、IDE配置篇

正所谓工欲善其事必先利其器,选择一个IDE,并配置好它的各项设置,对我们开发过程是非常有帮助的。 平时开发因为是选用Eclipse,所以在配置上如果一开始就配置好的话,开发的过程中就可以省去很多步骤。首先是IDE的选择,这里因为平时用得最多的是Eclipse,所以主要还是针对它来说。到官网上去下载IDE,如果不做JAVA
EE开发,建议选择第二个,当然。如果做安卓开发,也可以直接去下一个带有ADT和安卓SDK的Eclipse.而如果只是想做一些Java代码的普通编程,那就可以下一个普通的Eclipse.


图1-1 Eclipse版本选择


1.1、代码自动提示

Eclipse代码里面的代码提示功能默认是关闭的,只有输入“.”的时候才会提示功能,下面说一下如何修改eclipse配置,开启代码自动提示功能 Eclipse ->Window -> Perferences->Java -> Editor -> Content Assist 下面有三个选项,找到第二个“Auto activation triggers for Java:”选项 在其后的文本框中会看到一个“.”存在。这表示:只有输入“.”之后才会有代码提示和自动补全,我们要修改的地方就是这里。把该文本框中的“.”换掉,换成“abcdefghijklmnopqrstuvwxyz.”,这样,你在Eclipse里面写Java代码就可以做到按“abcdefghijklmnopqrstuvwxyz.”中的任意一个字符都会有代码提示。


图1-2 Eclipse自动提示设置

这是下个自动提示的例子


图1-3 自动提示例子


1.2、XML自动提示

在ssh或ssm框架中,都要写很多的XML配置代码。如果能在写的时候,自动显示一些提示代码,那就可以很快速的把代码写出来。

DTD 类型约束文件

1. Window->Preferences->XML->XML Catalog->User Specified Entries窗口中,选择Add 按纽2

.在Add XML Catalog Entry 对话框中选择或输入以下内容:

Location: F:/soft/programmingSoft/Framework/Ibatis/sql-map-config-2.dtd

Key Type: URI

KEY: http://ibatis.apache.org/dtd/sql-map-config-2.dtd
XSD 类型约束文件

1. Window->Preferences->XML->XML Catalog->User Specified Entries窗口中,选择Add 按纽

2.在Add XML Catalog Entry 对话框中选择或输入以下内容:

Location:F:/soft/programmingSoft/Framework/Spring/spring-framework-2.5.6.SEC01-with-dependencies/spring-framework-2.5.6.SEC01/dist/resources/spring-beans-2.5.xsd

Key Type: Schema Location

KEY: http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

图1-4 XML自动提示设置


图1-5 XML自动提示设置


1.3、注释自动生成

1、Java代码自动注释

在使用Eclipse 编写Java代码时,自动生成的注释信息都是按照预先设置好的格式生成的。打开Windows->Preferences->Java->Code Style->Code Templates,点击右边窗口中的Comments,可以看到有很多选项,我们便可对此注释信息模板进行编辑。


图1-6 Java自动提示生成设置

如我们希望在一个Java文件的开头设置作者信息、日期信息。

为类和方法做注释标准


图1-7 Java自动提示生成设置

注:方法的注释自动生成方式为:输入/** 然后按下enter即可。


图1-8 Java自动提示生成实例

或者在你需要添加注释的地方点击Sources->Ganarate Element Comment,或者使用快捷键 Alt+Shift+J ,则 eclipse 自动在该类前面添加注释。

2、jsp 文件部分

eclipse-->windows-->preferences-->Web-->JSP Files-->Editer-->Templates-->New JSP File-->单击[edit]按钮

在文件首行,添加如下内容:

Java代码

<%--

创建时间:${date}${time}

创 建 人:****

相关说明:

JDK1.6.0_21 tomcat6.0.29 servlet2.5

--%>

<%--

创建时间:${date}${time}

创 建 人:***

相关说明:

JDK1.6.0_21 tomcat6.0.29 servlet2.5

--%>

这样再创建 .java 或 .jsp 文件时,eclipse 就会为我们自动将注释写好。你还可以通过“导出”、“导入”功能,把自己的模板导出来,方便在其他机器上使用。


1.4 皮肤选择

在开发过程中,是不是感到白色的一片看得很不舒服,特别是晚上的时候,屏幕太亮了,对眼晴实在不好。所幸Eclipse有这方面的皮肤库。在Eclipse lua中有自带的一个皮肤库Dark,选择方法Window—>Preferences—>General—>Apperance选择主题为Dark,确认


图1-9 Eclipse皮肤选择

结果如下


图1-10 Eclipse皮肤设置实例

更多的皮肤模板请到这里来下吧!eclipsecolorthemes.org


图1-11 更多Eclipse皮肤选择


1.4、快捷操作键使用

快捷操作平时是我使用最多的,用键盘的速度肯定比用鼠标来得快,高手都是直接快捷键来定位各种问题。下面是一些我自己比较常用的快捷键,有兴趣的人也可以自己去百度下。这些操作强烈推荐一定要会,确实可以加快你编程的速度。

1、Ctrl+Shift+t:打开类型

如果你不是有意磨洋工,还是忘记通过源码树(source tree)打开的方式吧。用eclipse很容易打开接口的实现类。

2、Alt+左右方向键:返回与前进

我们经常会遇到看代码时Ctrl+左键,层层跟踪,然后迷失在代码中的情况,这时只需要按“Alt+左方向键”就可以退回到上次阅读的位置,同理,按“Alt+右方向键”会前进到刚才退回的阅读位置,就像浏览器的前进和后退按钮一样。

3、Ctrl+Shift+o:自动导入包

4、Ctrl+Shift+r:打开资源

这可能是所有快捷键组合中最省时间的了。这组快捷键可以让你打开你的工作区中任何一个文件,而你只需要按下文件名或mask名中的前几个字母,比如applic*.xml。美中不足的是这组快捷键并非在所有视图下都能用。

5、Ctrl+Shift+s:自动添加方法

可以添加一此set/get的方法,或toString等方法

6、Ctrl+Shift+/添加注释,也可以用Ctrl+Shift+\取消注释以/**/包围,也可以Ctrl+Shift+C,以//包围

7、Ctrl+o:快速outline

如果想要查看当前类的方法或某个特定方法,但又不想把代码拉上拉下,也不想使用查找功能的话,就用ctrl+o吧。它可以列出当前类中的所有方法及属性,你只需输入你想要查询的方法名,点击enter就能够直接跳转至你想去的位置。

8、ctrl+f:快速查找

9. alt+shift+r:重命名(或者F2)

重命名属性及方法在几年前还是个很麻烦的事,需要大量使用搜索及替换,以至于代码变得零零散散的。今天的Java IDE提供源码处理功能,Eclipse也是一样。现在,变量和方法的重命名变得十分简单,你会习惯于在每次出现更好替代名称的时候都做一次重命名。要使 用这个功能,将鼠标移动至属性名或方法名上,按下alt+shift+r,输入新名称并点击回车。就此完成。如果你重命名的是类中的一个属性,你可以点击alt+shift+r两次,这会呼叫出源码处理对话框,可以实现get及set方法的自动重命名。

10.、ctrl+.及ctrl+1:下一个错误及快速修改

ctrl+.将光标移动至当前文件中的下一个报错处或警告处。这组快捷键我一般与ctrl+1一并使用,即修改建议的快捷键。新版Eclipse的修改建 议做的很不错,可以帮你解决很多问题,如方法中的缺失参数,throw/catch exception,未执行的方法等等

11、快速复制一行

将光标放到某一行,按住Ctrl+Alt+Down,即可以在下面快速复制一行,按住Ctrl+Alt+Up,即可以在上面快速复制一行。


二、规范工程篇

软件开发是一个集体协作的过程,程序员之间的代码经常进行交换阅读,因此,Java源程序有一些约定俗成的命名规定,主要目的是为了提高Java程序的可读性以及管理上的方便性。好的程序代码应首先易于阅读,其次才是效率高低的问题。


2.1、代码注释风格

代码注释原则

1. 注释应该简单清晰,避免使用装饰性内容,也就是说,不要使用象广告横幅那样的注释语句。

2. 代码注释的目的是要使代码更易于被同时参与程序设计的开发人员以及其他后继开发人员理解。

3. 先写注释,后写代码。

写代码注释的最好方法是在写代码之前就写注释。这使你在写代码之前可以想想代码的功能和运行。而且这样确保不会遗漏注释。(如果程序的逻辑稍微复杂点的话,这样做非常有效)

4. 注释信息不仅要包括代码的功能,还应给出原因。

例如,下面例 1 中的代码显示金额在 $1,000 以上(包括 $1,000)的定单可给予 5% 的折扣。为什么要这样做呢?难道有一个商业法则规定大额定单可以得到折扣吗?这种给大额定单的特殊是有时限的呢,还是一直都这样?最初的程序设计者是否只是由于慷慨大度才这样做呢?除非它们在某个地方(或者是在源代码本身,或者是在一个外部文档里)被注释出来,否则你不可能知道这些。

if (grandTotal >= 1000.00){
grandTotal = grandTotal * 0.95;
}


更加详细的内容请看我的另一个博文这里:Java编程规范


2.2、代码命名

2.2.1、命名总体原则

1. 名字含义要明确,做到见名知义,如: User,Role, UserManager

2. 尽量使用英文名字作为变量名,如果要使用中文,请写上备注.

如:var hbType = null;// hb是中文“货币”的首字母缩写.

3. 采用大小写混合形式命名,提高名字的可读性

正确:UserManager

错误: usermanager

4. 尽量少用缩写,但如果一定要使用,就要谨慎地使用。

应该保留一个标准缩写的列表,并且在使用时保持一致。例如,想对单词“number”采用缩写,使用 num 这种格式,并且只使用这一种形式.注意:要维护缩写词汇列表.

5. 所有文件的名称都是大写字母开头,大小写混合, 如UserList.jsp

6. 所有目录的名称都是小写字母开头,大小写混合, 如userType

7. 变量名不能以下划线开头,如“_account”,”_userName”是不允许的,因为下划线开头的变量可能被OWK平台做为保留字占用.

8. 避免使用相似或者仅在大小写上有区别的名字。例如,不应同时使用变量名 persistentObject 和 persistentObjects,以及 anSqlDatabase 和 anSQLDatabase。

2.2.2、命名



包结构定义=${包单元}[.${包单元}]*

说明:

1:一个包是由一个或多个构造单元组成,各构造单元之间以点号”.”隔开

2:一个包单元由一个或多个词组成

3:包单元应该是名词

• 业务系统的包结构是com.cmb.zct.${业务系统名字}.${模块名}

• 包名全部小写,如:com.cmb.zct.tx.OA.common是不允许的.但有种情况下允许出现大写字母,就是当包单元是由多个词组成时.如: com.cmb.zct.tx.oa.userType.



• 使用完全的英文描述符,所有单词的第一个字母要大写,并且单词中大小写混合。

• 类名是名词或名词词组.如 LogManager

• 工具类以Util结尾 . 如FileUtil,StrUtil

• 异常类以Exception结尾.如RootDirNotExistsException

• 抽象类名以Abstract开头 AbstractGenerator

• 工厂类以Factory结尾,如 ConnFactory

• 接口同类名,大小写混合..

方法

• 成员函数的命名应采用完整的英文描述符,大小写混合使用:所有中间单词的第一个字母大写。成员函数名称的第一个单词常常采用一个有强烈动作色彩的动词。首个单词小写。如:getUserInfo, removeEquipment

参数变量

以 小写p开头



public void sendMessage(String pMessage){

...............

}

注意:javabean 的参数变量不带p。

常量名

采用完整的英文大写单词,在词与词之间用下划线连接。MAX_VALUE,DEFAULT_START_DATE

目录名

同包名,小写字母开头,如果有多个单词构成,则第2个以后的单词以大写字母开头。如user, userType目录

文件名

同类名命名规则,大写字母开头,大小写混合。如:EquipmentList.jsp

模块相关文件命名约束,为了方便说明,${MODEL_NAME}代表实际模块的名称,那各文件的名字必须满足下表格式要求.

文件 格式 举例

业务组件接口 I${MODEL_NAME}Facade.java IUserFacade.java

服务组件接口 I${MODEL_NAME}Service.java IUserService.java

业务组件实现类 ${MODEL_NAME}FacadeImpl.java UserFacadeImpl.java

服务组件实现类 ${MODEL_NAME}ServiceImpl.java IUserServiceImpl.java

测试类 ${MODEL_NAME}ServiceTest.java UserServiceTest.java

更加详细的内容请看我的另一个博文这里:Java编程规范


2.3、数据库规范

2.3.1、命名规范

1、 库表名、字段名尽量不用缩写(英文单词太长可适当缩写,但要能让人看明白含义,同一英语单词的缩写需保持一致)

2、 表名、字段名的各单词间必须用下划线分隔开

如 表名:USER_INFO 字段名:USER_CODE, USER_NAME

3、 必须为每个子系统定义独立的schema

2.3.2、代码编写规范

1、 对数据库表、存储过程的访问必须显示加上schema名称(mysql也叫database);

2、 不允许使用select * from 编写查询语句,必须显示写查询字段列表;

3、 不允许跨子系统直接访问库表,跨子系统必须通过服务接口进行调用;

4、 批量操作必须采用jdbc的batch方式提交;

5、 尽量使用JDK自带的API函数,不要自己写类似功能的函数;

6、 Public方法中,对外部传入的不可信任的参数,需要进行有效性校验;


2.4、性能规范

2.4.1、日志处理

1. 避免显式调用对象的toString方法
Object param1 = "param1";
// 显式调用toString
// 当当前日志级别为 info时,虽然没有打印到文件中,但是在方法调用参数传递时,已经做了一次toString的方法。
LOGGER.debug("打印一个参数:{}.", param1.toString());
// 正确的方式:
LOGGER.debug("打印一个参数:{}.", param1);


2. 对类中日志工具对象logger应声明为static。尽管一些logger对LogFactory工厂有一些优化,但是我们也必须防止代码没有必要的运行。

3. 避免在循环中打印大量的日志。

2.4.2、字符串处理

1. 对于常量字符串,不要通过new方式来创建;

2. 对于常量字符串之间的拼接,请使用“+”;对于字符串变量(不能在编译期间确定其具体值的字符串对象)之间的拼接,请使用StringBuilder/StringBuffer;

3. 在使用StringBuilder/StringBuffer进行字符串操作时,请尽量设定初始容量大小;也尽量避免通过String/CharSequence对象来构建StringBuffer对象(即:不要使用 new StringBuilder(String) 这个构造方法来创建StringBuilder对象);

4. 当查找字符串时,如果不需要支持正则表达式请使用indexOf(…)实现查找(特殊情况可以使用startsWith和endsWith);当需要支持正则表达式时,如果需要频繁的进行查找匹配,请直接使用正则表达式工具类实现查找;

5. 尽量避免使用String的split方法。

除非是必须的,否则应该避免使用split,split由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资源,如果确实 需要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char),频繁split的可以缓存结果。

2.4.3 对象创建、销毁处理

1. 在循环外定义变量;

2. 对局部变量不需要显式设置为null;

3. 尽量避免非常大的内存分配。

从数据库中读取的记录放到集合中时,要注意控制集合元素个数。

2.4.4 数组、集合处理

1. 数组的复制使用System.arraycopy(…)方法;

2. 对于数组、集合的拷贝、查找、排序等操作,如果是一般应用,可以优先采用java.util.Arrays和java.util.Collections 中提供的工具方法;但是对于热点代码,最好是参考java API中的方法实现,自己开发特定的排序等方法,以减少临时对象的创建;

3. 当需要在方法之间传递多个属性值时,从性能角度考虑,应优先采用结构体,而非ArrayList或Vector等集合类;

4. 在代码开发中,需要根据应用场景合理选择集合框架中的集合类,应用场景可按单线程和多线程来划分,也可按频繁插入、随机提取等具体操作场景来划分;

5. 定义集合对象时,请尽量设定初始容量大小;

2.4.5 IO处理

1. 在IO操作中,必须定义finally代码段,并在该代码段中执行IO关闭操作;

2. 进行IO读写操作时,必须使用缓冲机制;

2.4.6 多线程处理

1. 避免太多的使用 synchronized 关键字。尽量减小Synchronized的范围。

2. 在线程池中运行的子线程,其run方法不可以往外抛出异常。

2.4.6 其他规则

1. 避免在循环中对变量进行重复计算

避免使用这类的语句:for(int i=0; i<list.size(); i++)

应该使用:int size=list.size(); for(int i=0; i<size; i++)

更加优化的是使用迭代的方式:for(Object o: list)

2. 异常只能用于错误处理,不应该用来控制代码流程

当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。当需要创建一个 Exception时,JVM不得不说:先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。


三、常用代码篇

常用的代码其实是非常多的,这里我只是举例了一些,因为代码多了,整个文章就太长了。后续有空我再来重新一一整理上传。


3.1、日期处理类

日期处理主要完成了一些格式方面的转换
/**
*
* 类说明:日期工具类
*
* <p>
* 详细描述:
*
* @author *****
* @since *****
*/
public class DateUtil {
public final static String DATE_FROMAT = "yyyyMMdd";
public final static String TIME_FORMAT = "HHmmss";

/**
* 说明: 获取系统当前日期
*
* @param
* @return
* @
* @author ****
* @since 2014年5月22日
*/
public static int getCurIntPcDate()  {
return Integer.parseInt(getCurPcDate());
}
/**
* 说明: 获取系统当前日期
*
* @param
* @return
* @
* @author ****
* @since ****
*/
public static String getCurPcDate() {
java.util.Date currentDate = new java.util.Date();
SimpleDateFormat formatdate = new SimpleDateFormat(DATE_FROMAT);
return formatdate.format(currentDate);
}
/***
* 说明: 获取指定格式的系统当前日期
* @param
* @return
* @
* @author ****
* @since ****
*/
public static String getCurPcDate(String strFormat)
{
java.util.Date currentDate = new java.util.Date();
SimpleDateFormat formatdate = new SimpleDateFormat(strFormat);
return formatdate.format(currentDate);
}

/***
* 说明:  获取当时系统日期时间【YYYYMMDDHHmmss】
* @param
* @return
* @author ****
* @since ****
*/
public static String getCurPcDateTime()
{
java.util.Date currentDate = new java.util.Date();
SimpleDateFormat formatdate = new SimpleDateFormat(DATE_FROMAT+TIME_FORMAT);
return formatdate.format(currentDate);
}

/**
* 说明: 获取当时系统日期时间【YYYYMMDDHHmmss】
* @param
* @return
* @author ****
* @since ****
*/
public static Long getIntCurPcDateTime()
{
return Long.valueOf(getCurPcDateTime());
}

/**
* 说明: 获取系统当前时间
*
* @param
* @return 当前时间并格式化成“HHmmss”,如“123124”
* @
* @author ****
* @since ****
*/
public static String getCurPcTime()  {
java.util.Date currentDate = new java.util.Date();
SimpleDateFormat formatdate = new SimpleDateFormat(TIME_FORMAT);
return formatdate.format(currentDate);
}

/**
* 说明: 验证传入数值型日期[YYYYMMDD]是否合法
*
* @param
* @return
* @
* @author ****
* @return
* @since *****
*/
public static boolean checkDateFormat(int intDate) {
return checkDateFormat(String.valueOf(intDate));
}

/**
* 说明: 验证传入字符型日期[YYYYMMDD]是否合法
*
* @param
* @return
* @
* @author ****
* @since ****
*/
public static boolean checkDateFormat(String strDate) {
return checkDateFormat(strDate, DATE_FROMAT);
}

/**
* 说明: 验证传入字符型日期是否合法
*
* @param
* @return
* @
* @author ***
* @since ****
*/
public static boolean checkDateFormat(int intDate, String strFormat) {
return checkDateFormat(String.valueOf(intDate), DATE_FROMAT);
}

/**
* 说明: 验证传入字符型日期是否合法
*
* @param
* @return
* @
* @author ****
* @since ***
*/
public static boolean checkDateFormat(String strDate, String strFormat)
{
try {
DateUtils.parseDateStrictly(strDate, strFormat);
return true;
} catch (ParseException e) {
return false;
}
}

/**
* 说明: 验证传入数值型时间[HH24MMSS]是否合法
*
* @param
* @return
* @
* @author ****
* @return
* @since ****
*/
public static boolean checkTimeFormat(int intDate) {
String strDate = String.valueOf(intDate);
if(strDate.length() <6)
strDate = CommUtil.LeftFill(strDate, '0', 6);
System.out.println("curTime:"+strDate);
return checkTimeFormat(strDate);
}

/**
* 说明: 验证传入字符型时间[HH24MMSS]是否合法
*
* @param
* @return
* @
* @author ****
* @since ****
*/
public static boolean checkTimeFormat(String strDate) {
return checkTimeFormat(strDate, TIME_FORMAT);
}

/**
* 说明: 验证传入字符型时间是否合法
*
* @param
* @return
* @
* @author ****
* @since ****
*/
public static boolean checkTimeFormat(int intDate, String strFormat) {
return checkTimeFormat(String.valueOf(intDate), DATE_FROMAT);
}

/**
* 说明: 验证传入字符型时间是否合法
*
* @param
* @return
* @
* @author ****
* @since ***
*/
public static boolean checkTimeFormat(String strDate, String strFormat){
try {
DateUtils.parseDateStrictly(strDate, strFormat);
return true;
} catch (ParseException e) {
return false;
}
}

/**
* 说明: 日期转换
* @param strDate
* @return
*/
public static Date parseDate(String strDate){
return parseDate(strDate, DATE_FROMAT);
}

/**
* 说明: 日期转换
* @param strDate
* @param strFormat
* @return
*/
public static Date parseDate(String strDate,String strFormat){
try {
return DateUtils.parseDateStrictly(strDate, strFormat);
} catch (ParseException e) {
throw new ServiceException(CtsErrorCode.ERROR_FORMAT,new String[]{"交易日期"}, "日期:" + strDate);
}
}

/**
* 说明: 日期转换
* @param intDate
* @param strFormat
* @return
*/
public static Date parseDate(int intDate,String strFormat){
return parseDate(String.valueOf(intDate), strFormat);
}
/**
* 说明: 日期转换
* @param intDate
* @return
*/
public static Date parseDate(int intDate){
return parseDate(String.valueOf(intDate));
}
/**
* 日期转换成字符串
* @param date
* @param dateFormat
* @return
*/
public static String date2String(Date date,String dateFormat) {
SimpleDateFormat formatdate = new SimpleDateFormat(dateFormat);
return formatdate.format(date);
}
/**
* 日期转换成字符串
* @param date
* @param dateFormat
* @return 格式为YYYYMMDD
*/
public static String date2String(Date date) {
return date2String(date,DATE_FROMAT);
}
/**
* 日期转换成整数
* @param date
* @param dateFormat
* @return 格式为YYYYMMDD
*/
public static int date2Int(Date date) {
String str = date2String(date,DATE_FROMAT);
return Integer.parseInt(str);
}

/**
* 获取指定日期之后的相隔n年的日期
* @param transDate
* @param years
* @return
* @return Integer
*/
public static Integer getDateAfterYear(Integer transDate, int years) {
Calendar theCa = Calendar.getInstance();
theCa.setTime(parseDate(transDate));
theCa.add(Calendar.YEAR, years);
Date date = theCa.getTime();
SimpleDateFormat formatdate = new SimpleDateFormat(DATE_FROMAT);
return Integer.valueOf(formatdate.format(date));
}

/**
* 计算两个日期相差的天数
* @param beginDate 【YYYYMMDD】
* @param endDate  【YYYYMMDD】
* @return Integer
* @author ****
* @since ****
*/
public static Integer diffDate(Integer beginDate,Integer endDate){
Calendar theCa1= Calendar.getInstance();
Calendar theCa2= Calendar.getInstance();
theCa1.setTime(parseDate(beginDate));
theCa2.setTime(parseDate(endDate));
long between_days=(theCa2.getTimeInMillis()-theCa1.getTimeInMillis())/(1000*3600*24);
return Integer.parseInt(String.valueOf(between_days));
}
}


3.2、文件处理类

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*  类说明:文件操作工具类
*
* @author *****
* @since *****
*/
public class FileOperateAssistUtil {
// 日志记录
private static Logger logger = LoggerFactory.getLogger(FileOperateAssistUtil.class);
/**
*
* 方法说明: 创建文件目录,若路径存在,就不生成
*
* <p>
* 详细描述:
*
* @param
* @return
* @author *****
* @since *****
*/
public static void createDocDir(String dirName) {
File file = new File(dirName);
if (!file.exists()) {
file.mkdirs();
}
}
/**
*
* 方法说明: 创建文件目录
*
* <p>
* 详细描述:
*
* @param
* @return
* @author *****
* @since *****
*/
public static void isExistsMkDir(String dirName){
File file = new File(dirName);
if (!file.exists()) {
file.mkdirs();
}
}
/**
* 方法说明: 本地,在指定路径生成文件。若文件存在,则删除后重建。
*
* @param dirName
*            本地路径名,
* @param file
*            文件,
* @return List<Object>
* @author *****
* @since *****
*/
public static void creatFileByName(File file){
try {
if (file.exists()) {
file.delete();
logger.info("发现同名文件:{},先执行删除,再新建。", file.getAbsolutePath());
}
file.createNewFile();
logger.info("创建文件为:{}", file.getAbsolutePath());
}
catch (IOException e) {
logger.error("创建{}文件失败", file.getAbsolutePath(), e);
}
}
/**
*
* 说明:
* 详细描述:创建新文件,若文件存在则删除再创建,若不存在则直接创建
* @param
* @returnType File
* @since *****
* @author *****
*/
public static File newFile(String fileName) {
File file = new File(fileName);
creatFileByName(file);
return file;
}
/**
*
* 说明:
* 详细描述:关闭写入流
* @param
* @returnType void
* @since *****
* @author *****
*/
public static void closeWriter(Writer writer) {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
// throw new ServiceException(BatchErrorCode.FILE_CLOSE_EXCEPTION, e);
logger.error("Close Writer cause Exception:", e);
}
}
}
/**
*
* 说明:
* 详细描述:关闭写入流
* @param
* @returnType void
* @since *****
* @author *****
*/
public static void closeReader(Reader reader) {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.error("Close reader cause Exception:", e);
}
}
}
/**
*
* 说明:
* 详细描述:关闭随机读写流
* @param
* @returnType void
* @since *****
* @author *****
*/
public static void closeRandomAccessFile(RandomAccessFile raf){
if(raf != null){
try {
raf.close();
}catch (IOException e) {
throw new ServiceException(******,e, new String[]{"批量"});
}
}
}
public static String getBatchNo(String transDate, Long i) {
return transDate + getSerialNo(i);
}
public static String getFileBatchNo(String date) {
if(StringUtils.isBlank(date)){
return CtsDateUtil.getCurPcDate();
}
return date;
}
public static String getSerialNo(Long i) {
return CommUtil.LeftFill(String.valueOf(i), '0', 3);
}
public static String getSerialNo(int i) {
return CommUtil.LeftFill(String.valueOf(i), '0', 3);
}
/**
*
* 方法说明: 创建控制文件
*
* <p>
* 详细描述:
*
* @param
* @return
* @author *****
* @since*****
*/
public static void createControlFile(File dataFile, Long count) {
String controlFileName = dataFile.getAbsolutePath().replace(".DAT", ".CTL");
File controlFile = null;
BufferedWriter bw = null;
try {
controlFile = new File(controlFileName);
if (controlFile.exists()) {
controlFile.delete();
controlFile.createNewFile();
}
// 获取数据文件MD5
String dataFileMd5 = MD5EncoderUtil.getFileMd5(dataFile);
StringBuilder controlFileContext = new StringBuilder().append(dataFile.getName()).append("\t")
.append(dataFile.length()).append("\t").append(count.toString()).append("\t")
.append(dataFileMd5 == null ? "" : dataFileMd5);
// 将MD5写入控制文件
bw = new BufferedWriter(new FileWriter(controlFile, true));
bw.write(controlFileContext.toString());
bw.flush();
}
catch (Exception e) {
throw new ServiceException(*****, e, new String[]{"控制文件"}, "创建控制文件时发生异常");
}
finally {
if (bw != null) {
try {
bw.close();
}
catch (IOException e) {
throw new ServiceException(*****, e, new String[]{"控制文件"}, "创建控制文件时发生异常");
}
}
}
}
/**
*
* 方法说明: 校验MD5
*
* <p>
* 详细描述:
*
* @param
* @return
* @author *****
* @since *****
*/
public static boolean md5Valid(File dataFile) throws ServiceException {
String controlFileName = dataFile.getAbsolutePath().replace(".DAT", ".CTL");
// 获取数据文件的MD5
String dataFileMd5 = MD5EncoderUtil.getFileMd5(dataFile);
String controlFileMd5 = "";
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(new File(controlFileName)));
String tempString = reader.readLine();
// 获取控制文件中的MD5
if(StringUtils.isNotBlank(tempString)){
controlFileMd5 = tempString.substring(tempString.lastIndexOf("\t") + 1, tempString.length());
}else{
throw new ServiceException(CtsErrorCode.ERROR_VALIDATE_MD5, new String[]{"文件"}, "校验文件MD5时发生异常");
}
}
catch (Exception e) {
logger.error("校验文件MD5时发生异常", e);
throw new ServiceException(CtsErrorCode.ERROR_VALIDATE_MD5, e, new String[]{"文件"}, "校验文件MD5时发生异常");
}
finally {
if (reader != null) {
try {
reader.close();
}
catch (IOException e) {
throw new ServiceException(CtsErrorCode.ERROR_VALIDATE_MD5, e, new String[]{"文件"}, "校验文件MD5时发生异常");
}
}
}
return dataFileMd5.toUpperCase().equals(controlFileMd5.toUpperCase());
}
/**
* 方法说明: 将字符串拆解按特定标记解析,封装为String[]
*
* @param String
*            tempString 需要拆分的字符串
* @param String
*            tempString 拆分符号
* @param String
*            tempString 拆分符号出现次数
* @return List<Object>
* @throws ServiceException
* @author *****
* @since *****
*/
public static String[] parseStringToStringArray(String tempString, String sign, int num) {
List<Object> strlist = new ArrayList<Object>();
String[] strList = new String[num + 1];
try {
int i;
for (i = 0; i < num; i++) {
String s1 = tempString.substring(0, tempString.indexOf(sign)).trim();
tempString = tempString.substring(tempString.indexOf(sign) + 1).trim();
strlist.add(s1);
strList[i] = s1;
if (i == num - 1) {
strlist.add(tempString);
strList[i + 1] = tempString;
break;
}
}
}
catch (Exception e) {
logger.error("解析还款清算文件失败", e);

}
return strList;
}
/**
*
* 方法说明:格式化时间
*
* <p>
* 详细描述:
*
* @param
* @return
* @author *****
* @since *****
*/
public static String foamatTime(String transTime) {
return CommUtil.LeftFill(transTime, '0', 6);
}
/**
* 方法说明: 上传文件
*
* @param transDate
*            交易日期
* @param localPath
*            本地路径
* @param regName
*            文件名前缀
* @param remotePath
*            远程路径
* @return
* @throws ServiceException
* @author *****
* @since *****
*/
public static Long uploadFiles(String transDate, String localPath, String regName, String remotePath) {
SftpClient sftpClient = new SftpClient();
try
{
sftpClient.connect();
File[] fileList = listDataAndControlFile(localPath, regName + transDate);
List<String> fileNameList  = new ArrayList<String>();
Long count = 0L;
for (File file : fileList) {
count++;
fileNameList.add(file.getAbsolutePath());
}
if(count>0)
sftpClient.uploadBatch(remotePath, fileNameList);
return count;
}finally
{
sftpClient.disConnect();
}
}
public static void uploadFile(String loaclpath, String fileName, String remotePath) {
SftpClient sftpClient = new SftpClient();
try
{
File file = new File(loaclpath, fileName);
sftpClient.upload(remotePath, file.getAbsolutePath());
}finally
{
sftpClient.disConnect();
}
}
public static void uploadFile(String loaclpath, List<String> fileName, String remotePath) {
SftpClient sftpClient = new SftpClient();
try
{
List<String> fileNameList  = new ArrayList<String>();
Long count = 0L;
for (String item : fileName) {
count++;
fileNameList.add(loaclpath+"//"+item);
}
if(count>0)
sftpClient.uploadBatch(remotePath, fileNameList);
}finally
{
sftpClient.disConnect();
}
}
/***
* 按照指定格式分隔字符串
* @param tempString
* @param splitChar
* @return
* @return String[]
*/
public static String[] splitString(String tempString,String splitChar) {
String[] splits = (tempString.replace("||", "| | ") + (" ")).split(splitChar);
for(int i=0;i<splits.length;i++){
if(null == splits[i]){
splits[i]="";
}
}
return splits;
}
public static String packProperty(String value) {
if (value == null) {
return "";
}
return value.trim();
}
public static String packProperty(Integer value) {
if (value == null) {
return "";
}
return value.toString();
}
public static String packProperty(BigDecimal value) {
if (value == null) {
return "";
}
return value.toString();
}
/**
*
*  方法说明:<BR>
* 获取本地目录下过滤后的数据文件列表
*
* @param localPath 要查询的数据文件的路径
* @param namePrefix 要过滤出来的数据文件前缀
* @return File[] 文件列表
* @author *****
*/
public static File[] listDataFile(String localPath, final String namePrefix) {
FilenameFilter nameFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String fileName) {
return fileName.startsWith(namePrefix) && (fileName.endsWith(".DAT"));
}
};
File[] fileList = new File(localPath).listFiles(nameFilter);
return fileList == null ? new File[0] : fileList;
}
/**
*
* 方法说明: 获取本地目录下过滤后的数据文件和控制文件列表
*
* @param
* @return
* @author *****
* @since *****
*/
public static File[] listDataAndControlFile(String localPath, String reg) {
final String regName = reg;
logger.debug("localPath:"+localPath+",reg:"+reg);
FilenameFilter nameFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String fileName) {
return fileName.indexOf(regName) >= 0 && (fileName.endsWith(".DAT") || fileName.endsWith(".CTL"));
}
};
File[] fileList = new File(localPath).listFiles(nameFilter);
return fileList;
}
public static File[] deleteFilesFromDir(String localPath, String reg) {
File[] oldFileList = FileOperateAssistUtil.listDataAndControlFile(localPath,reg);
for (File file : oldFileList) {
file.delete();
}
return oldFileList;
}
public static String getBatchNoByFile(File file) {
String fileName = file.getName();
String str = fileName.substring(fileName.lastIndexOf("_") + 1, fileName.lastIndexOf("."));
return str.length() <= 3 ? str : "001";
}
}


3.3、DAO基类

package com.lz.ctsframework.core.support;
import java.util.List;
import org.apache.ibatis.annotations.Param;
/**
*
* <b>类说明:</b>dao基类
*
* <p>
* <b>详细描述:</b>
*
* @author **
* @since ***
*/
public interface IBaseDao<T,E,K> {
int countByCriteria(E criteria);
int deleteByCriteria(E criteria);
int deleteByPrimaryKey(K key);
int insert(T entity);
int insertSelective(T entity);
List<T> selectByCriteria(E criteria);
T selectByPrimaryKey(K key);
int updateByCriteriaSelective(@Param("record") T entity, @Param("example") E criteria);
//int updateByCriteria(@Param("record") T entity, @Param("example") E criteria);
int updateByPrimaryKeySelective(T entity);
//int updateByPrimaryKey(T entity);
}


3.4、JSON处理类

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
*
* <b>类说明:</b>Jackson工具类
*
* <p>
* <b>详细描述:</b>
*
* @author ****
* @since ***
*/
public class JacksonUtil {
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
private static final JsonFactory JSONFACTORY = new JsonFactory();
/**
* 转换Java Bean 为 json
*/
public static String beanToJson(Object o) throws JsonParseException {
StringWriter sw = new StringWriter();
JsonGenerator jsonGenerator = null;
try {
jsonGenerator = JSONFACTORY.createJsonGenerator(sw);
MAPPER.writeValue(jsonGenerator, o);
return sw.toString();
} catch (Exception e) {
throw new RuntimeException(e+"转换Java Bean 为 json错误");
} finally {
if (jsonGenerator != null) {
try {
jsonGenerator.close();
} catch (Exception e) {
throw new RuntimeException(e+"转换Java Bean 为 json错误");
}
}
}
}
/**
* json 转 javabean
*
* @param json
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object jsonToBean(String json, Class clazz) throws JsonParseException {
try {
//        	MAPPER.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return MAPPER.readValue(json, clazz);
} catch (Exception e) {
throw new RuntimeException(e+"json 转 javabean错误");
}
}
/**
* 转换Java Bean 为 HashMap
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> beanToMap(Object o) throws JsonParseException {
try {
return MAPPER.readValue(beanToJson(o), HashMap.class);
} catch (Exception e) {
throw new RuntimeException(e+"转换Java Bean 为 HashMap错误");
}
}
/**
* 转换Json String 为 HashMap
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> jsonToMap(String json, boolean collToString) throws JsonParseException {
Map<String, Object> map = null;
try {
map = MAPPER.readValue(json, HashMap.class);
} catch (IOException e) {
throw new RuntimeException(e+"转换Java Bean 为 HashMap错误");
}
if (collToString) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Collection || entry.getValue() instanceof Map) {
entry.setValue(beanToJson(entry.getValue()));
}
}
}
return map;
}
/**
* List 转换成json
*
* @param list
* @return
*/
public static String listToJson(List<Map<String, String>> list) throws JsonParseException {
JsonGenerator jsonGenerator = null;
StringWriter sw = new StringWriter();
try {
jsonGenerator = JSONFACTORY.createJsonGenerator(sw);
new ObjectMapper().writeValue(jsonGenerator, list);
jsonGenerator.flush();
return sw.toString();
} catch (Exception e) {
throw new RuntimeException(e+"List 转换成json错误");
} finally {
if (jsonGenerator != null) {
try {
jsonGenerator.flush();
jsonGenerator.close();
} catch (Exception e) {
throw new RuntimeException(e+"List 转换成json错误");
}
}
}
}
/**
* json 转List
*
* @param json
* @return
*/
@SuppressWarnings("unchecked")
public static List<Map<String, String>> jsonToList(String json) throws JsonParseException {
try {
if (json != null && !"".equals(json.trim())) {
JsonParser jsonParse = JSONFACTORY.createJsonParser(new StringReader(json));
return (List<Map<String, String>>) new ObjectMapper().readValue(jsonParse, ArrayList.class);
} else {
throw new RuntimeException("json 转List错误");
}
} catch (Exception e) {
throw new RuntimeException(e+"json 转List错误");
}
}
}


四、常用框架


4.1、日志打印-快速定位错

或许有人看到这里会认为日志不重要,因为大家平时用得最多的估计就是System.out.prinln()之类的,这种方法对于小程序是没问题。但是,对于一个完整的项目,有开发、有测试的。一方面,你如果开发过程中,每个地方都要手动的输出这些语句,岂不是很麻烦。特别是使用Mybatis或Hibernate之类的框架,你想看看程序跑的时候,调用的SQL语句。没有日志就很难做到。另一方面,项目如果部署在服务器之上,测试人员在进行测试时,就无法使用System.out.之类的语句来看输出了。这时,统一的把程序运行的日志输出到一个文件中去。然后通过一些linux的命令,就可以快速找到报错的信息是什么。这里兴趣的同学可以看我写的:Log4j详细使用教程

一个典型的Log4j结构:


图4-1 典型的Log4j结构

在小的项目中,可以日志的作用没那么明显,但是把日志和框架结合起来用就很常见,比如和Spring/mybatis/SprinMVC/Tomcat等的结合,打印出来的日志可以如下,这里日志的格式可以自己来控制,以及日志输入的等级。下面是控制台的输出日志


图4-2 Log4j控制台信息打印

下面是输出到外部的日志文件


图4-3 Log4j信息打印到文件


4.2、Maven-jar的自动管理

Maven还没出现之前,每次新建一个Java或web项目,都得往编程路径里放很多Jar包。传统引入jar的方式是将其放入web-inf->lib目录里面,无形中增大了项目,而且jar不能统一进行管理。使用Maven的好处之一就是通过配置POM.XML文件自动下载jar包,并且通过中心库统一进行管理、版本的控制等。

一个典型的maven的web项目结构:


图4-4 maven的web项目结构

使用Maven的一些好处

1. Maven的库是由开源组织维护,不需要我们再花精力去管第三方库,即使自己维护,也比较方便。

2. Maven对jar包的版本管理有工具上的支持,比如将Release版本和Snapshot版本区分开,有利于SCM管理。

3. Maven是标准,用过的人多,不需要额外培训。

4. Maven的plugin比较多,可以有更多功能,Maven现有体系比较开放,采用的技术相对比较通用和成熟,plugin的机制也可以便于我们扩展更多功能。

5. Maven的库下载是即用即下,不需要实现全部down下来。Maven的插件也是自动升级,可以方便的我们扩展新功能。

6. 可以很方便的与eclipse, IDEA这样的主流的IDE集成

7. 版本管理功能,这里的版本管理不是指第三方库的版本管理,而是项目的版本管理

8. 站点功能:它的出现让我们可以对项目的状态一目了然,可以自动的把项目的状态和各种报表以站点的形式发布到内部网或者外部网,可以随时随地查看项目状态。有很多中报表可以选择,包括,doc生成,代码规范的检查,自动bug检查,单元测试报表,单元测试的代码覆盖率报表。

总之,Maven作为一个构建工具,不仅帮我们自动化构建,还能抽象构建过程,提供构建任务实现.他跨平台,对外提供一致的操作接口,这一切足以使他成为优秀的,流行的构建工具.但是Maven不仅是构建工具,他还是一个依赖管理工具和项目信息管理工具.他还提供了中央仓库,能帮我们自动下载构件.使用Maven还能享受一个额外的好处,即Maven对于项目目录结构、测试用例命名方式等内容都有既定的规则,只要遵循了这些成熟的规则,用户在项目间切换的时候就免去了额外的学习成本,可以说是约定优于配置(Convention Over
Configuration)。

Maven环境搭建

a)Apache Maven下载站点:http://maven.apache.org/download.html

b)maven的安装

把Maven解压到安装目录后,需要设置两个环境变量——PATH和M2_HOME。设置这两个环境变量,假设Maven安装目录是 c:\Program Files\maven-2.0.9,打开-》计算机-》属性-》高级系统设置-》环镜变量。下载下来之后,解压,找个路径放进去, 把bin的位置设在环境变量里,新建环境变量MAVEN_HOME


图4-5 maven的环境变量配置

在PATH里加入maven的bin的路径


图4-6 maven的环境变量配置

c)验证Maven安装

由于Maven依赖Java运行环境,因此使用Maven之前需要配置Java的运行环境。下载并安装JDK,配置JDK的环境变量JAVA_HOME,否则maven将无法使用配置完毕后,在Windows命令提示符下,输入mvn -v测试一下,配置成功显示如图:


图4-7 maven验证安装


4.3、MyBatis自动代码生成

具体看我的另一博文http://blog.csdn.net/evankaka/article/details/47023955


4.4、SVN使用

SVN是Subversion的简称,是一个开放源代码的版本控制系统,相较于RCS、CVS,它采用了分支管理系统,它的设计目标就是取代CVS。集中式管理的工作流程如下图:


图4-8集中式管理的工作流程

集中式代码管理的核心是服务器,所有开发者在开始新一天的工作之前必须从服务器获取代码,然后开发,最后解决冲突,提交。所有的版本信息都放在服务器上。如果脱离了服务器,开发者基本上可以说是无法工作的。下面举例说明:开始新一天的工作:

1、从服务器下载项目组最新代码。

2、进入自己的分支,进行工作,每隔一个小时向服务器自己的分支提交一次代码(很多人都有这个习惯。因为有时候自己对代码改来改去,最后又想还原到前一个小时的版本,或者看看前一个小时自己修改了哪些代码,就需要这样做了)。

3、下班时间快到了,把自己的分支合并到服务器主分支上,一天的工作完成,并反映给服务器。这

就是经典的svn工作流程,从流程上看,有不少缺点,但也有优点。

安装

1、IDE中安装SVN插件

Eclipse中安装可以看,http://now51jq.blog.51cto.com/3474143/1571625

VS中可以安装VisualSVN,它的下载地址:https://www.visualsvn.com/。安装过程:http://blog.csdn.net/lincyang/article/details/5658274(VS上还推荐安装小番茄助手,很强大的一个代码提示插件)

2、TortoiseSVN

这是一个带界面的SVN软件,可以在windows上来使用。有需要可以到http://tortoisesvn.net/来下载。然后直接一路安装即可。


五、其它辅助工具

1、NodePate++

可以用来打开各种文件,如java,xml、配置文件等等,这个很方便,当然也可以用UltraEdit,主要是因为UltraEdit不开源,安装后还得去破解。

2、DBVisualizer

DbVisualizer是一个完全基于JDBC的跨平台数据库管理工具,内置SQL语句编辑器(支持语法高亮),凡是具有JDBC数据库接口的数据库都可以管理,已经在Oracle, Sybase, DB2, Informix, MySQL, InstantDB, Cloudcape, HyperSonic ,Mimer SQL上通过测试。功能很强大,支持市面上主流的所有数据库。

3、Fiddler

Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据(指cookie,html,js,css等文件,这些都可以让你胡乱修改的意思)。 Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。

这个工具我经常用,因为在MVC结构中,你想看看Controller层到底拦截了,或者写爬虫时看到底表单提交了什么,都可以用它来看

4、SecureCRT/FileZilla

SecureCRT是一款支持SSH(SSH1和SSH2)的终端仿真程序,简单地说是Windows下登录UNIX或Linux服务器主机的软件。

FileZilla的功能其实也是一个终端,只不过它带了界面,所以比较友好。一般可以两个结合着一起用,如果是一直反复的下载的上传文件倒是可以用它

5、其它Eclipse插件

编码标准:CheckStyle 插件URL:http://eclipse-cs.sourceforge.net/update/

代码重复:PMD的CPD 插件URL:http://pmd.sourceforge.net/eclipse/

代码覆盖率:Eclemma 插件URL:http://update.eclemma.org

依赖项分析:JDepend 插件URL:http://andrei.gmxhome.de/eclipse/

复杂度分析:Eclipse Metric 插件URL:http://metrics.sourceforge.net/update
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: