首页 > 代码库 > 可爱的Lombok

可爱的Lombok

        Lombok项目的主页链接http://projectlombok.org/index.html。

        自打Lombok引入项目之后,就不可救药的喜欢上了这一款工具。本文记录了在项目中应用Lombok时的经验,希望对更多的朋友有帮助。

        如下样例使用到了0.11.5版本的Lombok,目前最新版本为1.12.6。

        初学Lombok的使用时,建议配合Java的编译工具,便于理解Lombok在背后做的事情。

Lombok解决了什么问题

        没使用Lombok之前,定义JavaBean时,需要为每个成员定义getter/setter方法,无论手写或者自动生成,过程都不麻烦,但当Bean里的成员蛮多时,看代码的时候就比较心烦,无关的部分越来越多。随着项目的成长,代码的行数也奇迹般的增长,以至于项目经理在评估工作量时都要考虑适时的调整比率,麻烦呀。

        下面给出一个样例。

应用Lombok的效果
未使用Lombok使用Lombok反编译Lombok生成的class后得到的代码
class Person
{
    private String name;
    private int age;
    private String job;
    private Object info;


    public void setInfo(final Object info)
    {
        this.info = info;
    }


    public void setJob(final String job)
    {
        this.job = job;
    }


    public void setAge(final int age)
    {
        this.age = age;
    }


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


    public Object getInfo()
    {
        return info;
    }


    public String getJob()
    {
        return job;
    }


    public int getAge()
    {
        return age;
    }


    public String getName()
    {
        return name;
    }
}
@Data
class Person
{
    private String name;
    private int age;
    private String job;
    private Object info;
}
class Person
{
    private String name;
    private int age;
    private String job;
    private Object info;


    @Override
    public String toString()
    {
        return "Person(name=" + getName() + ", age=" + getAge() + ", job=" + getJob() + ", info=" + getInfo() + ")";
    }


    @Override
    public int hashCode()
    {
        final int PRIME = 31;
        int result = 1;
        final Object $name = getName();
        result = result * 31 + ($name == null ? 0 : $name.hashCode());
        result = result * 31 + getAge();
        final Object $job = getJob();
        result = result * 31 + ($job == null ? 0 : $job.hashCode());
        final Object $info = getInfo();
        result = result * 31 + ($info == null ? 0 : $info.hashCode());
        return result;
    }


    public boolean canEqual(final Object other)
    {
        return other instanceof Person;
    }


    @Override
    public boolean equals(final Object o)
    {
        if (o == this)
        {
            return true;
        }
        if (!(o instanceof Person))
        {
            return false;
        }
        final Person other = (Person) o;
        if (!other.canEqual(this))
        {
            return false;
        }
        final Object this$name = getName();
        final Object other$name = other.getName();
        if (this$name == null ? other$name != null : !this$name.equals(other$name))
        {
            return false;
        }
        if (getAge() != other.getAge())
        {
            return false;
        }
        final Object this$job = getJob();
        final Object other$job = other.getJob();
        if (this$job == null ? other$job != null : !this$job.equals(other$job))
        {
            return false;
        }
        final Object this$info = getInfo();
        final Object other$info = other.getInfo();
        return this$info == null ? other$info == null : this$info.equals(other$info);
    }


    public void setInfo(final Object info)
    {
        this.info = info;
    }


    public void setJob(final String job)
    {
        this.job = job;
    }


    public void setAge(final int age)
    {
        this.age = age;
    }


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


    public Object getInfo()
    {
        return info;
    }


    public String getJob()
    {
        return job;
    }


    public int getAge()
    {
        return age;
    }


    public String getName()
    {
        return name;
    }
}

        上述应用仅仅是Lombok的一个简单样例,但仍然可以说明很多问题。通过在在class关键字前增加@Data,Lombok除了自动生成所有非final成员的getter/setter方法外,还有额外的福利,lombok同时生成了toString、equals、hashCode方法,并且完全符合规范,这样缩减了代码的规模,也少了手写toString/equals/hashCode方法的烦恼。

Lombok可以做的更多

@ToString

        在调试代码时经常遇到一个问题,查看某个变量的值时,发现变量窗口展示的是一个奇怪的值(或者说对象在JVM内部表示的地址,这其实是toString方法的默认实现);想要查看对象内部各成员的值就需要逐层展开,这样才能看到内部的信息。类似的调试过程烦不胜烦,但对于定义了合适的toString方法的类的对象,调试时查看其内部成员的值则会简单许多,调试器会自动调用对象的toString方法,并将得到的字符串展示在变量值窗口,这无疑为调试带来了莫大便利。但手写toString方法其实非常麻烦,有过相关经历的朋友可能会深有体会。对于某个具体的类来说,出于安全或者性能或者其它方面的考虑,可能并不希望全部成员都出现在toString方法的返回值里。

        Lombok提供了@ToString来满足上述的需求。下面是使用@ToString的样例。

@ToString的样例
样例生成的代码
@ToString(exclude = { "age", "job" }, includeFieldNames = false, doNotUseGetters = true)
class Staff
{
    private String name;
    private int age;
    private String job;
    private Object info;
}

@ToString(callSuper = false)
class Student extends Staff
{
    private String schoolName;
    private int classNo;
}
class Staff
{
    private String name;
    private int age;
    private String job;
    private Object info;

    @Override
    public String toString()
    {
        return "Staff(" + name + ", " + info + ")";
    }
}

class Student extends Staff
{
    private String schoolName;
    private int classNo;

    @Override
    public String toString()
    {
        return "Student(schoolName=" + schoolName + ", classNo=" + classNo + ")";
    }
}


        样例使用了@ToString提供的如下能力:

  1. 使用exclude属性来控制某几个字段不出现在toString方法的结果中;
  2. 使用includeFieldNames属性来控制是否在toString方法的结果中出现成员变量的名称;
  3. 使用doNotUseGetters属性来控制在toString方法中是否使用getter方法来访问变量的值;
  4. 使用callSuper属性来控制是否要调用父类的toString方法,即在子类的toString方法输出时,是否同时将父类的成员一同输出;

@EqualsAndHashCode

        在项目开发过程中,虽然场景比较少,但仍然不可避免存在需要自定义equals或者hashCode方法的时候,当然这也是头疼的时候。根据《Effective Java》中的建议,equals方法和hashCode方法要同时实现,并且保证一致性。Lombok提供的@EqualsAndHashCode完美的解决了手写equals和hashCode方法时遇到的全部问题,不需要刻意关注底层的实现细节。如下是使用@EqualsAndHashCode的一个样例。

@EqualsAndHashCode的样例
样例生成的代码
@EqualsAndHashCode(exclude = { "age", "job" }, doNotUseGetters = true)
class Staff
{
    private String name;
    private int age;
    private String job;
    private Object info;
}

@EqualsAndHashCode(callSuper = false)
class Student extends Staff
{
    private String schoolName;
    private int classNo;
}
class Staff
{
    private String name;
    private int age;
    private String job;
    private Object info;

    @Override
    public int hashCode()
    {
        final int PRIME = 31;
        int result = 1;
        final Object $name = name;
        result = result * 31 + ($name == null ? 0 : $name.hashCode());
        final Object $info = info;
        result = result * 31 + ($info == null ? 0 : $info.hashCode());
        return result;
    }

    public boolean canEqual(final Object other)
    {
        return other instanceof Staff;
    }

    @Override
    public boolean equals(final Object o)
    {
        if (o == this)
        {
            return true;
        }
        if (!(o instanceof Staff))
        {
            return false;
        }
        final Staff other = (Staff) o;
        if (!other.canEqual(this))
        {
            return false;
        }
        final Object this$name = name;
        final Object other$name = other.name;
        if (this$name == null ? other$name != null : !this$name.equals(other$name))
        {
            return false;
        }
        final Object this$info = info;
        final Object other$info = other.info;
        return this$info == null ? other$info == null : this$info.equals(other$info);
    }
}

class Student extends Staff
{
    private String schoolName;
    private int classNo;

    @Override
    public int hashCode()
    {
        final int PRIME = 31;
        int result = 1;
        final Object $schoolName = schoolName;
        result = result * 31 + ($schoolName == null ? 0 : $schoolName.hashCode());
        result = result * 31 + classNo;
        return result;
    }

    @Override
    public boolean canEqual(final Object other)
    {
        return other instanceof Student;
    }

    @Override
    public boolean equals(final Object o)
    {
        if (o == this)
        {
            return true;
        }
        if (!(o instanceof Student))
        {
            return false;
        }
        final Student other = (Student) o;
        if (!other.canEqual(this))
        {
            return false;
        }
        final Object this$schoolName = schoolName;
        final Object other$schoolName = other.schoolName;
        if (this$schoolName == null ? other$schoolName != null : !this$schoolName.equals(other$schoolName))
        {
            return false;
        }
        return classNo == other.classNo;
    }
}

        只要在class关键字前增加@EqualsAndHashCode,Lombok就可以为我们生成一大票代码,相当给力。

        样例使用了@EqualsAndHashCode的如下能力:

  1. 使用exclude属性来控制某几个字段不出现在equals和hashCode方法中;
  2. 使用doNotUseGetters属性来控制在equals和hashCode方法中是否使用getter方法来访问变量的值;
  3. 使用callSuper属性来控制是否要调用父类的equals和hashCode方法;

@Getter和@Setter

        通过这两个注解,可以灵活控制是否为字段提供getter/settere方法,以及getter/setter方法的访问权限。由于使用非常简单,直接见样例。

@Getter和@Setter的样例
样例生成的代码
class Student
{
    @Getter
    @Setter
    private String name;

    @Getter
    private String schoolName;

    @Setter
    private int classNo;

    @Getter(AccessLevel.MODULE)
    private int age;

    @Getter(AccessLevel.NONE)
    private String job;

    @Getter(AccessLevel.PRIVATE)
    private Object info;
}
class Student
{
    private String name;
    private String schoolName;
    private int classNo;
    private int age;
    private String job;
    private Object info;

    public String getName()
    {
        return name;
    }

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

    public String getSchoolName()
    {
        return schoolName;
    }

    public void setClassNo(final int classNo)
    {
        this.classNo = classNo;
    }

    int getAge()
    {
        return age;
    }

    private Object getInfo()
    {
        return info;
    }
}

        通常情况下,在每个成员变量上手写@Getter或者@Setter比较麻烦,但优点是可以灵活控制getter或者setter方法的访问权限,具体可以参考lombok.AccessLevel类的定义,由于非常好理解,这里不再赘述。

@Data

        @Data的作用相当于是@ToString、@EqualsAndHashCode、@Getter、@Setter效果的集合,因而不再给出样例。

其它

        Lombok还提供了其它很多有用的注解,但由于在本人的项目中没有应用到,所以没有使用经验,有兴趣的朋友可以直接参考官网的资料。