时间 : 22-10-03 栏目 : Java技术 作者 : 冰镇宝贝321 评论 : 1 点击 : 1,799 次
继续上一篇 CodeView之写代码的小建议(一)
有些方法,与实例成员变量无关,就可以声明为静态方法。这一点,工具类用得很多
反例:
/** * BigDecimal的工具类 */ public class BigDecimalUtils { public BigDecimal ifNullSetZERO(BigDecimal in) { return in != null ? in : BigDecimal.ZERO; } public BigDecimal sum(BigDecimal ...in){ BigDecimal result = BigDecimal.ZERO; for (int i = 0; i < in.length; i++){ result = result.add(ifNullSetZERO(in[i])); } return result; }
因为BigDecimalUtils工具类的方法都没有static修饰,所以,你要使用的时候,每次都要new一下啦,那不就耗资源去反复创建对象了嘛!!
BigDecimalUtils bigDecimalUtils = new BigDecimalUtils(); bigDecimalUtils.sum(a,b);
所以可以声明成静态变量,使用的时候,直接类名.方法调用即可
正例:
/** * BigDecimal的工具类 */ public class BigDecimalUtils { public static BigDecimal ifNullSetZERO(BigDecimal in) { return in != null ? in : BigDecimal.ZERO; } public static BigDecimal sum(BigDecimal ...in){ BigDecimal result = BigDecimal.ZERO; for (int i = 0; i < in.length; i++){ result = result.add(ifNullSetZERO(in[i])); } return result; }
反例:
public void test(){ try{ //…抛出 IOException 的代码调用 //…抛出 SQLException 的代码调用 }catch(Exception e){ //用基类 Exception 捕捉的所有可能的异常,如果多个层次都这样捕捉,会丢失原始异常的有效信息哦 log.info(“Exception in test,exception:{}”, e); } }
正例:
public void test(){ try{ //…抛出 IOException 的代码调用 //…抛出 SQLException 的代码调用 }catch(IOException e){ //仅仅捕捉 IOException log.info(“IOException in test,exception:{}”, e); }catch(SQLException e){ //仅仅捕捉 SQLException log.info(“SQLException in test,exception:{}”, e); } }
反例:
// 函数封装 public static boolean isUserVip(Boolean isVip) { return Boolean.TRUE.equals(isVip); } // 使用代码 boolean isVip = isVip(user.getUserVip());
正例:
boolean isVip = Boolean.TRUE.equals(user.getUserVip());
函数不要过度封装,把意思表达清楚即可。并且,方法调用会引起入栈和出栈,导致消耗更多的CPU和内存,过度封装,会损耗性能的!
反例:
List<UserInfo> userList = new ArrayList<>(); if (isAll) { userList = userInfoDAO.queryAll(); } else { userList = userInfoDAO.queryActive(); }
正例:
List<UserInfo> userList ; if (isAll) { userList = userInfoDAO.queryAll(); } else { userList = userInfoDAO.queryActive(); }
看下这个浮点数计算的例子吧:
public class DoubleTest { public static void main(String[] args) { System.out.println(0.1+0.2); System.out.println(1.0-0.8); System.out.println(4.015*100); System.out.println(123.3/100); double amount1 = 3.15; double amount2 = 2.10; if (amount1 - amount2 == 1.05){ System.out.println("OK"); } } }
运行结果:
0.30000000000000004 0.19999999999999996 401.49999999999994 1.2329999999999999
因为计算机是以二进制存储数值的,对于浮点数也是。对于计算机而言,0.1无法精确表达,这就是为什么浮点数会导致精确度缺失的。因此,金额计算,一般都是用BigDecimal 类型
System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2))); //output: 0.3000000000000000166533453693773481063544750213623046875
其实,使用 BigDecimal 表示和计算浮点数,必须使用字符串的构造方法来初始化 BigDecimal,并且,还要关注BigDecimal的几位小数点,它有八种舍入模式等
(1)基本类型不能作为 Arrays.asList方法的参数,否则会被当做一个参数。
public class ArrayAsListTest { public static void main(String[] args) { int[] array = {1, 2, 3}; List list = Arrays.asList(array); System.out.println(list.size()); } } //运行结果 1
(2)Arrays.asList 返回的 List 不支持增删操作。
public class ArrayAsListTest { public static void main(String[] args) { String[] array = {"1", "2", "3"}; List list = Arrays.asList(array); list.add("5"); System.out.println(list.size()); } } // 运行结果 Exception in thread "main" java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:148) at java.util.AbstractList.add(AbstractList.java:108) at object.ArrayAsListTest.main(ArrayAsListTest.java:11)
Arrays.asList 返回的 List 并不是我们期望的 java.util.ArrayList,而是 Arrays 的内部类ArrayList。内部类的ArrayList没有实现add方法,而是父类的add方法的实现,是会抛出异常的呢。
(3)使用Arrays.asLis的时候,对原始数组的修改会影响到我们获得的那个List
public class ArrayAsListTest { public static void main(String[] args) { String[] arr = {"1", "2", "3"}; List list = Arrays.asList(arr); arr[1] = "4"; System.out.println("原始数组"+Arrays.toString(arr)); System.out.println("list数组" + list); } } //运行结果 原始数组[1, 4, 3] list数组[1, 4, 3]
应该大家都有过这样的经历,windows系统桌面如果打开太多文件或者系统软件,就会觉得电脑很卡。当然,我们linux服务器也一样,平时操作文件,或者数据库连接,IO资源流如果没关闭,那么这个IO资源就会被它占着,这样别人就没有办法用了,这就造成资源浪费。
所以使用完IO流,记得关闭哈。可以使用try-with-resource关闭的:
/* * */ try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) { // use resources } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); }
在方法函数内,基本类型参数以及临时变量,都是保存在栈中的,访问速度比较快。对象类型的参数和临时变量的引用都保存在栈中,内容都保存在堆中,访问速度较慢。在类中,任何类型的成员变量都保存在堆(Heap)中,访问速度较慢。
public class AccumulatorUtil { private double result = 0.0D; //反例 public void addAllOne( double[] values) { for(double value : values) { result += value; } } //正例,先在方法内声明一个局部临时变量,累加完后,再赋值给方法外的成员变量 public void addAll1Two(double[] values) { double sum = 0.0D; for(double value : values) { sum += value; } result += sum; } }
如果你的Sql一次性查出来的数据量比较多,建议分页处理。
反例:
select user_id,name,age from user_info ;
正例:
select user_id,name,age from user_info limit #{offset},#{pageSize};
如果偏移量特别大的时候,查询效率就变得低下。可以这接种方式优化:
//方案一 :返回上次查询的最大记录(偏移量) select id,name from user_info where id>10000 limit #{pageSize}. //方案二:order by + 索引 select id,name from user_info order by id limit #{offset},#{pageSize} //方案三:在业务允许的情况下限制页数:
一般我们写代码的时候,会以以下的方式实现遍历:
for (int i = 0; i < list.size; i++){ }
如果list数据量比较小那还好。如果list比较大时,可以优化成这样:
for (int i = 0, int length = list.size; i < length; i++){ }
对方法的调用,即使是只有一个语句,也是有有消耗的,比如创建栈帧。如果list比较大时,多次调用list.size也是会有资源消耗的。
很多bug都是因为修改了对外老接口,但是却不做兼容导致的。关键这个问题多数是比较严重的,可能直接导致系统发版失败的。新手程序员很容易就犯这个错误了哦~
所以,如果你的需求是在原来接口上修改,,尤其这个接口是对外提供服务的话,一定要考虑接口兼容。举个例子吧,比如dubbo接口,原本是只接收A,B参数,现在你加了一个参数C,就可以考虑这样处理。
//老接口 void oldService(A,B);{ //兼容新接口,传个null代替C newService(A,B,null); } //新接口,暂时不能删掉老接口,需要做兼容。 void newService(A,B,C);
String name = list.get(1).getName(); //list可能越界,因为不一定有2个元素哈
所以,应该采取措施,预防一下数组边界溢出,
正例:
if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){ String name = list.get(1).getName(); }
public class ArrayListTest { public static void main(String[] args) { List<String> list = new ArrayList<String>(1); list.add("公众号:捡田螺的小男孩"); String[] array21 = (String[])list.toArray();//类型转换异常 } }
因为返回的是Object类型,Object类型数组强转String数组,会发生ClassCastException。解决方案是,使用toArray()重载方法toArray(T[] a)
String[] array1 = list.toArray(new String[0]);//可以正常运行
远程操作或者数据库操作都是比较耗网络、IO资源的,所以尽量不在循环里远程调用、不在循环里操作数据库,能批量一次性查回来尽量不要循环多次去查。(但是呢,也不要一次性查太多数据哈,要分批500一次酱紫)
正例:
remoteBatchQuery(param);
反例:
for(int i=0;i<n;i++){ remoteSingleQuery(param) }
我们经常见的一些业务场景,就是先查下有没有记录,再进行对应的操作(比如修改)。但是呢,(查询+修改)合在一起不是原子操作哦,脑洞下多线程,就会发现有问题了
反例:
if(isAvailable(ticketId){ //非原子操作 1、给现金增加操作 2、deleteTicketById(ticketId) }else{ return "没有可用现金券"; }
为了更容易理解它,看这个流程图吧:
1.线程A加现金
2.线程B加现金
3.线程A删除票标志
4.线程B删除票标志
显然这样存在并发问题,正例应该利用数据库删除操作的原子性,如下:
if(deleteAvailableTicketById(ticketId) == 1){ //原子操作 1、给现金增加操作 }else{ return “没有可用现金券” }
①、它帮我们管理线程,避免增加创建线程和销毁线程的资源损耗。
②、提高响应速度。
③、重复利用。
同时呢,尽量不要所有业务都共用一个线程池,需要考虑线程池隔离。就是不同的关键业务,分配不同的线程池,然后线程池参数也要考虑恰当哈。之前写过几篇线程池的,觉得还不错,有兴趣的朋友可以看一下哈
反例:
public static void listDetail(List<UserInfo> userInfoList) { for (int i = 0; i < userInfoList.size(); i++) { //重复调用userList.size()方法了 } }
正例:
public static void listDetail(List<UserInfo> userInfoList) { int length = userInfoList.size(); for (int i = 0; i < length; i++) { //减少调用userList.size()方法,只在length变量调了一次。 } }
如果一次性把大文件或者数据库太多数据达到内存,是会导致OOM的。所以,为什么查询DB数据库,一般都建议分批。
读取文件的话,一般文件不会太大,才使用Files.readAllLines()。为什么呢?因为它是直接把文件都读到内存的,预估下不会OOM才使用这个吧,可以看下它的源码:
public static List<String> readAllLines(Path path, Charset cs) throws IOException { try (BufferedReader reader = newBufferedReader(path, cs)) { List<String> result = new ArrayList<>(); for (;;) { String line = reader.readLine(); if (line == null) break; result.add(line); } return result; } }
如果是太大的文件,可以使用Files.line()按需读取,当时读取文件这些,一般是使用完需要关闭资源流的哈。
日常开发中,经常需要调用第三方服务,或者分布式远程服务的的话,需要考虑:
①、异常处理(比如,你调别人的接口,如果异常了,怎么处理,是重试还是当做失败)
②、超时(没法预估对方接口一般多久返回,一般设置个超时断开时间,以保护你的接口)
③、重试次数(你的接口调失败,需不需要重试,需要站在业务上角度思考这个问题)
简单一个例子,你一个http请求调别人的服务,需要考虑设置connect-time,和retry次数。
JDK提供原生API方法,可以直接指定集合的容量,避免多次扩容损耗性能。这些方法的底层调用System.arraycopy方法实现,进行数据的批量拷贝效率更高。
反例:
public List<UserInfo> copyMergeList(List<UserInfo> user1List, List<UserInfo> user2List) { List<UserInfo> userList = new ArrayList<>(user1List.size() + user2List.size()); for (UserInfo user : user1List) { userList.add(user); } for (UserInfo user : user2List) { userList.add(user); } return user1List; }
正例:
public List<UserInfo> copyMergeList(List<UserInfo> user1List, List<UserInfo> user2List) { List<UserInfo> userList = new ArrayList<>(user1List.size() + user2List.size()); userList.addAll(user1List); userList.addAll(user2List); return user1List; }
写代码的时候,是没有必要写太多的注释的,好的方法变量命名就是最好的注释。但是,如果是业务逻辑很复杂的代码,真的非常有必要写清楚注释。清楚的注释,更有利于后面的维护。
除非注明,文章均为( 冰镇宝贝321 )原创,转载请保留链接: https://bkqv5.com/archives/641.html