从lombok想到的行号问题

本文探讨了Lombok在Java开发中的应用及其对代码行号的影响,详细解析了Lombok的工作原理,并通过与switch语句的对比,揭示了Java编译器如何处理行号信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


title: 从lombok想到的行号问题 tags:

  • lombok
  • linenumber
  • switch
  • class
  • javac categories: 工作日志 date: 2017-12-12 22:30:26

背景

lombok 是近几年来声名鹊起的java效率提升利器,对于lombok一直只是在某些开源项目中可以看到。在自身的开发中并未使用。在github上确实使用者还比较可观

主要一直认为存在如下问题

  1. 自动生成不够直观
  2. 必须IDE支持
  3. 行号对不上

一直认为行号对不上是最大的问题===》but错误的认知【想要认识到自己的错误还是要花点时间的~】

国外也有小伙伴表达了同样的担忧 stackoverflow.com/questions/3…

解释

首先了解lombok的原理

原理

自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。
举例来说,现在有一个实现了"JSR 269 API"的程序A,那么使用javac编译源码的时候具体流程如下:
1)javac对源代码进行分析,生成一棵抽象语法树(AST)
2)运行过程中调用实现了"JSR 269 API"的A程序
3)此时A程序就可以完成它自己的逻辑,包括修改第一步骤得到的抽象语法树(AST)
4)javac使用修改后的抽象语法树(AST)生成字节码文件
详细的流程图如下:

lombok本质上就是这样的一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下:
1)javac对源代码进行分析,生成一棵抽象语法树(AST)
2)运行过程中调用实现了"JSR 269 API"的lombok程序
3)此时lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
4)javac使用修改后的抽象语法树(AST)生成字节码文件

参考 blog.youkuaiyun.com/dslztx/arti…

语法糖之switch

首先针对该问题思考,这是在Javac做的事情 换言之 可以理解成javac的代码生成器 这个在每次java版本升级时都会有同样的changelog 通常我们称之为语法糖!

比如

  1. foreach迭代
  2. autoClose
  3. switch String

我们以switch举例

大家知道在java之前是不支持String的switch 但是支持int enum等。

那么设想我们使用String要如何处理呢?

我们看一下编译出来的class中如何描述

    private String handlerObjectName(TsStockOut tsStockOut, String idOwnOrg) {
        String var4 = tsStockOut.getBillType();
        byte var5 = -1;
        switch(var4.hashCode()) {
        case 2269:
            if (var4.equals("GD")) {
                var5 = 3;
            }
            break;
        case 66672:
            if (var4.equals("CGT")) {
                var5 = 0;
            }
            break;
        case 67493:
            if (var4.equals("DCD")) {
                var5 = 7;
            }
            break;
        case 69418:
            if (var4.equals("FCG")) {
                var5 = 6;
            }
            break;
        case 75584:
            if (var4.equals("LPD")) {
                var5 = 4;
            }
            break;
        case 75677:
            if (var4.equals("LSD")) {
                var5 = 5;
            }
            break;
        case 79273:
            if (var4.equals("PKD")) {
                var5 = 1;
            }
            break;
        case 79707:
            if (var4.equals("PYD")) {
                var5 = 2;
            }
        }
     
        String ObjName;
        switch(var5) {
        case 0:
            String idStockReturn = tsStockOut.getIdSourceBill();
            TsStockReturn tsStockReturn = this.stockReturnService.getStockReturnById(idStockReturn, idOwnOrg);
            ObjName = tsStockReturn.getSupplierName();
            break;
        case 1:
            ObjName = "盘亏";
            break;
        case 2:
            ObjName = "盘盈";
            break;
        case 3:
        case 4:
        case 5:
            TsMaintainVO maintainVO = this.maintainService.selectMaintainById(tsStockOut.getIdSourceBill());
            if (maintainVO == null) {
                throw new BussinessException("源单已经不存在!" + tsStockOut.getIdSourceBill());
            }
     
            if (StringUtils.isBlank(maintainVO.getCarNoWhole())) {
                ObjName = maintainVO.getNaCustomer();
            } else {
                ObjName = maintainVO.getNaCustomer() + "【" + maintainVO.getCarNoWhole() + "】";
            }
            break;
        case 6:
            String purchaseId = tsStockOut.getIdSourceBill();
            TsPurchase tsPurchase = this.purchaseService.getPurchaseById(purchaseId, idOwnOrg);
            if (tsPurchase == null) {
                throw new BussinessException("源采购单已经不存在!" + tsStockOut.getIdSourceBill());
            }
     
            ObjName = tsPurchase.getSupplierName();
            break;
        case 7:
            AllotVo allotVo = this.allotService.getAllotInfoIn(tsStockOut.getIdSourceBill());
            CustomerCarVO customerCarVO = this.customerCarService.queryCustomerByIdOrgSource(allotVo.getIdOrgIn(), allotVo.getIdOrgOut());
            CustomerCarVO customer = this.customerCarService.getCustomerById(customerCarVO.getIdCustomer(), allotVo.getIdOrgOut());
            ObjName = customer.getName();
            break;
        default:
            ObjName = StringUtils.isBlank(tsStockOut.getObjectName()) ? "手工" : tsStockOut.getObjectName();
        }
     
        return ObjName;
    }
复制代码

而在源码中如何书写的呢?

    private String handlerObjectName(TsStockOut tsStockOut, String idOwnOrg) {
       String ObjName;
           switch (tsStockOut.getBillType()) {
               case AppStatus.CGT:
                   String idStockReturn = tsStockOut.getIdSourceBill();
                   TsStockReturn tsStockReturn = this.stockReturnService.getStockReturnById(idStockReturn, idOwnOrg);
                   ObjName = tsStockReturn.getSupplierName();
                   break;
               case AppStatus.PKD:
                   ObjName = "盘亏";
                   break;
               case AppStatus.PYD:
                   ObjName = "盘盈";
                   break;
               case AppStatus.GD:
               case AppStatus.LPD:
               case AppStatus.LSD:
                   TsMaintainVO maintainVO = this.maintainService.selectMaintainById(tsStockOut.getIdSourceBill());
                   if (maintainVO == null) {
                       throw new BussinessException("源单已经不存在!" + tsStockOut.getIdSourceBill());
                   }
                   if(StringUtils.isBlank(maintainVO.getCarNoWhole())){
                       ObjName = maintainVO.getNaCustomer();
                   }else{
                       ObjName = maintainVO.getNaCustomer() + "【" + maintainVO.getCarNoWhole() + "】";
                   }
                   break;
               case AppStatus.FCG:
                   String purchaseId = tsStockOut.getIdSourceBill();
                   TsPurchase tsPurchase = this.purchaseService.getPurchaseById(purchaseId, idOwnOrg);
                   if (tsPurchase == null) {
                       throw new BussinessException("源采购单已经不存在!" + tsStockOut.getIdSourceBill());
                   }
                   ObjName = tsPurchase.getSupplierName();
                   break;
               case AppStatus.DCD:
                   AllotVo allotVo = allotService.getAllotInfoIn(tsStockOut.getIdSourceBill());
                   CustomerCarVO customerCarVO = customerCarService.queryCustomerByIdOrgSource(allotVo.getIdOrgIn(), allotVo.getIdOrgOut());
                   CustomerCarVO customer = customerCarService.getCustomerById(customerCarVO.getIdCustomer(), allotVo.getIdOrgOut());
                   ObjName = customer.getName();
                   break;
               default:
                   ObjName = StringUtils.isBlank(tsStockOut.getObjectName()) ? "手工" : tsStockOut.getObjectName();
                   break;
           }
       return ObjName;
    }
复制代码

很明显其实仍然是使用int作为switch参数的

万幸hashCode方法返回是int类型!

为了防止出现碰巧出现同样hashcode的String javac还生成了equals方法 防止误判!

从上述可知 我们的代码已经发生了变化

那问题来了!行号咋办?

行号浅析

既然我们在日常工作中能够在异常中获得正常的行号!那么必然存在某个持久化文件用来存储行号!

那么首当其冲的就是class文件。我们知道实质上java运行的是class文件 因此考虑行号其实存储在class文件中

答案是肯定的!

LineNumberTale属性:用于描述Java源码的行号与字节码行号之间的对应关系,非运行时必需属性,会默认生成至Class文件中,可以使用Javac的-g:none或-g:lines关闭或要求生成该项属性信息,其结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

line_number_table_length

1

line_number_info

line_number_table

line_number_table_length

line_number_table是一组line_number_info类型数据的集合,其所包含的line_number_info类型数据的数量为line_number_table_length,line_number_info结构如下:

类型

名称

数量

说明

u2

start_pc

1

字节码行号

u2

line_number

1

Java源码行号

不生成该属性的最大影响是:1,抛出异常时,堆栈将不会显示出错的行号;2,调试程序时无法按照源码设置断点

在class文件中有一块区域用来存储行号,默认情况下使用javac就可以生成 而我们使用maven插件编译

在maven插件中可以配置

    /**
     
      * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a
     
      * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>.
     
      * If debug level is not specified, by default, nothing will be appended to <code>-g</code>.
     
      * If debug is not turned on, this attribute will be ignored.
     
      *
     
      * @since 2.1
     
      */
     
     @Parameter( property = "maven.compiler.debuglevel" )
     
     private String debuglevel;
复制代码

我们可以看到maven默认情况下使用 javac -g

    -g
    
    Generate all debugging information, including local variables. By default, only line number and source file information is generated.
    
    -g:none
    
    Do not generate any debugging information.
    
    -g:{keyword list}
    
    Generate only some kinds of debugging information, specified by a comma separated list of keywords. Valid keywords are:
    
    sourceSource file debugging informationlinesLine number debugging informationvarsLocal variable debugging information
复制代码

因此可以判断我们正常使用是行号会由javac写入到class文件中!

因此关于lombok的行号问题白担心那么久!!!

<think>好的,我现在需要解决用户关于Lombok Builder在继承场景下的使用问题。用户提到了当使用extend继承时,子类无法通过Builder来构建父类属性值,并希望得到教程、示例和解决方案。 首先,我得回顾一下Lombok的@Builder注解。通常,@Builder会为类生成一个建造者模式的结构,包括一个内部静态Builder类,以及相应的链式方法。但问题出现在子类继承父类时,父类的属性无法被包含在子类的Builder中。这是因为Lombok默认只会处理当前类的属性,不会自动包含父类的字段。 接下来,我需要思考可能的解决方案。根据已有的引用,用户提到引用[2]中的问题,即子类无法通过Builder构建父类属性。解决方案可能需要手动扩展Builder类,或者使用其他Lombok注解组合。例如,在子类中手动编写Builder类,同时包含父类的属性,但这可能比较繁琐。 另外,引用[4]提到可以通过配置文件lombok.config设置全局行为,比如处理equals和HashCode时调用父类方法。或许类似的配置可以影响Builder的行为,但我不确定是否有这样的配置选项。可能需要查阅Lombok的官方文档,看是否有相关配置支持Builder继承。 另一个思路是结合@SuperBuilder注解。Lombok在较新版本中引入了@SuperBuilder来处理继承问题。这个注解会为父类和子类生成兼容的Builder,允许子类包含父类的属性。不过需要确认用户的Lombok版本是否支持,因为@SuperBuilder是在1.18.2版本之后引入的实验性功能。如果用户使用的是旧版本,可能需要升级。 假设用户可以使用@SuperBuilder,那么具体的步骤应该是: 1. 在父类和子类上都使用@SuperBuilder注解,而不是普通的@Builder。 2. 确保Lombok版本支持该注解,并在IDE中安装了对应的插件。 3. 编写示例代码,展示父类和子类的Builder用法,确保父类的属性可以被正确初始化。 如果用户不能升级Lombok版本,可能需要手动扩展Builder类。例如,在父类中使用@Builder,并在子类中自定义Builder,继承父类的Builder,并添加子类的属性。这种方法需要手动编写部分代码,但可以在旧版本中实现需求。 比如,父类使用@Builder,子类则手动创建一个继承自父类Builder的内部Builder类,并添加子类的字段。同时,需要覆盖build()方法,以确保正确创建子类实例。这可能比较复杂,但可行。 另外,引用[5]提到@Accessors注解,但不确定是否与Builder有关。可能需要排除这个方向。 现在需要将这些思路整理成步骤,并给出代码示例。同时,要注意用户的引用中提到的问题和可能的解决方案,比如引用[2]中的手动解决方法,或使用@SuperBuilder的更简洁方式。 总结可能的解决方案: 1. 使用Lombok的@SuperBuilder注解(需要较新版本) 2. 手动编写子类的Builder,继承父类的Builder,并添加子类字段(适用于旧版本) 接下来需要详细说明这两种方法,并提供示例代码。同时,提醒用户注意版本兼容性和配置问题,比如引用[4]中的配置文件可能需要调整,但可能不适用于Builder的情况。 最后,确保回答结构清晰,分点说明,并包含示例代码。同时,生成相关问题,如如何处理多级继承,或如何配置Lombok以支持这些功能。</think>### Lombok Builder在继承场景下的使用教程与解决方案 当使用Lombok的`@Builder`注解时,若父类已定义字段,**子类默认无法通过自动生成的Builder直接初始化父类属性**。以下是两种解决方案及示例: --- #### 方案一:使用`@SuperBuilder`注解(推荐) **适用条件**:Lombok版本 ≥ 1.18.2,需启用实验性功能。 1. **在父类和子类上添加`@SuperBuilder`注解**: ```java import lombok.experimental.SuperBuilder; @SuperBuilder public class Parent { private String parentField; } @SuperBuilder public class Child extends Parent { private String childField; } ``` 2. **调用子类Builder时初始化父类属性**: ```java Child child = Child.builder() .parentField("父类值") .childField("子类值") .build(); ``` **优点**:代码简洁,无需手动编写Builder逻辑[^2]。 --- #### 方案二:手动扩展Builder(兼容旧版本) 若无法升级Lombok,可通过以下步骤实现继承: 1. **为父类添加`@Builder`注解**: ```java import lombok.Builder; @Builder public class Parent { private String parentField; } ``` 2. **在子类中手动定义Builder并继承父类Builder**: ```java import lombok.Builder; public class Child extends Parent { private String childField; @Builder(builderMethodName = "childBuilder") public Child(String parentField, String childField) { super(parentField); this.childField = childField; } // 手动扩展Builder类 public static class ChildBuilder extends Parent.ParentBuilder { private String childField; @Override public Child build() { return new Child(super.parentField, childField); } } } ``` 3. **调用子类Builder**: ```java Child child = Child.childBuilder() .parentField("父类值") .childField("子类值") .build(); ``` **注意**:需手动维护构造函数和Builder继承关系,适合简单继承场景[^2]。 --- ### 关键配置与注意事项 1. **版本兼容性**:`@SuperBuilder`为实验性功能,需在`lombok.config`中添加: ``` lombok.experimental.superBuilder=true ``` 2. **IDE支持**:确保IDE安装了Lombok插件,否则可能无法识别生成的代码[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值