美文网首页java
lombok深度指南

lombok深度指南

作者: 雨中星辰0 | 来源:发表于2019-01-02 16:46 被阅读0次

    Contents

    介绍

    Project Lombok是一个java库,它可以自动插入你的编辑器并构建工具,增强你的java代码。
    永远不要再写另一个getter或equals方法,使用一个注解,您的类具有一个功能齐全的构建器,自动化您的日志记录变量等等:

    Image illustrating the availability of new methods on a data class in Eclipse.

    安装

    idea 安装lombok

    Jetbrains的IntelliJ IDEA的编辑与lombok兼容。

    添加Lombok IntelliJ插件以添加对IntelliJ的lombok支持:

    • File > Settings > Plugins
    • 点击 Browse repositories...
    • 搜索 Lombok Plugin
    • 点击 Install plugin
    • 重启IntelliJ IDEA

    Lombok 注解

    典型的Java项目将数百行代码用于定义简单数据类所需的样板文件并不罕见。这些类通常包含这些字段的若干字段,getter和setter,以及equals和 hashCode实施方式。在最简单的场景中,Lombok项目可以将这些类减少到必需的字段和单个@Data注解。

    当然,最简单的场景不一定是开发人员日常所面临的场景。出于这个原因,Project Lombok中有许多注解允许对类的结构和行为进行更细粒度的控制。

    @Getter and @Setter

    @Getter@Setter注解分别为字段生成getter和setter。生成的getter方法正确地遵循了布尔属性的约定,从而为任何布尔字段foo生成一个isFoo getter方法名,而不是getFoo

    需要注意的是,如果带注解字段所属的类包含与要生成的getter或setter同名的方法,无论参数或返回类型如何,都不会生成相应的方法。

    @Getter@Setter注解都采用一个可选参数来指定生成方法的访问级别。

    Lombok注解代码:

    @Getter @Setter private boolean employed = true;
    @Setter(AccessLevel.PROTECTED) private String name;
    

    等效的Java源代码:

    private boolean employed = true;
    private String name;
    
    public boolean isEmployed() {
        return employed;
    }
    
    public void setEmployed(final boolean employed) {
        this.employed = employed;
    }
    
    protected void setName(final String name) {
        this.name = name;
    }
    

    @NonNull

    @NonNull注释用于指示对对应成员进行快速失败null检查的需要。当放置在Lombok正在为其生成setter方法的字段上时,将生成null检查,如果提供了null值,将导致NullPointerException。此外,如果Lombok正在为拥有的类生成构造函数,那么字段将被添加到构造函数签名中,null检查将包含在生成的构造函数代码中。

    IntelliJ IDEA和FindBugs等中的 注解镜像@NotNull@NonNull注解。对于主题的这些变化,Lombok是注解不可知的。如果Lombok遇到任何使用该名称的注解注解的成员,@NotNull或者@NonNull它将通过生成适当的相应代码来遵守它。Project Lombok的作者进一步评论说,如果将此类型的注解添加到Java,则Lombok版本将被删除。

    Family的Lombok注解代码:

    @Getter @Setter @NonNull
    private List<Person> members;
    

    等价Java源代码::

    @NonNull
    private List<Person> members;
    
    public Family(@NonNull final List<Person> members) {
        if (members == null) throw new java.lang.NullPointerException("members");
        this.members = members;
    }
        
    @NonNull
    public List<Person> getMembers() {
        return members;
    }
    
    public void setMembers(@NonNull final List<Person> members) {
        if (members == null) throw new java.lang.NullPointerException("members");
        this.members = members;
    }
    

    @ToString

    此注释生成toString方法的实现。默认情况下,任何非静态字段都将以名称-值对的形式包含在方法的输出中。如果需要,可以通过将注释参数includeFieldNames设置为false来排除在输出中包含属性名。

    通过在exclude 参数中包含其字段名称,可以从生成的方法的输出中排除特定字段。或者,该of参数可用于仅列出输出中所需的那些字段。toString通过将callSuper参数 设置为,还可以包括超类方法的输出true

    Lombok 注解代码:

    @ToString(callSuper=true,exclude="someExcludedField")
    public class Foo extends Bar {
        private boolean someBoolean = true;
        private String someStringField;
        private float someExcludedField;
    }
    

    等价Java源代码:

    public class Foo extends Bar {
        private boolean someBoolean = true;
        private String someStringField;
        private float someExcludedField;
        
        @java.lang.Override
        public java.lang.String toString() {
            return "Foo(super=" + super.toString() +
                ", someBoolean=" + someBoolean +
                ", someStringField=" + someStringField + ")";
        }
    }
    

    @EqualsAndHashCode

    这个类级注解将导致Lombok生成两者 equalshashCode方法,因为这两者是由hashCode契约本质上绑定在一起的。默认情况下,两个方法都将考虑类中非静态或瞬态的任何字段。很像@ToStringexclude提供参数是为了防止字段包含在生成的逻辑中。也可以使用该 of参数仅列出应考虑的那些字段。

    同样@ToStringcallSuper 这个注解有一个参数。将其设置为true将导致 在考虑当前类中的字段之前equals通过调用equals超类来验证相等性。对于该hashCode方法,它导致在hashCode计算散列时结合超类的结果。设置callSuper为true时,请注意确保父类中的equals方法正确处理实例类型检查。如果父类检查该类是否属于特定类型而不仅仅是两个对象的类相同,则可能导致不希望的结果。如果超类使用生成的Lombok equals方法,这不是问题。但是,其他实现可能无法正确处理此情况。另请注意,callSuper当类仅扩展时Object,无法设置 为true ,因为这会导致实例相等性检查使字段比较短路。这是由于生成的方法调用了equals实现 Object,如果被比较的两个实例不是同一个实例,则返回false。因此,在这种情况下,Lombok将生成编译时错误。

    Lombok 注解代码:

    @EqualsAndHashCode(callSuper=true,exclude={"address","city","state","zip"})
    public class Person extends SentientBeing {
        enum Gender { Male, Female }
    
        @NonNull private String name;
        @NonNull private Gender gender;
        
        private String ssn;
        private String address;
        private String city;
        private String state;
        private String zip;
    }
    

    等价Java源代码::

    public class Person extends SentientBeing {
        
        enum Gender {
            /*public static final*/ Male /* = new Gender() */,
            /*public static final*/ Female /* = new Gender() */;
        }
        @NonNull
        private String name;
        @NonNull
        private Gender gender;
        private String ssn;
        private String address;
        private String city;
        private String state;
        private String zip;
        
        @java.lang.Override
        public boolean equals(final java.lang.Object o) {
            if (o == this) return true;
            if (o == null) return false;
            if (o.getClass() != this.getClass()) return false;
            if (!super.equals(o)) return false;
            final Person other = (Person)o;
            if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
            if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false;
            if (this.ssn == null ? other.ssn != null : !this.ssn.equals(other.ssn)) return false;
            return true;
        }
        
        @java.lang.Override
        public int hashCode() {
            final int PRIME = 31;
            int result = 1;
            result = result * PRIME + super.hashCode();
            result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
            result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode());
            result = result * PRIME + (this.ssn == null ? 0 : this.ssn.hashCode());
            return result;
        }
    }
    

    @Data

    @Data注解很可能在项目lombok工具箱中最常用的注解。它结合的功能@ToString@EqualsAndHashCode@Getter@Setter。本质上,使用 @Data的一类是一样的带有默认注解类@ToString@EqualsAndHashCode 以及与两个注解每个字段@Getter 和@Setter。对类进行注解@Data 也会触发Lombok的构造函数生成。这会添加一个公共构造函数,它将任何@NonNullfinal 字段作为参数。这提供了普通旧Java对象(PO​​JO)所需的一切。

    虽然@Data非常有用,但它不提供与其他Lombok注解相同的控制粒度。要覆盖默认方法生成行为,请使用其他Lombok注解之一注解类,字段或方法,并指定必要的参数值以实现所需的效果。

    @Data确实提供了一个可用于生成静态工厂方法的参数选项。将staticConstructor参数的值设置为 所需的方法名称将导致Lombok将生成的构造函数设置为private,并公开给定名称的静态工厂方法。

    Lombok 注解代码:

    @Data(staticConstructor="of")
    public class Company {
        private final Person founder;
        private String name;
        private List<Person> employees;
    }
    

    等价Java源代码:

    public class Company {
        private final Person founder;
        private String name;
        private List<Person> employees;
        
        private Company(final Person founder) {
            this.founder = founder;
        }
        
        public static Company of(final Person founder) {
            return new Company(founder);
        }
        
        public Person getFounder() {
            return founder;
        }
        
        public String getName() {
            return name;
        }
        
        public void setName(final String name) {
            this.name = name;
        }
        
        public List<Person> getEmployees() {
            return employees;
        }
        
        public void setEmployees(final List<Person> employees) {
            this.employees = employees;
        }
        
        @java.lang.Override
        public boolean equals(final java.lang.Object o) {
            if (o == this) return true;
            if (o == null) return false;
            if (o.getClass() != this.getClass()) return false;
            final Company other = (Company)o;
            if (this.founder == null ? other.founder != null : !this.founder.equals(other.founder)) return false;
            if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
            if (this.employees == null ? other.employees != null : !this.employees.equals(other.employees)) return false;
            return true;
        }
        
        @java.lang.Override
        public int hashCode() {
            final int PRIME = 31;
            int result = 1;
            result = result * PRIME + (this.founder == null ? 0 : this.founder.hashCode());
            result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
            result = result * PRIME + (this.employees == null ? 0 : this.employees.hashCode());
            return result;
        }
        
        @java.lang.Override
        public java.lang.String toString() {
            return "Company(founder=" + founder + ", name=" + name + ", employees=" + employees + ")";
        }
    }
    

    @Cleanup

    @Cleanup注解可以用来保证分配的资源被释放。当使用带注解的局部变量时@Cleanup,任何后续代码都包含在一个 try/finally块中,该块保证在当前作用域的末尾调用cleanup方法。默认情况下,@Cleanup 假设清理方法命名为“close”,与输入和输出流一样。但是,可以为注解的value参数提供不同的方法名称。只有不带参数的清理方法才能与此注解一起使用。

    使用@Cleanup注解时还需要注意一点。如果清理方法抛出异常,它将抢占方法体中引发的任何异常。这可能导致问题的实际原因被掩盖,并且在选择使用Project Lombok的资源管理时应该考虑到这一点。此外,随着Java 7中的自动资源管理,这个特定的注解可能相对短暂。

    Lombok注解代码:

    public void testCleanUp() {
        try {
            @Cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream();
            baos.write(new byte[] {'Y','e','s'});
            System.out.println(baos.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    等价Java源代码:

    public void testCleanUp() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                baos.write(new byte[]{'Y', 'e', 's'});
                System.out.println(baos.toString());
            } finally {
                baos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    @Synchronized

    在一个方法上使用synchronized关键字可能会导致不好的结果,任何一个开发多线程软件的开发人员都可以证明这一点。synchronized关键字在实例方法或静态方法的类对象中会锁定当前对象。这意味着开发人员控制之外的代码有可能锁定同一个对象,从而导致死锁。通常建议在单独的对象上显式锁定,该对象专门用于此目的,而不是以允许非请求锁定的方式公开。Project Lombok正是为此目的提供了@Synchronized注解。

    @Synchronized注解一个实例方法将提示Lombok生成一个名为lock的私有锁定字段,该方法将在执行之前锁定该字段。类似地,以相同的方式注解静态方法将为静态方法生成一个名为`LOCK`的私有静态对象,以便以相同的方式使用。可以通过为注解的值参数提供字段名来指定不同的锁定对象。当提供字段名时,开发人员必须定义该属性,因为Lombok不会生成它。

    Lombok注解代码:

    private final java.lang.Object $ lock = new java.lang.Object [0];
    private DateFormat format = new SimpleDateFormat(“MM-dd-YYYY”);
    
    public String synchronizedFormat(Date date){
        synchronized($ lock){
            return format.format(date);
        }
    }
    

    @SneakyThrows

    @SneakyThrows可能是具有最多批评者的项目lombok注解,因为它是对已检查异常的直接攻击。关于使用已检查异常的问题存在很多分歧,大量开发人员认为这是一个失败的实验。这些开发人员会喜欢@SneakyThrows。在已检查/未检查的异常栏的另一侧的那些开发人员很可能将此视为隐藏潜在问题。

    IllegalAccessException如果IllegalAccessException或某些父类未在throws子句中列出,则 抛出通常会生成“未处理的异常”错误:

    Screenshot of Eclipse generating an error message regarding unhandled exceptions.

    当用@ sneakythrow注解时,错误就消失了.

    Screenshot of a method annotated with @SneakyThrows and generating no error in Eclipse.

    默认情况下,@SneakyThrows将允许抛出任何已检查的异常而不在 throws子句中声明。通过向注解Class<? extends Throwable>value参数提供一个throwable classes()数组,可以将其限制为一组特定的异常 .

    Lombok 注解代码:

    @SneakyThrows
    public void testSneakyThrows() {
        throw new IllegalAccessException();
    }
    

    等价Java源代码:

    public void testSneakyThrows() {
        try {
            throw new IllegalAccessException();
        } catch (java.lang.Throwable $ex) {
            throw lombok.Lombok.sneakyThrow($ex);
        }
    }
    

    看一下上面的代码和签名 Lombok.sneakyThrow(Throwable)会让大多数人认为异常被包装在一个RuntimeException 并重新抛出,但事实并非如此。该sneakyThrow 方法永远不会正常返回,而是将提供的throwable完全保持不变。

    成本和收益

    与任何技术选择一样,使用Project Lombok也会产生正面和负面影响。将Lombok的注解合并到项目中可以大大减少在IDE中生成或手工编写的样板代码行数。这样可以减少维护开销,减少错误并提高可读性。

    这并不是说在项目中使用Project Lombok注解没有缺点。Lombok项目主要旨在填补Java语言的空白。因此,可能会发生对语言的更改,从而妨碍使用Lombok的注解,例如添加第一类属性支持。此外,当与基于注解的对象关系映射(ORM)框架结合使用时,数据类上的注解数量可能开始变得难以处理。这在很大程度上被Lombok注解取代的代码量所抵消。但是,那些避免经常使用注解的人可能会选择另一种方式。

    缺什么?

    Project Lombok提供了delombok用等效源代码替换Lombok注解的实用程序。可以通过命令行对整个源目录执行此操作

    java -jar lombok.jar delombok src -d src-delomboked
    

    或者,提供Ant任务以结合到构建过程中。

    <target name="delombok">
        <taskdef classname="lombok.delombok.ant.DelombokTask"
            classpath="WebRoot/WEB-INF/lib/lombok.jar" name="delombok" />
        <mkdir dir="src-delomboked" />
        <delombok verbose="true" encoding="UTF-8" to="src-delomboked"
            from="src" />
    </target>
    

    两者delombok和相应的Ant任务都打包在核心lombok.jar下载中。除了允许Lombok注解在使用Google Web Toolkit(GWT)或其他不兼容的框架构建的应用程序中有用之外,delombok在Person类上运行 还可以轻松地将使用Lombok注解编写的类与包含等效样板内联的代码进行对比。

    package com.ociweb.jnb.lombok;
    
    import java.util.Date;
    
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.NonNull;
    
    @Data
    @EqualsAndHashCode(exclude={"address","city","state","zip"})
    public class Person {
        enum Gender { Male, Female }
    
        @NonNull private String firstName;
        @NonNull private String lastName;
        @NonNull private final Gender gender;
        @NonNull private final Date dateOfBirth;
        
        private String ssn;
        private String address;
        private String city;
        private String state;
        private String zip;
    }
    

    使用Project Lombok注解的代码比包含样板的等效类更简洁。

    package com.ociweb.jnb.lombok;
    
    import java.util.Date;
    import lombok.NonNull;
    
    public class Person {
        
        enum Gender {
            /*public static final*/ Male /* = new Gender() */,
            /*public static final*/ Female /* = new Gender() */;
        }
        @NonNull
        private String firstName;
        @NonNull
        private String lastName;
        @NonNull
        private final Gender gender;
        @NonNull
        private final Date dateOfBirth;
        private String ssn;
        private String address;
        private String city;
        private String state;
        private String zip;
        
        public Person(@NonNull final String firstName, @NonNull final String lastName,
                @NonNull final Gender gender, @NonNull final Date dateOfBirth) {
            if (firstName == null)
                throw new java.lang.NullPointerException("firstName");
            if (lastName == null)
                throw new java.lang.NullPointerException("lastName");
            if (gender == null)
                throw new java.lang.NullPointerException("gender");
            if (dateOfBirth == null)
                throw new java.lang.NullPointerException("dateOfBirth");
            this.firstName = firstName;
            this.lastName = lastName;
            this.gender = gender;
            this.dateOfBirth = dateOfBirth;
        }
        
        @NonNull
        public String getFirstName() {
            return firstName;
        }
        
        public void setFirstName(@NonNull final String firstName) {
            if (firstName == null)
                throw new java.lang.NullPointerException("firstName");
            this.firstName = firstName;
        }
        
        @NonNull
        public String getLastName() {
            return lastName;
        }
        
        public void setLastName(@NonNull final String lastName) {
            if (lastName == null)
                throw new java.lang.NullPointerException("lastName");
            this.lastName = lastName;
        }
        
        @NonNull
        public Gender getGender() {
            return gender;
        }
        
        @NonNull
        public Date getDateOfBirth() {
            return dateOfBirth;
        }
        
        public String getSsn() {
            return ssn;
        }
        
        public void setSsn(final String ssn) {
            this.ssn = ssn;
        }
        
        public String getAddress() {
            return address;
        }
        
        public void setAddress(final String address) {
            this.address = address
       }
    

    相关文章

      网友评论

        本文标题:lombok深度指南

        本文链接:https://www.haomeiwen.com/subject/mewzlqtx.html