Project Lombok

是时候向setter/getterhashcode/equals、以及constructors/toString等样板式代码 (Boilerplate Code) 说再见了,您只需要一个注解:@Data 就能统统搞定了。Lombok 是一款可以通过简单的注解形式,来帮助开发者简化并消除 Java 代码臃肿的工具 。它不但能够减少您的代码编写量,还能够帮助您打理那些生成的字节码。

参考链接:Project Lombok 官方指南

介绍

" Boilerplate "是形容在应用程序中许多重复的几乎没有变化的代码的术语。很多人批评Java “写代码成吨成吨的写” ,就是因为很多项目里都存在这种代码。这个问题的出现部分是因为Java语言的本身局限性,另一部分是因为广泛的Java类库的设计。Project Lombok的出现旨在通过一系列简单的注解来消除这些不得不在看上去有很冗余的样板式代码。
虽然基于注解的指示用法,实现绑定,甚至用来生成框架使用的代码,像Spring,Mybatis等等都大量使用了基于注解的开发。但是它们通常不用于生成应用程序直接使用的代码。部分原因是在开发时需要注解生成的代码立刻生效。Project Lombok就是这么做的。Project Lombok可以集成到IDE中,对于开发者,Project Lombok能够立即生效。
举例说明,简单添加 @Data注解数据类,如下,将会生成一系列方法在IDE里。
Project Lombok -- 简洁优雅Java代码利器 (之一)

安装

Project Lombok是一个单独的Jar文件(jar包),上面提供的官方链接可以进行下载。它提供了API供开发人员查看。大多数系统中,简单的双击Jar文件就可以启动安装程序。如果系统未配置为正确启动jar文件,则还可以从命令行运行,如下所示:

java -jar lombok.jar

安装程序将尝试检测支持的IDE的位置。如果无法正确确定IDE的安装位置,则可以手动指定位置。只需单击Install / Update,IDE集成即可完成。Project Lombok -- 简洁优雅Java代码利器 (之一)
实践表明:常用的 Eclipse, NetBeans 和 IntelliJ IDEA 都是支持的。
但是,简单双击这件事一看就不是程序员干的,正经程序员推荐使用 Maven 。
如下所示,在 pom.xml文件中添加依赖:

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>0.9.2</version>
    </dependency>
</dependencies>
<repositories>
    <repository>
        <id>projectlombok.org</id>
        <url>http://projectlombok.org/mavenrepo</url>
    </repository>
</repositories>
Lombok注解使用
@Getter and @Setter

@Getter @Setter注解生成方法。生成的getter方法遵循布尔属性的约定。举个栗子,对于布尔属性字段foo,生成isFoo,而不是getFoo。应当注意的是,如果注解字段所属的类包含与要生成的setter/getter同名的方法,则无论参数或返回类型如何,都不会生成相应的方法。
@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注解用于指示对相应成员fast-fail空值检查的必要性。当生成setter方法时,将对字段进行空值检查,如果提供空值,将抛出NullPointerException异常提示。此外,如果生成构造函数,则将该字段添加到构造函数的签名中,并且在生成的构造函数代码中进行空值检查。

Lombok注解代码 class Family

@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方法,默认情况下,任何非静态字段都将以name-value对的形式包含在方法的输出中。根据需要,可以通过将注解参数includeFieldNames设置为false,在输出中抑制生成包含属性名称。
通过在exclude参数中包含其字段名称,可以从生成方法的输出中排除特定字段。或者,of参数可用于仅列出输出中所需的那些字段。通过将callSuper参数设置为true,还可以包含超类的toString方法的输出。

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

这个类级别的注释将生成equalshashCode方法,因为这两个方法本质上由hashCode原理绑定在一起。默认情况下,类中的任何不是static / transient修饰的字段都要考虑到。很像@ToString,参数exclude用来从生成方法的输出中排除特定字段,同样的,of参数可用于仅列出输出中所需的那些字段。
还是像@ToString,设置callSuper参数为true,将导致equals通过在考虑当前类中的字段之前调用来自超类的equals来验证相等性。对于hashCode方法,它导致在计算哈希值时结合超类的hashCode的结果。将callSuper设置为true时,请注意确保父类中的equals方法正确处理实例类型检查。如果父类检查该类属于特定类型而不仅仅是两个对象的类相同,则可能导致不希望的结果。如果超类使用Lombok生成的equals方法,则这不是问题。但是,其他实现可能无法正确处理此情况。
另外注意到,当类进继承自Object,无法将callSuper设置为true,这会导致实例等式检查使字段比较短路。这是由于生成的方法调用Object上的equals实现,如果被比较的两个实例不是同一个实例,则返回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

这个注解是使用最多的,把@ToString @EqualsAndHashCode
@Getter @Setter几个注解的功能集成在一起。本质上,差别不大。使用@Data注释类也会触发Lombok的构造函数生成。 这会添加一个公共构造函数,它将任何@NonNullfinal字段作为参数。 这提供了普通Java对象(POJO)所需的一切。
虽然这个注解很有用,但是它不像其他注解能提供很多控制参数,减少了可操作空间。@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注解可用于确保释放已分配的资源。当使用该注解局部变量时,任何后续代码都包含在try / finally块中,该块保证在当前作用域结束时调用cleanup方法。默认情况下,@Cleanup假定清理方法命名为“close”,与输入和输出流一样。 但是,可以为注释的value参数提供不同的方法名称。 只有不带参数的清理方法才能与此注释一起使用。
另外还需注意一点,如果清理方法抛出异常,它将抢占方法体中抛出的任何异常。这可能导致问题的实际原因被掩盖,并且在选择使用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关键字将在实例方法的情况下锁定当前对象this或类对象,这意味着开发人员控制之外的代码可能会锁定同一个对象,从而导致死锁。建议明确地锁定一个单独的对象,该对象仅专用于该目的,而不是以允许未经请求的锁定的方式公开。
使用@Synchronized注解实例方法将提示Lombok生成一个名为$lock的私有锁定字段,该方法将在执行之前锁定该字段。

Lombok注解代码:

private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");

@Synchronized
public String synchronizedFormat(Date date) {
    return format.format(date);
}

对应的java代码:

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

这个注解也许是收到最多批评的了吧,因为它是对checked exceptions的直接攻击。关于使用已检查异常的问题存在很多分歧,大量开发人员认为这是一个失败的实验,这些开发人员会喜欢@SneakyThrows。 在已检查/未检查的异常栏的另一侧的那些开发人员很可能将此视为隐藏潜在问题。
抛出IllegalAccessException通常会生成一个Unhandled exception错误,
如果 IllegalAccessException,或某些父类,未列在throws子句中:
Project Lombok -- 简洁优雅Java代码利器 (之一)
但是当使用该注解,就不会报错。
Project Lombok -- 简洁优雅Java代码利器 (之一)
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);
    }
}
优缺点

与任何技术选择一样,使用Project Lombok也会产生正面和负面影响。将Lombok的注解合并到项目中可以大大减少在IDE中生成或手工编写的样板代码行数。这样可以减少维护开销,减少错误并提高可读性。
这并不是说在项目中使用Project Lombok注释没有缺点。 Lombok项目主要旨在填补Java语言的空白。因此,可能会发生对语言的更改,从而妨碍使用Lombok的注解,例如添加第一类属性支持。此外,当与基于注解的对象关系映射(ORM)框架结合使用时,数据类上的注解数量可能开始变得难以处理。这在很大程度上被Lombok注解取代的代码量所抵消。但是,那些避免频繁使用注解的人可能会选择另一种方式。

总结

Lombok项目是实用开发人员的强大工具。它提供了一组有用的注解,用于从Java类中消除大量的样板代码。在最好的情况下,仅仅五个字符就可以替换数百行代码。结果是Java类干净,简洁且易于维护。然而,这些好处需要付出代价。IDE和JDK升级存在破坏的风险,以及围绕项目目标和实施的争议。所有这一切转化为与任何技术选择必须考虑的内容没有什么不同。总会有收获和损失。问题在于,Lombok是否可以为手头的项目提供更多的价值而不是代价。如果不出意外,Lombok肯定会为java语言特征的讨论注入新的活力,从任何角度来看都是一种胜利。

祝进步

相关文章: