时间 : 22-10-03 栏目 : Java技术 作者 : 冰镇宝贝321 评论 : 1 点击 : 2,972 次
继续上一篇 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