最近尝试在团队里发起一个技术探讨会
尝试推动大家每次选一个小的技术点,进行分享,然后落地成开发规范、code review List
开篇总要说下背景,所以免不了啰嗦几句
首先,代码是程序猿的脸面,怎么能不上心
不久前,看了篇知乎上的文章,顿时菊花一紧,心想,此时难不成在某个地方也有人在吐槽哥的代码
出来混总是要还的,这话还真应验了,前段时间,接触的系统,那个惨不忍睹
喘口气,擦下汗,代码设计这个事情,还真马虎不了,还是多上点心好
知乎文章地址在此,感兴趣可自行阅读
http://www.zhihu.com/question/32039226?from=timeline&isappinstalled=1
另外也由于目前团队存在些问题,让我去思考怎么更好地提升自己的同时,也让团队的小伙伴去提升
前段时间线上出过问题:
找了半天,原来是代码里有redis的危险操作,keys*操作
想来想去,除了可以加强流程规范外,可以尝试弄几轮技术探讨,然后落地成开发规范,code review list
所以就有下面要开始的这个系列主题内容《技术练级》
主要涉及代码规范、系统设计、参数配置优化这些内容
技术成长就如同升级打怪,没有捷径,只有不断的攒经验
磨刀不误砍柴工,大家一起坚持
废话有点多,各位大侠见谅,下面进入正题把
———分割线———
第一期的分享,就从简单的开发实践规范开始,主题是:从合理使用工具开始
合理的选择第三方工具,能够极大的提高我们的开发效率,避免重复造轮子
这些工具经过严格的测试与验证,比自己写得,在性能与稳定性上定然会好很多的
今天要分享的工具为:
guava(Google)
fastjson(阿里)
joda-time(Google)
part1,参数校验
对于参数校验,一般都会习惯这样写
if (StringUtils.isBlank(userName)) {
throw new RuntimeException("名字为空");
}
if (0L == startTime || (0L == endTime)) {
throw new RuntimeException("时间为空");
}
如果if条件比较多,那很可能整屏都是这样的参数校验,对于有洁癖的你肯定在想,有没有更优雅的方式呢
答案当然是有的,guava该出场了
现在可以这样写
Preconditions. checkArgument(userId > 0 || userId == 0 , "userId %s <= 0" , userId) ;
Preconditions. checkArgument(startTime < endTime , "startTime %s >= endTime %s" , startTime , endTime) ;
Preconditions. checkNotNull(userName , "userName is null") ;
Preconditions. checkElementIndex(index , size , "out of bound") ;
// check pass , good news
// just continue to run
} catch ( IllegalArgumentException e) {
// handle log
} catch ( NullPointerException e) {
// handle log
} catch ( IndexOutOfBoundsException e) {
// handle log
}
Preconditions.checkArgument 可以根据传进来的条件进行判断,不满足会抛出 IllegalArgumentException,可以传入异常的信息,我们可以catch住然后进一步处理
Preconditions.checkNotNull 非空判断简单如此,传入要校验的对象以及异常信息,对象为空就抛空指针异常
Preconditions.checkElementIndex 这个可以作为越界的校验,具体就不说了,一看就懂
part2,类型结构增强
新建一个list,一般会这样写
List<String> list = new ArrayList<String>();
而使用 guava ,则可以这样写
List<String> list1 = Lists.newArrayList();
当然从JDK7开始,也可以这样写了,新增了个diamond的类型符
List<String> list2 = new ArrayList<>();
如果要创建的同时进行初始化,可以用guava这样写
List<String> list3 = Lists.newArrayList("alpha", "beta", "gamma");
虽然可以直接使用JDK的Arrays方法,但感觉guava的更好理解
List<String> list4 = Arrays.asList("a", "b");
如果知道未来要使用的空间大小,也可以这样
List<String> exactly100 = Lists.newArrayListWithCapacity(100);
字符串拼接分割处理,是我们也经常会遇到的
guava提供了非常便利的API接口
String str = Joiner. on( "|").skipNulls().join(appIds) ;
System. out.println(str) ; //1|2|3|4|5
List< String> appIdStr = Splitter. on( "|").splitToList(str) ;
System. out.println(appIdStr) ;
Map< String , String> maps = ImmutableMap. of( "1" , "11" , "2" , "22" , "3" , "33") ;
String mapStr = Joiner. on( "|").withKeyValueSeparator( ":").join(maps) ;
System. out.println(mapStr) ; //1:11|2:22|3:33
Map< String , String> maps2 = Splitter. on( "|").withKeyValueSeparator( ":").split(mapStr) ;
System. out.println(maps2) ;
part3,localcache
有时候我们会需要在本地JVM构建一个缓存,来存放可能会频繁访问的数据
一般情况都会采用像ConcurrentHashMap这样的数据结构来存储数据,用它的好处也比较明显,一来对API接口都比较熟悉,二是使用起来也简单容易理解
不好的地方就是,需要我们自己控制缓存的大小,有时候可能还会存在一些已经过期的数据,但还占用着内存,而这个过期数据的清除也没有很好的方法,自己写的话,需要花点精力还特别容易出问题
对此,guava给我们提供了一个选择,带有数据量限制与过期数据清除功能的本地缓存API
使用guava提供的API,可以方便的构建出本地缓存
我们可以设置最大的存储量限制、数据多久失效
这个cache内部也是基于Map实现的,使用上就跟普通的hashmap一样,直接调用 get 方法来获取数据即可;cache内部的并发控制也采用了类似ConcurrentHashMap的分段锁的机制
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(5)
.expireAfterAccess(3, TimeUnit.SECONDS)
.removalListener(new MyListener())
.build(
new CacheLoader<String, String>() {
public String load(String key) throws Exception {
System.out.println("load "+key);
return key + new DateTime().toString(" yyyy-MM-dd HH:mm:ss");
}
}
);
static class MyListener implements RemovalListener {
@Override
public void onRemoval(RemovalNotification notification) {
System.out.println("key value was removed: "+notification.getKey()+" "+notification.getValue());
}
}
cache.get(key);
guava官方文档在此: https://github.com/google/guava/wiki
英文翻译版: http://ifeve.com/google-guava/
part4,json
我们使用json的场景蛮多,如:
在网络传输上,使用json格式是个比较方便好阅读的方式
部分存储系统的里数据,会使用json格式,然后读出来会直接反序列化成对象来使用,甚是方便
至于json工具呢,这里推荐使用fastjson
官方称 fastjson性能比目前的json工具都快
fastjson API使用起来简单好理解
直接上官方的例子,我加了个日期字段,User的一个属性没有赋值,然后使用的序列化方法换成JSON.toJSONStringWithDateFormat
Group group = new Group();
group.setId(0L);
group.setName("admin");
User guestUser = new User();
guestUser.setId(2L);
guestUser.setName("guest");
guestUser.setDate(new Date());
User rootUser = new User();
rootUser.setId(3L);
// rootUser.setName("root");
group.addUser(guestUser);
group.addUser(rootUser);
结果输出如下:
//fastjson 序列化
String jsonString = JSON.toJSONStringWithDateFormat(group, "yyyy-MM-dd HH:mm:ss.SSS");
//{"id":0,"name":"admin","users":[{"date":"2015-12-16 21:00:30.592","id":2,"name":"guest"},{"id":3}]}
System.out.println(jsonString);
//fastjson 反序列化
Group newGroup = JSON.parseObject(jsonString, Group.class);
//Group{id=0, name='admin', users=[User{id=2, name='guest', date=Wed Dec 16 21:00:30 CST 2015}, User{id=3, name='null', date=null}]}
System.out.println(newGroup);
可以看到,对于有日期的对象,可以序列化成固定格式
同时,反序列化能够自动识别日期的数据
这里要注意下,反序列化时,对于没有值得字符串属性,会默认赋值 “null”
fastjson 官方地址: https://github.com/alibaba/fastjson
part5,日期时间
我们经常需要获取一个日期时间,并以一个比较容易阅读的格式来展示
好像用fastjson的API也可以
String jsonDateStr = JSON.toJSONStringWithDateFormat(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
System.out.println(jsonDateStr);//2015-12-16 22:06:10.330
不过更加专业的方法当然是使用时间相关API了,这里介绍 joda-time
DateTime dt = new DateTime();
System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));//2015-12-17 21:59:12
创建一个日期时间对象,并格式化输出,好简单
也可以创建固定时间的日期对象
DateTime dateTime=new DateTime(2015, 12, 15, 18, 23, 55);
可以处理时间字符串,转化成日期对象,如下:
DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
DateTime dateTime2 = DateTime.parse("2015-12-17 23:22:45", format);
对获取的时间可以进行操作,加一天,加一月,减小时、分钟,就是一个方法调用这么简单
dateTime = dateTime.plusDays(1) // 增加天
.plusYears(1)// 增加年
.plusMonths(1)// 增加月
.plusWeeks(1)// 增加星期
.minusMinutes(1)// 减分钟
.minusHours(1)// 减小时
.minusSeconds(1);// 减秒数
获取当前具体的某天某点,可以这样取,dateTime本身提供非常便利的接口
System.out.println(dateTime.getYear());
System.out.println(dateTime.getMonthOfYear());
System.out.println(dateTime.getDayOfMonth());
System.out.println(dateTime.getDayOfWeek());
System.out.println(dateTime.getHourOfDay());
System.out.println(dateTime.getMinuteOfHour());
时间的比较直接调用接口即可,如下:如当前时间比较或者两个时间比较
dateTime.isBeforeNow();
dateTime.isAfter(dateTime2);
joda-time与JDK能够简单方便的转换
Date date = new Date();
DateTime fromJDKDate = new DateTime(date);
Date toJDKDate = dateTime.toDate();
“麻麻再也不用担心我的’时间管理’了”
JDK8之前,JDK的时间API接口特别不好用,但JDK8之后,一切都不一样了
原来新的时间API接口负责人就是joda-time的开发者
在JDK8里,我们也终于能够好好的跟时间玩耍了
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
System.out.println(localDateTime.getHour());
System.out.println(localDateTime.getDayOfMonth());
localDateTime = LocalDateTime.of(2015, 12, 20, 20, 20, 20);
System.out.println(localDateTime);
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(localDateTime.format(f));
localDateTime = LocalDateTime.parse("2015-12-17 23:22:45", f);
System.out.println(localDateTime.format(f));
在没有升级到JDK8前,还是用joda-time把
joda-time 官方地址: http://www.joda.org/joda-time/index.html
另外还有经常使用的工具如Apache common包,Apache HttpComponents包(替换HTTPclient)
忠告:
这些工具用好了就是一把利剑
而要用好他们还是得稍稍了解他们的用法及API的大致原理,避免踩坑