• Lombok的注解


    参考:
    https://www.cnblogs.com/death00/p/11722152.html
    https://www.cnblogs.com/heyonggang/p/8638374.html
    https://blog.csdn.net/baidu_35085676/article/details/89193416

    什么Lombok

    官方介绍如下:

    Project Lombok makes java a spicier language by adding 'handlers' that know how to build and compile simple, boilerplate-free, not-quite-java code.
    

    大致意思是 Lombok 通过增加一些"处理程序",可以让 Java 代码变得简洁、快速。

    Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。

    Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。

    Lombok的使用跟引用jar包一样,可以在官网(https://projectlombok.org/download)下载jar包,也可以使用maven添加依赖:

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

    Lombok中常用额注解如下:

    注解名称 功能
    @Setter 自动添加类中所有属性相关的 set 方法
    @Getter 自动添加类中所有属性相关的 get 方法
    @Builder 使得该类可以通过 builder (建造者模式)构建对象
    @RequiredArgsConstructor 生成一个该类的构造方法,禁止无参构造
    @ToString 重写该类的toString()方法
    @EqualsAndHashCode 重写该类的equals()和hashCode()方法
    @Data 等价于上面的@Setter、@Getter、@RequiredArgsConstructor、@ToString、@EqualsAndHashCode

    @Getter和@Setter

    在实体类中,为了增强数据的安全性和隐蔽性,通常会对数据和与数据有关的方法进行封装;

    1. 将类中的属性设置为private(私有的),只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
    2. 对每个属性提供对外的公共方法访问,也就是创建一对赋取值方法(getting方法、setting方法),用于对私有属性的访问。

    @Getter和@Setter注解使用:

    • 注解在属性上,表示为属性提供getting、setting方法。
    • 注解在上,表示为类的所有属性提供getting、setting方法。

    样例:

    import lombok.Getter;
    import lombok.Setter;
    
    @getter
    @setter
    public class User {
        String name;
      
        String address;
    }
    

    @toString

    注解在上。

    Lombok会生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。

    @ToString(exclude="id")
    public class ToStringExample {
      private static final int STATIC_VAR = 10;
      private String name;
      private Shape shape = new Square(5, 10);
      private String[] tags;
      private int id;
      
      public String getName() {
        return this.getName();
      }
      
      @ToString(callSuper=true, includeFieldNames=true)
      public static class Square extends Shape {
        private final int width, height;
        
        public Square(int width, int height) {
          this.width = width;
          this.height = height;
        }
      }
    }
    
    • @ToString的includeFieldNames属性默认为true,可以为toString()方法的输出增加一些清晰度(即打印出字段名称)。
    • 默认情况下,将打印所有非静态字段。如果要跳过某些字段,可以使用exclude属性。
    • 也可精确指定要被toString的字段,首先设置设onlyExplicitlyIncluded属性为true,然后使用include属性标记要包含的字段。
    • 通过设置callSuper属性为true,可以将超类实现toString的输出包含到当前输出中。请注意,toString()的默认实现java.lang.Object几乎没有意义,因此除非你主动继承了另一个类,否则你这样做没有意义。
    • 可以更改用于标识成员的名称@ToString.Include(name = “some other name”),name相当于给字段起别名;
    • 也可以通过更改成员的打印顺序@ToString.Include(rank = -1)。没有等级的成员被认为是等级0,等级数字大的成员首先被打印,等级相同的成员以它们在源文件中出现的顺序打印。

    @Builder

    @Builder注解在上。

    @Builder使用创建者模式又叫建造者模式。简单来说,就是一步步创建一个对象,它对用户屏蔽了里面构建的细节,但却可以精细地控制对象的构造过程。
    @Builder注解的类,可以让你以下面显示的那样调用你的代码,来初始化你的实例对象:

    @Builder
    public class Card {
        private int id;
        private String name;
        private boolean sex;
    }
    
    Card card = Card.builder().id(10).name("dasd").sex(true).build();
    

    @Builder的使用:

    • 可以放在类,构造函数或方法上。虽然放在类上和放在构造函数上这两种模式是最常见的用例,但@Builder最容易用放在方法的用例来解释。

    @Builder的优点

    • 不需些太多的set方法来定义属性内容
    • 写法更优雅

    @Builder注解对类做了啥?

    我们反编译上面定义的Card类,可以看到:

    public class Card {
        private int id;
        private String name;
        private boolean sex;
    
        Card(int id, String name, boolean sex) {
            this.id = id;
            this.name = name;
            this.sex = sex;
        }
    
        public static Card.CardBuilder builder() {
            return new Card.CardBuilder();
        }
    
        public static class CardBuilder {
            private int id;
            private String name;
            private boolean sex;
    
            CardBuilder() {
            }
    
            public Card.CardBuilder id(int id) {
                this.id = id;
                return this;
            }
    
            public Card.CardBuilder name(String name) {
                this.name = name;
                return this;
            }
    
            public Card.CardBuilder sex(boolean sex) {
                this.sex = sex;
                return this;
            }
    
            public Card build() {
                return new Card(this.id, this.name, this.sex);
            }
    
            public String toString() {
                return "Card.CardBuilder(id=" + this.id + ", name=" + this.name + ", sex=" + this.sex + ")";
            }
        }
    }
    
    1. 创建一个名为CardBuilder的内部静态类,并具有和实体类相同的属性。(称为构建器)
      • 在构建器中:实体类中的所有属性和未初始化final字段,都会在“构建器”中创建对应属性。
      • 在构建器中:创建一个无参的default构造函数。
      • 在构建器中:对于实体类中的每个参数,都会对应创建类似于setter的方法,只不过方法名与该参数名相同。返回值是“构建器”本身。
      • 在构建器中:创建一个build()方法,调用此方法,就会根据设置的值进行创建“实体类对象”。
      • 在构建器中:同时也会生成一个toString()方法。
    2. 在实体类中:会创建一个builder()方法,它的目的是用来创建构建器。

    所以,如下创建实例的过程就可以解释为:

    1. Card.builder()创建一个“构建器”.
    2. 通过setter方法(方法名就是属性名),设置“构建器”中属性值。
    3. 调用“构建器”的build()方法,根据“构建器”创建实体类实例。
    Card card = Card.builder().id(10).name("dasd").sex(true).build();
    

    @Builder的缺点

    最明显的一点,在生成实体类实例之前,实际上是先创建了一个“构建器”实例,这样很明显额外占用了内存。

    @Builder的toBuilder参数

    如果使用格式是:

    @Builder(toBuilder = true)
    

    则“构造器”中会新生成一个toBuilder方法,允许你将一个实例化好的Card更新字段生成新的Card实例。

    public Card.CardBuilder toBuilder() {
        return (new Card.CardBuilder()).id(this.id).name(this.name).sex(this.sex);
    }
    

    @EqualsAndHashCode

    1. 此注解会生成equals(Object other) 和 hashCode()方法。
    2. 它默认使用非静态,非瞬态的属性
    3. 可通过参数exclude排除一些属性
    4. 可通过参数of指定仅使用哪些属性
    5. 它默认仅使用该类中定义的属性且不调用父类的方法
    6. 可通过callSuper=true解决上一点问题。让其生成的方法中调用父类的方法。
    @EqualsAndHashCode(exclude={"id", "shape"})
    public class EqualsAndHashCodeExample {
      private transient int transientVar = 10;
      private String name;
      private double score;
      private Shape shape = new Square(5, 10);
      private String[] tags;
      private int id;
      
      public String getName() {
        return this.name;
      }
      
      @EqualsAndHashCode(callSuper=true)
      public static class Square extends Shape {
        private final int width, height;
        
        public Square(int width, int height) {
          this.width = width;
          this.height = height;
        }
      }
    }
    

    四、@Data

    注解在类上。提供类所有属性的getting和setting方法,此外还提供了equals、canEqual、hashCode、toString方法。
    @Data注解是SpringBoot提供的更广泛注解,其包含了@Getter、@Setter、@ToString、@EqualsAndHashCode等

    @Data注解的功能:

    1. 为类提供读写功能,从而不用写getter、setter方法。
    2. 为类提供 equals()、hashCode()、toString() 方法。

    @Data注解使用:

    1. 在maven库中添加依赖
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>1.16.10</version>
                </dependency>
    
    1. 实体类上添加@Data注解即可生效

    使用@Data注解自动生成的equals()问题

    @Data是包含了@EqualsAndHashCode的功能,那么它究竟是如何重写equals()和hashCode()方法的呢?
    我们定义一个类TestA:

    @Data
    public class TestA {
        String oldName;
    }
    

    我们将其编译后的 class 文件进行反编译:

    public class TestA {
        String oldName;
    
        public TestA() {
        }
    
        public String getOldName() {
            return this.oldName;
        }
        public void setOldName(String oldName) {
            this.oldName = oldName;
        }
    
        public boolean equals(Object o) {
            // 判断是否是同一个对象
            if (o == this) {
                return true;
            }
            // 判断是否是同一个类
            else if (!(o instanceof TestA)) {
                return false;
            } else {
                TestA other = (TestA) o;
                if (!other.canEqual(this)) {
                    return false;
                } else {
                    // 比较类中的属性(注意这里,只比较了当前类中的属性)
                    Object this$oldName = this.getOldName();
                    Object other$oldName = other.getOldName();
                    if (this$oldName == null) {
                        if (other$oldName != null) {
                            return false;
                        }
                    } else if (!this$oldName.equals(other$oldName)) {
                        return false;
                    }
    
                    return true;
                }
            }
        }
    
        protected boolean canEqual(Object other) {
            return other instanceof TestA;
        }
    
        public int hashCode() {
            int PRIME = true;
            int result = 1;
            Object $oldName = this.getOldName();
            int result = result * 59 + ($oldName == null ? 43 : $oldName.hashCode());
            return result;
        }
    
        public String toString() {
            return "TestA(oldName=" + this.getOldName() + ")";
        }
    }
    

    针对其equals()方法,当它进行属性比较时,其实只比较了当前类中的属性。如果你不信的话,我们再来创建一个类TestB,它是TestA的子类:

    @Data
    public class TestB extends TestA {
        private String name;
        private int age;
    }
    

    我们将其编译后的 class 文件进行反编译:

    public class TestB extends TestA {
        private String name;
        private int age;
        public TestB() {
        }
    
        public String getName() {
            return this.name;
        }
        public int getAge() {
            return this.age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
        public void setAge(int age) {
            this.age = age;
        }
    
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof TestB)) {
                return false;
            } else {
                TestB other = (TestB)o;
                if (!other.canEqual(this)) {
                    return false;
                } else {
                    // 注意这里,真的是只比较了当前类中的属性,并没有比较父类中的属性
                    Object this$name = this.getName();
                    Object other$name = other.getName();
                    if (this$name == null) {
                        if (other$name == null) {
                            return this.getAge() == other.getAge();
                        }
                    } else if (this$name.equals(other$name)) {
                        return this.getAge() == other.getAge();
                    }
    
                    return false;
                }
            }
        }
    
        protected boolean canEqual(Object other) {
            return other instanceof TestB;
        }
    
        public int hashCode() {
            int PRIME = true;
            int result = 1;
            Object $name = this.getName();
            int result = result * 59 + ($name == null ? 43 : $name.hashCode());
            result = result * 59 + this.getAge();
            return result;
        }
    
        public String toString() {
            return "TestB(name=" + this.getName() + ", age=" + this.getAge() + ")";
        }
    }
    

    按照代码的理解,如果两个子类对象,其子类中的属性相同、父类中的属性不同时,利用equals()方法时,依旧会认为这两个对象相同,测试一下:

    public static void main(String[] args) {
            TestB t1 = new TestB();
            TestB t2 = new TestB();
    
            t1.setOldName("123");
            t2.setOldName("12345");
    
            String name = "1";
            t1.name = name;
            t2.name = name;
    
            int age = 1;
            t1.age = age;
            t2.age = age;
    
            System.out.println(t1.equals(t2));
            System.out.println(t2.equals(t1));
            System.out.println(t1.hashCode());
            System.out.println(t2.hashCode());
            System.out.println(t1 == t2);
            System.out.println(Objects.equals(t1, t2));
        }
    

    结果为:

    true
    true
    6373
    6373
    false
    true
    

    So,使用@Data时候要注意。
    比如,有多个类有相同的部分属性,把它们定义到父类中,恰好id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other) 和 hashCode()方法判定为相等,从而导致出错。
    修复此问题的方法:

    1. 使用@Getter @Setter @ToString代替@Data,并自定义equals(Object other) 和 hashCode()方法,比如有些类只需要判断主键id是否相等即足矣。
    2. 或者使用在使用@Data时同时加上@EqualsAndHashCode(callSuper=true)注解。

    @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

    1. @NoArgsConstructor
      注解在上,为类提供一个全参的构造方法。

    注:

    • 当类中有 final 字段没有被初始化时,编译器会报错,此时可用 @NoArgsConstructor(force = true),然后就会为没有初始化的final字段设置默认值 0/false/null。
    • 对于具有约束的字段(例如 @NonNull 字段),不会生成检查或分配,因此请注意,正确初始化这些字段之前,这些约束无效。
    1. @AllArgsConstructor
      注解在上,为类提供一个无参的构造方法。
      默认生成的方法是 public 的,如果要修改方法修饰符可以设置 AccessLevel 的值。如:
    @AllArgsConstructor(access = AccessLevel.PROTECTED)
    

    3、@RequiredArgsConstructor
    注解在上,会生成构造方法(可能带参数也可能不带参数)。
    注意:

    • 如果带参数,这参数只能是以 final 修饰的未经初始化的字段或者是以 @NonNull 注解的未经初始化的字段。
    • 该注解还可以用 @RequiredArgsConstructor(staticName="methodName") 生成一个指定名称的静态方法,返回一个调用相应的构造方法产生的对象。
      如:
    // 使用注解
    @RequiredArgsConstructor(staticName = "hangge")
    public class Shape {
        private int x;
        @NonNull
        private double y;
        @NonNull
        private String name;
    }
     
    // 不使用注解
    public class Shape {
        private int x;
        private double y;
        private String name;
     
        public Shape(double y, String name){
            this.y = y;
            this.name = name;
        }
     
        public static Shape hangge(double y, String name){
            return new Shape(y, name);
        }
    }
    

    @NonNull

    @NonNull可以标注在方法、字段、参数之上,表示对应的值不可以为空。Lombok会生成一个非空的声明,可用于校验参数,能帮助避免空指针。

    import lombok.NonNull;
    
    public class NonNullExample extends Something {
      private String name;
      
      public NonNullExample(@NonNull Person person) {
        super("Hello");
        this.name = person.getName();
      }
    }
    

    @Cleanup

    该注解能帮助我们自动调用close()方法,很大的简化了代码。

    import lombok.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);
        }
      }
    }
    

    Lombok的优缺点

    1. 优点:
      • 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率。
      • 让代码变得简洁,不用过多的去关注相应的方法
      • 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
    2. 缺点:
      • 不支持多种参数构造器的重载
      • 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

    Lombok虽然有很多优点,但Lombok更类似于一种IDE插件,项目也需要依赖相应的jar包。Lombok依赖jar包是因为编译时要用它的注解,为什么说它又类似插件?因为在使用时,eclipse或IntelliJ IDEA都需要安装相应的插件,在编译器编译时通过操作AST(抽象语法树)改变字节码生成,变向的就是说它在改变java语法。它不像spring的依赖注入或者mybatis的ORM一样是运行时的特性,而是编译时的特性。这里我个人最感觉不爽的地方就是对插件的依赖!因为Lombok只是省去了一些人工生成代码的麻烦,但IDE都有快捷键来协助生成getter/setter等方法,也非常方便。

    知乎上有位大神发表过对Lombok的一些看法:

    这是一种低级趣味的插件,不建议使用。JAVA发展到今天,各种插件层出不穷,如何甄别各种插件的优劣?能从架构上优化你的设计的,能提高应用程序性能的 ,
    实现高度封装可扩展的..., 像lombok这种,像这种插件,已经不仅仅是插件了,改变了你如何编写源码,事实上,少去了代码你写上去又如何?
    如果JAVA家族到处充斥这样的东西,那只不过是一坨披着金属颜色的屎,迟早会被其它的语言取代。
    虽然话糙但理确实不糙,试想一个项目有非常多类似Lombok这样的插件,个人觉得真的会极大的降低阅读源代码的舒适度。

    虽然非常不建议在属性的getter/setter写一些业务代码,但在多年项目的实战中,有时通过给getter/setter加一点点业务代码,能极大的简化某些业务场景的代码。所谓取舍,也许就是这时的舍弃一定的规范,取得极大的方便。

    我现在非常坚信一条理念,任何编程语言或插件,都仅仅只是工具而已,即使工具再强大也在于用的人,就如同小米加步枪照样能赢飞机大炮的道理一样。结合具体业务场景和项目实际情况,无需一味追求高大上的技术,适合的才是王道。

    Lombok有它的得天独厚的优点,也有它避之不及的缺点,熟知其优缺点,在实战中灵活运用才是王道。

  • 相关阅读:
    js中的字符串,以及ES5新增的字符串方法
    数组排序,数组去重(扩展运算符),ES5中数组新增的几个方法
    js中的对象(object:对象和数组)以及对象的常用方法
    http与https
    TCP的三次握手四次挥手
    前后端分离与不分离
    了解一下express中间件的意思以及next()
    Java堆,栈,堆栈
    Java构造器与new关键字
    命令行连接db2数据库
  • 原文地址:https://www.cnblogs.com/yickel/p/14501839.html
Copyright © 2020-2023  润新知