Java学习之代码规范总结

1. MyBatis 不要为了多个查询条件而写 1 = 1

  • 内容介绍

    当遇到多个查询条件,使用where 1=1 可以很方便的解决我们的问题,但是这样很可能会造成非常大的性能损失,因为添加了"where 1=1"的过滤条件之后,数据库系统就无法使用索引等查询优化策略,数据库系统将会被迫对每行数据进行扫描(即全表扫描) 以比较此行是否满足过滤条件,当表中的数据量较大时查询速度会非常慢;此外,还会存在SQL注入的风险

  • 反例

    
      <select id="queryBookInfo" parameterType="com.tjt.platform.entity.BookInfo" resultType="java.lang.Integer">
        
        select count(*) from t_rule_BookInfo t 
        where 1=1
        <if test="title !=null and title !='' ">
          AND title = #{title} 
        </if> 
        <if test="author !=null and author !='' ">
          AND author = #{author}
        </if> 
      </select>
    
    
    
  • 正例

    
      <select id="queryBookInfo" parameterType="com.tjt.platform.entity.BookInfo" resultType="java.lang.Integer">
        
        select count(*) from t_rule_BookInfo t
        <where>
          <if test="title !=null and title !='' ">
            AND title = #{title} 
          </if>
          <if test="author !=null and author !='' "> 
            AND author = #{author}
          </if>
        </where> 
      </select>
    
    
  • 其他

    UPDATE 操作也一样,可以用标记代替 1=1

2. 迭代 entrySet() 获取 Map 的 key 和 value

  • 内容介绍

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

  • 反例

    
      //Map 获取value 
      HashMap<String, String> map = new HashMap<>();
      for (String key : map.keySet()){
          String value = map.get(key);
      }
    
    
  • 正例

    
      //Map 获取key & value
      HashMap<String, String> map = new HashMap<>();
      for (Map.Entry<String,String> entry : map.entrySet()){
          String key = entry.getKey();
          String value = entry.getValue();
      }
    
      // JDK8推荐使用
      map.forEach((K, V) -> {
    
        System.out.println("Key : " + K);
    
        System.out.println("Value : " + V);
    
      });
    
    
    

3. 使用Collection.isEmpty() 检测空

  • 内容介绍

    使用Collection.size() 来检测是否为空在逻辑上没有问题,但是使用Collection.isEmpty() 使得代码更易读,并且可以获得更好的性能;除此之外,任何Collection.isEmpty() 实现的时间复杂度都是O(1),不需要多次循环遍历,但是某些通过Collection.size() 方法实现的时间复杂度可能是O(n)。

  • 反例

    
      LinkedList<Object> collection = new LinkedList<>();
      if (collection.size() == 0){
          System.out.println("collection is empty.");
      }
    
    
    
  • 正例

    
    
      LinkedList<Object> collection = new LinkedList<>();
      if (collection.isEmpty()){
          System.out.println("collection is empty.");
      }
    
      //检测是否为null 可以使用CollectionUtils.isEmpty()
      if (CollectionUtils.isEmpty(collection)){
          System.out.println("collection is null.");
    
      }
    
    
    
    
    

4. 初始化集合时尽量指定其大小

  • 内容介绍

    尽量在初始化时指定集合的大小,能有效减少集合的扩容次数,因为集合每次扩容的时间复杂度很可能时O(n),耗费时间和性能。

  • 反例

    
      //初始化list,往list 中添加元素反例:
      int[] arr = new int[]{1,2,3,4};
      List<Integer> list = new ArrayList<>();
      for (int i : arr){
          list.add(i);
      }
    
    
    
  • 正例

    
      //初始化list,往list 中添加元素正例:
      int[] arr = new int[]{1,2,3,4};
      //指定集合list 的容量大小
      List<Integer> list = new ArrayList<>(arr.length);
      for (int i : arr){
          list.add(i);
      }
    
    
    
    

5. 使用 StringBuilder 拼接字符串

  • 内容介绍

    一般的字符串拼接在编译期 Java 会对其进行优化,但是在循环中字符串的拼接 Java 编译期无法执行优化,所以需要使用 StringBuilder 进行替换。

  • 反例

    
      //在循环中拼接字符串反例
      String str = "";
      for (int i = 0; i < 10; i++){
          //在循环中字符串拼接Java 不会对其进行优化
          str += i;
      }
    
    
    
    
    
  • 正例

    
      //在循环中拼接字符串正例
      String str1 = "Love";
      String str2 = "Courage";
      String strConcat = str1 + str2;  //Java 编译器会对该普通模式的字符串拼接进行优化
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < 10; i++){
          //在循环中,Java 编译器无法进行优化,所以要手动使用StringBuilder
          sb.append(i);
      }
    
    
    
    

6. 若需频繁调用 Collection.contains 方法则使用Set

  • 内容介绍

    在Java 集合类库中,List的contains 方法普遍时间复杂度为O(n),若代码中需要频繁调用contains 方法查找数据则先将集合list 转换成HashSet 实现,将O(n) 的时间复杂度将为O(1)

  • 反例

    
      //频繁调用Collection.contains()
      List<Object> list = new ArrayList<>();
      for (int i = 0; i <= Integer.MAX_VALUE; i++){
          //时间复杂度为O(n)
          if (list.contains(i))
          System.out.println("list contains "+ i);
      }
    
    
    
    
  • 正例

    
      //频繁调用Collection.contains()
      List<Object> list = new ArrayList<>();
      Set<Object> set = new HashSet<>();
      for (int i = 0; i <= Integer.MAX_VALUE; i++){
          //时间复杂度为O(1)
          if (set.contains(i)){
              System.out.println("list contains "+ i);
          }
      }
    
    
    

7. 使用静态代码块实现赋值静态成员变量

  • 内容介绍

    对于集合类型的静态成员变量,应该使用静态代码块赋值,而不是使用集合实现来赋值。

  • 反例

    
      //赋值静态成员变量反例
      private static Map<String, Integer> map = new HashMap<String, Integer>(){
          {
              map.put("Leo",1);
              map.put("Family-loving",2);
              map.put("Cold on the out side passionate on the inside",3);
          }
      };
      private static List<String> list = new ArrayList<>(){
          {
              list.add("Sagittarius");
              list.add("Charming");
              list.add("Perfectionist");
          }
      };
    
    
  • 正例

    
      //赋值静态成员变量正例
      private static Map<String, Integer> map = new HashMap<String, Integer>();
          
          static {
              map.put("Leo",1);
              map.put("Family-loving",2);
              map.put("Cold on the out side passionate on the inside",3);
          }
    
      private static List<String> list = new ArrayList<>();
          
          static {
              list.add("Sagittarius");
              list.add("Charming");
              list.add("Perfectionist");
          }
    
    
    
    

8. 工具类中屏蔽构造函数

  • 内容介绍

    工具类是一堆静态字段和函数的集合,其不应该被实例化;但是,Java 为每个没有明确定义构造函数的类添加了一个隐式公有构造函数,为了避免不必要的实例化,应该显式定义私有构造函数来屏蔽这个隐式公有构造函数

  • 反例

    
      public class PasswordUtils {
        //工具类构造函数反例
        private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);
    
        public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";
    
        public static String encryptPassword(String aPassword) throws IOException {
            return new PasswordUtils(aPassword).encrypt();
        }
    
      }
    
    
    
  • 正例

    
      public class PasswordUtils {
        //工具类构造函数正例
        private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);
    
        //定义私有构造函数来屏蔽这个隐式公有构造函数
        private PasswordUtils(){}
    
        public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";
    
        public static String encryptPassword(String aPassword) throws IOException {
            return new PasswordUtils(aPassword).encrypt();
        }
    
      }
    
    
    
    

9. 删除多余的异常捕获并抛出

  • 内容介绍

    用 catch 语句捕获异常后,若什么也不进行处理,就只是让异常重新抛出,这跟不捕获异常的效果一样,可以删除这块代码或添加别的处理

  • 反例

    
      //多余异常
      private static String fileReader(String fileName)throws IOException{
    
          try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
              String line;
              StringBuilder builder = new StringBuilder();
              while ((line = reader.readLine()) != null) {
                  builder.append(line);
              }
              return builder.toString();
          } catch (Exception e) {
              //仅仅是重复抛异常 未作任何处理
              throw e;
          }
      }
    
    
    
  • 正例

    
      //多余异常
      private static String fileReader(String fileName)throws IOException{
    
          try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
              String line;
              StringBuilder builder = new StringBuilder();
              while ((line = reader.readLine()) != null) {
                  builder.append(line);
              }
              return builder.toString();
              //删除多余的抛异常,或增加其他处理:
              /*catch (Exception e) {
                  return "fileReader exception";
              }*/
          }
      }
    
    
    
    
    

10. 字符串转化使用String.valueOf(value) 代替 " " + value

  • 内容介绍

    把其它对象或类型转化为字符串时,使用String.valueOf(value) 比 “”+value 的效率更高

  • 反例

    
      //把其它对象或类型转化为字符串
      int num = 520;
      // "" + value
      String strLove = "" + num;
    
    
    
  • 正例

    
      //把其它对象或类型转化为字符串
      int num = 520;
      // String.valueOf() 效率更高
      String strLove = String.valueOf(num);
    
    
    
    

11. 避免使用new BigDecimal(double)

  • 内容介绍

    new BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

  • 反例

    
      BigDecimal bigDecimal = new BigDecimal(0.11D); // 输出结果:0.11000000000000000055511151231257827021181583404541015625
      
    
    
    
    
  • 正例

    
      BigDecimal bigDecimal1 = BigDecimal.valueOf(0.11D); // 输出结果:0.11
      
    
    
    

12. 返回空数组和集合而非 null

  • 内容介绍

    若程序运行返回null,需要调用方强制检测null,否则就会抛出空指针异常;返回空数组或空集合,有效地避免了调用方因为未检测null 而抛出空指针异常的情况,还可以删除调用方检测null 的语句使代码更简洁。

  • 反例

    
      //返回null 反例
      public static Result[] getResults() {
          return null;
      }
    
      public static List<Result> getResultList() {
          return null;
      }
    
      public static Map<String, Result> getResultMap() {
          return null;
      }
    
    
  • 正例

    
      //返回空数组和空集正例
      public static Result[] getResults() {
          return new Result[0];
      }
    
      public static List<Result> getResultList() {
          return Collections.emptyList();
      }
    
      public static Map<String, Result> getResultMap() {
          return Collections.emptyMap();
      }
    
    

13. 优先使用常量或确定值调用 equals 方法

  • 内容介绍

    对象的equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals 方法。

  • 反例

    
      //调用 equals 方法反例
      private static boolean fileReader(String fileName)throws IOException{
    
          // 可能抛空指针异常
          return fileName.equals("Charming");
    
      }
    
    
    
  • 正例

    
    
      //调用 equals 方法正例
      private static boolean fileReader(String fileName)throws IOException{
    
          // 使用常量或确定有值的对象来调用 equals 方法
          return "Charming".equals(fileName);
    
          //或使用: java.util.Objects.equals() 方法
          return Objects.equals("Charming",fileName);
      }
    
    
    
    

14. 枚举的属性字段必须是私有且不可变

  • 内容介绍

    枚举通常被当做常量使用,如果枚举中存在公共属性字段或设置字段方法,那么这些枚举常量的属性很容易被修改;理想情况下,枚举中的属性字段是私有的,并在私有构造函数中赋值,没有对应的 Setter 方法,最好加上 final 修饰符

  • 反例

    
      public enum SwitchStatus {
          // 枚举的属性字段反例
          DISABLED(0, "禁用"),
          ENABLED(1, "启用");
    
          public int value;
          private String description;
    
          private SwitchStatus(int value, String description) {
              this.value = value;
              this.description = description;
          }
    
          public String getDescription() {
              return description;
          }
    
          public void setDescription(String description) {
              this.description = description;
          }
      }
    
    
  • 正例

    
      public enum SwitchStatus {
          // 枚举的属性字段正例
          DISABLED(0, "禁用"),
          ENABLED(1, "启用");
    
          // final 修饰
          private final int value;
          private final String description;
    
          private SwitchStatus(int value, String description) {
              this.value = value;
              this.description = description;
          }
    
          // 没有Setter 方法
          public int getValue() {
              return value;
          }
    
          public String getDescription() {
              return description;
          }
      }
    
    
    

15. String.split(String regex)部分关键字需要转译

  • 内容介绍

    使用字符串String 的split 方法时,传入的分隔字符串是正则表达式,则部分关键字(比如 .| 等)需要转义

  • 反例

    
      // String.split(String regex) 
      String[] split = "a.ab.abc".split(".");
      System.out.println(Arrays.toString(split));   // 结果为[]
    
      String[] split1 = "a|ab|abc".split("|");
      System.out.println(Arrays.toString(split1));  // 结果为["a", "|", "a", "b", "|", "a", "b", "c"]
    
    
    
  • 正例

    
      // String.split(String regex) 正例
      // . 需要转译
      String[] split2 = "a.ab.abc".split("\\.");
      System.out.println(Arrays.toString(split2));  // 结果为["a", "ab", "abc"]
    
      // | 需要转译
      String[] split3 = "a|ab|abc".split("\\|");
      System.out.println(Arrays.toString(split3));  // 结果为["a", "ab", "abc"]
    
    
    

16. final 可提高程序响应效率

  • 内容介绍

    声明成 final 的情况:

    (1) 不需要重新赋值的变量,包括类属性、局部变量。

    (2) 对象参数前加 final ,表示不允许修改引用的指向。

    (3) 类方法确定不允许被重写。

17. 关于 hashCode 和 equals 的处理,遵循如下规则

  • 内容介绍

    (1) 只要重写 equals ,就必须重写 hashCode

    (2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以** Set 存储的对象必须重写这两个方法**。

    (3) 如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals

18. 不要在 foreach 循环里进行元素的 remove / add 操作。

  • 内容介绍

    remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁

  • 反例

    
      // java.util.ConcurrentModificationException
      List<String> a = new ArrayList<String>();
    
      a.add("1");
    
      a.add("2");
    
      for (String temp : a) {
    
          if("1".equals(temp)){ // 把“1”换成“2”,会抛出java.util.ConcurrentModificationException异常
              a.remove(temp);
          }
      }
    
      System.out.println(a);
    
    
    
  • 正例

    
    
      List<String> a = new ArrayList<String>();
    
      a.add("1");
    
      a.add("2");
    
      Iterator<String> it = a.iterator();
    
      while(it.hasNext()){
          String temp = it.next();
          if("2".equals(temp)){
              it.remove();
          }
      }
    
      System.out.println(a);
    
    
    

19. 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式

  • 内容介绍

    让开发人员更加明确线程池的运行规则,规避资源耗尽的风险。Executors 返回的线程池对象的弊端如下:

    (1) FixedThreadPool 和 SingleThreadPool:

    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

    (2) CachedThreadPool 和 ScheduledThreadPool:

    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

20. 利用延迟关联或者子查询优化超多分页场景

  • 内容介绍

    MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写

  • 反例

    
      select * from1 where 条件 LIMIT 100000,20;
    
    
  • 正例

    
      -- 先快速定位需要获取的 id 段,然后再关联:(优化在可以少查表1的很多字段)
      SELECT a.* FROM1 a, (select id from1 where 条件 LIMIT 100000,20 ) b where a.id=b.id;
    
    
    

21. 建组合索引的时候,区分度最高的在最左边。

  • 内容介绍

    存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a > ? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

  • 正例

    
      -- 正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 IND_表名_编号 索引即可。
    
    
    

22. 如果有全球化需要,所有的字符存储与表示,均以 utf-8 编码

  • 内容介绍

    字符计数方法 注意:

    SELECT LENGTH(“轻松工作”); 返回为12

    SELECT CHARACTER_LENGTH(“轻松工作”); 返回为4

    如果要使用表情,那么使用 utfmb4 来进行存储,注意它与 utf-8 编码的区别。

23. 选择合适的字符存储长度

  • 内容介绍

    合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度

  • 正例

    的年龄用 unsigned tinyint(表示范围 0-255,人的寿命不会超过 255 岁);

    海龟 就必须是 smallint;

    太阳的年龄,就必须是 int;

    如果是所有恒星的年龄都加起来, 那么就必须使用 bigint

24. 小数类型为 decimal,禁止使用 float 和 double

  • 内容介绍

    float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储

25. 其他规范

(1) 数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。

(2) 不得使用外键与级联,一切外键概念必须在应用层解决。

(3) 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

说明:

参考链接

  • 如何更规范化编写Java 代码

    https://www.cnblogs.com/taojietaoge/p/11575376.html

  • Java编码规范总结(腾讯+阿里)

    https://blog.csdn.net/pursue_vip/article/details/89890672

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值