您的位置:首页 > 编程语言 > Java开发

使用注解简化Java开发中的样板代码——Lombok框架

2018-02-14 23:56 1076 查看
前言

不少人诟病Java过于臃肿,重复,有人评价写Java就像写作文,一个单词一个单词的往下敲,所以后来在JVM平台上也诞生了许多以改善Java为目标的语言,如Scala,Kotlin。我觉得,Java这一点既可以被评价为臃肿,也可以认为是中正平和,严谨有加,对于新手程序员还是非常友好的,不必在莫名奇妙的缩写方法和指代参数中“寻章摘句”,对面向对象还是三分认识的时候,严谨的编码约束是一种行之有效的引导。

不过,对于在码农届浸淫已久的老手,对比那些后起之秀的新语言,Java确实在方方面面显得有些叠床架屋。例如,在开发过程中,我们通常都会定义大量的JavaBean,然后通过IDE去生成其属性的构造器、getter、setter、equals、hashcode、toString方法,当要对某个属性进行改变时,比如命名、类型等,都需要重新去生成上面提到的这些方法,因此被许多苦于此类的人诟病为为了面向对象而特别写成面向对象,那么有没有办法可以简化此类操作呢,有的。今天就介绍一个框架,可以一定程度上简化Java的繁杂的代码。

使用lombok框架简化Java开发中的样板代码

Lombok是什么?

按官网的定义:

Lombok工程是一个自动插入你的编译器和构建工具的Java类库,使你的Java代码更加简洁。使用后,再也不需要写getter或者equals等重复内容,并提供快速访问Java对象的方法。

其实  Lombok就是一个使用注解形式来简化Java开发中一些必须有但是写的多了就很会感觉繁琐又臃肿的Java代码的工具。

最常用的就是给一个类添加构造器getter、setter、equals、hashcode、toString等必须方法

为什么要使用lombok

一个普通的类

博客blog 只有三个成员属性

看着还是很简洁的

public class blog
{
private String name;

private String writer;

private long size;

}


我们按最普通的 只添加get和set方法

public class blog {

private String name;

private String writer;

private long size;

public String getName() {
return name;
}

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

public String getWriter() {
return writer;
}

public void setWriter(String writer) {
this.writer = writer;
}

public long getSize() {
return size;
}

public void setSize(long size) {
this.size = size;
}
}


哦 已经有点晕了 那再加上含参构造呢

public class blog {

private String name;

private String writer;

private long size;

public blog(String name, String writer, long size) {
this.name = name;
this.writer = writer;
this.size = size;
}

public blog(String na
12438
me) {
this.name = name;
}

public blog(long size) {
this.size = size;
}

public String getName() {
return name;
}

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

public String getWriter() {
return writer;
}

public void setWriter(String writer) {
this.writer = writer;
}

public long getSize() {
return size;
}

public void setSize(long size) {
this.size = size;
}
}


呃 确实很臃肿 看着都头疼

只有三个成员属性

为了操作他们竟然需要增加这么多方法

然而这还不是个完善的JavaBean

缺少equals hashcode toString等方法

添加上这些后

public class blog {

private String name;

private String writer;

private long size;

public blog(String name, String writer, long size) {
this.name = name;
this.writer = writer;
this.size = size;
}

public blog(String name) {
this.name = name;
}

public blog(long size) {
this.size = size;
}

public String getName() {
return name;
}

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

public String getWriter() {
return writer;
}

public void setWriter(String writer) {
this.writer = writer;
}

public long getSize() {
return size;
}

public void setSize(long size) {
this.size = size;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof blog)) return false;

blog blog = (blog) o;

if (getSize() != blog.getSize()) return false;
if (!getName().equals(blog.getName())) return false;
return getWriter().equals(blog.getWriter());
}

@Override
public int hashCode() {
int result = getName().hashCode();
result = 31 * result + getWriter().hashCode();
result = 31 * result + (int) (getSize() ^ (getSize() >>> 32));
return result;
}

@Override
public String toString() {
return "blog{" +
"name='" + name + '\'' +
", writer='" + writer + '\'' +
", size=" + size +
'}';
}
}


哦 这可真令人烦躁

一个类仅仅是三个成员属性

要达到完善的可用状态

就要添加如此之多的方法

真是令人头特

无怪许多人攻击java过于臃肿

毕竟C# 都提供了

public string name{get;set;}


这种简化方案

如果使用lombok呢

@Data
public class blog {

private String name;

private String writer;

private long size;

}


只要这样就可以了

没错 仅仅只是添加一个@Data注解

就省去了如此之多的样板代码

这个blog类看上去也是十分的清晰简介

突出了最重要的部分——他的成员属性

lombok既简化了样板代码 ,又突出了一个类(JavaBean)

的核心内容 所以十分有使用的必要

有人问为什么要在意JavaBean的样板方法内容多少

反正都是自动生成的

这个问题最后解答

如何使用lombok

引入依赖

Maven

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


Gradle

provided group: 'org.projectlombok', name: 'lombok', version: '1.16.18'


安装开发环境插件

lombok使用注解生成方法 所以没有显示的方法代码

如果不配置开发环境插件 编译器会报错的

以Intellij idea为例

1.启用注解处理器



2.安装lombok插件



注意这里我已经安装了 所以是update 没安装的话是install

安装完后重启下开发环境

这样就可以在开发中使用lombok的注解了

lombok的使用

lombok提供了许多注解来简化Java开发中的样板代码
最常用的有
@Data

@Getter @Setter

@ToString

@EqualsAndHashCode

@RequiredConstructor

@AllArgsConstructor

@NoArgsConstructor

@Builder

基本都是添加在JavaBean上用以简化样板代码的

@Getter@Setter

作用于属性上,自动生成get,set方法.

public class Student{
@Getter@Setter
private String name;
}


效果就等同于

public class Student{

private String name;

public String getName() {
return name;
}

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


也可以添加在类上

@Getter@Setter
public class Student{
private String name;
private String age;
}


等效于

public class Student {
private String name;
private String age;

public String getName() {
return name;
}

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

public String getAge() {
return age;
}

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


所以 一般此类注解都是直接添加在类上

@ToString

用户自定义的类 如果不重写ToString方法

则类的ToString就是默认Object的ToString实现

返回一个类似Student@78951的用户地址引用标识符

显而易见 我们并不希望看到这个 而是希望显示出类的数值内容

@ToString
public class Student {
private String name;
private String age;

}


使用lombok的@ToString注解

等效于重写ToString方法

public class Student {
private String name;
private String age;

public String toString() {
return "Student(name=" + this.name + ", age=" + this.age + ")";
}
}


可以看到 重写后的toString方法已经提供了我们想要知道的类信息

@EqualsAndHashCode

重写equals和hashcode方法 可以方便的比较类的值是否相等

@EqualsAndHashCode
public class Student {
private String name;
private String age;
}


等效于

public boolean equals(Object o) {
if(o == this) {
return true;
} else if(!(o instanceof Student)) {
return false;
} else {
Student other = (Student)o;
if(!other.canEqual(this)) {
return false;
} else {
Object this$name = this.name;
Object other$name = other.name;
if(this$name == null) {
if(other$name != null) {
return false;
}
} else if(!this$name.equals(other$name)) {
return false;
}

Object this$age = this.age;
Object other$age = other.age;
if(this$age == null) {
if(other$age != null) {
return false;
}
} else if(!this$age.equals(other$age)) {
return false;
}

return true;
}
}
}

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

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


看以看到 lombok注解实现了一整套的值相等的判断算法

相比方法内容全部显示展现的Javabean 添加注解的类代码非常简洁

@RequiredConstructor

@AllArgsConstructor

@NoArgsConstructor

这三个注解可以说是见文知意 分别是含参构造 全参构造 和无参构造

根据需要添加即可

@Data

一个JavaBean要完善可用

那就必须要有构造 get set方法 并重写toString equals hashcode方法

使用lombok 也要分别添加 以上注解

@Getter
@Setter
@RequiredArgsConstructor
@EqualsAndHashCode
@ToString public class Student { private String name; private String age; }


未免还是显得繁琐了

既然这些都是必须的 不如简化到底

这就有了最开始我们看到的@Data注解

@Data
public class Student {
private String name;
private String age;

}


只用添加一个@Data注解

所有的必备方法便准备完毕

@Value

此注解用于快速定义一个不可变类 其他功能和@Data是一样的

就是说 添加了@Value的类

其类被final修饰 成员属性也增加了final修饰符

@Value
public class Student {
private String name;
private String age;
}


编译后

public final class Student {
private final String name;
private final String age;

@ConstructorProperties({"name", "age"})
public Student(String name, String age) {
this.name = name;
this.age = age;
}

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

public String getAge() {
return this.age;
}

public boolean equals(Object o) {
if(o == this) {
return true;
} else if(!(o instanceof Student)) {
return false;
} else {
Student other = (Student)o;
Object this$name = this.getName();
Object other$name = other.getName();
if(this$name == null) {
if(other$name != null) {
return false;
}
} else if(!this$name.equals(other$name)) {
return false;
}

Object this$age = this.getAge();
Object other$age = other.getAge();
if(this$age == null) {
if(other$age != null) {
return false;
}
} else if(!this$age.equals(other$age)) {
return false;
}

return true;
}
}

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

public String toString() {
return "Student(name=" + this.getName() + ", age=" + this.getAge() + ")";
}
}


可以看到和@Data 一样 被@Value修饰的类也重写了equals hashcode tostring

只是此类本身和类的成员属性都是不可变的 同时也没有提供set方法

通过构造函数初始化后就不能更改了 同时类也不能被继承

所以@Value注解功能是快速生成不可变类

@Builder

此注解会为类生成一个构建器方法

《Effective Java》中提出一个观点 构建器比构造函数更有效 更清晰

我是比较赞同此观点的 尤其是在一些成员函数特别多的大类中

构建器比构造函数要好用的多

不过现在的Java中并没有提供构建器

遗憾的是大多数开发环境也没有提供自动生成构建器的功能

所以很少见到使用构建器

但是lombok提供了一个解决方案 添加一个@Builder注解

构建器方法便可用了

@Builder
public class Student {
private String name;
private String age;

}


编译后的代码

public class Student {
private String name;
private String age;

@ConstructorProperties({"name", "age"})
Student(String name, String age) {
this.name = name;
this.age = age;
}

public static StudentBuilder builder() {
return new StudentBuilder();
}
public static class StudentBuilder {
private String name;
private String age;

StudentBuilder() {
}

public Student.StudentBuilder name(String name) {
this.name = name;
return this;
}

public Student.StudentBuilder age(String age) {
this.age = age;
return this;
}

public Student build() {
return new Student(this.name, this.age);
}

public String toString() {
return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ")";
}
}
}


类内部已经生成一个构建器方法 包含了各成员属性的构建

调用的时候

Student student=Student.builder()
.name("student")
.age("18")
.build();


非常的清晰 新建一个类 链式调用其赋值方法

可以明确的知道是赋值给哪个对应的成员属性

而不是构造函数模糊的形参列表

@Builder还有一个相关注解 @Singular

用于标注在集合类型的成员属性上

可以防止重复调用给此成员属性赋值

因为集合类重复调用赋值就是向此集合内部添加数据

而不是替换

日志

lombok还提供了日志类注解 包含
@CommonsLog

@Log

@Log4j

@Log4j2

@XSlf4j

@JBossLog

囊括了大多数Java中用到的日志组件

以Slf4j为例

@Slf4j
public class Student {
private String name;
private String age;

}


等价于

public class Student {
private static final Logger log = LoggerFactory.getLogger(Student.class);
private String name;
private String age;
}


将与业务无关的日志功能从代码体中剥离出来

用注解标示 既减少了代码量

还可以显著减少错误

因为

private static final Logger log = LoggerFactory.getLogger(Student.class);


这行代码 大多数人是懒得写的

都是以复制粘贴为主

经常把getLogger(Student.class)里的类也粘贴过来

造成日志错误

下面介绍一些一般不用在Model上的注解

这些注解可谓是 真·奇技淫巧

@NoNull

添加在成员变量或者形参上

用于检测变量是否为空

如果为空 则抛出一个空指针异常

这个不建议使用

成员属性的判空可以使用Bean Validation来做

抛出空指针一般是需要避免的

@Synchronized

自动添加同步锁

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

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


等价于

private final Object $lock = new Object[0];
private DateFormat format = new SimpleDateFormat("YYYY-MM-dd");

public String synchronizedFormat(Date date) {
synchronized ($lock) {
return format.format(date);
}
}


可以看到该方法并不是直接作用在方法上

而是锁代码块

@Cleanup

自动关闭流

public class Cleanups {
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);
}
}
}


等效于

class CleanupExample {
public static void main(String[] args) throws IOException {
InputStream in = new FileInputStream(args[0]);
try {
OutputStream out = new FileOutputStream(args[1]);
try {
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
} finally {
if (out != null) {
out.close();
}
}
} finally {
if (in != null) {
in.close();
}
}
}
}


其他非注解功能

var/val

用过Ktolin的都知道 Ktolin提供的推定类val var

C#里也有 推定类var 和linq结合很方便

推定类是编译器自动根据上下推断类型

有些不符合java面向对象 强类型的格局

不过看看其他各种语言都提供了

java的一切为了面向对象的格局本身也在做改变

JEP268标准也引入了局部变量类型推断

据说Java10就会引入推定类var

lombok就提供了这一功能

val 不可变推定类 初始化后就不能变化了 和ktolin里面的val是一样的

public class ValExample {
public String example() {
val example = new ArrayList<String>();
example.add("Hello, World!");
val foo = example.get(0);
return foo.toLowerCase();

}
public void example2() {
val map = new HashMap<Integer, String>();
map.put(0, "zero");
map.put(5, "five");
for (val entry : map.entrySet()) {
System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
}
}
}


可以看到val类型是编译器根据上下文自动推断出来的

var 可变推定类 可以通过重新分配来更改为另一个值的变量,和java中声明正常变量的方式一样。

目前lombok还没有提供var的正式内容 但在官网上已经有了阐述

预测和ktolin种的var类相同的用法

配置项

在程序根目录下建立lombok.config文件就可以配置lombok的一些选项



如果使用idea开发环境 添加了lombok支持的项目 配置项是可以智能提示的

常用的有

通用的

lombok.*.falgUsage 可以设置使用lombok时是否提示错误 有error waring等

get/set的设置项

lombok.accessors.chain true为允许链式调用 默认false不允许

lombok.accessors.fluent true为get set方法首字符小写 默认false 首字母大写

最后

有观点反对使用lombok 声称lombok的自动代码生成影响了维护性

个人认为 这个观点放在后面那几个带有点“奇技淫巧”意思的注解上

可能还有点道理

对于JavaBean上应用的注解和日志组建类注解来说 纯属杞人忧天

之前说过 有人觉得model类的必备方法 都可以用环境自动生成

用不着简化

此言差矣 如果做过大型项目就知道

多人团队的情况下 指望其他人优秀的维护每个模型类就是异想天开

类内部方法和变量乱七八糟的搅合在一起

根本不能一目了然的看清楚有多少属性 况且

一个class的属性是一直变化的 今天可能增加一个字段 明天可能删除一个字段。

每次变化都需要修改对应的模板代码

还有不过避免的出现超级类

成员属性多的惊人 和方法混在一起 简直就是噩梦

《Effective Java》指出 每个用户自定义的类都应该遵循Java的规约

重写equals和hashcode方法 确保每个类都是可比较的

问题是 很多人根本就不重写equals和hashcode方法

埋下了无数地雷

尤其是项目太大 成员太多的情况下 根本没人力去检视每个提交的代码

如果使用了lombok的@Data注解呢 这一切都不是问题了

日志组件也是同理

复制粘贴日志启动段代码

造成了无数错误日志

使用@Slf4j/@log4j 轻松解决

目前Spring官方的Spring Initializr都出现了lombok的自动配置

还有什么不使用的理由呢?!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: