Java库Lombok常用注解使用

news2024/11/19 18:20:46

Lombok已经是很多Java项目最常用的库之一了,我也一直在用,但是仅限于@Data、@XxxConstructer、@Slf4j之类的注解,没有看过其它的注解。
直到前段时间看到别人的代码,使用了一个@SneakyThrows注解,搜索了一下,才发现也是Lombok的注解,而且解决了我的一个痛点,这才决定花点时间研究一下Lombok的其它注解。
Lombok的所有注解,可以去官网看看:https://projectlombok.org/features/

原理简述

1、Lombok需要在IDE中安装插件(比如最新版的IDEA,安装完就已经默认集成了Lombok插件)
在这里插入图片描述

2、在项目的pom.xml里添加lombok依赖,并在相应的代码上添加lombok的注解
3、依赖于JSR 269: Pluggable Annotation Processing API,Lombok自定义了注解处理器,会在项目编译时,对有注解的代码,增加新的代码,再最终生成字节码,过程参考:https://developer.aliyun.com/article/995108

常用注解

下面介绍一下我常用的那些注解,部分我觉得用处不大的就不介绍,你可以参考官网明细列表

1、@val 和 @var

这2个是用于局部变量的类型推断注解,减少书写局部变量类型的代码:

  • val是定义final类型的变量:
    val a = 123; 等效于 final int a = 123;
  • var是定义普通变量:
    var a = 123; 等效于 int a = 123;
    注:从Java10开始,已经引入了var关键字和推断能力,不需要再使用lombok的var了

另:对var的类型推断,也有很多人反对,认为可读性差,这个见仁见智吧,我比较喜欢,尤其是类型名特别长的时候,而且好用的IDE(比如IDEA),是会自动显示出对应的变量类型的,如:
在这里插入图片描述

2、@NonNull

用于方法或构造函数的参数,减少书写判断参数是否null的代码,如:

public static String nonNullDemo2(@NonNull Integer arg) {
    return arg.toString();
}

上述代码,最终生成的实际代码如下,会自动添加判空抛异常的代码:

public static String nonNullDemo2(@NonNull Integer arg) {
    if (arg == null) {
        throw new NullPointerException("arg is marked non-null but is null");
    }
    return arg.toString();
}

注:也可以用于类的成员属性上,但是必须配合AllArgsConstructor或RequiredArgsConstructor使用,不推荐

3、@Data

这是一个组合注解,通常放在Dto、Entity之类的实体类对象上。
这个注解组合了:

  • @ToString
  • @EqualsAndHashCode
  • @Getter
  • 没被定义为final的字段上加 @Setter
  • @RequiredArgsConstructor

以上注解作用,请参考下面对应注解的说明

4、@Getter/@Setter

自动生成指定字段的get/set方法,可以加在类上,影响该类的所有字段,也可以加在指定字段上,如:

public class GetterSetterDemo {
    @Getter
    public static class Dto1 {
        private int id;

        private String name;
    }

    @Setter
    public static class Dto2 {
        private int id;

        private String name;
    }

    public static class Dto3 {
        @Getter
        private int id;
        @Setter
        private String name;
    }
}

上述代码,生成的实际代码如下:

public class GetterSetterDemo {
    public static class Dto1 {
        private int id;
        private String name;

        public int getId() {
            return this.id;
        }
        public String getName() {
            return this.name;
        }
    }

    public static class Dto2 {
        private int id;
        private String name;

        public void setId(final int id) {
            this.id = id;
        }
        public void setName(final String name) {
            this.name = name;
        }
    }

    public static class Dto3 {
        private int id;
        private String name;

        public int getId() {
            return this.id;
        }
        public void setName(final String name) {
            this.name = name;
        }
    }
}

5、@Accessors(chain=true)

这个注解要配合@Setter使用,注解加了chain=true,表示每个set方法返回值是this对象本身,而不是默认的void,方便进行链式赋值,如:

@Setter
@Accessors(chain = true)
public class AccessorsDemo {
    private int id;
    private String name;
}

定义了这样一个类,我们可以链式赋值:

public AccessorsDemo TestAccessors() {
    return new AccessorsDemo()
            .setId(123)
            .setName("abc");
}

上面的class类的代码,最终生成的代码如下:

public class AccessorsDemo {
    private int id;
    private String name;

    public AccessorsDemo setId(final int id) {
        this.id = id;
        return this;
    }
    public AccessorsDemo setName(final String name) {
        this.name = name;
        return this;
    }
}

注1:之前我不知道有这个注解,一直喜欢用@Builder,后续再也不用@Builder了,全部改用这个注解
注2:@Accessors注解还有2个属性,我觉得意义不大,就不介绍了,看官网吧

6、@SneakyThrows

这个注解的作用是抑制方法里抛出的编译时异常。
我们写代码时,如果语句抛出了异常,要么自己加try,要么在方法的签名后面增加异常抛出声明,我是觉得比较恶心(C#就不需要)。
以前我都要手工套一个try{}catch(Exception ex){throw new RuntimeException(ex);},有这个注解再也不需要这么做了。
如:

@SneakyThrows
public int test1() {
    throw new Exception("abc");
}

最终生成的代码如下:

@SneakyThrows
public int test1() {
    try{
        throw new Exception("abc");
    } catch (UnsupportedEncodingException e) {
      throw Lombok.sneakyThrow(e);
    }
}

7、@Synchronized

对整个方法加锁,串行执行这个方法,
该注解默认定义一个私有的类变量,并用该变量对方法里的代码进行加锁。

  • 静态方法,定义静态的类变量,作为加锁对象
  • 类成员方法,定义类成员变量,作为加锁对象
  • 默认所有静态方法共用同一个锁对象,所有成员方法共用同一个锁对象
  • 注解的value参数,可以定义锁对象的变量名,以使用不同的锁对象
  • 注意:类成员方法使用的是类成员变量作为锁对象,所以初始化的2个类,无法使用同一个锁

参考代码:

public class SynchronizedDemo {
    private static int num;

    @Synchronized
    @SneakyThrows
    public void syncMethod() {
        num++;
        System.out.println("before-num:" + num);
        Thread.sleep(3000);
        num++;
        System.out.println("after-num:" + num);
    }

    @Synchronized
    @SneakyThrows
    public static void syncStaticMethod() {
        num++;
        System.out.println("before-num:" + num);
        Thread.sleep(3000);
        num++;
        System.out.println("after-num:" + num);
    }

    @Synchronized
    @SneakyThrows
    public static void syncStaticMethod2() {
        num++;
        System.out.println("2-before-num:" + num);
        Thread.sleep(3000);
        num++;
        System.out.println("2-after-num:" + num);
    }
}

最终生成的代码如下:

public class SynchronizedDemo {
    private final Object $lock = new Object[0];
    private static int num;
    private static final Object $LOCK = new Object[0];

    public void syncMethod() {
        synchronized (this.$lock) {
            num++;
            System.out.println("before-num:" + Integer.valueOf(num));
            Thread.sleep(3000L);
            num++;
            System.out.println("after-num:" + Integer.valueOf(num));
        }
    }

    public static void syncStaticMethod() {
        synchronized ($LOCK) {
            num++;
            System.out.println("before-num:" + Integer.valueOf(num));
            Thread.sleep(3000L);
            num++;
            System.out.println("after-num:" + Integer.valueOf(num));
        }
    }

    public static void syncStaticMethod2() {
        synchronized ($LOCK) {
            num++;
            System.out.println("2-before-num:" + Integer.valueOf(num));
            Thread.sleep(3000L);
            num++;
            System.out.println("2-after-num:" + Integer.valueOf(num));
        }
    }
}

根据生成的代码可知,这个注解,内部方法互调,注解也是生效的;
不像Bean的那些注解,内部互调无效,必须通过代理调用。

8、@Cleanup

用于需要回收资源的局部变量上,减少书写finally代码,自行回收资源 的代码,
该注解要求变量有命名为close的方法,如果没有,可以自定义,如以下2个示例代码:

public class CleanupDemo {
    @SneakyThrows
    public static void test(HttpServletResponse response) {
        @Cleanup InputStream inputStream = new ByteArrayInputStream("I'm a string".getBytes());
        @Cleanup OutputStream outputStream = response.getOutputStream();
        inputStream.transferTo(outputStream);
    }
    @SneakyThrows
    public static void test2(HttpServletResponse response) {
        @Cleanup OutputStream outputStream = response.getOutputStream();
        @Cleanup("doClear") var tmp = new CleanClass();// 默认要求注解的变量有close方法,可以修改
        outputStream.write(tmp.now.getBytes());
    }

    // 定义一个带资源清理的类
    public static class CleanClass {
        public String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
        public void doClear() {
            now = null;
        }
    }
}

上述代码,最终生成的实际代码如下,会自动添加finally代码去回收资源:

public class CleanupDemo {
    public static void test(HttpServletResponse response) {
        InputStream inputStream = new ByteArrayInputStream("I'm a string".getBytes());
        ServletOutputStream outputStream = response.getOutputStream();
        try {
            inputStream.transferTo(outputStream);
            if (Collections.singletonList(outputStream).get(0) != null) {
                outputStream.close();
            }
            if (Collections.singletonList(inputStream).get(0) != null) {
                inputStream.close();
            }
        } catch (Throwable th) {
            if (Collections.singletonList(outputStream).get(0) != null) {
                outputStream.close();
            }
            throw th;
        }
    }
    public static void test2(HttpServletResponse response) {
        ServletOutputStream outputStream = response.getOutputStream();
        CleanClass tmp = new CleanClass();
        try {
            outputStream.write(tmp.now.getBytes());
            if (Collections.singletonList(tmp).get(0) != null) {
                tmp.doClear();
            }
            if (Collections.singletonList(outputStream).get(0) != null) {
                outputStream.close();
            }
        } catch (Throwable th) {
            if (Collections.singletonList(tmp).get(0) != null) {
                tmp.doClear();
            }
            throw th;
        }
    }
    public static class CleanClass {
        public String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
        public void doClear() {
            this.now = null;
        }
    }
}

9、@ToString

自动重写Object.toString()方法,生成一个拼接所有字段的toString()方法,如:

@ToString
public class ToStringDemo {
    private int id;
    private final String name = "";
    private final Environment env = null;
}

最终生成的代码:

public class ToStringDemo {
    private int id;
    private final String name = "";
    private final Environment env = null;

    public String toString() {
        int i = this.id;
        Objects.requireNonNull(this);
        return "ToStringDemo(id=" + i + ", name=" + "" + ", env=" + this.env + ")";
    }
}

10、@EqualsAndHashCode

自动重写Object.hashCode() 和 Object.equals()方法
如:

@EqualsAndHashCode
public class EqualsAndHashCodeDemo {
    private int id;
    private String name;
}

最终生成的代码:

public class EqualsAndHashCodeDemo {
    private int id;
    private String name;

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        }
        if (o instanceof EqualsAndHashCodeDemo) {
            EqualsAndHashCodeDemo other = (EqualsAndHashCodeDemo) o;
            if (other.canEqual(this) && this.id == other.id) {
                Object this$name = this.name;
                Object other$name = other.name;
                return this$name == null ? other$name == null : this$name.equals(other$name);
            }
            return false;
        }
        return false;
    }

    protected boolean canEqual(final Object other) {
        return other instanceof EqualsAndHashCodeDemo;
    }

    public int hashCode() {
        int result = (1 * 59) + this.id;
        Object $name = this.name;
        return (result * 59) + ($name == null ? 43 : $name.hashCode());
    }
}

注:这个注解有个坑,hashCode和equals这2个方法,都只比较当前类的所有字段,
如果这是个子类,这2个方法也只比较自己定义的字段,不会比较父类字段,
所以如果你的类是子类,要把注解改成:@EqualsAndHashCode(callSuper = true) 避免比较出问题,或把对象作为key加入Map时导致数据丢失。

11、@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

  • @NoArgsConstructor: 自动生成无参构造函数
  • @RequiredArgsConstructor: 把类所有字段作为参数,自动生成构造函数
  • @NoArgsConstructor: 为所有定义为final的字段,自动生成有参构造函数
    这个最好用,尤其是那些@Service@Component的spring Bean类,用了这个注解,就不需要再去用@Autowired注解了,如:
@RequiredArgsConstructor
public class RequiredArgsConstructorDemo {
    private int id;
    private final String name;
    private final Environment env;
}

生成代码如下,构造函数里没有id参数:

public class RequiredArgsConstructorDemo {
    private int id;
    private final String name;
    private final Environment env;

    public RequiredArgsConstructorDemo(final String name, final Environment env) {
        this.name = name;
        this.env = env;
    }
}

12、@Slf4j

自动为类生成一个日志成员变量,其实日志相关的注解有好多个,参考 分别指向不同的日志框架。
我用的最多的就是@Slf4j,如:

@Slf4j
public class Slf4jDemo {
    public void doLog() {
        log.debug("DEBUG日志:当前时间:{}", LocalDateTime.now());
        log.info("INFO日志:当前时间:{}", LocalDateTime.now());
        log.warn("warn日志:当前时间:{}", LocalDateTime.now(), new Exception("我是异常"));
        log.error("error日志:当前时间:{}", LocalDateTime.now(), new Exception("我是异常"));
    }
}

最终生成的代码:

public class Slf4jDemo {
    private static final Logger log = LoggerFactory.getLogger(Slf4jDemo.class);

    public void doLog() {
        log.debug("DEBUG日志:当前时间:{}", LocalDateTime.now());
        log.info("INFO日志:当前时间:{}", LocalDateTime.now());
        log.warn("warn日志:当前时间:{}", LocalDateTime.now(), new Exception("我是异常"));
        log.error("error日志:当前时间:{}", LocalDateTime.now(), new Exception("我是异常"));
    }
}

13、@Builder

自动生成builder()方法,进行链式赋值,最后生成对象,看最终代码还创建了内部类,比较复杂。
推荐改用上面的 @Accessors(chain=true)

实际代码

所有的代码已经上传到Github,可以参考:https://github.com/youbl/study/tree/master/study-codes/lombok-demo

争论问题

确实网上非常多,比如:

  • Lombok是个黑盒,导致代码可读性不好,我反而觉得代码量少了,代码可读性更高了。
    如果你了解了它的原理,就更不是问题了。
    举个简单的例子,你调用了jdk封装的方法,如果不研究方法源码,也没有深入了解这个方法做了什么,可能有什么坑,那么它就跟Lombok差不多,你一样会在使用中出现各种问题。
  • Lombok污染性太强,团队里有一个人在用,就会导致其它人不得不用。
    这是个问题,但是现在Lombok的使用已经越来越广泛了,我接触过的公司、团队,没发现不使用的,连IDEA也默认集成了,之前跟Lombok类似的2个库,我是没见过有团队在使用。
    当然也可能是我眼见比较少,如果你确实讨厌Lombok,可以使用Delombok插件解决这个问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633117.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试真题 JavaScript 实现【数字涂色】【2022Q4 100分】,附详细解题思路

一、题目描述 疫情过后,希望小学终于又重新开学了,三年二班开学第一天的任务是将后面的黑板报重新制作。 黑板上已经写上了N个正整数,同学们需要给这每个数分别上一种颜色。 为了让黑板报既美观又有学习意义,老师要求同种颜色的…

粘包和半包的解决

粘包产生 public class HelloWordServer {static final Logger log LoggerFactory.getLogger(HelloWordServer.class);public static void main(String[] args) {NioEventLoopGroup boss new NioEventLoopGroup(1);NioEventLoopGroup worker new NioEventLoopGroup();try {…

Java实现微信公众号直接发送参数二维码给用户

文章目录 前言一、参数二维码的作用二、功能实现1. 生成带参数二维码2. 上传二维码图片3. 发送带参数二维码给用户 总结 前言 公众号开发近些年是一个比较热门的方向,今天为大家讲解的是用Java如何实现自动生成二维码图片,有如何把这个和用户信息单独绑…

Python3数据分析与挖掘建模(12)复合分析-相关分析与实现示例

1. 相关分析 1.1 概述 相关分析是一种统计分析方法,用于研究两个或多个变量之间的关系和相互影响程度。它帮助我们了解变量之间的线性关系、趋势和相关程度。 在相关分析中,常用的指标是相关系数,用于衡量两个变量之间的相关程度。最常见的…

linux 定时任务

可以用非root用户创建定时任务 Linux crontab 是用来定期执行程序的命令。 当安装完成操作系统之后,默认便会启动此任务调度命令。 crond 命令每分钟会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。 注意:新创建的 cro…

怎么查询电脑的登录记录及密码更改情况?

源头是办公室公用的电脑莫名其妙打不开了,问别人也都不知道密码是多少 因为本来就没设密码啊!(躺倒) 甚至已经想好了如果是50万想攻破电脑,被po抓住要怎么花这笔钱了 是我想太多 当然最后也没解决,莫名…

27 getcwd 的调试

前言 同样是一个 很常用的 glibc 库函数 不管是 用户业务代码 还是 很多类库的代码, 基本上都会用到 获取当前路径 不过 我们这里是从 具体的实现 来看一下 测试用例 就是简单的使用了一下 getcwd rootubuntu:~/Desktop/linux/HelloWorld# cat Test04Getcwd.c #inc…

11.DIY可视化-拖拽设计1天搞定主流小程序-小程序首页公告详情页面

小程序首页公告详情页面 本教程均在第一节中项目启动下操作 小程序首页公告详情页面前言一、添加界面,布局1.设定组件样式:数据绑定 二. 新增接口三:绑定公告四.查看效果五.动态参数设置 :之前是指定了公告单条数据2.优化还在那时详情页<p>标签:借助工具查看,清空绑定修改…

【树莓派】树莓派4B镜像安装(使用Raspberry Pi image)

本文主要记录下如何使用Raspberry Pi image 软件进行树莓派镜像进行安装。 官网&#xff1a;Raspberry Pi OS – Raspberry Pi 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1G7z1Fdvk5Chmhj894WPU3A 提取码&#xff1a;xnzw 一、格式化SD卡 若SD卡存在…

【Linux】按键驱动程序

【Linux】按键驱动程序 前言&#xff1a; 一、按键驱动程序的背景知识 1.1 查询方式 1.2 休眠-唤醒方式 1.3 poll方式 1.4 异步通知 1.5 总结 二、按键驱动程序的框架 三、按键驱动程序实战 3.1 头文件&#xff08;button_drv.h&#xff09; 3.2 驱动程序&#xf…

我的开源实践之路!这一路我遇到的困难和收获总结

Datawhale干货 作者&#xff1a;诸葛子房&#xff0c;Datawhale成员 从参与Apache开源项目&#xff0c;到凭借业务需求独自开发个人开源项目&#xff1b;从项目开源出来无人问津到至今500star&#xff0c;多个企业级用户&#xff0c;在开源过程中&#xff0c;我也从走过低谷&a…

Matplotlib的一些总结

plt.figure(numNone, figsizeNone, dpiNone, facecolorNone, edgecolorNone, frameonTrue) 参数说明&#xff1a; 1.num&#xff1a;图像编码或者名称&#xff0c;数字是编码&#xff0c;字符串是名称 2.figsize&#xff1a;宽和高&#xff0c;单位是英尺 3.dpi&#xff1a;指…

chatgpt赋能python:Python怎么取二进制低三位?

Python怎么取二进制低三位&#xff1f; 在Python编程中&#xff0c;处理位运算是一个非常常见的任务。其中&#xff0c;取二进制低三位也是其中的一项操作。那么&#xff0c;如何实现这个操作呢&#xff1f;本篇文章将为大家介绍Python如何取二进制低三位的方法。 什么是二进…

苹果Vision Pro:虚拟现实走进个人计算机未来

一段时间以来&#xff0c;虚拟现实&#xff08;VR&#xff09;这个概念以其无限的潜力吸引了全世界&#xff0c;用户可以进入身临其境的计算机生成的环境中&#xff0c;这些环境通常模糊了数字和物理世界之间的界线。多年来&#xff0c;VR 技术持续以惊人的速度发展&#xff0c…

软考A计划-系统架构师-学习笔记-第三弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

yolov4——你总能在这找到你想要的答案

目录 一&#xff1a;前言 二&#xff1a;一些数据增强的方法 三&#xff1a;自提议 四&#xff1a;dropout 普通的dropout yolov4的dropblock 五&#xff1a;Label smothing 标签平滑 六&#xff1a; GIOU&#xff0c;DIOU&#xff0c;CIOU 七&#xff1a; 对网络结构的…

代码随想录算法训练营第五十五天 | 力扣 392.判断子序列, 115.不同的子序列

392.判断子序列 题目 392. 判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace&quo…

Mysql 经典面试题总结

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

Flask+pyecharts实现电影数据分析可视化

之前有写过pyecharts实现电影数据分析可视化和Djangopyecharts实现电影数据分析可视化&#xff0c;但是综合起来感觉还是有缺陷&#xff0c;所以我使用Flaskpyecharts重新整合一下电影数据可视化。 下面是完成后的截图 这应该就算是可视化大屏了吧 文章目录 代码结构index.cs…

MM32F3273G8P火龙果开发板MindSDK开发教程3 - Sysclk的配置

MM32F3273G8P火龙果开发板MindSDK开发教程3 - Sysclk的配置 1、时钟初始化流程 一般流程为startup_mm32f3273g.s中调用system_mm32f3273g.c中的SystemInit函数完成系统时钟的初始&#xff0c;而system_mm32f3273g.c中函数是空的。 原来MindSdk时钟初始化的流程放到了clock_i…