一些代码优化的tips(JAVA)

整理数据来源:

1)SonarQueb

2)阿里巴巴规范

3)代码整洁之道

 

1:做有意义的区分:

public static void copyChars(char al[], char a2[]) {
        for (int i = 0; i < al.length; i++) {
            a2[i] = al[i];
        }
    }

如果参数名改为 source和 destination,这个函数就会像样许多。

 

2:没有区别的命名

1)Product、ProductData、ProductInfo

2)Custmoer、CustomerObject,Object多余, NameString和Name,String多余

3)getActiveCount, getActivtCountInfo

4)MoneyAmount, amount

 

3:如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了例如,下面两个声明的差别:

Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

函数的参数量应该少。没参数最好,一个次之,两个、三个再次之。三个以上的参数非常值得质疑,应坚决避免。

如果参数确实过多,把参数分装到一个类或者使用构造者模式。

 

4:函数单一性原则:函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态或是返回该对象的有关信息。两样都干常会导致混乱。

复制代码
public class User {
    private String name;
    private int age;

    // 违反单一性原则的函数
    public String updateUserInfo(String newName, int newAge) {
        // 更新用户信息
        this.name = newName;
        this.age = newAge;

        // 返回用户信息
        
        return "User information: " + this.name + ", " + this.age;
} }
复制代码

 

改成:

复制代码
public class User {
    private String name;
    private int age;

    // 仅负责更新用户信息的函数
    public void updateInfo(String newName, int newAge) {
        this.name = newName;
        this.age = newAge;
    }

    // 仅负责返回用户信息的函数
    public String getUserInfo() {
        return "User information: " + this.name + ", " + this.age;
    }
}
复制代码

 

 

5:使用异常替代错误返回码,C 语言没有异常这样的语法机制,返回错误码便是最常用的出错处理方式

  1. 清晰的代码流程: 使用异常能够使代码流程更为清晰。在正常情况下,代码按照顺序执行;而在异常发生时,可以通过捕获异常的方式跳转到相应的错误处理逻辑,使代码的执行路径更为明确。

  2. 分离正常流程和错误处理逻辑: 异常提供了一种分离正常流程和错误处理逻辑的机制。正常情况下的代码不会因为错误处理逻辑而变得混乱,而错误处理逻辑则专注于处理异常情况。

  3. 减少错误代码的嵌套: 使用异常能够减少嵌套的错误检查代码。如果使用错误返回码,代码可能需要嵌套多层条件语句来检查各种错误情况,而异常处理可以更为简洁。

  4. 易于扩展: 异常机制允许更灵活地处理不同类型的错误。你可以定义不同的异常类,每个类用于表示特定类型的错误,从而提高代码的可扩展性。

  5. 避免遗漏错误检查: 错误返回码需要开发人员显式地检查每次调用的返回值,而异常在发生时会中断正常的代码流程,避免了遗漏错误检查的问题。

  6. 更具语义: 异常的使用能够使代码更具语义,因为异常通常用于表示某种不可预测的错误状况,而不仅仅是一种返回码。

 

6:抽离TryCatch 代码块
Try/catch 代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把ty 和catch 代码块的主体部分抽离出来,另外形成函数。

复制代码
public class Example {
    public void someMethod() {
        try {
            // 正常流程的代码块
            performNormalOperation();
        } catch (SomeException e) {
            // 异常处理的代码块
            handleException(e);
        }
    }

    // 抽离出的正常流程代码
    private void performNormalOperation() {
        // 具体的正常流程代码
    }

    // 抽离出的异常处理代码
    private void handleException(SomeException exception) {
        // 具体的异常处理代码
    }
}
复制代码

 

7:代码中不要返回null,要抛出异常或者返回特例对象

复制代码
// 不推荐的方式,返回 null 表示未找到
public String findUserEmailById(String userId) {
    // 查询用户邮箱的逻辑
    String email = database.lookupUserEmail(userId);
    if (email != null) {
        return email;
    } else {
        return null; // 返回 null 表示未找到
    }
}
复制代码

推荐:

复制代码
// 推荐的方式,使用异常或者返回特例对象
public String findUserEmailById(String userId) {
    // 查询用户邮箱的逻辑
    String email = database.lookupUserEmail(userId);
    if (email != null) {
        return email;
    } else {
        throw new UserNotFoundException("User not found for id: " + userId);
        // 或者,根据情况返回一个特定的值,如空字符串
        // return "";
    }
}
复制代码

调用者不需要在每个方法调用之后检查返回值,从而简化了代码流程。只有在发生异常时才需要处理错误情况。

 

或者返回空对象:

复制代码
public List<User> listUser() {
    // 使用 userListRepostity 对象执行数据库查询,获取用户列表
    List<User> userList = userListRepostity.selectByExample(new UserExample());    
    
    // 检查查询结果是否为空
    if (CollectionUtils.isEmpty(userList)) {    
        // 如果用户列表为空,返回一个空的 ArrayList(使用 Guava 类库提供的 Lists 工具类创建)
        return Lists.newArrayList();
    }    
    
    // 如果用户列表不为空,直接返回查询结果
    return userList;
}
复制代码

 

适合返回空对象的情况通常是在返回集合(如 List)等容器对象的时候,因为这样可以提供一个空的容器,而不是 null。这使得调用者可以直接对容器进行操作,而无需在每次操作前进行 null 检查。

对于复杂的对象,抛出异常可能是一种更合适的策略,尤其是在对象的属性中存在关键信息,而这些信息在对象为空的情况下是无法提供的。在这种情况下,抛出异常可以迅速引起注意,防止调用者继续操作可能导致错误的对象。

 

8:不要写冗余的注释

复制代码
/**
  *默认构造函数
  */
  protected AnnualDateRule();
  /**
    *每月天数
    */
  private int dayofMonth;
/**
  *
  *@return 每月天数
  */
public int getDayOfMonth(){
    return dayofMonth;
}
复制代码

不用的代码要不删掉,要不注释说明不要删。如果注释了大段代码,又不做任何说明,其他人看见了也不敢删掉,或者本来是还有用的代码被误删了。

这样导致注释掉的代码堆积在一起,越来越臃肿。

 

9:尽量避免布尔型参数" 是一个常见的函数设计原则。

如果一个简单的布尔参数能够清晰表达函数的意图,且函数逻辑简单,那么使用一个布尔参数是合理的。

例子1:枚举替代bool:
// 布尔值
public void processPermission(String userId, boolean isAdmin) {
    // 处理管理员权限逻辑
}

// 枚举
public void processPermission(String userId, UserRole role) {
    // 处理权限逻辑

更具可读性: 枚举常常具有更具描述性的名称,这使得代码更容易理解。相比之下,布尔值可能不提供足够的上下文信息,而开发者需要查看文档或者函数定义才能理解其含义。

避免歧义: 布尔值可能存在歧义,因为 truefalse 可能被错误地理解。枚举通过明确的命名和取值,降低了这种歧义的可能性。

更好的扩展性: 如果将来需要添加更多的选项,枚举更容易扩展,而不会改变函数的签名。相比之下,如果使用布尔值,添加新的选项可能需要修改函数的参数列表。

例子2:违法单一责任原则

复制代码
public class PermissionHandler {
    public void processPermission(String userId, boolean isAdmin) {
        if (isAdmin) {
            // 处理管理员权限逻辑
        } else {
            // 处理普通用户权限逻辑
        }
    }
}
复制代码

上述代码通过 isAdmin 参数来判断用户是管理员还是普通用户,然后执行相应的逻辑。这样的设计可能存在问题,因为 processPermission 函数涉及到两种不同的逻辑,而这两种逻辑可能会随着时间的推移而扩展,导致函数的职责不单一。

 

10:尽量用多态替代switch/case
复制代码
public void drawShape(ShapeType shapeType) {
    switch (shapeType) {
        case CIRCLE:
            // 绘制圆形的操作
            break;
        case RECTANGLE:
            // 绘制矩形的操作
            break;
        // 可能还有其他图形类型的处理
    }
}
复制代码

这样的设计可能不够灵活,尤其是当需要添加新的图形类型时,就需要修改这个函数并添加新的 case。

通过应用多态性,我们可以改进这个设计。首先,定义一个基类 Shape

public abstract class Shape {
    public abstract void draw();
}
复制代码
public class Circle extends Shape {
    @Override
    public void draw() {
        // 绘制圆形的操作
    }
}

public class Rectangle extends Shape {
    @Override
    public void draw() {
        // 绘制矩形的操作
    }
}

// 可能还有其他图形类型的类
复制代码
public void drawShape(Shape shape) {
    shape.draw();
}

当需要添加新的图形类型时,只需要创建一个新的继承自 Shape 的子类,并实现其 draw 方法,而无需修改原有的代码,符合开闭原则。


11:

 12:

 

13:不要使用e.printStackTrace()

不要使用的理由:

  • e.printStackTrace()和框架日志是两套输出系统,打印出的堆栈日志跟业务代码日志是交错混合在一起的,通常排查异常日志不太方便。
  • e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,那么,用户的请求就卡住了

 

14:异常日志不要只打一半,要输出全部错误信息

try {
    //业务代码处理
} catch (Exception e) {
    // 错误
    LOG.error('你的程序有异常啦');
}

正确的用法:

try{
  // 业务代码处理
}catch(Exception e){
  log.error("你的程序有异常啦",e);
}

 

15:使用占位符,避免字符串拼接产生的性能损耗

Timber.d("packageName--->" +packageName);
Timber.d("packageName--->%s", packageName);

 

16:集合初始化尽量指定大小 java 的集合类用起来十分方便,但是看源码可知,集合也是有大小限制的。每次扩容的时间复杂度很有可能是 O(n) ,所以尽量指定可预知的集合大小,能减少集合的扩容次数。

反例:

int[] arr = new int[]{1, 2, 3};
List<Integer> list = new ArrayList<>();
for (int i : arr) {
    list.add(i);
}

正例:

int[] arr = new int[]{1, 2, 3};
List<Integer> list = new ArrayList<>(arr.length);
for (int i : arr) {

 

17:工具类是一堆静态字段和函数的集合,不应该被实例化。但是,Java 为每个没有明确定义构造函数的类添加了一个隐式公有构造函数。所以,为了避免 java "小白"使用有误,应该显式定义私有构造函数来屏蔽这个隐式公有构造函数。

反例:

public class MathUtils {
public static final double PI = 3.1415926D;
public static int sum(int a, int b) {
return a + b;
}
}

正例:

public class MathUtils {
public static final double PI = 3.1415926D;
private MathUtils() {}
public static int sum(int a, int b) {
return a + b;
}
}

 

18:

避免使用捕获异常的方式处理空指针异常,而是通过代码规避(比如检测不为空),是一种更好的做法,主要有以下几个原因:

  1. 性能开销: 捕获异常本身是有一定的性能开销的。当发生空指针异常时,Java虚拟机需要构建异常对象,跳转到相应的异常处理代码,这会导致一些额外的开销。相比之下,通过代码规避可以避免这些额外的性能开销。

  2. 代码可读性: 异常通常应该用于处理意外的、不可预测的情况,而不应该用于控制流。使用异常处理空指针异常可能使代码更难理解,因为异常通常被认为是处理异常情况的手段,而不是在正常控制流中的应用。

反例:

public String getUserName(User user) {
try {
return user.getName();
} catch (NullPointerException e) {
return null;
}
}

正例:

public String getUserName(User user) {
if (Objects.isNull(user)) {
return null;
}
return user.getName();
}

 

19:当循环中只需要 Map 的主键时,迭代 keySet() 是正确的。但是,当需要主键和取值时,迭代 entrySet() 才是更高效的做法,比先迭代 keySet() 后再去 get 取值性能更佳。

反例:

复制代码
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

// 使用 keySet() 迭代主键,再通过 get() 方法获取值
for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println("Key: " + key + ", Value: " + value);
}
复制代码

正例:

复制代码
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

// 使用 entrySet() 迭代主键和取值
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
}
复制代码

 

20:用同步代码块代替同步方法:加入mkdir需要1ms,uploadFile和sendMessage需要100ms,这样就能显著降低锁的颗粒度。

反例:

 
public synchronized doSave(String fileUrl) {
    mkdir();
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

正例:

复制代码
 
public void doSave(String path,String fileUrl) {
    synchronized(this) {
      if(!exists(path)) {
          mkdir(path);
       }
    }
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}
复制代码

 

21:复制文件的时候,使用可缓冲的IO流:使用可缓冲的IO流(Buffered IO Stream)在复制文件时可以提高性能的原因主要是减少了实际的物理IO次数。当你使用非缓冲的IO流时,每次读取或写入都会直接涉及到磁盘或网络操作,而这些操作通常是相对较慢的。使用缓冲IO流可以通过在内存中建立一个缓冲区,将数据暂时存储在缓冲区中,然后批量地进行IO操作,减少了直接与外部资源进行IO的次数,从而提高了性能。

非缓冲:

复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopyWithoutBuffer {
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("source.txt");
            FileOutputStream fos = new FileOutputStream("destination.txt");

            int byteRead;
            while ((byteRead = fis.read()) != -1) {
                fos.write(byteRead);
            }

            fis.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
复制代码

在这个例子中,每次调用read()write()方法都会导致实际的IO操作,这可能会导致性能较差,特别是当文件较大时。

复制代码
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopyWithBuffer {
    public static void main(String[] args) {
        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.txt"));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("destination.txt"));

            int bytesRead;
            byte[] buffer = new byte[4096]; // 缓冲区大小为4KB
            while ((bytesRead = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }

            bis.close();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
复制代码

 

22:

当编写可能会抛异常的代码时,先写好 try-catch-finally 再往里堆逻辑

 

23:

根据业务定义不同的 异常类,尽量避免直接使用 ThrowableExceptionRuntimeException 捕获 业务层面 的异常。

清晰的异常层次结构: 使用业务定义的异常类能够建立清晰的异常层次结构。每个业务异常类都可以继承自通用的基础异常类,这有助于组织和管理异常,使异常结构更具层次性。

复制代码
public class BusinessException extends Exception {
    // 自定义业务异常类
}

public class DataNotFoundException extends BusinessException {
    // 数据未找到异常类
}

public class InvalidInputException extends BusinessException {
    // 无效输入异常类
}
复制代码
复制代码
try {
    // 业务逻辑代码
} catch (DataNotFoundException e) {
    // 处理数据未找到异常
    log.warn("数据未找到:" + e.getMessage());
} catch (InvalidInputException e) {
    // 处理无效输入异常
    log.warn("无效输入:" + e.getMessage());
} catch (BusinessException e) {
    // 处理其他业务异常
    log.error("业务异常:" + e.getMessage());
} catch (Exception e) {
    // 处理其他异常
    log.error("未知异常:" + e.getMessage());
}
复制代码

 

24:尽量减少对变量的重复计算

for (int i = 0; i < list.size(); i++)
{...}

改为:

for (int i = 0, int length = list.size(); i < length; i++)
{...}

在list.size()很大的时候,就减少了很多的消耗

 

25:乘法和除法使用移位操作,如果你读过JDK的源码,比如:ThreadLocal、HashMap等类,你就会发现,它们的底层都用了位运算。因为用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的

<< 左移相当于乘以 2;>> 右移相当于除以 2;

for (val = 0; val < 100000; val += 5)
{
    a = val * 8;
    b = val / 2;
}

改成:

for (val = 0; val < 100000; val += 5)
{
    a = val << 3;
    b = val >> 1;
}

 

26:对资源的close()建议分开操作,避免资源未关闭导致的内存泄露

try{
  XXX.close();
  YYY.close();
}catch (Exception e)
{...}

改为:

try{ XXX.close(); 
}catch (Exception e)
{ ... }

try{ YYY.close();
}
catch (Exception e)

 

27:包命名全部采用小写,不用下划线区分单词

 

 // 正确的包名 package com.example.myapp;

// 错误的包名 package com.example.MyApp;

                     package com.example.my_app;

28:不要使用异常来控制流程, 异常的本意是处理异常情况,而不是作为正常的控制流程的一部分。过度使用异常来实现正常的控制流会使代码变得难以理解,降低代码的可读性。其他开发人员可能会难以理解代码的预期行为,导致维护困难。

复制代码
try {
    while (true) {
        // 一些操作
        if (someCondition) {
            throw new RuntimeException("循环已经完成");
        }
    }
} catch (RuntimeException e) {
    // 处理异常
}
复制代码

或者try空指针,然后在catch里面执行业务代码

复制代码
public static int getLength(String text) {
        try {
            return text.length();  // 可能抛出 NullPointerException
        } catch (NullPointerException e) {
            // 在 catch 块中执行业务代码
            System.out.println("Caught NullPointerException: " + e.getMessage());
            return 0;  // 返回默认值
        }
    }
复制代码

 

29:类成员与方法的可见性最小化

如果是一个private的方法,想删除就删除。
如果一个public的service方法,或者一个public的成员变量,删除一下,不得思考很多。

 

 30:不要使用双括号初始化技巧,非静态匿名内部类可能导致内存泄露

复制代码
import java.util.HashMap;
import java.util.Map;

public class DoubleBraceInitializationExample {

    public static void main(String[] args) {
        // Noncompliant code using Double Brace Initialization
        Map<String, String> source = new HashMap<String, String>() {{
            put("firstName", "John");
            put("lastName", "Smith");
        }};
        
        // The source map is now an instance of an anonymous inner class,
        // which may lead to memory leaks if this instance is held by other objects.
    }
}
复制代码

改成:

复制代码
import java.util.HashMap;
import java.util.Map;

public class ProperInitializationExample {

    public static void main(String[] args) {
        // Compliant code using explicit put calls
        Map<String, String> source = new HashMap<String, String>();
        source.put("firstName", "John");
        source.put("lastName", "Smith");
        
        // The source map is a regular HashMap instance, without the use of Double Brace Initialization.
    }
}
复制代码

 

posted @   蜗牛攀爬  阅读(14)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示