12工具篇(D3_Lombok)

目录

一、基本介绍

二、Lombok使用说明

1. 基本介绍

2. 安装插件

IDEA在线安装Lombok插件

IDEA离线安装Lombok插件

3. 引入依赖坐标

4. Lombok注解功能说明

@NonNull

@Getter&@Setter

@Cleanup

@ToString

@EqualsAndHashCode

@Constructor

@RequiredArgsConstructor

方式一:注解注入

方式二:构造器注入

方式三:setter注入

方式四:@RequiredArgsConstructor

@Data

@SneakyThrows

@Synchronized

@Slf4j

@Builder

@Accessors

@Accessors(chain=true)

@Accessors(fluent = true)

@Accessors(prefix = "f")

5. Lombok原理分析

5.1 简介

5.2 两种解析

运行时解析

编译时解析

Annotation Processing Tool

Pluggable Annotation Processing API

6. 为什么不推荐使用Lombok

6.1 高侵入性,强迫队友

6.2 代码可调试性降低

6.3 影响版本升级

6.4 注解使用有风险

6.5 可能会破坏封装性


一、基本介绍

官网文档:https://projectlombok.org/

官方说明:

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.

Never write another getter or equals method again, with one annotation your class has a fully featured builder,

Automate your logging variables, and much more.

Project Lombok是一个java库,它可以自动插入到您的编辑器和构建工具中,为您的java增添趣味。

永远不要再写另一个getter或equals方法了,只要有一个注释,你的类就有了一个功能齐全的构建器,自动化你的日志变量,

等等。

Lombok简单理解就是一个Java类库,通过注解的形式帮助开发减少一些结构化代码的开发工作,提高开发效率,

比如通过@Data注解,class在编译的时候会自动生成get,set,equals,hash,toString等方法,

避免写大量的代码,减少了代码量,也使代码看起来更加简洁。

尤其是一些对象属性需要改动的时候,每次改动都需要重新生成get,set,equals,hash,toString等方法,

而使用注解则可以避免此问题。

二、Lombok使用说明

1. 基本介绍

如果要使用lombok,首先开发工具IntelliJ IDEA或者Eclipse需要先安装插件支持,其次需要引入依赖。

2. 安装插件

IDEA在线安装Lombok插件

File > Settings > Plugins >Marketplace,搜索Lombok,点击install,弹窗Accept,然后安装好后Restart IDEA。

IDEA离线安装Lombok插件

首先下载离线插件,这里要选择idea对应的版本,否则不兼容。

下载地址:Lombok - IntelliJ IDEs Plugin | Marketplace

3. 引入依赖坐标

Maven项目可以在pom.xml中配置依赖坐标即可。

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>

        <artifactId>lombok</artifactId>

        <version>1.18.24</version>

        <scope>provided</scope>

    </dependency>

</dependencies>

注意:

provided表示该包只在编译和测试的时候用,项目真正打成包时不会将Lombok包打进去。

Lombok还支持其他构建方法,比如Ant、Gradle、Kobalt,有需要的可以参考官网的Install菜单下的Build Tools,

其他使用方法也可以参考Install菜单。

4. Lombok注解功能说明

官方API文档:https://projectlombok.org/api/

@NonNull

@NonNull该注解用在属性或构造器上

Lombok会生成一个非空的声明,可用于校验参数,能帮助避免空指针。

    /**
     * 1.@NonNull该注解用在属性或构造器上,Lombok会生成一个非空的声明,可用于校验参数,能帮助避免空指针。
     * 参数User为null时产生异常:NullPointerException
     */
    public static String getName(@NonNull User user) {
        return user.getName();
    }

    /**
     * 等价@NonNull
     */
    public static String getName2(User user) {
        if (user == null) {throw new NullPointerException("user is marked non-null but is null");}
        return user.getName();
    }

@Getter&@Setter

@Getter 和@Setter 注解在类或字段

注解在类时为所有字段生成getter,setter方法,注解在字段上时只为该字段生成getter,setter方法。

@ToString(exclude = {"phone"})
public class Student {
    @Getter
    @Setter
    private String name;

    /**
     * 只生成set方法,且作用范围 修饰符PROTECTED
     */
    @Setter(AccessLevel.PROTECTED)
    private int age;
    /**
     * 只生成get方法,且作用范围 修饰符PUBLIC
     */
    @Getter(AccessLevel.PUBLIC)
    private String address;

    @Getter
    @Setter
    private String phone;
}

@Cleanup

@Cleanup这个注解用在变量前面

可以保证此变量代表的资源会被自动关闭,默认是调用资源的close()方法,

如果该资源有其它关闭方法,可使用@Cleanup(“methodName”)来指定要调用的方法。

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[1024];
     while (true) {
       int r = in.read(b);
       if (r == -1) break;
       out.write(b, 0, r);
     }
 }

@ToString

@ToString 注解在类,添加toString方法

@ToString在JavaBean或类JavaBean中使用,使用此注解会自动重写对应的toStirng方法,

默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割,

通过callSuper参数来指定是否引用父类,includeFieldNames参数设为true,就能明确的输出toString()属性。

  • @ToString(exclude=”column”)**意义:**排除column列所对应的元素,即在生成toString方法时不包含column参数;
  • @ToString(exclude={“column1″,”column2″})**意义:**排除多个column列所对应的元素,其中间用英文状态下的逗号进行分割,即在生成toString方法时不包含多个column参数;
  • @ToString(of=”column”)**意义:**只生成包含column列所对应的元素的参数的toString方法,即在生成toString方法时只包含column参数;;
  • @ToString(of={“column1″,”column2”})**意义:**只生成包含多个column列所对应的元素的参数的toString方法,其中间用英文状态下的逗号进行分割,即在生成toString方法时只包含多个column参数;

@EqualsAndHashCode

@EqualsAndHashCode 注解在类,生成hashCode和equals方法

@EqualsAndHashCode默认情况下,

会使用所有非静态(non-static)和非瞬态(non-transient)属性来生成equals和hasCode,

也能通过exclude注解来排除一些属性。

@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;
    }
  }
}

@Constructor

@NoArgsConstructor、@RequiredArgsConstructor和@AllArgsConstructor

这三个注解都是用在类上的,NoArgsConstructor 注解在类生成无参的构造方法。

@RequiredArgsConstructor 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。

@AllArgsConstructor 注解在类,生成包含类中所有字段的构造方法。

三个注解都可以指定生成的构造方法的访问权限,

同时,第二个注解还可以用@RequiredArgsConstructor(staticName=”methodName”)的形式生成一个指定名称的静态方法,

返回一个调用相应的构造方法产生的对象。

@RequiredArgsConstructor(staticName = "myShape")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
public class Shape {
    private int x;
    @NonNull
    private double y;
    @NonNull
    private String name;
}

@RequiredArgsConstructor

Lombok提供的 @RequiredArgsConstructor 方式(会生成一个包含常量,和标识了NotNull的变量的构造方法)

在springboot项目中,controller或service层中需要注入多个mapper接口或者另外的service接口,

这时候代码中就会有多个@AutoWired注解,使得代码看起来什么的混乱。

lombok提供了一个注解:

@RequiredArgsConstructor(onConstructor =@_(@Autowired))

写在类上面可以代替@AutoWired注解,需要注意的是:在注入的时候需要用final定义,或者使用@notnull注解

方式一:注解注入

Controller

public class FooController {
  @Autowired
  //@Inject
  private FooService fooService;

  //简单的使用例子,下同
  public List<Foo> listFoo() {
      return fooService.list();
  }
}
方式二:构造器注入

Controller

public class FooController {

  private final FooService fooService; 

  @Autowired

  public FooController(FooService fooService) {

      this.fooService = fooService;

  }

  //使用方式上同,略

}
方式三:setter注入
@Controller

public class FooController {

  private FooService fooService;

  //使用方式上同,略

  @Autowired

  public void setFooService(FooService fooService) {

      this.fooService = fooService;

  }

}
方式四:@RequiredArgsConstructor
@RequiredArgsConstructor
public class VerifyController {

    private final VerifyService verifyService;

    private final InvitationService invitationService;

    private final VerificationCodeService verificationCodeService;

}

@Data

@Data 注解在类

生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。

特别注意:Lombok的@Data注解生成的EqualsAndHashCode默认不支持父类在使用Lombok过程中,

如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果。

举一个简单的例子:

我们知道,当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。

但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,

会默认是@EqualsAndHashCode(callSuper=false),

这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性,

无论父类属性访问权限是否开放,这就可能得到意想不到的结果。

以下是测试验证。

User,UserCustomer,UserEmployee

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
}
@Data
public class UserCustomer extends User {
    private String customerId;
}
@Data
@EqualsAndHashCode(callSuper = true)
public class UserEmployee extends User{
    private String empId;
}

LombokDataTest

/**
 * Lombok的@Data注解生成的EqualsAndHashCode默认不支持父类
 * 在使用Lombok过程中,如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果。
 * 举一个简单的例子:我们知道,当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。
 * 但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,会默认是@EqualsAndHashCode(callSuper=false),
 * 这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性,无论父类属性访问权限是否开放,
 * 这就可能得到意想不到的结果。
 **/
public class LombokDataTest {
    public static void main(String[] args) {
        //1.@Data默认@EqualsAndHashCode(callSuper = false)
        compareUserCustomerWithCallSuperFalse();

        System.out.println("--------------------------------");

        //2.@Data指定@EqualsAndHashCode(callSuper = true)
        compareUserEmployeeWithCallSuperFalse();
    }

    /**
     * 2.@Data指定@EqualsAndHashCode(callSuper = true)
     */
    private static void compareUserEmployeeWithCallSuperFalse() {
        UserEmployee userEmployee = new UserEmployee();
        userEmployee.setName("jerry");
        userEmployee.setEmpId("123");

        UserEmployee userEmployee2 = new UserEmployee();
        userEmployee2.setName("jerry");
        userEmployee2.setEmpId("123456");

        UserEmployee userEmployee3 = new UserEmployee();
        userEmployee3.setName("jerry2");
        userEmployee3.setEmpId("123");

        //false,父类中的Name相同,子类中的EmpId不同,可以校验出来
        System.out.println("【userEmployee】:" + userEmployee.toString());
        System.out.println("【userEmployee2】:" + userEmployee2.toString());
        System.out.println("【userEmployee3】:" + userEmployee3.toString());

        System.out.println("【userEmployee & userEmployee2】:" + userEmployee.equals(userEmployee2));
        //false,父类中的Name不同,子类中的EmpId相同,可以校验出来
        System.out.println("【userEmployee & userEmployee3】:" + userEmployee.equals(userEmployee3));
    }

    /**
     * 1.@Data默认@EqualsAndHashCode(callSuper = false)
     */
    private static void compareUserCustomerWithCallSuperFalse() {
        UserCustomer userCustomer = new UserCustomer();
        userCustomer.setName("jerry");
        userCustomer.setCustomerId("123");

        UserCustomer userCustomer2 = new UserCustomer();
        userCustomer2.setName("jerry");
        userCustomer2.setCustomerId("123456");

        UserCustomer userCustomer3 = new UserCustomer();
        userCustomer3.setName("jerry2");
        userCustomer3.setCustomerId("123");

        System.out.println("【userCustomer】:" + userCustomer.toString());
        System.out.println("【userCustomer2】:" + userCustomer2.toString());
        System.out.println("【userCustomer3】:" + userCustomer3.toString());

        //false,父类中的Name相同,子类中的customerId不同,可以校验出来
        System.out.println("【userCustomer & userCustomer2】:" + userCustomer.equals(userCustomer2));
        //true,父类中的Name不同,子类中的customerId相同,无法校验出来
        System.out.println("【userCustomer & userCustomer3】:" + userCustomer.equals(userCustomer3));
    }
}

@SneakyThrows

@SneakyThrows这个注解用在方法上

可以将方法中的代码用try-catch语句包裹起来,捕获异常并在catch中用Lombok.sneakyThrow(e)把异常抛出,可以使用

public class SneakyThrows implements Runnable {
    @SneakyThrows(UnsupportedEncodingException.class)
    public String utf8ToString(byte[] bytes) {
        return new String(bytes, "UTF-8");
    }
 
    @SneakyThrows
    public void run() {
        throw new Throwable();
    }
}

@Synchronized

@Synchronized

这个注解用在类方法或者实例方法上,效果和synchronized关键字相同,区别在于锁对象不同,

对于类方法和实例方法,synchronized关键字的锁对象分别是类的class对象和this对象,

而@Synchronized得锁对象分别是私有静态final对象LOCK和私有final对象lock,

当然,也可以自己指定锁对象

@Slf4j

@Slf4j 注解在类

生成log变量,严格意义来说是常量。

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

@Builder

**建造者模式简介:**Builder 使用创建者模式又叫建造者模式。

简单来说,就是一步步创建一个对象,它对用户屏蔽了里面构建的细节,但却可以精细地控制对象的构造过程。

注解类Builder.java

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
    String builderMethodName() default "builder";

    String buildMethodName() default "build";

    String builderClassName() default "";

    boolean toBuilder() default false;

    AccessLevel access() default AccessLevel.PUBLIC;

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Default {
    }

    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ObtainVia {
        String field() default "";

        String method() default "";

        boolean isStatic() default false;
    }
}

在企业开发中,一般在领域对象实体上标注@Builder,

其作用就相当于@AllArgsConstructor(access = AccessLevel.PRIVATE),@Builder一般与@Getter结合使用。

编写测试实体类

import lombok.Builder;
import lombok.Getter;
 
@Builder
//@Getter
public class Person {
  private String name;
 
  private String id;
 
  private String phoneNumeber;
}

编写测试类

public class Test {
  public static void main(String[] args) {
 
 
    Person.PersonBuilder builder = Person.builder();
    builder.phoneNumeber("11111")
        .id("1123")
        .name("asdd").build();
    System.out.println(builder);
 
 
  }
}

编译并执行的结果为:

Person.PersonBuilder(name=asdd, id=1123, phoneNumeber=11111)

编译后的字节码分析:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
 
public class Person {
  private String name;
  private String id;
  private String phoneNumeber;
 
  Person(String name, String id, String phoneNumeber) {
    this.name = name;
    this.id = id;
    this.phoneNumeber = phoneNumeber;
  }
 
  public static Person.PersonBuilder builder() {
    return new Person.PersonBuilder();
  }
 
  public String getName() {
    return this.name;
  }
 
  public String getId() {
    return this.id;
  }
 
  public String getPhoneNumeber() {
    return this.phoneNumeber;
  }
 
  public static class PersonBuilder {
    private String name;
    private String id;
    private String phoneNumeber;
 
    PersonBuilder() {
    }
 
    public Person.PersonBuilder name(String name) {
      this.name = name;
      return this;
    }
 
    public Person.PersonBuilder id(String id) {
      this.id = id;
      return this;
    }
 
    public Person.PersonBuilder phoneNumeber(String phoneNumeber) {
      this.phoneNumeber = phoneNumeber;
      return this;
    }
 
    public Person build() {
      return new Person(this.name, this.id, this.phoneNumeber);
    }
 
    public String toString() {
      return "Person.PersonBuilder(name=" + this.name + ", id=" + this.id + ", phoneNumeber=" + this.phoneNumeber + ")";
    }
  }
}

@Builder的作用

生成一个全属性的构造器

生成了一个返回静态内部类PersonBuilder对象的方法

生成了一个静态内部类PersonBuilder,这个静态内部类包含Person类的三个属性,无参构造器,

三个方法名为属性名的方法,返回Person对象的build方法,输出静态内部类三个属性的toString()方法。

建造者使用过程

 Person.PersonBuilder builder = Person.builder();
    builder.phoneNumeber("11111")
        .id("1123")
        .name("asdd").build();
    System.out.println(builder);

先实例化内部类对象并返回,然后为调用内部类的方法为内部类的属性赋值,

build()方法就是将内部类PersonBuilder的属性值传入Person构造器中,实例化Person对象。

以上即为对于@Builder的简单使用。

@Accessors

@Accessors(chain=true)

链式访问,该注解设置chain=true,生成setter方法返回this(也就是返回的是对象),代替了默认的返回void。

package com.pollyduan;

import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain=true)
public class User {
    private Integer id;
    private String name;
    private Integer age;

    public static void main(String[] args) {
        //开起chain=true后可以使用链式的set
        User user=new User().setAge(31).setName("pollyduan");//返回对象
        System.out.println(user);
    }

}
@Accessors(fluent = true)

与chain=true类似,区别在于getter和setter不带set和get前缀。

package com.pollyduan;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(fluent=true)
public class User {
    private Integer id;
    private String name;
    private Integer age;

    public static void main(String[] args) {
        //fluent=true开启后默认chain=true,故这里也可以使用链式set
        User user=new User().age(31).name("pollyduan");//不需要写set
        System.out.println(user);
    }

}
@Accessors(prefix = "f")

set方法忽略指定的前缀。不推荐大神们这样去命名。

package com.pollyduan;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(prefix = "f")
public class User {
    private String fName = "Hello, World!";

    public static void main(String[] args) {
        User user=new User();
        user.setName("pollyduan");//注意方法名
        System.out.println(user);
    }

}

5. Lombok原理分析

5.1 简介

Lombok核心之处就是对于注解的解析上。

JDK5引入了注解的同时,也提供了两种解析方式。

5.2 两种解析

运行时解析

运行时能够解析的注解,必须将@Retention设置为RUNTIME,这样就可以通过反射拿到该注解。

java.lang.reflect反射包中提供了一个接口AnnotatedElement,

该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口,

对反射熟悉的朋友应该都会很熟悉这种解析方式。

编译时解析

编译时解析有两种机制,分别简单描述下:

Annotation Processing Tool

apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,

自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:

api都在com.sun.mirror非标准包下没有集成到javac中,需要额外运行

Pluggable Annotation Processing API

JSR 269自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,

javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,javac执行的过程如下:

就是一个实现了“JSR 269 API”的程序。

在使用javac的过程中,它产生作用的具体流程如下:

  • javac对源代码进行分析,生成了一棵抽象语法树(AST)
  • 运行过程中调用实现了“JSR 269 API”的Lombok程序
  • 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
  • javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)通过读Lombok源码,发现对应注解的实现都在HandleXXX中,比如@Getter注解的实现在HandleGetter.handle()。还有一些其它类库使用这种方式实现,比如Google Auto、Dagger等等。

6. 为什么不推荐使用Lombok

Lombok的优点显而易见,可以帮助我们省去很多冗余代码,

实际上,从我个人角度来看,Java开发项目中,并不推荐使用Lombok,

下面我们来看一下为什么不推荐使用Lombok,它都有哪些缺点?

6.1 高侵入性,强迫队友

Lombok插件的使用,要求开发者一定要在IDE中安装对应的插件。

不仅自己要安装,任何和你协同开发的人都要安装。

如果有谁未安装插件的话,使用IDE打开一个基于Lombok的项目的话会提示找不到方法等错误,导致项目编译失败。

更重要的是,如果我们定义的一个jar包中使用了Lombok,

那么就要求所有依赖这个jar包的所有应用都必须安装插件,这种侵入性是很高的。

6.2 代码可调试性降低

Lombok确实可以帮忙减少很多代码,因为Lombok会帮忙自动生成很多代码。

但是,这些代码是要在编译阶段才会生成的,所以在开发的过程中,其实很多代码其实是缺失的。

这就给代码调试带来一定的问题,我们想要知道某个类中的某个属性的getter方法都被哪些类引用的话,就没那么简单了。

6.3 影响版本升级

Lombok对于代码有很强的侵入性,就可能带来一个比较大的问题,那就是会影响我们对JDK的升级。

按照如今JDK的升级频率,每半年都会推出一个新的版本,但是Lombok作为一个第三方工具,并且是由开源团队维护的,

那么他的迭代速度是无法保证的。

所以,如果我们需要升级到某个新版本的JDK的时候,若其中的特性在Lombok中不支持的话就会受到影响。

还有一个可能带来的问题,就是Lombok自身的升级也会受到限制。

因为一个应用可能依赖了多个jar包,而每个jar包可能又要依赖不同版本的Lombok,

这就导致在应用中需要做版本仲裁,而我们知道,jar包版本仲裁是没那么容易的,而且发生问题的概率也很高。

6.4 注解使用有风险

在使用Lombok过程中,如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果。

举一个简单的例子:

我们知道,当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。

但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,

会默认是@EqualsAndHashCode(callSuper=false),

这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性,无论父类属性访问权限是否开放,

这就可能得到意想不到的结果。

6.5 可能会破坏封装性

使用过程中如果不小心,在一定程度上就会破坏代码的封装性。

举个简单的例子:

我们定义一个购物车类,并且使用了@Data注解:

@Data
public class ShoppingCart { 
    //商品数目
    private int itemsCount; 
    //总价格
    private double totalPrice; 
    //商品明细
    private List items = new ArrayList<>();
}

我们知道,购物车中商品数目、商品明细以及总价格三者之前其实是有关联关系的,如果需要修改的话是要一起修改的。

但是,我们使用了Lombok的@Data注解,对于itemsCount 和 totalPrice这两个属性,

虽然我们将它们定义成 private 类型,但是提供了 public 的 getter、setter方法。

外部可以通过 setter 方法随意地修改这两个属性的值,我们可以随意调用 setter 方法,

来重新设置 itemsCount、totalPrice 属性的值,这也会导致其跟 items 属性的值不一致。

而面向对象封装的定义是:

通过访问权限控制,隐藏内部数据,外部仅能通过类提供的有限的接口访问、修改内部数据。

所以,暴露不应该暴露的 setter 方法,明显违反了面向对象的封装特性。

好的做法应该是不提供getter/setter,而是只提供一个public的addItem方法,

同时取修改itemsCount、totalPrice以及items三个属性。

因此,在此种情况下,就不适合使用Lombok,或者只用@Getter不用@Setter,

而别直接使用@Data,在使用过程中,需要多多小心。

Lombok虽好,但缺点也不少,如果你在公司团队开发中被强X了,你就只能使用,

如果新项目开发,能不用就尽量别用了,否则坑也不少的!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/952635.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32如何测量运行的时钟频率

前言 环境&#xff1a; 芯片&#xff1a;STM32F103C8T6 Keil&#xff1a;V5.24.2.0 一、简介STM32F103C8T6的时钟源 ①HSI 内部高速时钟,RC振荡器&#xff0c;频率为8MHz&#xff0c;精度不高。②HSE 外部高速时钟,可接石英/陶瓷谐振器&#xff0c;频率范围为4MHz~16MHz&…

【物流管理系统 - IDEAJavaSwingMySQL】基于Java实现的物流管理系统导入IDEA教程

有问题请留言或私信 步骤 下载项目源码&#xff1a;项目源码 解压项目源码到本地 打开IDEA 左上角&#xff1a;文件 → 新建 → 来自现有源代码的项目 找到解压在本地的项目源代码文件&#xff0c;点击确定&#xff0c;根据图示步骤继续导入项目 查看项目目录&#xff…

时序数据库InfluxDB—介绍与性能测试

目录 一、简述 二、主要特点 三、基本概念 1、主要概念 2、保留策略 3、连续查询 4、存储引擎—TSM Tree 5、存储目录 四、基本操作 1、Java-API操作 五、项目中的应用 六、单节点的硬件配置 七、性能测试 1、测试环境 2、测试程序 3、写入测试 4、查询测试 一…

探索数据存储的奥秘:深入理解B树与B+树

key value 类型的数据红黑树&#xff08;最优二叉树&#xff0c;内存最优&#xff09;&#xff0c;时间复杂度&#xff1a;O&#xff08;logn&#xff09;,调整方便&#xff1b;一个结点分出两个叉B树一个节点可以分出很多叉数据量相等的条件下&#xff1a;红黑树的层数很高&am…

element ui前端小数计算精度丢失的问题如何解决?

文章目录 前言一、什么是精度丢失&#xff1f;产生精度丢失的原因如何避免或减少精度丢失的影响 二、实际项目开发实例举例以项目预算模块为例如何解决精度丢失 总结 前言 在《工程投标项目管理系统》项目开发中工程项目预算、成本管理、财务管理等模块的开发中不可避免的要和…

小程序textarea组件键盘弹起会遮挡住输入框

<textarea value"{{remark}}" input"handleInputRemark" ></textarea> 如下会有遮挡&#xff1a; 一行代码搞定 cursor-spacing160 修改后代码 <textarea value"{{remark}}" input"handleInputRemark" cursor-spacin…

k8s笔记29--使用kyverno提高运维效率

k8s笔记29--使用kyverno提高运维效率 介绍原理安装应用场景自动修正测试环境pod资源强制 Pod 标签限制容器镜像来源禁止特权容器其它潜在场景 注意事项说明 介绍 Kyverno是一个云原生的策略引擎&#xff0c;它最初是为k8s构建的&#xff0c;现在也可以在k8s集群之外用作统一的…

如何理解机器学习中的线性模型 ?

在机器学习中&#xff0c;线性模型是一类重要且基础的模型&#xff0c;它假设目标变量&#xff08;输出&#xff09;是输入变量&#xff08;特征&#xff09;的线性组合。线性模型的核心思想是通过优化模型的参数&#xff0c;使模型能够捕捉输入与输出之间的线性关系。以下是线…

数据结构初阶---排序

一、排序相关概念与运用 1.排序相关概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的…

树莓派-5-GPIO的应用实验之GPIO的编码方式和SDK介绍

文章目录 1 GPIO编码方式1.1 管脚信息1.2 使用场合1.3 I2C总线1.4 SPI总线2 RPI.GPIO2.1 PWM脉冲宽度调制2.2 静态函数2.2.1 函数setmode()2.2.2 函数setup()2.2.3 函数output()2.2.4 函数input()2.2.5 捕捉引脚的电平改变2.2.5.1 函数wait_for_edge()2.2.5.2 函数event_detect…

学习RocketMQ

1.为什么要用MQ&#xff1f; 消息队列是一种“先进先出”的数据结构 其应用场景主要包含以下4个方面&#xff1a; 1.1 异步解耦​ 最常见的一个场景是用户注册后&#xff0c;需要发送注册邮件和短信通知&#xff0c;以告知用户注册成功。传统的做法有以下两种&#xff1a; …

3DGabor滤波器实现人脸特征提取

import cv2 import numpy as np# 定义 Gabor 滤波器的参数 kSize 31 # 滤波器核的大小 g_sigma 3.0 # 高斯包络的标准差 g_theta np.pi / 4 # Gabor 函数的方向 g_lambda 10.0 # 正弦波的波长 g_gamma 0.5 # 空间纵横比 g_psi np.pi / 2 # 相位偏移# 生成 Gabor 滤…

LabVIEW自动扫描与图像清晰度检测

要在LabVIEW中实现通过电机驱动相机进行XY方向扫描&#xff0c;找到物品并获取最清晰的图像&#xff0c;可以采用以下方案&#xff1a; 1. 系统概述 硬件组成&#xff1a;电机驱动的XY扫描平台、工业相机、控制器&#xff08;如NI的运动控制卡&#xff09;、计算机。 软件平台…

Vue3(一)

1.Vue3概述 Vue3的API由Vue2的选项式API改为了组合式API。但是&#xff0c;也是Vue2中的选项式API也是兼容的。 2.创建Vue3项目 create-vue 是 Vue 官方新的脚手架工具&#xff0c;底层切换到了 vite。使用create-vue创建项目的步骤如下&#xff1a; 安装 create-vue npm i…

使用wav2vec 2.0进行音位分类任务的研究总结

使用wav2vec 2.0进行音位分类任务的研究总结 原文名称&#xff1a; Using wav2vec 2.0 for phonetic classification tasks: methodological aspects 研究背景 自监督学习在语音中的应用 自监督学习在自动语音识别任务中表现出色&#xff0c;例如说话人识别和验证。变换器模型…

STM32学习(十)

I2C模块内部结构 I2C&#xff08;Inter-Integrated Circuit&#xff09;模块是一种由Philips公司开发的二线式串行总线协议&#xff0c;用于短距离通信&#xff0c;允许多个设备共享相同的总线‌。 ‌硬件连接简单‌&#xff1a;I2C通信仅需要两条总线&#xff0c;即SCL&…

深入Android架构(从线程到AIDL)_22 IPC的Proxy-Stub设计模式04

目录 5、 谁来写Proxy及Stub类呢? 如何考虑人的分工 IA接口知识取得的难题 在编程上&#xff0c;有什么技术可以实现这个方法&#xff1f; 范例 5、 谁来写Proxy及Stub类呢? -- 强龙提供AIDL工具&#xff0c;给地头蛇产出Proxy和Stub类 如何考虑人的分工 由框架开发者…

Mysql--运维篇--日志管理(连接层,SQL层,存储引擎层,文件存储层)

MySQL提供了多种日志类型&#xff0c;用于记录不同的活动和事件。这些日志对于数据库的管理、故障排除、性能优化和安全审计非常重要。 一、错误日志 (Error Log) 作用&#xff1a; 记录MySQL服务器启动、运行和停止期间遇到的问题和错误信息。 查看&#xff1a; 默认情况下…

现代谱估计的原理及MATLAB仿真(二)(AR模型法、MVDR法、MUSIC法)

现代谱估计的原理及MATLAB仿真AR参数模型法&#xff08;参数模型功率谱估计&#xff09;、MVDR法&#xff08;最小方差无失真响应法&#xff09;、MUSIC法&#xff08;多重信号分类法&#xff09; 文章目录 前言一、AR参数模型1 原理2 MATLAB仿真 二、MVDR法1 原理2 MATLAB仿真…

搭建docker私有化仓库Harbor

Docker私有仓库概述 Docker私有仓库介绍 Docker私有仓库是个人、组织或企业内部用于存储和管理Docker镜像的存储库。Docker默认会有一个公共的仓库Docker Hub,而与Docker Hub不同,私有仓库是受限访问的,只有授权用户才能够上传、下载和管理其中的镜像。这种私有仓库可以部…