<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
dependency>
@NoArgsConstructor
的使用方式与@AllArgsConstructor相同
/**
*示例类1
*/
@AllArgsConstructor
public class Demo {
private String name; //姓名
private int age; //年龄
}
/**
*示例类2
*/
@AllArgsConstructor
class Parent {
private Integer id; //ID
}
/*编译后的两个class如下:*/
/**
*示例类1
*/
public class Demo {
private String name; //姓名
private int age; //年龄
public Demo(String name, int age) {
this.name = name; //姓名
this.age = age; //年龄
}
}
//第二个类
class Parent {
private Integer id; //ID
public Parent(Integer id) {
this.id = id; //ID
}
}
此注解并不会把父类的属性id拿到Demo的构造器里面去,这是需要注意的地方。并且它也没有默认的构造器了
@AllArgsConstructor(access = AccessLevel.PROTECTED, staticName = "test")
public class Demo {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
}
/*编译后class如下:*/
public class Demo {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
private Demo(String name, int age) {
this.name = name;
this.age = age;
}
protected static Demo test(String name, int age) {
return new Demo(name, age);
}
}
可以指定生成的构造器的访问权限。但是,但是如果指定了一个静态方法,那么构造器会自动会被private,只通过静态方法对外提供反问,并且我们发现final的属性值,是不会放进构造函数里面的。
@RequiredArgsConstructor
public class Demo {
private final int finalVal = 10; //最终值
@NonNull
private String name; //姓名
@NonNull
private int age; //年龄
}
/*编译后class如下:*/
public class Demo {
private final int finalVal = 10; //最终值
@NonNull
private String name; //姓名
@NonNull
private int age; //年龄
public Demo(@NonNull String name, @NonNull int age) {
if (name == null) {
throw new NullPointerException("name is marked @NonNull but is null");
} else {
this.name = name;
this.age = age;
}
}
}
解释:该注解会识别@nonNull字段,然后以该字段为元素产生一个构造函数。备注:如果所有字段都没有@nonNull注解,那效果同NoArgsConstructor
非常推荐的一种构建值对象的方式。缺点就是父类的属性不能产于builder
@Builder
public class Demo {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
}
/*编译后class如下:*/
public class Demo {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
Demo(String name, int age) {
this.name = name;
this.age = age;
}
public static Demo.DemoBuilder builder() {
return new Demo.DemoBuilder();
}
public static class DemoBuilder {
private String name;
private int age;
DemoBuilder() {
}
public Demo.DemoBuilder name(String name) {
this.name = name;
return this;
}
public Demo.DemoBuilder age(int age) {
this.age = age;
return this;
}
public Demo build() {
return new Demo(this.name, this.age);
}
public String toString() {
String var10000 = this.name;
return this.age;
}
}
这样我们构造一个对象就可以优雅的这么来:
public static void main(String[] args) {
Demo demo = Demo.builder().name("aa").age(10).build(); //如同链式调用
System.out.println(demo);
}
这个注解用在变量前面,可以保证此变量代表的资源会被自动关闭,默认是调用资源的close()方法。如果该资源有其它关闭方法,可使用@Cleanup(“methodName”)来指定要调用的方法,就用输入输出流来
public static void main(String[] args) throws Exception {
//读取本地文件
@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);
}
}
/*编译后class如下:*/
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream(args[0]);//读取本地文件
try {
FileOutputStream out = new FileOutputStream(args[1]);//写入文件
try {
byte[] b = new byte[1024];
while(true) {
int r = in.read(b);
if (r == -1) {
return;
}
out.write(b, 0, r);
}
} finally {
if (Collections.singletonList(out).get(0) != null) {
out.close();
}
}
} finally {
if (Collections.singletonList(in).get(0) != null) {
in.close();
}
}
}
就这么简单的一个注解,就实现了优雅的关流操作哟。
相当于注解集合。效果等同于 @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor 由于生成的代码篇幅太长,这里就不给demo了,效果同上5个注解的效果,牛掰!!!
需要注意的是,这里不包括@NoArgsConstructor和@AllArgsConstructor
@Value注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法。
生成toString方法
@ToString
public class Demo {
private final int finalVal = 10;
private transient String name = "aa";
private int age;
}
public static void main(String[] args) throws Exception {
Demo demo = new Demo();
System.out.println(demo); //Demo(finalVal=10, age=0)
}
我们发现静态字段它是不输出的。有些关键的属性,可以控制toString的输出,我们可以了解一下
@ToString(
//是否使用字段名
includeFieldNames = true,
//排除某些字段
exclude = {"name"},
//只使用某些字段
of = {"age"},
//是否让父类字段也参与 默认false
callSuper = true
)
备注:大多数情况下,使用默认的即可,毕竟大多数情况都是POJO
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
@EqualsAndHashCode.Exclude private Shape shape = new Square(5, 10);
private String[] tags;
@EqualsAndHashCode.Exclude 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;
}
}
}
/*编译后class如下:*/
import java.util.Arrays;
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;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof EqualsAndHashCodeExample)) return false;
EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (Double.compare(this.score, other.score) != 0) return false;
if (!Arrays.deepEquals(this.tags, other.tags)) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.score);
result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.tags);
return result;
}
protected boolean canEqual(Object other) {
return other instanceof EqualsAndHashCodeExample;
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Square)) return false;
Square other = (Square) o;
if (!other.canEqual((Object)this)) return false;
if (!super.equals(o)) return false;
if (this.width != other.width) return false;
if (this.height != other.height) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + super.hashCode();
result = (result*PRIME) + this.width;
result = (result*PRIME) + this.height;
return result;
}
protected boolean canEqual(Object other) {
return other instanceof Square;
}
}
}
这一对注解不用说就应该知道是啥意思了吧
用在成员变量上面或者类上面,相当于为成员变量生成对应的get和set方法,同时还可以为生成的方法指定访问修饰符,当然,默认为public这两个注解直接用在类上,可以为此类里的所有非静态成员变量生成对应的get和set方法。如果是final变量,那就只会有get方法
@Getter
@Setter
public class Demo {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
}
/*编译后class如下:*/
public class Demo {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
public Demo() {
}
public int getFinalVal() {
Objects.requireNonNull(this);
return 10;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
这个注解可以用在成员方法或者构造方法的参数前面,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常。
//成员方法参数加上@NonNull注解
public String getName(@NonNull Person p){
return p.getName();
}
/*编译后class如下:*/
public String getName(@NonNull Person p){
if(p==null){
throw new NullPointerException("person");
}
return p.getName();
}
这个注解用在方法上,可以将方法中的代码用try-catch语句包裹起来,捕获异常并在catch中用Lombok.sneakyThrow(e)把异常抛出,可以使用@SneakyThrows(Exception.class)的形式指定抛出哪种异常
@SneakyThrows(UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
/*编译后此方法如下:*/
@SneakyThrows(UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
try{
return new String(bytes, "UTF-8");
}catch(UnsupportedEncodingException uee){
throw Lombok.sneakyThrow(uee);
}
}
Lombok.sneakyThrow的代码
public static RuntimeException sneakyThrow(Throwable t) {
if (t == null) {
throw new NullPointerException("t");
} else {
return (RuntimeException)sneakyThrow0(t);
}
}
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T {
throw t;
}
其实就是转化为了RuntimeException
这个注解用在类方法或者实例方法上,效果和synchronized关键字相同,区别在于锁对象不同,对于类方法和实例方法,synchronized关键字的锁对象分别是类的class对象和this对象,而@Synchronized得锁对象分别是私有静态final对象LOCK和私有final对象lock,当然,也可以自己指定锁对象。
@Synchronized
public static void hello() {
System.out.println("world");
}
@Synchronized
public int answerToLife() {
return 42;
}
@Synchronized("readLock")
public void foo() {
System.out.println("bar");
}
/*编译后方法如下:*/
public static void hello() {
Object var0 = $LOCK;
synchronized($LOCK) {
System.out.println("world");
}
}
public int answerToLife() {
Object var1 = this.$lock;
synchronized(this.$lock) {
return 42;
}
}
public void foo() {
Object var1 = this.readLock;
synchronized(this.readLock) {
System.out.println("bar");
}
}
这些个注解用在类上,可以省去从日志工厂生成日志对象这一步,直接进行日志记录,具体注解根据日志工具的不同而不同,同时,可以在注解中使用topic来指定生成log对象时的类名。不同的日志注解总结如下。
@CommonsLog
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@JBossLog
private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
@Log
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2
private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
@Slf4j这个注解还是非常有用的
@Slf4j
class Parent {
}
//topic的名称
@Slf4j
@CommonsLog(topic = "commonLog")
class Parent {
}
/*编译后class如下:*/
class Parent {
private static final Logger log = LoggerFactory.getLogger(Parent.class);
Parent() {
}
}
class Parent {
private static final Logger log = LoggerFactory.getLogger("commonLog");
Parent() {
}
}
这个注解要搭配@Getter与@Setter使用,用来修改默认的setter与getter方法的形式。所以单独使用是没有意义的
@Accessors(fluent = true) //链式调用
@Getter //get方法
@Setter //set方法
public class Demo extends Parent {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
}
/*编译后class如下:*/
public class Demo extends Parent {
private final int finalVal = 10; //最终值
private String name; //姓名
private int age; //年龄
public Demo() {
}
public int finalVal() {
Objects.requireNonNull(this);
return 10;
}
public String name() {
return this.name;
}
public int age() {
return this.age;
}
public Demo name(String name) { //方法返回的是对象
this.name = name;
return this;
}
public Demo age(int age) { //方法返回的是对象
this.age = age;
return this;
}
}
注意:下面两个赋值方法(set)返回的是对象
它的三个参数:
@Accessors(prefix = "xxx")
@Getter
@Setter
public class Demo extends Parent {
private final int finalVal = 10; //最终值
private String xxxName; //姓名
private int age; //年龄
}
/*编译后class如下:*/
public class Demo extends Parent {
private final int finalVal = 10; //最终值
private String xxxName; //姓名
private int age; //年龄
public Demo() {
}
public String getName() {
return this.xxxName;
}
public void setName(String xxxName) {
this.xxxName = xxxName;
}
}
我们发现prefix可以在生成get/set的时候,去掉xxx等prefix前缀,达到很好的一致性。但是,但是需要注意,因为此处age没有匹配上xxx前缀,所有根本就不给生成,所以使用的时候一定要注意。属性名没有一个以其中的一个前缀开头,则属性会被lombok完全忽略掉,并且还会产生一个警告。
注意:公共 非静态方法代理模式,把字段的方法代理给类,默认代理所有方法。
public class Demo extends Parent {
private final int finalVal = 10; //最终值
@Delegate
private String xxxName; //姓名
private int age; //年龄
}
/*编译后class如下:*/
//把String类的公共 非静态方法全拿来了
public class Demo extends Parent {
private final int finalVal = 10;
private String xxxName;
private int age;
public Demo() {
}
public int length() {
return this.xxxName.length();
}
public boolean isEmpty() {
return this.xxxName.isEmpty();
}
public char charAt(int index) {
return this.xxxName.charAt(index);
}
public int codePointAt(int index) {
return this.xxxName.codePointAt(index);
}
……//等等
}
注意:它不能用于基本数据类型字段比如int,只能用在包装类型比如Integer
参数:
package javax.annotation;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* 目的:@Generated标记已生成的源代码,也区分单个文件中用户编写的代码和生成的代码
* 1. 使用时,必须有代码生成器的名称(完整名)
* 2. 生成日期必须遵循 ISO 8601标准。例如:2001-07-04T12:08:56.235-0700
* @author TCM
* @create 2017年10月16日下午2:06:28
* @since Common Annotations 1.0
*/
@Documented
@Retention(SOURCE)
@Target({PACKAGE, TYPE, ANNOTATION_TYPE, METHOD, CONSTRUCTOR, FIELD,
LOCAL_VARIABLE, PARAMETER})
public @interface Generated {
/**
* 代码生成器的名称
* 使用完整名,如:com.acme.generator.CodeGen
* @return
*/
String[] value();
/**
* 生成源代码的日期
* @return
*/
String date() default "";
/**
* 代码生成器生成代码包含的注释
* @return
*/
String comments() default "";
}
import lombok.Builder;
import lombok.Singular;
import java.util.Collection;
import java.util.Set;
import java.util.SortedMap;
import com.google.common.collect.ImmutableList;
@Builder
public class SingularExample<T extends Number> {
private @Singular Set<String> occupations;
private @Singular("axis") ImmutableList<String> axes;
private @Singular SortedMap<Integer, T> elves;
private @Singular Collection<?> minutiae;
}
/*编译后class如下:*/
import java.util.Collection;
import java.util.Set;
import java.util.SortedMap;
import com.google.common.collect.ImmutableList;
public class SingularExample<T extends Number> {
private Set<String> occupations;
private ImmutableList<String> axes;
private SortedMap<Integer, T> elves;
private Collection<?> minutiae;
SingularExample(Set<String> occupations, ImmutableList<String> axes, SortedMap<Integer, T> elves, Collection<?> minutiae) {
this.occupations = occupations;
this.axes = axes;
this.elves = elves;
this.minutiae = minutiae;
}
public static class SingularExampleBuilder<T extends Number> {
private java.util.ArrayList<String> occupations;
private com.google.common.collect.ImmutableList.Builder<String> axes;
private java.util.ArrayList<Integer> elves$key;
private java.util.ArrayList<T> elves$value;
private java.util.ArrayList<java.lang.Object> minutiae;
SingularExampleBuilder() {
}
public SingularExampleBuilder<T> occupation(String occupation) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.add(occupation);
return this;
}
@java.lang.SuppressWarnings("all")
public SingularExampleBuilder<T> occupations(java.util.Collection<? extends String> occupations) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.addAll(occupations);
return this;
}
public SingularExampleBuilder<T> axis(String axis) {
if (this.axes == null) {
this.axes = com.google.common.collect.ImmutableList.builder();
}
this.axes.add(axis);
return this;
}
public SingularExampleBuilder<T> axes(java.lang.Iterable<? extends String> axes) {
if (this.axes == null) {
this.axes = com.google.common.collect.ImmutableList.builder();
}
this.axes.addAll(axes);
return this;
}
public SingularExampleBuilder<T> elf(Integer elfKey, T elfValue) {
if (this.elves$key == null) {
this.elves$key = new java.util.ArrayList<Integer>();
this.elves$value = new java.util.ArrayList<T>();
}
this.elves$key.add(elfKey);
this.elves$value.add(elfValue);
return this;
}
public SingularExampleBuilder<T> elves(java.util.Map<? extends Integer, ? extends T> elves) {
if (this.elves$key == null) {
this.elves$key = new java.util.ArrayList<Integer>();
this.elves$value = new java.util.ArrayList<T>();
}
for (java.util.Map.Entry<? extends Integer, ? extends T> $lombokEntry : elves.entrySet()) {
this.elves$key.add($lombokEntry.getKey());
this.elves$value.add($lombokEntry.getValue());
}
return this;
}
public SingularExampleBuilder<T> minutia(java.lang.Object minutia) {
if (this.minutiae == null) {
this.minutiae = new java.util.ArrayList<java.lang.Object>();
}
this.minutiae.add(minutia);
return this;
}
public SingularExampleBuilder<T> minutiae(java.util.Collection<?> minutiae) {
if (this.minutiae == null) {
this.minutiae = new java.util.ArrayList<java.lang.Object>();
}
this.minutiae.addAll(minutiae);
return this;
}
public SingularExample<T> build() {
java.util.Set<String> occupations;
switch (this.occupations == null ? 0 : this.occupations.size()) {
case 0:
occupations = java.util.Collections.emptySet();
break;
case 1:
occupations = java.util.Collections.singleton(this.occupations.get(0));
break;
default:
occupations = new java.util.LinkedHashSet<String>(this.occupations.size() < 1073741824 ? 1 + this.occupations.size() + (this.occupations.size() - 3) / 3 : java.lang.Integer.MAX_VALUE);
occupations.addAll(this.occupations);
occupations = java.util.Collections.unmodifiableSet(occupations);
}
com.google.common.collect.ImmutableList<String> axes = this.axes == null ? com.google.common.collect.ImmutableList.<String>of() : this.axes.build();
java.util.SortedMap<Integer, T> elves = new java.util.TreeMap<Integer, T>();
if (this.elves$key != null)
for (int $i = 0; $i < (this.elves$key == null ? 0 : this.elves$key.size()); $i++)
elves.put(this.elves$key.get($i), this.elves$value.get($i));
elves = java.util.Collections.unmodifiableSortedMap(elves);
java.util.Collection<java.lang.Object> minutiae;
switch (this.minutiae == null ? 0 : this.minutiae.size()) {
case 0:
minutiae = java.util.Collections.emptyList();
break;
case 1:
minutiae = java.util.Collections.singletonList(this.minutiae.get(0));
break;
default:
minutiae = java.util.Collections.unmodifiableList(new java.util.ArrayList<java.lang.Object>(this.minutiae));
}
return new SingularExample<T>(occupations, axes, elves, minutiae);
}
@java.lang.Override
public java.lang.String toString() {
return "SingularExample.SingularExampleBuilder(occupations=" + this.occupations + ", axes=" + this.axes + ", elves$key=" + this.elves$key + ", elves$value=" + this.elves$value + ", minutiae=" + this.minutiae + ")";
}
}
@java.lang.SuppressWarnings("all")
public static <T extends Number> SingularExampleBuilder<T> builder() {
return new SingularExampleBuilder<T>();
}
}
为什么不用@Val和@Var?
val可以作为局部变量声明的类型,而不必编写实际类型。val注解将从初始化程序表达式中推断类型。局部变量也将成为最终变量。此功能仅适用于局部变量和foreach循环,不适用于字段(实体类的成员变量)。同时,初始化表达式是必需的。
var和val的差别在于,val修饰的局部变量没有被标记为final。
public class ValVarExample {
public static void main(String[] args) {
val name = "cauchy6317";
var age = Integer.valueOf(12);
System.out.println(name.getClass());
System.out.println(age.getClass());
}
}
/*编译后class如下:*/
public class ValVarExample {
public ValVarExample() {
}
public static void main(String[] args) {
String name = "cauchy6317";
Integer age = 12;
System.out.println("cauchy6317".getClass());
System.out.println(age.getClass());
}
}
可以看到,val修饰的name是String类型,var修饰age是Integer类型,然而反编译后的name并没有加上final修饰。但是,如果重新为name赋值,编译会出错。
@FieldDefaults和@Value也有这功能
Lombok在v1.18.2版本中针对这个问题增加了@SuperBuilder注解,子类和父类中都添加@SuperBuilder注解,子类builder即可继承父类builder。
@SuperBuilder
public class Parent {
// get,set添加在指定属性上
@Getter @Setter private int id;
//如果需要让build出的对象属性使用默认值,需要添加@Builder.Default注解
@Builder.Default
@Getter @Setter private String name = "unknown";
}
@SuperBuilder
@Data
public class Child extends Parent {
private String ext;
}
//调用示例
Child c1 = Child.builder().id(1).name("名称").ext("子类属性ext").build();
System.out.println(c1.getId());
注意: 关于builder的继承,Lombok Plugin尚未更新支持@SuperBuilder,所以以上写法在IDE下还会提示编译错误,等更新吧
@UtilityClass
public class Utility {
public String getName() {
return "name";
}
}
public static void main(String[] args) {
// Utility utility = new Utility(); 构造函数为私有的,
System.out.println(Utility.getName());
}
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.With;
public class WithExample {
@With(AccessLevel.PROTECTED) @NonNull private final String name;
@With private final int age;
public WithExample(String name, int age) {
if (name == null) throw new NullPointerException();
this.name = name;
this.age = age;
}
}
/*编译后class如下:*/
import lombok.NonNull;
public class WithExample {
private @NonNull final String name;
private final int age;
public WithExample(String name, int age) {
if (name == null) throw new NullPointerException();
this.name = name;
this.age = age;
}
protected WithExample withName(@NonNull String name) {
if (name == null) throw new java.lang.NullPointerException("name");
return this.name == name ? this : new WithExample(name, age);
}
public WithExample withAge(int age) {
return this.age == age ? this : new WithExample(name, age);
}
}
建议去掉@NoArgsConstructor
添加@AllArgsConstructor