Dragon Slaying в Java — как изменить синтаксическое дерево

Java
Dragon Slaying в Java — как изменить синтаксическое дерево

существуетЛомбок часто используется, но знаете ли вы, как это работает?Ломбок часто используется, но знаете ли вы, как это работает? (два)В двух статьях представлен основной принцип Ломбока, по сути, это предложение изменить путем изменения.абстрактное синтаксическое деревои понял. В двух вышеприведенных статьях уже говорилось о соответствующих точках знаний абстрактного синтаксического дерева, Если вам не ясно, вы можете взглянуть.

Весь код, задействованный в этой статье, доступен на github.

Весь код, задействованный в этой статье, доступен на github.

Весь код, задействованный в этой статье, доступен на github.

В Интернете не так много связанных документов по API о том, как изменить абстрактное синтаксическое дерево Java, поэтому в этой статье приведены соответствующие точки знаний для последующего использования.

Введение JCTree

JCTree является базовым классом элементов синтаксического дерева и содержит важное поле pos, которое используется для указания положения текущего узла синтаксического дерева (JCTree) в синтаксическом дереве, поэтому мы не можем напрямую использовать ключевое слово new для создания узлов синтаксического дерева. , даже если нет смысла его создавать. Кроме того, в сочетании с шаблоном посетителя структура данных и обработка данных разделены.Часть исходного кода выглядит следующим образом:

 1public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
2
3    public int pos = -1;
4
5    ...
6
7    public abstract void accept(JCTree.Visitor visitor);
8
9    ...
10}

Мы видим, что JCTree — это абстрактный класс, здесь мы сосредоточимся на нескольких подклассах JCTree.

  1. Заявление JC:утверждениеУзел синтаксического дерева, общие подклассы следующие
    • JCBlock:блок операторовСинтаксисный узел дерева
    • JCRВозврат:оператор возвратаузел синтаксического дерева
    • JCClassDecl:определение классаузел синтаксического дерева
    • JCVariableDecl:Определение поля/переменнойузел синтаксического дерева
  2. JCMethodDecl:определение методаузел синтаксического дерева
  3. Модификаторы JCM:знак доступаузел синтаксического дерева
  4. JCEExpression:выражениеУзел синтаксического дерева, общие подклассы следующие
    • JCAназначить:оператор присваиванияузел синтаксического дерева
    • JCIdent:идентификаторУзлы синтаксического дерева, которые могут быть переменными, типами, ключевыми словами и т. д.

Описание Tree Maker

TreeMaker используется для создания ряда узлов синтаксического дерева.Выше мы сказали, что создание JCTree не может быть создано напрямую с использованием нового ключевого слова, поэтому Java предоставляет нам инструмент TreeMaker, который установит созданный нами объект JCTree при его создании. .pos, поэтому для создания узлов синтаксического дерева необходимо использовать контекстно-зависимый объект TreeMaker.

Для ознакомления с конкретным API см.TreeMakerAPI, а затем сосредоточьтесь на нескольких часто используемых методах.

TreeMaker.Modifiers

TreeMaker.Modifiersметод созданиязнак доступаУзел синтаксического дерева (JCModifiers), исходный код выглядит следующим образом

 1public JCModifiers Modifiers(long flags) {
2    return Modifiers(flags, List.< JCAnnotation >nil());
3}
4
5public JCModifiers Modifiers(long flags,
6    List<JCAnnotation> annotations) {
7        JCModifiers tree = new JCModifiers(flags, annotations);
8        boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0;
9        tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos;
10        return tree;
11}
  1. флаги: флаги доступа
  2. аннотации: список аннотаций

Где флаги могут использовать класс перечисленияcom.sun.tools.javac.code.FlagsДля представления, например, мы можем использовать его так, чтобы сгенерировать следующий флаг доступа.

1treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);
2
3public static final

TreeMaker.ClassDef

TreeMaker.ClassDef используется для созданияопределение классаУзел синтаксиса (JCClassDecl), исходный код выглядит следующим образом:

 1public JCClassDecl ClassDef(JCModifiers mods,
2    Name name,
3    List<JCTypeParameter> typarams,
4    JCExpression extending,
5    List<JCExpression> implementing,
6    List<JCTree> defs) {
7        JCClassDecl tree = new JCClassDecl(mods,
8                                     name,
9                                     typarams,
10                                     extending,
11                                     implementing,
12                                     defs,
13                                     null);
14        tree.pos = pos;
15        return tree;
16}
  1. моды: флаги доступа, к которым можно получить доступ черезTreeMaker.Modifiersсоздавать
  2. имя: имя класса
  3. typeparams: список общих параметров
  4. расширение: родительский класс
  5. реализация: реализованный интерфейс
  6. defs: подробное описание определения класса, включая определение полей, методов и т. д.

TreeMaker.MethodDef

TreeMaker.MethodDef используется для созданияопределение методаУзел синтаксического дерева (JCMethodDecl), исходный код выглядит следующим образом

 1public JCMethodDecl MethodDef(JCModifiers mods,
2    Name name,
3    JCExpression restype,
4    List<JCTypeParameter> typarams,
5    List<JCVariableDecl> params,
6    List<JCExpression> thrown,
7    JCBlock body,
8    JCExpression defaultValue) {
9        JCMethodDecl tree = new JCMethodDecl(mods,
10                                       name,
11                                       restype,
12                                       typarams,
13                                       params,
14                                       thrown,
15                                       body,
16                                       defaultValue,
17                                       null);
18        tree.pos = pos;
19        return tree;
20}
21
22public JCMethodDecl MethodDef(MethodSymbol m,
23    Type mtype,
24    JCBlock body) {
25        return (JCMethodDecl)
26            new JCMethodDecl(
27                Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())),
28                m.name,
29                Type(mtype.getReturnType()),
30                TypeParams(mtype.getTypeArguments()),
31                Params(mtype.getParameterTypes(), m),
32                Types(mtype.getThrownTypes()),
33                body,
34                null,
35                m).setPos(pos).setType(mtype);
36}
  1. моды: флаги доступа
  2. имя: имя метода
  3. рестайп: возвращаемый тип
  4. typeparams: список общих параметров
  5. params: список параметров
  6. брошено: список объявлений исключений
  7. тело: тело метода
  8. defaultValue: метод по умолчанию (который может быть по умолчанию в интерфейсе)
  9. m: символ метода
  10. mtype: тип метода. Содержит несколько типов, универсальные типы параметров, типы параметров методов, типы параметров исключений и типы возвращаемых параметров.

Тип возвращаемого значения retype заполнить null илиtreeMaker.TypeIdent(TypeTag.VOID)Оба представляют возвращаемый тип void

TreeMaker.VarDef

TreeMaker.VarDef используется для созданияОпределение поля/переменнойУзел синтаксического дерева (JCVariableDecl), исходный код выглядит следующим образом

 1public JCVariableDecl VarDef(JCModifiers mods,
2    Name name,
3    JCExpression vartype,
4    JCExpression init) {
5        JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null);
6        tree.pos = pos;
7        return tree;
8}
9
10public JCVariableDecl VarDef(VarSymbol v,
11    JCExpression init) {
12        return (JCVariableDecl)
13            new JCVariableDecl(
14                Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())),
15                v.name,
16                Type(v.type),
17                init,
18                v).setPos(pos).setType(v.type);
19}
  1. моды: флаги доступа
  2. имя: имя параметра
  3. вартайп: тип
  4. init: оператор инициализации
  5. v: переменный символ

TreeMaker.Ident

TreeMaker.Ident используется для созданияидентификаторУзел синтаксического дерева (JCIdent), исходный код выглядит следующим образом

 1public JCIdent Ident(Name name) {
2        JCIdent tree = new JCIdent(name, null);
3        tree.pos = pos;
4        return tree;
5}
6
7public JCIdent Ident(Symbol sym) {
8        return (JCIdent)new JCIdent((sym.name != names.empty)
9                                ? sym.name
10                                : sym.flatName(), sym)
11            .setPos(pos)
12            .setType(sym.type);
13}
14
15public JCExpression Ident(JCVariableDecl param) {
16        return Ident(param.sym);
17}

TreeMaker.Return

TreeMaker.Return используется для созданияоператор возврата(JCRETURN), исходный код выглядит следующим образом

1public JCReturn Return(JCExpression expr) {
2        JCReturn tree = new JCReturn(expr);
3        tree.pos = pos;
4        return tree;
5}

TreeMaker.Select

TreeMaker.Select используется для созданияДоступ к домену/доступ к методу(Доступ к методу здесь только для получения имени, вызов метода должен использовать TreeMaker.Apply) узел синтаксического дерева (JCFieldAccess), исходный код выглядит следующим образом

 1public JCFieldAccess Select(JCExpression selected,
2    Name selector) 
3{
4        JCFieldAccess tree = new JCFieldAccess(selected, selector, null);
5        tree.pos = pos;
6        return tree;
7}
8
9public JCExpression Select(JCExpression base,
10    Symbol sym) {
11        return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type);
12}
  1. выбрано:.выражение слева от оператора
  2. селектор:.выражение в правой части оператора

Ниже приведен пример: оператор Java, сгенерированный одним оператором, является вторым оператором.

1一. TreeMaker.Select(treeMaker.Ident(names.fromString("this")), names.fromString("name"));
2
3二. this.name

TreeMaker.NewClass

TreeMaker.NewClass используется для созданияновое заявлениеУзел синтаксического дерева (JCNewClass), исходный код выглядит следующим образом:

1public JCNewClass NewClass(JCExpression encl,
2    List<JCExpression> typeargs,
3    JCExpression clazz,
4    List<JCExpression> args,
5    JCClassDecl def) {
6        JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def);
7        tree.pos = pos;
8        return tree;
9}
  1. encl: Я не совсем понимаю значение этого параметра, я вижу, что во многих примерах этот параметр имеет значение null.
  2. Typeargs: список типов параметров
  3. Clazz: тип объекта, который будет создан
  4. args: список аргументов
  5. def: определение класса

TreeMaker.Apply

TreeMaker.Apply используется для созданиявызов методаУзел синтаксического дерева (JCMethodInvocation), исходный код выглядит следующим образом:

1public JCMethodInvocation Apply(List<JCExpression> typeargs,
2    JCExpression fn,
3    List<JCExpression> args) {
4        JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);
5        tree.pos = pos;
6        return tree;
7}
  1. typeargs: список типов параметров
  2. FN: Вызов вызова
  3. args: список аргументов

TreeMaker.Assign

Пользователь TreeMaker.Assign созданоператор присваиванияУзел синтаксического дерева (JCAssign), исходный код выглядит следующим образом:

1ublic JCAssign Assign(JCExpression lhs,
2    JCExpression rhs) {
3        JCAssign tree = new JCAssign(lhs, rhs);
4        tree.pos = pos;
5        return tree;
6}
  1. lhs: выражение в левой части оператора присваивания
  2. RHS: Заявление о присвоении

TreeMaker.Exec

TreeMaker.Exec используется для созданияисполняемый операторУзел синтаксического дерева (JCExpressionStatement), исходный код выглядит следующим образом:

1public JCExpressionStatement Exec(JCExpression expr) {
2        JCExpressionStatement tree = new JCExpressionStatement(expr);
3        tree.pos = pos;
4        return tree;
5}

TreeMaker.Apply и TreeMaker.Assign должны обернуть слой TreeMaker.Exec, чтобы получить JCExpressionStatement.

TreeMaker.Block

TreeMaker.Block используется для созданиясоставное утверждениеУзел синтаксического дерева (JCBlock), исходный код выглядит следующим образом:

1public JCBlock Block(long flags,
2    List<JCStatement> stats) {
3        JCBlock tree = new JCBlock(flags, stats);
4        tree.pos = pos;
5        return tree;
6}
  1. флаги: флаги доступа
  2. статистика: список заявлений

Введение в com.sun.tools.javac.util.List

Когда мы работаем с абстрактным синтаксическим деревом, иногда это включает в себя работу со списком, но этот список — это не то, что мы часто используем.java.util.Listноcom.sun.tools.javac.util.List, этот Список довольно странный, это цепная структура, есть головные узлы и хвостовые узлы, но только хвостовой узел является Список, тут достаточно понять.

 1public class List<A> extends AbstractCollection<A> implements java.util.List<A> {
2    public A head;
3    public List<A> tail;
4    private static final List<?> EMPTY_LIST = new List<Object>((Object)null, (List)null) {
5        public List<Object> setTail(List<Object> var1) {
6            throw new UnsupportedOperationException();
7        }
8
9        public boolean isEmpty() {
10            return true;
11        }
12    };
13
14    List(A head, List<A> tail) {
15        this.tail = tail;
16        this.head = head;
17    }
18
19    public static <A> List<A> nil() {
20        return EMPTY_LIST;
21    }
22
23    public List<A> prepend(A var1) {
24        return new List(var1, this);
25    }
26
27    public List<A> append(A var1) {
28        return of(var1).prependList(this);
29    }
30
31    public static <A> List<A> of(A var0) {
32        return new List(var0, nil());
33    }
34
35    public static <A> List<A> of(A var0, A var1) {
36        return new List(var0, of(var1));
37    }
38
39    public static <A> List<A> of(A var0, A var1, A var2) {
40        return new List(var0, of(var1, var2));
41    }
42
43    public static <A> List<A> of(A var0, A var1, A var2, A... var3) {
44        return new List(var0, new List(var1, new List(var2, from(var3))));
45    }
46
47    ...
48}

com.sun.tools.javac.util.ListBuffer

так какcom.sun.tools.javac.util.ListНеудобен в использовании, поэтому на нем инкапсулирован слой.Этот класс инкапсуляцииListBuffer, такие операции аналогичны тем, которые мы обычно используемjava.util.ListИспользование очень похоже.

 1public class ListBuffer<A> extends AbstractQueue<A> {
2
3    public static <T> ListBuffer<T> of(T x) {
4        ListBuffer<T> lb = new ListBuffer<T>();
5        lb.add(x);
6        return lb;
7    }
8
9    /** The list of elements of this buffer.
10     */
11    private List<A> elems;
12
13    /** A pointer pointing to the last element of 'elems' containing data,
14     *  or null if the list is empty.
15     */
16    private List<A> last;
17
18    /** The number of element in this buffer.
19     */
20    private int count;
21
22    /** Has a list been created from this buffer yet?
23     */
24    private boolean shared;
25
26    /** Create a new initially empty list buffer.
27     */
28    public ListBuffer() {
29        clear();
30    }
31
32    /** Append an element to buffer.
33     */
34    public ListBuffer<A> append(A x) {
35        x.getClass(); // null check
36        if (shared) copy();
37        List<A> newLast = List.<A>of(x);
38        if (last != null) {
39            last.tail = newLast;
40            last = newLast;
41        } else {
42            elems = last = newLast;
43        }
44        count++;
45        return this;
46    }
47    ........
48}

Введение в com.sun.tools.javac.util.Names

Это класс инструментов, который создает нам имя, имена классов, методов и параметров нужно создавать через этот класс. Одним из методов, который часто используется в нем, являетсяfromString(), общее использование выглядит следующим образом.

1Names names  = new Names()
2names. fromString("setName");

Практическая тренировка

Выше у нас есть общее представление о том, как работать с абстрактными синтаксическими деревьями, а затем мы напишем несколько реальных случаев, чтобы углубить наше понимание.

переменная корреляция

Параметры, которыми мы часто манипулируем в классе, являются переменными, так как же использовать возможности абстрактного синтаксического дерева для манипулирования переменными? Далее мы проделаем некоторые операции над переменными.

Создание переменных

например, для созданияprivate String age;Такая переменная, заимствующая то, что мы сказали вышеVarDefметод

1// 生成参数 例如:private String age;
2treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE), names.fromString("age"), treeMaker.Ident(names.fromString("String")), null);

назначить переменную

Например, мы хотим сгенерироватьprivate String name = "BuXueWuShu"или используйтеVarDefметод

1// private String name = "BuXueWuShu"
2treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE),names.fromString("name"),treeMaker.Ident(names.fromString("String")),treeMaker.Literal("BuXueWuShu"))

Добавьте два литерала

Например, мы генерируемString add = "a" + "b";, заимствуя то, что мы сказали вышеExecМетоды иAssignметод

1// add = "a"+"b"
2treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("add")),treeMaker.Binary(JCTree.Tag.PLUS,treeMaker.Literal("a"),treeMaker.Literal("b"))))

+= синтаксис

Например, мы хотим сгенерироватьadd += "test", что похоже на литерал выше.

1// add+="test"
2treeMaker.Exec(treeMaker.Assignop(JCTree.Tag.PLUS_ASG, treeMaker.Ident(names.fromString("add")), treeMaker.Literal("test")))

++ синтаксис

Например, для генерации++i

1treeMaker.Exec(treeMaker.Unary(JCTree.Tag.PREINC,treeMaker.Ident(names.fromString("i"))))

метод, связанный

Мы работали с переменными, поэтому в основном нам нужно генерировать методы, так как же генерировать и оперировать методами? Далее мы продемонстрируем метод работы, связанный с методом.

Нет параметров, нет возвращаемого значения

Мы можем использовать вышеупомянутыйMethodDefметод создания

 1/*
2    无参无返回值的方法生成
3    public void test(){
4
5    }
6 */
7// 定义方法体
8ListBuffer<JCTree.JCStatement> testStatement = new ListBuffer<>();
9JCTree.JCBlock testBody = treeMaker.Block(0, testStatement.toList());
10
11JCTree.JCMethodDecl test = treeMaker.MethodDef(
12        treeMaker.Modifiers(Flags.PUBLIC), // 方法限定值
13        names.fromString("test"), // 方法名
14        treeMaker.Type(new Type.JCVoidType()), // 返回类型
15        com.sun.tools.javac.util.List.nil(),
16        com.sun.tools.javac.util.List.nil(),
17        com.sun.tools.javac.util.List.nil(),
18        testBody,    // 方法体
19        null
20);

С параметрами и без возвращаемого значения

Мы можем использовать вышеупомянутыйMethodDefметод создания

 1/*
2    无参无返回值的方法生成
3    public void test2(String name){
4        name = "xxxx";
5    }
6 */
7ListBuffer<JCTree.JCStatement> testStatement2 = new ListBuffer<>();
8testStatement2.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("name")),treeMaker.Literal("xxxx"))));
9JCTree.JCBlock testBody2 = treeMaker.Block(0, testStatement2.toList());
10
11// 生成入参
12JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("name"),treeMaker.Ident(names.fromString("String")), null);
13com.sun.tools.javac.util.List<JCTree.JCVariableDecl> parameters = com.sun.tools.javac.util.List.of(param);
14
15JCTree.JCMethodDecl test2 = treeMaker.MethodDef(
16        treeMaker.Modifiers(Flags.PUBLIC), // 方法限定值
17        names.fromString("test2"), // 方法名
18        treeMaker.Type(new Type.JCVoidType()), // 返回类型
19        com.sun.tools.javac.util.List.nil(),
20        parameters, // 入参
21        com.sun.tools.javac.util.List.nil(),
22        testBody2,
23        null
24);

Есть параметры и возвращаемые значения

 1 /*
2    有参有返回值
3    public String test3(String name){
4       return name;
5    }
6 */
7
8ListBuffer<JCTree.JCStatement> testStatement3 = new ListBuffer<>();
9testStatement3.append(treeMaker.Return(treeMaker.Ident(names.fromString("name"))));
10JCTree.JCBlock testBody3 = treeMaker.Block(0, testStatement3.toList());
11
12// 生成入参
13JCTree.JCVariableDecl param3 = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("name"),treeMaker.Ident(names.fromString("String")), null);
14com.sun.tools.javac.util.List<JCTree.JCVariableDecl> parameters3 = com.sun.tools.javac.util.List.of(param3);
15
16JCTree.JCMethodDecl test3 = treeMaker.MethodDef(
17        treeMaker.Modifiers(Flags.PUBLIC), // 方法限定值
18        names.fromString("test4"), // 方法名
19        treeMaker.Ident(names.fromString("String")), // 返回类型
20        com.sun.tools.javac.util.List.nil(),
21        parameters3, // 入参
22        com.sun.tools.javac.util.List.nil(),
23        testBody3,
24        null
25);

специальный

Мы узнали, как определять параметры и как определять методы.На самом деле, есть еще много операторов, которые нужно изучить, например, как генерировать новые операторы, как генерировать операторы вызова метода и как генерировать операторы if. j Далее мы изучим более специальный синтаксис.

новый объект

 1// 创建一个new语句 CombatJCTreeMain combatJCTreeMain = new CombatJCTreeMain();
2JCTree.JCNewClass combatJCTreeMain = treeMaker.NewClass(
3        null,
4        com.sun.tools.javac.util.List.nil(),
5        treeMaker.Ident(names.fromString("CombatJCTreeMain")),
6        com.sun.tools.javac.util.List.nil(),
7        null
8);
9JCTree.JCVariableDecl jcVariableDecl1 = treeMaker.VarDef(
10        treeMaker.Modifiers(Flags.PARAMETER),
11        names.fromString("combatJCTreeMain"),
12        treeMaker.Ident(names.fromString("CombatJCTreeMain")),
13        combatJCTreeMain
14);

вызов метода (без аргументов)

 1JCTree.JCExpressionStatement exec = treeMaker.Exec(
2        treeMaker.Apply(
3                com.sun.tools.javac.util.List.nil(),
4                treeMaker.Select(
5                        treeMaker.Ident(names.fromString("combatJCTreeMain")), // . 左边的内容
6                        names.fromString("test") // . 右边的内容
7                ),
8                com.sun.tools.javac.util.List.nil()
9        )
10);

вызов метода (с параметрами)

 1// 创建一个方法调用 combatJCTreeMain.test2("hello world!");
2JCTree.JCExpressionStatement exec2 = treeMaker.Exec(
3        treeMaker.Apply(
4                com.sun.tools.javac.util.List.nil(),
5                treeMaker.Select(
6                        treeMaker.Ident(names.fromString("combatJCTreeMain")), // . 左边的内容
7                        names.fromString("test2") // . 右边的内容
8                ),
9                com.sun.tools.javac.util.List.of(treeMaker.Literal("hello world!")) // 方法中的内容
10        )
11);

если оператор

 1/*
2    创建一个if语句
3    if("BuXueWuShu".equals(name)){
4        add = "a" + "b";
5    }else{
6        add += "test";
7    }
8 */
9// "BuXueWuShu".equals(name)
10JCTree.JCMethodInvocation apply = treeMaker.Apply(
11        com.sun.tools.javac.util.List.nil(),
12        treeMaker.Select(
13                treeMaker.Literal("BuXueWuShu"), // . 左边的内容
14                names.fromString("equals") // . 右边的内容
15        ),
16        com.sun.tools.javac.util.List.of(treeMaker.Ident(names.fromString("name")))
17);
18//  add = "a" + "b"
19JCTree.JCExpressionStatement exec3 = treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("add")), treeMaker.Binary(JCTree.Tag.PLUS, treeMaker.Literal("a"), treeMaker.Literal("b"))));
20//  add += "test"
21JCTree.JCExpressionStatement exec1 = treeMaker.Exec(treeMaker.Assignop(JCTree.Tag.PLUS_ASG, treeMaker.Ident(names.fromString("add")), treeMaker.Literal("test")));
22
23JCTree.JCIf anIf = treeMaker.If(
24        apply, // if语句里面的判断语句
25        exec3, // 条件成立的语句
26        exec1  // 条件不成立的语句
27);

Адрес источника

Суммировать

На бумаге в конце концов я чувствую себя мелким, и я абсолютно точно знаю, что это дело должно быть сделано. Я надеюсь, что после прочтения этой статьи вы сможете протестировать его самостоятельно на этой машине. Установите несколько параметров самостоятельно, изучите Lombok самостоятельно, научитесь генерировать методы get и set, хотя эти знания в основном не используются в повседневной разработке, но если вы используете эти знания, другие не будут, а вы будете, пробел На самом деле, он медленно открылся.Весь код, задействованный в этой статье, доступен на github., потяните вниз и выполните глобальный поискCombatJCTreeProcessorкласс видно.

Если вам интересно, можете обратить внимание на мой новый паблик аккаунт и поискать [Сумка с сокровищами программиста]. Или просто отсканируйте код ниже.