1. Знакомство с Ломбоком
LombokЭто подключаемый модуль разработки Java, который позволяет разработчикам Java устранять длинные и громоздкие коды в бизнес-проектах с помощью некоторых определенных аннотаций, особенно для простых объектов модели Java (POJO).Используя подключаемый модуль Lombok в среде разработки, разработчики Java могут сэкономить много времени на многократном создании таких методов, как hashCode и equals, а также таких методов, как accessor и toString, для различных моделей бизнес-объектов.Для этих методов Lombok может автоматически генерировать эти методы для нас во время компиляции исходного кода, но это не снижает производительность программы, как рефлексия.
2. Ломбок установка
2.1 Инструменты сборки
Gradle
существуетbuild.gradle
Добавьте в файл зависимость lombok:
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.10'
annotationProcessor 'org.projectlombok:lombok:1.18.10'
}
Maven
в проекте Мавенpom.xml
Добавьте в файл зависимость lombok:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
Ant
предположим вlib
уже есть в каталогеlombok.jar
, а затем настройте задачу javac:
<javac srcdir="src" destdir="build" source="1.8">
<classpath location="lib/lombok.jar" />
</javac>
2.2 IDE
Поскольку Lombok генерирует код только на этапе компиляции, исходный код, аннотированный Lombok, будет выделен в среде IDE, а ошибку можно устранить, установив соответствующий плагин для среды IDE. Здесь это не подробно описано, конкретный метод установки может относиться кSetting up Lombok with Eclipse and IntelliJЭта статья.
3. Подробное объяснение Ломбока
Примечание. В следующем примере используется версия Lombok 1.18.10.
3.1 Аннотации @Getter и @Setter
ты можешь использовать@Getter
или@Setter
Аннотируйте любой класс или поле, и Lombok автоматически сгенерирует методы получения/установки по умолчанию.
@Getter аннотация
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
// 若getter方法非public的话,可以设置可访问级别
lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
AnyAnnotation[] onMethod() default {};
// 是否启用延迟初始化
boolean lazy() default false;
}
@Setter аннотация
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {
// 若setter方法非public的话,可以设置可访问级别
lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
AnyAnnotation[] onMethod() default {};
AnyAnnotation[] onParam() default {};
}
Пример использования
package com.semlinker.lombok;
@Getter
@Setter
public class GetterAndSetterDemo {
String firstName;
String lastName;
LocalDate dateOfBirth;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class GetterAndSetterDemo {
String firstName;
String lastName;
LocalDate dateOfBirth;
public GetterAndSetterDemo() {
}
// 省略其它setter和getter方法
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
Lazy Getter
@Getter
Аннотация поддерживает ленивый атрибут, который по умолчанию имеет значение false. Если установлено значение true, включается ленивая инициализация, т. е. инициализация происходит при первом вызове метода получения.
Пример
package com.semlinker.lombok;
public class LazyGetterDemo {
public static void main(String[] args) {
LazyGetterDemo m = new LazyGetterDemo();
System.out.println("Main instance is created");
m.getLazy();
}
@Getter
private final String notLazy = createValue("not lazy");
@Getter(lazy = true)
private final String lazy = createValue("lazy");
private String createValue(String name) {
System.out.println("createValue(" + name + ")");
return null;
}
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class LazyGetterDemo {
private final String notLazy = this.createValue("not lazy");
private final AtomicReference<Object> lazy = new AtomicReference();
// 已省略部分代码
public String getNotLazy() {
return this.notLazy;
}
public String getLazy() {
Object value = this.lazy.get();
if (value == null) {
synchronized(this.lazy) {
value = this.lazy.get();
if (value == null) {
String actualValue = this.createValue("lazy");
value = actualValue == null ? this.lazy : actualValue;
this.lazy.set(value);
}
}
}
return (String)((String)(value == this.lazy ? null : value));
}
}
Как видно из приведенного выше кода, при вызове метода getLazy, если значение оказывается нулевым, операция инициализации будет выполняться в синхронизированном блоке кода.
3.2 Constructor Annotations
Аннотация @NoArgsConstructor
использовать@NoArgsConstructor
Аннотации могут генерировать конструкторы по умолчанию для указанных классов,@NoArgsConstructor
Аннотации определяются следующим образом:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
// 若设置该属性,将会生成一个私有的构造函数且生成一个staticName指定的静态方法
String staticName() default "";
AnyAnnotation[] onConstructor() default {};
// 设置生成构造函数的访问级别,默认是public
AccessLevel access() default lombok.AccessLevel.PUBLIC;
// 若设置为true,则初始化所有final的字段为0/null/false
boolean force() default false;
}
Пример
package com.semlinker.lombok;
@NoArgsConstructor(staticName = "getInstance")
public class NoArgsConstructorDemo {
private long id;
private String name;
private int age;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class NoArgsConstructorDemo {
private long id;
private String name;
private int age;
private NoArgsConstructorDemo() {
}
public static NoArgsConstructorDemo getInstance() {
return new NoArgsConstructorDemo();
}
}
Аннотация @AllArgsConstructor
использовать@AllArgsConstructor
Аннотация может генерировать конструктор со всеми членами для указанного класса,@AllArgsConstructor
Аннотации определяются следующим образом:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {
// 若设置该属性,将会生成一个私有的构造函数且生成一个staticName指定的静态方法
String staticName() default "";
AnyAnnotation[] onConstructor() default {};
// 设置生成构造函数的访问级别,默认是public
AccessLevel access() default lombok.AccessLevel.PUBLIC;
}
Пример
package com.semlinker.lombok;
@AllArgsConstructor
public class AllArgsConstructorDemo {
private long id;
private String name;
private int age;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class AllArgsConstructorDemo {
private long id;
private String name;
private int age;
public AllArgsConstructorDemo(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
@RequiredArgsConstructorDemo аннотация
использовать@RequiredArgsConstructor
Аннотации могут генерировать соответствующие конструкторы для переменных-членов, которые должны быть инициализированы в указанном классе, например окончательные переменные-члены,@RequiredArgsConstructor
Аннотации определяются следующим образом:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RequiredArgsConstructor {
// 若设置该属性,将会生成一个私有的构造函数且生成一个staticName指定的静态方法
String staticName() default "";
AnyAnnotation[] onConstructor() default {};
// 设置生成构造函数的访问级别,默认是public
AccessLevel access() default lombok.AccessLevel.PUBLIC;
}
Пример
package com.semlinker.lombok;
@RequiredArgsConstructor
public class RequiredArgsConstructorDemo {
private final long id;
private String name;
private int age;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class RequiredArgsConstructorDemo {
private final long id;
private String name;
private int age;
public RequiredArgsConstructorDemo(long id) {
this.id = id;
}
}
3.3 Аннотация @EqualsAndHashCode
использовать@EqualsAndHashCode
Аннотации могут генерировать методы equals и hashCode для указанных классов,@EqualsAndHashCode
Аннотации определяются следующим образом:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {
// 指定在生成的equals和hashCode方法中需要排除的字段列表
String[] exclude() default {};
// 显式列出用于identity的字段,一般情况下non-static,non-transient字段会被用于identity
String[] of() default {};
// 标识在执行字段计算前,是否调用父类的equals和hashCode方法
boolean callSuper() default false;
boolean doNotUseGetters() default false;
AnyAnnotation[] onParam() default {};
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@Target({})
@interface AnyAnnotation {}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Exclude {}
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Include {
String replaces() default "";
}
}
Пример
package com.semlinker.lombok;
@EqualsAndHashCode
public class EqualsAndHashCodeDemo {
String firstName;
String lastName;
LocalDate dateOfBirth;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class EqualsAndHashCodeDemo {
String firstName;
String lastName;
LocalDate dateOfBirth;
public EqualsAndHashCodeDemo() {
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof EqualsAndHashCodeDemo)) {
return false;
} else {
EqualsAndHashCodeDemo other = (EqualsAndHashCodeDemo)o;
if (!other.canEqual(this)) {
return false;
} else {
// 已省略大量代码
}
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $firstName = this.firstName;
int result = result * 59 + ($firstName == null ? 43 : $firstName.hashCode());
Object $lastName = this.lastName;
result = result * 59 + ($lastName == null ? 43 : $lastName.hashCode());
Object $dateOfBirth = this.dateOfBirth;
result = result * 59 + ($dateOfBirth == null ? 43 : $dateOfBirth.hashCode());
return result;
}
}
3.4 Аннотация @ToString
использовать@ToString
Аннотация может генерировать метод toString для указанного класса,@ToString
Аннотации определяются следующим образом:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
// 打印输出时是否包含字段的名称
boolean includeFieldNames() default true;
// 列出打印输出时,需要排除的字段列表
String[] exclude() default {};
// 显式的列出需要打印输出的字段列表
String[] of() default {};
// 打印输出的结果中是否包含父类的toString方法的返回结果
boolean callSuper() default false;
boolean doNotUseGetters() default false;
boolean onlyExplicitlyIncluded() default false;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Exclude {}
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Include {
int rank() default 0;
String name() default "";
}
}
Пример
package com.semlinker.lombok;
@ToString(exclude = {"dateOfBirth"})
public class ToStringDemo {
String firstName;
String lastName;
LocalDate dateOfBirth;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class ToStringDemo {
String firstName;
String lastName;
LocalDate dateOfBirth;
public ToStringDemo() {
}
public String toString() {
return "ToStringDemo(firstName=" + this.firstName + ", lastName=" +
this.lastName + ")";
}
}
3.5 Аннотация @Data
@Data
Аннотации имеют тот же эффект, что и совместное использование следующих аннотаций:
- @ToString
- @Getter
- @Setter
- @RequiredArgsConstructor
- @EqualsAndHashCode
@Data
Аннотации определяются следующим образом:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
String staticConstructor() default "";
}
Пример
package com.semlinker.lombok;
@Data
public class DataDemo {
private Long id;
private String summary;
private String description;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class DataDemo {
private Long id;
private String summary;
private String description;
public DataDemo() {
}
// 省略summary和description成员属性的setter和getter方法
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof DataDemo)) {
return false;
} else {
DataDemo other = (DataDemo)o;
if (!other.canEqual(this)) {
return false;
} else {
// 已省略大量代码
}
}
}
protected boolean canEqual(Object other) {
return other instanceof DataDemo;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.getId();
int result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $summary = this.getSummary();
result = result * 59 + ($summary == null ? 43 : $summary.hashCode());
Object $description = this.getDescription();
result = result * 59 + ($description == null ? 43 : $description.hashCode());
return result;
}
public String toString() {
return "DataDemo(id=" + this.getId() + ", summary=" + this.getSummary() + ", description=" + this.getDescription() + ")";
}
}
3.6 Аннотация @Log
если вы будете@Log
Варианты , в классе (для любой системы ведения журналов, которую вы используете); после этого у вас будет статическое конечное поле журнала, которое вы затем можете использовать для вывода журналов.
@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);
@CommonsLog
private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
3.7 @Синхронизированная аннотация
@Synchronized
является более безопасным вариантом модификатора метода synchronized. иsynchronized
Опять же, эта аннотация может применяться только к статическим методам и методам экземпляра. Он работает какsynchronized
ключевое слово, но оно заблокировано на другом объекте.synchronized
Когда ключевое слово применяется к методу экземпляра, оно блокирует объект this, а при применении к статическому методу оно блокирует объект класса. Для методов, объявленных с аннотацией @Synchronized, он блокирует$LOCK
или$lock
.@Synchronized
Аннотации определяются следующим образом:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Synchronized {
// 指定锁定的字段名称
String value() default "";
}
Пример
package com.semlinker.lombok;
public class SynchronizedDemo {
private final Object readLock = new Object();
@Synchronized
public static void hello() {
System.out.println("world");
}
@Synchronized
public int answerToLife() {
return 42;
}
@Synchronized("readLock")
public void foo() {
System.out.println("bar");
}
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class SynchronizedDemo {
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();
public SynchronizedDemo() {
}
public static void hello() {
synchronized($LOCK) {
System.out.println("world");
}
}
public int answerToLife() {
synchronized(this.$lock) {
return 42;
}
}
public void foo() {
synchronized(this.readLock) {
System.out.println("bar");
}
}
}
3.8 Аннотация @Builder
использовать@Builder
Аннотация может реализовывать шаблон построителя для данного класса, и аннотацию можно поместить в класс, конструктор или метод.@Builder
Аннотации определяются следующим образом:
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
@Target(FIELD)
@Retention(SOURCE)
public @interface Default {}
// 创建新的builder实例的方法名称
String builderMethodName() default "builder";
// 创建Builder注解类对应实例的方法名称
String buildMethodName() default "build";
// builder类的名称
String builderClassName() default "";
boolean toBuilder() default false;
AccessLevel access() default lombok.AccessLevel.PUBLIC;
@Target({FIELD, PARAMETER})
@Retention(SOURCE)
public @interface ObtainVia {
String field() default "";
String method() default "";
boolean isStatic() default false;
}
}
Пример
package com.semlinker.lombok;
@Builder
public class BuilderDemo {
private final String firstname;
private final String lastname;
private final String email;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class BuilderDemo {
private final String firstname;
private final String lastname;
private final String email;
BuilderDemo(String firstname, String lastname, String email) {
this.firstname = firstname;
this.lastname = lastname;
this.email = email;
}
public static BuilderDemo.BuilderDemoBuilder builder() {
return new BuilderDemo.BuilderDemoBuilder();
}
public static class BuilderDemoBuilder {
private String firstname;
private String lastname;
private String email;
BuilderDemoBuilder() {
}
public BuilderDemo.BuilderDemoBuilder firstname(String firstname) {
this.firstname = firstname;
return this;
}
public BuilderDemo.BuilderDemoBuilder lastname(String lastname) {
this.lastname = lastname;
return this;
}
public BuilderDemo.BuilderDemoBuilder email(String email) {
this.email = email;
return this;
}
public BuilderDemo build() {
return new BuilderDemo(this.firstname, this.lastname, this.email);
}
public String toString() {
return "BuilderDemo.BuilderDemoBuilder(firstname=" + this.firstname + ", lastname=" + this.lastname + ", email=" + this.email + ")";
}
}
}
3.9 Аннотация @SneakyThrows
@SneakyThrows
Аннотации используются для автоматического генерирования проверенных исключений без явного генерирования их в методе с помощью инструкции throw.@SneakyThrows
Аннотации определяются следующим образом:
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface SneakyThrows {
// 设置你希望向上抛的异常类
Class<? extends Throwable>[] value() default java.lang.Throwable.class;
}
Пример
package com.semlinker.lombok;
public class SneakyThrowsDemo {
@SneakyThrows
@Override
protected Object clone() {
return super.clone();
}
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class SneakyThrowsDemo {
public SneakyThrowsDemo() {
}
protected Object clone() {
try {
return super.clone();
} catch (Throwable var2) {
throw var2;
}
}
}
3.10 Аннотация @NonNull
Вы можете использовать параметры метода или конструктора@NonNull
аннотацию, он автоматически сгенерирует для вас ненулевой оператор проверки.@NonNull
Аннотации определяются следующим образом:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface NonNull {
}
Пример
package com.semlinker.lombok;
public class NonNullDemo {
@Getter
@Setter
@NonNull
private String name;
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class NonNullDemo {
@NonNull
private String name;
public NonNullDemo() {
}
@NonNull
public String getName() {
return this.name;
}
public void setName(@NonNull String name) {
if (name == null) {
throw new NullPointerException("name is marked non-null but is null");
} else {
this.name = name;
}
}
}
3.11 @Чистая аннотация
@Clean
Аннотации используются для автоматического управления ресурсами. Прежде чем они будут использованы в локальных переменных, ресурсы будут автоматически очищены перед выполнением и выходом из текущей области переменных.try-finally
код, подобный этому, чтобы закрыть поток.
@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.SOURCE)
public @interface Cleanup {
// 设置用于执行资源清理/回收的方法名称,对应方法不能包含任何参数,默认名称为close。
String value() default "close";
}
Пример
package com.semlinker.lombok;
public class CleanupDemo {
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);
}
}
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
package com.semlinker.lombok;
public class CleanupDemo {
public CleanupDemo() {
}
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream(args[0]);
try {
FileOutputStream out = new FileOutputStream(args[1]);
try {
byte[] b = new byte[10000];
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();
}
}
}
}
3.11 @С аннотацией
Применить к полю класса@With
После аннотации,withFieldName(newValue)
метод, который вызовет соответствующий конструктор на основе newValue для создания экземпляра, соответствующего текущему классу.@With
Аннотации определяются следующим образом:
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface With {
AccessLevel value() default AccessLevel.PUBLIC;
With.AnyAnnotation[] onMethod() default {};
With.AnyAnnotation[] onParam() default {};
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@Target({})
public @interface AnyAnnotation {
}
}
Пример
public class WithDemo {
@With(AccessLevel.PROTECTED)
@NonNull
private final String name;
@With
private final int age;
public WithDemo(String name, int age) {
if (name == null) throw new NullPointerException();
this.name = name;
this.age = age;
}
}
После того, как приведенный выше код будет скомпилирован Lombok, будет сгенерирован следующий код:
public class WithDemo {
@NonNull
private final String name;
private final int age;
public WithDemo(String name, int age) {
if (name == null) {
throw new NullPointerException();
} else {
this.name = name;
this.age = age;
}
}
protected WithDemo withName(@NonNull String name) {
if (name == null) {
throw new NullPointerException("name is marked non-null but is null");
} else {
return this.name == name ? this : new WithDemo(name, this.age);
}
}
public WithDemo withAge(int age) {
return this.age == age ? this : new WithDemo(this.name, age);
}
}
3.12 Другие функции
val
val используется перед локальными переменными, что эквивалентно объявлению переменной как final, и Lombok автоматически выполнит вывод типа во время компиляции. Пример использования 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());
}
}
}
Приведенный выше код эквивалентен:
public class ValExample {
public String example() {
final ArrayList<String> example = new ArrayList<String>();
example.add("Hello, World!");
final String foo = example.get(0);
return foo.toLowerCase();
}
public void example2() {
final HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(0, "zero");
map.put(5, "five");
for (final Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
}
}
}
До сих пор был представлен мощный инструмент Lombok. Если вас интересует принцип его реализации, то рекомендуется прочитатьДорога кода обезьяныбольшой пареньДесять минут, чтобы понять использование и принцип ЛомбокаЭта статья.
Примерный адрес проекта:Github - springboot2-lombok