时间 : 22-09-06 栏目 : Java技术 作者 : 冰镇宝贝321 评论 : 2 点击 : 4,028 次
工作了几年,看过很多思考不够深入的代码,因此写一下总结吧,主要是一些代码更好的建议,这次进行下汇总。希望大家日常写代码多点思考,多点总结,加油!同时哪里有不对的,也望指出,感谢哈~
我们经常遇到类似的业务场景,如,判断某个用户userId是否是会员。
(反例): 一些小伙伴会这样实现,先查从用户信息表查出用户记录,然后再去判断是否是会员:
<select id="selectUserByUserId" resultMap="BaseResultMap">
selct user_id , vip_flag from user_info where user_id =#{userId};
</select>
boolean isVip (String userId){
UserInfo userInfo = userInfoDAp.selectUserByUserId(userId);
return UserInfo!=null && "Y".equals(userInfo.getVipFlag())
}
(正例): 针对这种业务场景,其实更好的实现,是直接select count一下,如下:
<select id="countVipUserByUserId" resultType="java.lang.Integer">
selct count(1) from user_info where user_id =#{userId} and vip_flag ='Y';
</select>
boolean isVip (String userId){
int vipNum = userInfoDAp.countVipUserByUserId(userId);
return vipNum>0
}
假设业务需求是这样:如果用户是会员,并且第一次登陆时,需要发一条通知的短信。假如没有经过思考,代码很可能直接这样写了。
if(isUserVip && isFirstLogin){
sendMsgNotify();
}
假设总共有5个请求进来,isUserVip通过的有3个请求,isFirstLogin通过的有1个请求。 那么以上代码,isUserVip执行的次数为5次,isFirstLogin执行的次数也是3次,如下:

如果调整一下isUserVip和isFirstLogin的顺序呢?
if(isFirstLogin && isUserVip ){
sendMsg();
}
isFirstLogin执行的次数是5次,isUserVip执行的次数是1次,如下:

如果你的isFirstLogin,判断逻辑只是select count 一下数据库表,isUserVip也是select count 一下数据库表的话,显然,把isFirstLogin放在前面更高效。
反例:
select * from user_info where user_id =#{userId};
正例:
selct user_id , vip_flag from user_info where user_id =#{userId};
节省资源、减少网络开销。
可能用到覆盖索引,减少回表,提高查询效率。
如果你的变量,后面的逻辑判断,一定会被赋值;或者说,只是一个字符串变量,直接初始化字符串常量就可以了,没有必要愣是要new String().
反例:
String s = new String ("欢迎关注公众号:冰镇小屋");
正例:
String s= "欢迎关注公众号:冰镇小屋";
阿里的开发手册,也明确提到这个点:

假设你的map要存储的元素个数是15个左右,最优写法如下
//initialCapacity = 15/0.75+1=21 Map map = new HashMap(21); 又因为hashMap的容量跟2的幂有关,所以可以取32的容量 Map map = new HashMap(32);
反例:
try{
// do something
}catch(Exception e){
log.info(",你的程序有异常啦");
}
正例:
try{
// do something
}catch(Exception e){
log.info("你的程序有异常啦:",e); //把exception打印出来
}
反例中,并没有把exception出来,到时候排查问题就不好查了啦,到底是SQl写错的异常还是IO异常,还是其他呢?所以应该把exception打印到日志中哦~
我们在打印日志的时候,经常想看下一个请求参数对象request是什么。于是很容易有类似以下这些代码:
publick Response dealWithRequest(Request request){
log.info("请求参数是:".request.toString)
}
打印结果如下:请求参数是:local.Request@49476842
这是因为对象的toString方法,默认的实现是“类名@散列码的无符号十六进制”。所以你看吧,这样子打印日志就没啥意思啦,你都不知道打印的是什么内容。
所以一般对象(尤其作为传参的对象),都覆盖重写toString()方法:
class Request {
private String age;
private String name;
@Override
public String toString() {
return "Request{" +
"age='" + age + '\'' +
", name='" + name + '\'' +
'}';
}
}
publick Response dealWithRequest(Request request){
log.info("请求参数是:".request.toString)
}
打印结果如下:请求参数是:Request{age='26', name='
假设有这么一个公有方法,形参有四个。。。
public void getUserInfo(String name,String age,String sex,String mobile){
// do something ...
}
如果现在需要多传一个version参数进来,并且你的公有方法是类似dubbo这种对外提供的接口的话,那么你的接口是不是需要兼容老版本啦?
public void getUserInfo(String name,String age,String sex,String mobile){
// do something ...
}
/**
* 新接口调这里
*/
public void getNewUserInfo(String name,String age,String sex,String mobile,String version){
// do something ...
}
所以呢,一般一个方法的参数,一般不宜过长。过长的参数列表,不仅看起来不优雅,并且接口升级时,可能还要考虑新老版本兼容。如果参数实在是多怎么办呢?可以用个DTO对象包装一下这些参数呢~如下:
public void getUserInfo(UserInfoParamDTO userInfoParamDTO){
// do something ...
}
class UserInfoParamDTO{
private String name;
private String age;
private String sex;
private String mobile;
}
用个DTO对象包装一下,即使后面有参数变动,也可以不用动对外接口了,好处杠杠的。
反例:
/**
*
* @desc: 复制一张图片文件
*/
public class MainTest {
public static void main(String[] args) throws FileNotFoundException {
long begin = System.currentTimeMillis();
try (FileInputStream input = new FileInputStream("C:/456.png");
FileOutputStream output = new FileOutputStream("C:/789.png")) {
byte[] bytes = new byte[1024];
int i;
while ((i = input.read(bytes)) != -1) {
output.write(bytes,0,i);
}
} catch (IOException e) {
log.error("复制文件发生异常",e);
}
log.info("常规流读写,总共耗时ms:"+(System.currentTimeMillis() - begin));
}
}
运行结果:常规流读写,总共耗时ms:52
使用FileInputStream、FileOutputStream实现文件读写功能,是没有什么问题的。但是呢,可以使用缓冲流BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream等,减少IO次数,提高读写效率。
如果是不带缓冲的流,读取到一个字节或者字符的,就会直接输出数据了。而带缓冲的流,读取到一个字节或者字符时,先不输出,而是等达到缓冲区的最大容量,才一次性输出。
正例:
/**
* @desc: 复制一张图片文件
*/
public class MainTest {
public static void main(String[] args) throws FileNotFoundException {
long begin = System.currentTimeMillis();
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C:/456.png"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("C:/789.png"))) {
byte[] bytes = new byte[1024];
int i;
while ((i = input.read(bytes)) != -1) {
output.write(bytes,0,i);
}
} catch (IOException e) {
log.error("复制文件发生异常",e);
}
log.info("总共耗时ms"+(System.currentTimeMillis() - begin));
}
}
运行结果:缓冲流读写,总共耗时ms:12
反例:
public Response dealRequest(Request request){
UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
if(Objects.isNull(request)){
return ;
}
insertUserVip(request.getUserId);
}
private int insertUserVip(String userId){
//又查了一次
UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
//插入用户vip流水
insertUserVipFlow(userInfo);
....
}
很显然,以上程序代码,已经查到 userInfo,然后又把userId传下去,又查多了一次。。。实际上,可以把userInfo传下去的,这样可以省去一次查表操作,程序更高效。
正例:
public Response dealRequest(Request request){
UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
if(Objects.isNull(request)){
return ;
}
insertUserVip(userInfo);
}
private int insertUserVip(UserInfo userInfo){
//插入用户vip流水
insertUserVipFlow(userInfo);
....
}
反例:
if("0".equals(userInfo.getVipFlag)){
//非会员,提示去开通会员
tipOpenVip(userInfo);
}else if("1".equals(userInfo.getVipFlag)){
//会员,加勋章返回
addMedal(userInfo);
}
正例:
if(UserVipEnum.NOT_VIP.getCode.equals(userInfo.getVipFlag)){
//非会员,提示去开通会员
tipOpenVip(userInfo);
}else if(UserVipEnum.VIP.getCode.equals(userInfo.getVipFlag)){
//会员,加勋章返回
addMedal(userInfo);
}
public enum UserVipEnum {
VIP("1","会员"),
NOT_VIP("0","非会员"),:;
private String code;
private String desc;
UserVipEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
写代码的时候,不要一时兴起,就直接使用魔法值哈。使用魔法值,维护代码起来很难受的。
反例:
public class Task {
private final long timeout = 10L;
...
}
正例:
public class Task {
private static final long TIMEOUT = 10L;
...
}
因为如果定义为static,即类静态常量,在每个实例对象中,它只有一份副本。如果是成员变量,每个实例对象中,都各有一份副本。显然,如果这个变量不会变的话,定义为静态常量更好一些。
NullPointerException 在我们日常开发中非常常见,我们代码开发过程中,一定要对空指针保持灵敏的嗅觉。
主要有这几类空指针问题:
包装类型的空指针问题
级联调用的空指针问题
Equals方法左边的空指针问题
ConcurrentHashMap 类似容器不支持 k-v为 null。
集合,数组直接获取元素
对象直接获取属性
反例:
public class NullPointTest {
public static void main(String[] args) {
String s = null;
if (s.equals("666")) { //s可能为空,会导致空指针问题
System.out.println("公众号:冰镇小屋,干货满满");
}
}
}
反例:
public static void testIgnoreException() throws Exception {
try {
// 搞事情
} catch (Exception e) {
//捕获了异常,啥事情不做,日志也不打??
}
}
正例:
public static void testIgnoreException() {
try {
// 搞事情
} catch (Exception e) {
log.error("异常了,联系开发小哥哥看看哈",e);
}
}
JDK8出现了新特性-Lambda表达式。Lambda表达式不仅比匿名内部类更加优雅,并且在大多数虚拟机中,都是采用invokeDynamic指令实现,相对于匿名内部类,效率也更高
反例:
public void sortUserInfoList(List<UserInfo> userInfoList){
userInfoList.sort(new Comparator<UserInfo>() {
@Override
public int compare(UserInfo user1, UserInfo user2) {
Long userId1 = user1.getUserId();
Long userId2 = user2.getUserId();
return userId1.compareTo(userId2);
}});
}
正例:
public void sortUserInfoList(List<UserInfo> userInfoList){
userInfoList.sort((user1, user2) -> {
Long userId1 = user1.getUserId();
Long userId2 = user2.getUserId();
return userId1.compareTo(userId2);
});
}
假设业务流程这样:需要在用户登陆时,添加个短信通知它的粉丝。 很容易想到的实现流程如下:

假设提供sendMsgNotify服务的系统挂了,或者调用sendMsgNotify失败了,那么用户登陆就失败了。。。 一个通知功能导致了登陆主流程不可用,明显地捡了芝麻丢西瓜。那么有没有鱼鱼熊掌兼得的方法呢?有的,给发短信接口捕获异常处理,或者另开线程异步处理,如下:

因此,添加通知类等不是非主要,可降级的接口时,应该静下心来考虑是否会影响主要流程,思考怎么处理最好。
日常开发中,我们经常需要处理日期。我们要当时日期格式化的时候,年份是大写YYYY的坑。
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("2019-12-31 转 YYYY-MM-dd 格式后 " + dtf.format(testDate));
运行结果:2019-12-31 转 YYYY-MM-dd 格式后 2020-12-31
为什么明明是2019年12月31号,就转了一下格式,就变成了2020年12月31号了?因为YYYY是基于周来计算年的,它指向当天所在周属于的年份,一周从周日开始算起,周六结束,只要本周跨年,那么这一周就算下一年的了。正确姿势是使用yyyy格式。

正例:
public final class Tools {
public static void testFinal(){
System.out.println("工具类方法");
}
}
一个类指定了final修饰符,它不会被继承了,并且其所有方法都是final的了。Java编译器会找机会内联所有的final方法,提升了Java运行效率。
之前看到项目有类似的代码。静态变量依赖于spring容器的bean。
private static SmsService smsService = SpringContextUtils.getBean(SmsService.class);
private static SmsService smsService =null;
//使用到的时候采取获取
public static SmsService getSmsService(){
if(smsService==null){
smsService = SpringContextUtils.getBean(SmsService.class);
}
return smsService;
}
除非注明,文章均为( 冰镇宝贝321 )原创,转载请保留链接: https://bkqv5.com/archives/633.html