Java 必备之 Lombok 必知必会_程序员闻人的博客-程序员宅基地

技术标签: Java 必备  

公众号

1. 前言

在目前众多编程语言中,Java 语言的表现还是抢眼,不论是企业级服务端开发,还是 Andorid 客户端开发,都是作为开发语言的首选,甚至在大数据开发领域,Java 语言也能占有一席之地,如 Hadoop,Spark,Flink 大数据等。而作为已经诞生 24 年的 Java 相比其他语言来说,编写起来略显得冗长和复杂,而为了能极大提升 Java 开发的效率和代码简洁性,一个 Java 库 Lombok 就这样诞生了。

首先我们还是看下 Lombok 官方的描述:

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

从上面的说明里我们可以初步认识一下 Lombok,一个作用于编辑器和构建工具的 Java 库,可以对编写的 Java 代码进行增强,比如说不用再写实体类的 getter 方法,equals 方法而是自动生成,自动生成日志输出变量等等,减少重复模板的代码。大概知道了 Lombok 框架提供的功能后,接下来我们就真正使用一下 Lombok 提供的注解,看它是如何帮助我们提高书写 Java 代码的简洁性和效率的。

本文主要内容涉及如下:

  • Lombok 插件安装
  • Lombok 常用注解使用

示例项目:https://github.com/wrcj12138aaa/lombok-actions

  • lombok-actions:

环境支持:

  • JDK 8
  • SpringBoot 2.1.4
  • Maven 3.6.0

2. 正文

2.1 安装 Lombok

使用 Lombok 之前我们先要在所使用的 IDE 中进行集成安装,这里以 IDEA 为例,安装步骤十分简单:

  • 前往 File -> Settings -> Plugin -> Marketplace ,搜索 Lombok

    image-20190602193640583

  • 选择搜索结果 Lombok ,点击 Install 安装。

  • 安装完成后重启即可。

基于 Eclipse 的 Lombok 插件安装方法这里就不详细描述了,官方也给了对应的文档说明:https://projectlombok.org/setup/eclipse

在 IDE 安装了 Lombok 插件后,我们就可以在 pom.xml 文件中添加 Lombok 的依赖进行使用了。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
    <scope>provided</scope>
</dependency>

注意:pom 依赖设置 scope 为 provided,是为了让 Lombok 库不被打包进程序。

2.2 @Getter/@Setter

通常我们编写实体类无论多少个字段,都要为其提供 gettersetter 方法,如下面的示例类 User.java

image-20190602195006495

我们常会遇到这种情况:某个实体类新增和修改某个字段,我们都需要单独处理调整,十分麻烦并且重复。这时候如果我们使用 Lombok 提供 @Getter/@Setter 注解就能帮我们省去 getter 和 setter 方法的维护,由 Lombok 对 User 类自动生成 gettersetter 方法,两者最终的字节码时一样的,而我们现在在 User.java 上编写的代码仅仅 7 行即可:

@Getter
@Setter
public class User {
    
    private Integer id;
    private String username;
    private String password;
}

然后用测试类 UserTests.java 测试结果如下:

public class UserTests {
    
    @Test
    public void test() {
    
        User user = new User();
        user.setUsername("one");
        user.setPassword("zxc123");
        Assert.assertEquals(user.getUsername(), "one");
        Assert.assertEquals(user.getPassword(), "zxc123");
    }
}

@Getter/@Setter 注解不仅可以使用在类上,还可以使用在字段上,这样就是表示针对该字段自动生成 getter /setter 方法。

@Getter
@Setter
private String password;

这里该注解使用在类上,还是在字段上的区别就是,如果注解使用在类上,只针对这个类的非静态字段有效。

需要注意的一点是:如果 @Getter 注解修饰了 boolean 类型的变量,其生成的 getter 方法签名是 isXXX 形式,而不是 getXXX形式。

除此之外,@Getter/@Setter 还提供访问权限控制的属性 lombok.AccessLevel value(), 默认为 PUBLIC,而其他选值都是枚举类型:MODULE, PROTECTED, PACKAGE, PRIVATE

2.3 @NonNull

顾名思义,@NonNull 用于标记类中不能允许为 null 的字段或者参数上,任何使用该字段的地方都生成空指针判断代码,若@NonNull 标记的变量为 null,抛出 NullPointException (NPE) 异常。比如下面示例代码:

public class User {
    
    private Integer id;
    private String username;
    private String password;

    public User(Integer id, @NonNull String username, @NonNull String password) {
    
        this.id = id;
        this.username = username;
        this.password = password;
    }
}

使用了 @NonNull 注解之后我们可以获取到反编译之后的字节码信息如下,这就是 Lombok 给我们生成的最终的代码:

public class User {
    
    private Integer id;
    private String username;
    private String password;

    public User(Integer id, @NonNull String username, @NonNull String password) {
    
        if (username == null) {
    
            throw new NullPointerException("username is marked non-null but is null");
        } else if (password == null) {
    
            throw new NullPointerException("password is marked non-null but is null");
        } else {
    
            this.id = id;
            this.username = username;
            this.password = password;
        }
    }
}

2.4 构造器注解

再来看下平时经常会遇见的场景,为实体类编写构造器方法,Lombok 提供了三个不同构造器注解 @NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor 分别对用不同构造器方法处理方式,接下来就一一描述。

  • @NoArgsConstructor 为实体类生成无参的构造器方法

  • @AllArgsConstructor 为实体类生成除了static修饰的字段之外带有各参数的构造器方法。

  • @RequiredArgsConstructor 为实体类生成指定字段的构造器方法,而这些字段需要被 final,或者 @NonNull 修饰。

    ```java
      @RequiredArgsConstructor
      public class User3 {
          private Integer id;
          private final String username;
          @NonNull
          private String password;
      }
    ```
    

    编译成功后使用构造器方法时就是这样的效果:User3 user3 = new User3("user3", "zxc123");

2.5 @ToString

@ToString 会给类自动生成易阅读的 toString 方法,带上有所非静态字段的属性名称和值,这样就十分便于我们日常开发时进行的打印操作。

@Getter
@Setter
@AllArgsConstructor
@ToString
public class User2 {
    
    private Integer id;
    private String username;
    private String password;
}

最终编译成字节码,反编译结果如下:

public class User2 {
    
    private Integer id;
    private String username;
    private String password;
    // 省去 setter/getter
    public String toString() {
    
        return "User2(id=" + this.getId() + ", username=" + this.getUsername() + ", password=" + this.getPassword() + ")";
    }
}

另外,注解 @ToString 还支持设置指定哪些字段的日志化输出,哪些不需要出现在 toString 方法中。使用属性 @ToString.Exclude排除不需要在 toString 中出现的字段,使用 @ToString.Include标记需要出现在 toString 中的字段,具体用法可参见示例:

@Getter
@Setter
@AllArgsConstructor
@ToString
public class User2 {
    
    @ToString.Exclude
    private Integer id;
    @ToString.Include
    private String username;
    @ToString.Include
    private String password;
}

打印 User2 对象的日志效果就是:User2(username=user2, password=zcx123)

2.6 @EqualsAndHashCode

@EqualsAndHashCode 注解就是用于根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法,方便我们用于对象间的比较。类似 @ToString@EqualsAndHashCode 还可以使用需要作为比较的字段和排除不需要比较的字段,具体用法可以看如下示例:

@Getter
@Setter
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class User4 {
    
    @EqualsAndHashCode.Exclude
    private Integer id;
    @EqualsAndHashCode.Include
    private String username;
    @EqualsAndHashCode.Exclude
    private String password;
}

写完实体类代码,我们编写测试方法试下效果:

@Test
public void testEqual() {
    
    User4 user4 = new User4(1, "user4", "zxc");
    User4 user4_2 = new User4(1, "user4", "123");
    Assert.assertEquals(user4, user4_2); // ture
}

2.7 @Data/@Value

@Data/@Value 注解,提供了更综合的生成代码功能,等价于下面几个注解

@Getter
@Setter
@AllArgsConstructor
@ToString
@EqualsAndHashCode

两个注解都只能使用在类上,与 @Data 不同, @Value 用来修饰不可变的类上。一般实体类没有特别的限制的话,通常可以直接使用 @Data 注解修饰。

2.8 @Builder

@Builder 是一个非常强大的注解,提供了一种基于建造者模式的构建对象的 API。使用 @Builder 注解为给我们的实体类自动生成 builder() 方法,并且直接根据字段名称方法进行字段赋值,最后使用 build()方法构建出一个实体对象。

@Data
@Builder
public class User6 {
    
    private Integer id;
    private String username;
    private String password;
}

@Test
public void testBuilder() {
    
    User6 user6 = User6.builder().id(1).username("user6").password("zxc123").build();
    log.warn("testLog: {}", user6); // User6(id=1, username=user6, password=zxc123)
}

需要注意的是 @Builder 不支持父类字段的生成,当一个实体类存在父类时,@Builder 只能生成当前类的字段构建方法。若需要用到父类的字段方法时, Lombok 提供了新的注解 @SuperBuilder 来应对这种情况,下面是 @SuperBuilder 注解的使用方式:

@SuperBuilder
@Getter
@Setter
public class Parent {
    
   private int id;
   private String name;
}

@SuperBuilder
@Data
public class Child extends Parent {
    
    private String childName;
}

调用示例:

Child child = Child.builder().id(1).name("父类名称").childName("子类名称").build();
System.out.println(child.getId());

由于 Lombok Plugin 还未更新支持@SuperBuilder,所以以上写法在 IDEA 下还会提示编译错误,无法找到 builder()方法。

也可以参考此文方式去处理继承的情况:https://reinhard.codes/2015/09/16/lomboks-builder-annotation-and-inheritance/

2.9 日志注解

正对程序类中常见不同框架 Logger 对象,Lombok 也提供了注解,来自动生成 Logger 对象,实现优雅地输出日志,只需要在类上使用日志注解如 @Log。当然 Lombok 支持了多个日志框架,并且提供对应的注解如下:

  • @CommonsLog 等价效果: private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);

  • @Flogger 等价效果: private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();

  • @JBosLog 等价效果: private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);

  • @Log 等价效果: private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());

  • @Log4j 等价效果: private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);

  • @Log4j2 等价效果: private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);

  • @Slf4j 等价效果: private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);

  • @XSlf4j 等价效果: private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

下面代码使用 @Slf4j 注解进行日志输出:

@Slf4j
public class UserTests {
    
    // ....
    @Test
    public void testLog() {
    
        User5 user5 = new User5();
        user5.setId(1);
        user5.setUsername("user5");
        user5.setPassword("zxc123");
        log.warn("testLog: {}", user5);
   // 21:57:15.488 [main] WARN com.one.learn.lombok.UserTests - testLog: User5(id=1, username=user5, password=zxc123)
    }
}

2.10 @Cleanup

@Cleanup 用于标记需要释放清理操作的资源对象变量,如 FileInputStream, FileOutputStream 等,标记之后资源对象使用完毕后,就会被自动关闭和清理,实际上这里 Lombok 实现效果与 Java7 特性 try with resource 一样, 为我们屏蔽了关闭资源的模板代码,下面给出 @Cleanup 的使用示例:

public class CleanupExample {
    
    public static void main(String[] args) throws IOException {
    
        @Cleanup InputStream in = new FileInputStream(args[0]);
        @Cleanup OutputStream out = new FileOutputStream(args[1]);
        byte[] b = new byte[10000];
        while (true) {
    
            int r = in.read(b);
            if (r == -1) {
    
                break;
            }
            out.write(b, 0, r);
        }
    }
}

CleanupExample.java 编译生成的字节码反编译可以得到如下结果:

public class CleanupExample {
    
    //...
    public static void main(String[] args) throws IOException {
    
        FileInputStream in = new FileInputStream(args[0]);
        try {
    
            FileOutputStream out = new FileOutputStream(args[1]);
            try {
    
                byte[] b = new byte[10000];
                while(true) {
    
                    int r = in.read(b);
                    if (r == -1) {
    
                        return;
                    }
                    out.write(b, 0, r);
                }
            } finally {
    
                if (Collections.singletonList(out).get(0) != null) {
    
                    out.close();
                }
            }
        } finally {
    
            if (Collections.singletonList(in).get(0) != null) {
    
                in.close();
            }
        }
    }
}

2.11 @SneakyThrows

@SneakyThrows 主要用于在没有 throws 关键字的情况下,隐蔽地抛出受检查异常,为我们平常开发中需要异常抛出时省去的 throw 操作,下面为使用 @SneakyThrows 的示例代码:

public class SneakyThrowsExample implements Runnable {
    
  @SneakyThrows(UnsupportedEncodingException.class)
  public String utf8ToString(byte[] bytes) {
    
    return new String(bytes, "UTF-8");
  }

  @SneakyThrows
  public void run() {
    
    throw new Throwable();
  }
}

最终编译成字节码,反编译结果如下:

public class SneakyThrowsExample implements Runnable {
    
    public SneakyThrowsExample() {
    
    }

    public String utf8ToString(byte[] bytes) {
    
        try {
    
            return new String(bytes, "UTF-8");
        } catch (UnsupportedEncodingException var3) {
    
            throw var3;
        }
    }

    public void run() {
    
        try {
    
            throw new Throwable();
        } catch (Throwable var2) {
    
            throw var2;
        }
    }
}

2.12 val/var

val/var 用于局部变量的修饰,有了这注解修饰后,变量的类型就会自动通过等号右边的表达式推断出来,这个功能借鉴于许多编程语言的自动类型推断的特性。 而 valvar 的区别在于, val 用于修饰不可变变量,var 修饰可变变量。当 val 修饰的变量被重新赋值时,编译器就会提示异常:Error: java: 无法为最终变量 X 分配值。实际用法也比较简单,可参考下面代码:

@Slf4j
public class VarValExample {
    
    public static void main(String[] args) {
    
        val text = "abc";
        // text = "1"; // Error: java: 无法为最终变量 text 分配值`。
        var num = 1;
        num = 2;
        log.info("text:{},num:{}", text, num); // text:abc,num:2
    }
}

3. 结语

到这里我们学习了 Lombok 的近乎 80% 常用的注解,应用在我们的日常开发中已经是绰绰有余了,开始尝试 使用 Lombok 吧,慢慢地就会感受下效率的提升以及代码的优雅。

如果读完觉得有收获的话,欢迎点【好看】,点击文章头图,扫码关注【闻人的技术博客】???。

4. 参考

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_24535471/article/details/90745565

智能推荐

js中对象和数组的浅拷贝和深拷贝,改变新数据,不影响原数据。_浅拷贝改变原数组_pmm0316的博客-程序员宅基地

1.浅拷贝定义: 个人理解为只是针对一层数据的拷贝有效,多层嵌套结构的数据则无效。1.1 示例:Object.assign()---数组的拷贝let a1 = ['hello', '2018']let b1 = Object.assign([], a1)// 改变b1中的值b1[0] = 'hi'/** * 输出结果发现 * 改变b1数据 * a1数组没有改变,b..._浅拷贝改变原数组

浅谈ng-content,ng-template与ng-container的区别_ng content_花心小坚果的博客-程序员宅基地

1.ng-contentng-content的作用是内容投影,主要用法是在父组件中将html投影到子组件中,具体写法如下父组件html<app-zippy-basic> <p>Is content projection cool?</p></app-zippy-basic>子组件tsimport { Component } from '@angular/core';@Component({ selector: 'app-zippy-b_ng content

小程序 给data数据里面的对象 添加属性_给data中鼎苑的对象添加新的属性_weixin_45725092的博客-程序员宅基地

data: { ObjGroud: { ce1: "112" } }, onLoad() { this.setData({ ['ObjGroud.name1']: "测试1", }) console.log(this.data.ObjGroud) }_给data中鼎苑的对象添加新的属性

Python中数据在内存中存储汇总_XIANWEN2014的博客-程序员宅基地

python 语言的三大基本点是:数据结构、函数、对象类。基本上所有应用都是围绕这三点进行展开的。一、数据结构:1.1python中变量存储方式:  python中变量赋值是将对象的引用地址给变量,其类似于c语言中的指针;而c语言中变量赋值,存储的是对象的值,只有用指针变量指向对象,才是将对象的地址给指针变量保存。例如python中:a=1d=a #将a中1的引用...

linux下lighttpd安装与配置_rc.lighttpd_bytxl的博客-程序员宅基地

http://blog.csdn.net/wangxuefeng_yctc/article/details/5652654由于一个电子商务网站,我需要建立一个图片服务器,考虑再三,我决定采用lighttpd来作为其软件载体。以前我用过apapche http,相对apache http 来说lighttpd在静态文件的展示方面有着不错的性能提升,个人认为apache http功_rc.lighttpd

迪杰斯特拉(Dijkstra)算法之两点之间的最短距离问题_迪克斯特拉算法的例题_花开归矣的博客-程序员宅基地

1.概述(1)与弗洛伊德(Floyd)算法一样,迪杰斯特拉(Dijkstra)算法也是一种用于寻找给定的加权图中顶点间最短路径的算法,主要特点是以出发点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止2. 迪杰斯特拉(Dijkstra)算法 与 弗洛伊德(Floyd)算法 的区别(1)迪杰斯特拉(Dijkstra)算法:选定图中某一个顶点作为出发顶点,求出出发顶点到其他顶..._迪克斯特拉算法的例题

随便推点

HuMoments函数_hu moments_qq_2773878606的博客-程序员宅基地

一、-------------------------------------------------------------------HuMoments函数-------------------------------------------------------------------------------------------1、函数作用:利用图像的矩,计算出图像的不变矩,,_hu moments

将分支推送到远程存储库时遇到错误: rejected Updates were rejected because the remote contains work that you do not ha..._土伦的博客-程序员宅基地

将分支推送到远程存储库时遇到错误: rejected Updates were rejected because the remote contains work that you do not have locally在仓库目录下执行git pull origin master --allow-unrelated-histories之后就可以成功的pull,pu..._将分支推送到远程存储库时遇到错误

Linux下的基础指令(六) 权限相关 su sudo umask chmod chown chgrp_唯梦轻语的博客-程序员宅基地

权限  Linux的最大特点是一个多用户的操作系统,允许多个用户同时操作。因此为了保护文件不被任意破坏修改,将每个用户进行权利的限制,使其可以允许且仅允许用户进行相对应权限的操作。Linux将其分为两种大的权限,分别是用户对系统的操作权限以及对文件的访问权限。对系统的操作权限Linux下有两种用户:超级用户(root)与普通用户* 超级用户:,也成管理员用户,可以在Linux系统下做任何事情,不受限制(在使用时,尽量慎重使用,操作不当对系统损害太大)* 普通用户:在Linux下做有限的事情超级

mysql 执行多个存储过程_MySQL存储过程、Pandas和“执行多个语句时使用multi=True”...-程序员宅基地

注意-正如MaxU在下面建议的,该问题特定于mysql.connector如果不使用mysql,则不会发生py。希望这能帮别人省去一些麻烦使用Python、Pandas和mySQL,根本无法获得一个存储过程来返回结果,更不用说在数据帧中了。在我不断收到关于多个查询的错误,但是我运行的存储过程是非常简单的参数驱动查询。在不管我用什么存储过程,结果总是一样的事实上,下面的测试过程(sp_test)是以..._pymysql multi=true

valgrind使用整理_valgrind 使用_程序猿来是你的博客-程序员宅基地

valgrind使用整理时间:20180703参考文献:https://www.cnblogs.com/AndyStudy/p/6409287.html valgrind 工具介绍和简单的使用https://blog.csdn.net/shixin_0125/article/details/78590796 linux工具之检测内存泄漏-valgrindhttps://www.cnblogs.c..._valgrind 使用

结算模块设计_weixin_33878457的博客-程序员宅基地

支付系统和结算系统关系很密切,在钱收到自己的账户里面,不可能就放在那里不管了,在用户用完产品之后,大家要对对账,看看用户用了我们的什么服务,有哪些要收费的项目,用户需要付那些钱,钱有没有付清楚,钱有没有算错,等用户觉得这些收费没错,确认账单了。大家进行结算,这些结算的数据,后面可供财务制作报表,当然财务有另外一套逻辑,但是起码要提供结算的数据。这里给..._仓储结算模块账单设计

推荐文章

热门文章

相关标签