Lombok 让代码“亚健康”?你真的用对了吗?

  |   0 评论   |   0 浏览

 如遇图片加载失败,可尝试使用手机流量访问
👉3万字总结Java自学、进阶线路图、学习资料

大家好,我是一航!

周末的时候,和朋友闲聊,说最近在加班,赶进度,搞项目重构;然后跟我在吐槽,团队不让用 Lombok,还列了一堆的缺点说让代码变的"亚健康",本来进度就挺赶的,因为一些基础的代码,消磨着精力和时间,心挺累的...

我个人呢,是挺喜欢 lombok的,也一直推荐身边的朋友去使用;虽然很多人反对 lombok的朋友细数了他的各种罪状,但是仍然没有办法说服我放弃使用 lombok;至少,目前在我的团队,是适用的;

在讨论Lombok的各种问题之前,还是先还是来说说什么是Lombok以及日常用法,防止有朋友没有使用过;或者并没有使用到全部的功能,导致不知道在讲什么。

那么一起先来体验一下他的优势吧。

Lombok

什么是Lombok?

Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如getter() 、setter() 、 hashCode() 和 equals() 这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。

基础使用

  • 安装插件(必装

     如遇图片加载失败,可尝试使用手机流量访问

  • 导入依赖

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
        <optional>true</optional>
    </dependency>
    
  • 使用lombok代码之前对象

    public class User {
        private Long id;
    
        private String name;
    
        private Integer age;
    
        private String addr;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getAddr() {
            return addr;
        }
    
        public void setAddr(String addr) {
            this.addr = addr;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            User user = (User) o;
            return id.equals(user.id) &&
                    name.equals(user.name) &&
                    age.equals(user.age) &&
                    addr.equals(user.addr);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(id, name, age, addr);
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", addr='" + addr + '\'' +
                    '}';
        }
    }
    

     如遇图片加载失败,可尝试使用手机流量访问

  • 使用Lombok

    通过4个注解就能完成上面那些冗长的方法;

    @Getter
    @Setter
    @ToString
    @EqualsAndHashCode
    public class User {
        private Long id;
    
        private String name;
    
        private Integer age;
    
        private String addr;
    }
    

    同样,上面冗长的注解,也可以通过一个 @Data注解来代替:
    一下子是不是变的简洁多了...

    @Data
    public class User {
        private Long id;
    
        private String name;
    
        private Integer age;
    
        private String addr;
    }
    

     如遇图片加载失败,可尝试使用手机流量访问

注解说明

  • @Setter

    自动生成属性的set方法

     如遇图片加载失败,可尝试使用手机流量访问

    • AccessLevel.NONE

      表示不生成set方法,如果在类上,就是所有变量都没有set方法,如果在变量上,就指明单个变量没有set方法

      @Setter(AccessLevel.NONE)
      @Getter(AccessLevel.NONE)
      private Integer age;
      
  • @Getter

    自动生成属性的get方法

     如遇图片加载失败,可尝试使用手机流量访问

  • @EqualsAndHashCode

    自动生成equals()、canEquals()和hashCode()方法

     如遇图片加载失败,可尝试使用手机流量访问

  • @ToString

    自动生成toString方法

     如遇图片加载失败,可尝试使用手机流量访问

  • @NonNull

    指明对象不允许为空,会自动在构造方法,成员方法上面加上非空的校验

     如遇图片加载失败,可尝试使用手机流量访问

  • @Builder

    自动生成一个对象的构造器

     如遇图片加载失败,可尝试使用手机流量访问

    public static void main(String[] args) {
        UserBuilder userBuilder = new UserBuilder();
        User user = userBuilder.id(1L)
                .name("张三")
                .age(10)
                .addr("北京")
                .build();
    }
    

    注:加上 @Builder注解之后,虽然能沟通构造器去构建对象,但是大部分框架都是采用的get/set进行取值/赋值;所以在使用建议同时加上@Getter/@Setter注解

  • @NoArgsConstructor

    自动生成无参构造方法

     如遇图片加载失败,可尝试使用手机流量访问

    • @NoArgsConstructor(staticName = "getUser")

      自动生成一个返回对象的静态方法,方法名称为staticName变量指定的名称

      public static User getUser() {
          return new User();
      }
      
  • @AllArgsConstructor

    自动生成包含所有属性的构造方法

     如遇图片加载失败,可尝试使用手机流量访问

  • @RequiredArgsConstructor

    自动生成包含必要参数的构造方法,也就是被 final@NonNull修饰的变量

     如遇图片加载失败,可尝试使用手机流量访问

  • @Data

    这个是使用频率非常高的一个注解,日常开发中的DTO对象,Entity对象普遍都会加上这个注解

    这个是使用频率非常高的一个注解,@Data = @Setter+ @Getter +@ToString+ @EqualsAndHashCode + @RequiredArgsConstructor

     如遇图片加载失败,可尝试使用手机流量访问

  • @Value

    @Value = @Getter +@ToString+ @EqualsAndHashCode + @RequiredArgsConstructor

    需要注意的是,这个注解生成的代码是不包含set方法,是因为他会所有变量都使用 final修饰,然后通过构造方法对所有变量进行赋值。

     如遇图片加载失败,可尝试使用手机流量访问

  • @Log

    自动生成一个log静态常量,常用的注解之一 @Slf4j

    private static final Logger log = LoggerFactory.getLogger(User.class);
    

     如遇图片加载失败,可尝试使用手机流量访问

    • 更多日志注解

      不同的日志框架对应的不同的注解

      @CommonsLog
      private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(User.class);
      @JBossLog
      private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(User.class);
      @Log
      private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(User.class.getName());
      @Log4j
      private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(User.class);
      @Log4j2
      private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(User.class);
      @Slf4j
      private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(User.class);
      @XSlf4j
      private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(User.class);
      
  • @Cleanup

    自动生成释放资源的代码,默认是调用资源的 close()方法,也可以自己指定释放的方法,如:@Cleanup("shutDown")

     如遇图片加载失败,可尝试使用手机流量访问

  • @Accessors

    Accessor的中文含义是存取器,@Accessors用于配置getter和setter方法的生成结果

    • @Accessors(fluent = true)

      fluent表示流畅的;默认为false,设置为true时,getter和setter方法全部和属性名同名,并且setter方法返回当前对象

       如遇图片加载失败,可尝试使用手机流量访问

    • @Accessors(chain = true)

      chain表示链式的,默认为false,当设置为true时,所有的Setter方法名都以set开头并返回当前对象;

       如遇图片加载失败,可尝试使用手机流量访问

    • @Accessors(prefix = {"pre","my"})

      去掉指定前缀;遵循驼峰命名的变量,通过prefix指定前缀的集合之后,setter方法将会自动去掉对应的前缀,如下图,preName变量的set方法为 setName();myAddr的set方法为 setAddr()

       如遇图片加载失败,可尝试使用手机流量访问

  • @Synchronized

    synchronized关键词的用法差不多,加上 @Synchronized注解之后,会自动对方法类的内容使用 synchronized关键词包裹

    注:个人不太建议采用这个注解进行加锁,最主要的原因时粒度太粗了;所以需要酌情考虑是否真的合适!

所有的注解加起来,是不是能为我们日常开发省去很多的体力活?答案是肯定的,这也是他吸引了这么多人使用的最根本原因;

主要的问题

上面的示例以及涵盖了Lombok的绝大部分的使用场景;确实为日常开发带来了很多便捷,但这些优点的背后同样也衍生出了一些不得不说的问题:

强耦合、被迫使用

文章一开始就有说到,使用lombok必须安装一个插件,才能正常编译通过,如果是团队协作开发,某一天你加入了Lombok,其他人并没有使用,那么他在同步你的代码之后,需要被迫使用Lombok插件,否则编译无法通过;无形中增加了代码、工具的耦合度

如果你要做开发项目,我的建议是,老老实实把这个插件给卸载了吧,因为这可能直接影响到你开源项目的使用率;特别是插件类的开源项目。

阅读性差

源码的主要目的是为了:阅读;使用Lombok之后,相关的方法都是在编译器自动生成的,在源码上并没有体现出来,从而导致无法直观的看到具体的实现逻辑,增加了理解成本;

当对代码进行debug的时候,自动生成的代码无法进行逐行调试,增加了对问题的排查成本;

误用

举几个简单的例子

  • 几个构造方法的注解,不同的注解有着不同的效果,不能一味的使用 @AllArgsConstructor生成一个大而全的构造方法讲所有属性都对外暴露了,需要在不同的场景,使用不同的注解;
  • @Synchronized注解,如果方法中只有一小段代码需要加上锁,使用@Synchronized直接是将整个方法都加上了锁,从而导致粒度变粗,性能变差;
  • 比如对象只需要get方法,但是在使用过程中把 @Data注解给安排上了,虽然get方法都有了,但同时也增加了所有的set方法也都给安排上了,无效中增加了代码的安全性问题。

这些使用,虽然在最终的结果上面,并没有带来什么大的影响,但是无形中产生了一些隐患,一旦隐患堆积多了之后,就会产生一种插件不好用的错觉。

技术债

如果是个人使用,Lombok的学习成本并不高,就那么十来个注解,可能花一会儿功夫就了解清楚了;但当团队决定使用Lombok的时候,就需要要求每一个成员都能够熟练的掌握,那最终带来的成本就是几何倍了;

总结

本文不仅列举出了Lombok详细的用法,还列举了其优缺点,同样这也正是赞成派和反对派各自所持不同的立场;凡事都具备两面性,不能完全从一个角度去钻牛角尖,那样本身就失去了技术创新的意义了;

优点就不过分的吹捧,缺点不过分的贬低;最终的使用还得看是否真的合适;

至于不足的那些方方面面,也并不是完全无计可施;使用框架的最终目的是提高生产力被迫使用技术债这些问题,团队需要在一开始的时候就讨论清楚,并结合团队间的影响、学习成本等方面来权衡利弊,如果利大于弊,那就可以安排上,通过系统的学习、培训之后,自然也就不会出现 误用这些问题了;

至于 阅读性差;Lombok所生成的方法,几乎都是通用的,标准的那一批;作用、含义基本都很容易达成共识的,所以只要详细的理解了各注解的作用,在代码的理解上,也就不会成为问题;

那么最后,一起来投个票吧!



标题:Lombok 让代码“亚健康”?你真的用对了吗?
作者:码霸霸
地址:https://lupf.cn/articles/2021/11/22/1637546249472.html