避免类型转换的陷阱

学习目标

  • 避免类型转换的陷阱。

成功分配

在 ABAP 中,允许您在不同数据类型的变量之间执行值分配。执行此操作时,ABAP 会尝试将源字段的值转换为目标字段的类型。转换是否成功取决于源字段的值是否与目标字段的数据类型兼容。

在此的第一个示例中,我们正在尝试将字符串的值分配给整数字段。由于字符串 12345 的值是有效整数,因此分配成功。第二个分配也成功。

试用 - 成功任务

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

  2. 将以下代码复制到 METHOD if_oo_adt_classrun~main. 和 ENDMETHOD. 语句之间的编辑器中:

DATA var_string TYPE string.
DATA var_int TYPE i.
DATA var_date TYPE d.
data var_pack type p length 3 decimals 2.
var_string = `12345`.
var_int = var_string.
out->write( 'Conversion successful' ).
var_string = `20230101`.
var_date = var_string.
out->write( |String value: { var_string }| ).
out->write( |Date Value: { var_date date = user }| ).
  1. 按 Ctrl + F3 激活类。

  2. 在代码周围播放以熟悉主题。

未成功分配

值分配中的类型转换并不总是成功的。有时,源值与目标字段的类型不兼容。此处显示的两个分配都会导致异常,如果保持不变,则会导致运行时错误。

当我们尝试将字符串值 ABCDE 分配给整数字段时,系统会识别出字符串的内容不能表示为数字,并触发异常 CX_SY_CONVERSION_NO_NUMBER。在第二个示例中,我们尝试将值 1000 分配给长度为 3 和 2 个小数位的压缩数字。因此,变量具有 5 位,其中 2 位是小数位,因此最大值 999.99。因此,分配导致算术溢出,系统响应异常 CX_SY_CONVERSION_OVERFLOW。

截断和舍入

某些分配会导致数据丢失或准确性。例如,如果将字符字段的值分配给较短的字段,则该值将被截断。在此示例中,我们尝试将 5 个字符分配给长度为 3 的字符字段。在这种情况下,第四和第五个字符将丢失。

如果为数字字段分配值且目标字段中的小数位数不足,系统将使用算术舍入。在此处的两个示例中,一个季度可以准确表示为 0.25,但第八个 - 0.125 - 向上舍入为 0.13。

试用 - 截断和舍入

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

  2. 将以下代码复制到 METHOD if_oo_adt_classrun~main. 和 ENDMETHOD. 语句之间的编辑器中:

DATA long_char TYPE c LENGTH 10.
DATA short_char TYPE c LENGTH 5.
DATA result TYPE p LENGTH 3 DECIMALS 2.
long_char = 'ABCDEFGHIJ'.
short_char = long_char.
out->write( long_char ).
out->write( short_char ).
result = 1 / 8.
out->write( |1 / 8 is rounded to { result NUMBER = USER }| ).
  1. 按 Ctrl + F3 激活类。

  2. 在代码周围播放以熟悉主题。

分配的意外结果

有时,两种不同数据类型之间的分配结果似乎是意外的。例如,如果将日期字段分配给整数字段,则值为自 0001 年 1 月 1 日起的天数。同样,如果将时间字段指派给整数,则结果是自午夜起的秒数。

如果将字符字段或字符串分配给类型为 N 的变量,系统将放弃任何不是数字的字符,将字段中的剩余数字右对齐排列,并使用前导零填充剩余空格。

从技术角度来看,日期字段是字符字段。因此,您可以将值分配到与有效日期不对应的 ABAP 中的日期字段(请注意,当用户通过 UI 输入日期时无法执行此操作,因为 UI 层会验证日期)。

内嵌声明的陷阱

在算术表达式中使用内联声明时,系统会从表达式的右侧派生新变量的类型。由于数字文字 5 和 10 是整数,因此变量 result1 和 result2 也创建为整数。虽然乘法结果正确,但除法结果向上舍入为 1。

试用 - 意外的分配结果

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

  2. 将以下代码复制到 METHOD if_oo_adt_classrun~main. 和 ENDMETHOD. 语句之间的编辑器中:

DATA var_date TYPE d.
DATA var_int TYPE i.
DATA var_string TYPE string.
DATA var_n TYPE n LENGTH 4.
var_date = cl_abap_context_info=>get_system_date( ).
var_int = var_date.
out->write( |Date as date| ).
out->write( var_date ).
out->write( |Date assigned to integer| ).
out->write( var_int ).
var_string = `R2D2`.
var_n = var_string.
out->write( |String| ).
out->write( var_string ).
out->write( |String assigned to type N| ).
out->write( var_n ).
  1. 按 Ctrl + F3 激活类。

  2. 在代码周围播放以熟悉主题。

强制类型的转换

观看此视频,了解如何显式和隐式强制类型转换。

视频:本节含 SAP 官方视频(视频 ID 1_8nsh3go1),需在 learning.sap.com 在线观看。

试用 - 强制类型的转换

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

  2. 将以下代码复制到 METHOD if_oo_adt_classrun~main. 和 ENDMETHOD. 语句之间的编辑器中:

* result has type C.
* and is displayed unformatted in the console
DATA(result1) = '20230101'.
out->write( result1 ).
* result2 is forced to have type D
* and is displayed with date formatting in the console
DATA(result2) = CONV d( '20230101' ).
out->write( result2 ).
* The method do_something( ) has an importing parameter of type string.
* Attempting to pass var results in a syntax error
* The CONV #( ) expression converts var into the expected type
* Note that CONV #( ) can lead to conversion exceptions
* lcl_class=>do_something( var ).
  1. 按 Ctrl + F3 激活类。

  2. 在代码周围播放以熟悉主题。

防止截断和舍入

观看此视频,了解如何防止截断和舍入。

视频:本节含 SAP 官方视频(视频 ID 1_08pt2l0w),需在 learning.sap.com 在线观看。

试用 - 防止截断和舍入

  1. 创建实施接口 IF_OO_ADT_CLASSRUN 的新类。

  2. 将以下代码复制到 METHOD if_oo_adt_classrun~main. 和 ENDMETHOD. 语句之间的编辑器中:

DATA var_date   TYPE d.
DATA var_pack   TYPE p LENGTH 3 DECIMALS 2.
DATA var_string TYPE string.
DATA var_char   TYPE c LENGTH 3.
var_pack = 1 / 8.
out->write( |1/8 = { var_pack NUMBER = USER }| ).
TRY.
var_pack = EXACT #( 1 / 8 ).
CATCH cx_sy_conversion_error.
out->write( |1/8 has to be rounded. EXACT triggered an exception| ).
ENDTRY.
var_string = 'ABCDE'.
var_char   = var_string.
out->write( var_char ).
TRY.
var_char = EXACT #( var_string ).
CATCH cx_sy_conversion_error.
out->write( 'String has to be truncated. EXACT triggered an exception' ).
ENDTRY.
var_date = 'ABCDEFGH'.
out->write( var_Date ).
TRY.
var_date = EXACT #( 'ABCDEFGH' ).
CATCH cx_sy_conversion_error.
out->write( |ABCDEFGH is not a valid date. EXACT triggered an exception| ).
ENDTRY.
var_date = '20221232'.
out->write( var_date ).
TRY.
var_date = EXACT #( '20221232' ).
CATCH cx_sy_conversion_error.
out->write( |2022-12-32 is not a valid date. EXACT triggered an exception| ).
ENDTRY.
  1. 按 Ctrl + F3 激活类。

  2. 在代码周围播放以熟悉主题。

避免有问题的类型转换

仔细查看 ABAP 类的控制台输出,并注意到承运商名称已被截断。您还会注意到,为货物航班显示的承运商标识和转机标识存在问题。识别有问题的类型转换作为这些问题的源,并相应地更正代码。

模板:

  • /LRN/CL_S4D401_ATS_SQL_TRACE(全局类)

解决方案:

  • /LRN/CL_S4D401_TCS_TYPE_CONV(全局类)

任务 1: 复制模板(可选)

复制模板类 /LRN/CL_S4D401_ATS_SQL_TRACE。如果已完成上一练习,则可以跳过此任务并继续编辑类 ZCL_##_SOLUTION

步骤

  1. 将类 /LRN/CL_S4D401_ATS_SQL_TRACE 复制到您自己的包中的类(建议名称:ZCL_##_SOLUTION,其中 ## 代表组编号)。

  2. 在项目浏览器中,右键单击类 /LRN/CL_S4D401_ATS_SQL_TRACE 以打开上下文菜单。

  3. 从上下文菜单中,选择复制....

  4. 在包字段中输入的名称。在名称字段中,输入 ZCL_##_SOLUTION,其中 ## 代表组编号。

  5. 选择下一步

  6. 确认传输请求并选择完成

  7. 激活复制。

  8. Ctrl + F3 激活类。

任务 2: 检测问题类型转换

以控制台应用形式执行类。检查控制台输出,尤其要注意货物航班的承运商名称、承运商标识和转机标识。查找产生此输出的代码并将数据追溯到源 - 请注意潜在的问题类型转换。使用 EXACT 表达式验证是否存在数据丢失的类型转换。

步骤

  1. 执行 ABAP 类 ZCL_##_SOLUTION 作为控制台应用。

  2. 在编辑器中打开类,然后按 F9

  3. 检查控制台输出。

  4. 控制台视图中向下滚动。

  5. 找到以*承运商名称:*开头的行。

  6. 进一步向下滚动到以 找到合适的货物航班 开头的行并检查下一行。

  7. 返回到 ABAP 类的源代码并分析方法 if_oo_adt_classrun~main 的实施。

  8. 导航到方法 get_output

  9. 在方法调用 carrier->get_output( ) 中,将光标置于方法名称 get_output 上,然后按 F3。或者,您可以按住 Ctrl 键并单击方法名称。

  10. 在表达式 me->name 中,将光标置于**名称上,然后按 F2 显示属性名称** 的代码元素信息。

  11. 将光标置于 r_result 上,然后按 F2 显示 get_output 方法的返回参数 r_result 的代码元素信息。

  12. 在代码元素信息中,单击 tt_output 以显示此类型的代码元素信息。

  13. 单击 t_output 导航到 tt_output 表类型的行类型的代码元素信息。

  14. 更改参数 r_result 的行类型。使用可变长度的字符型行类型。

  15. 从元素信息底部的工具栏中,选择在编辑器中打开。或者,您可以按 F3 导航到类型 t_output 的定义。

  16. 在语句 TYPES t_output TYPE c LENGTH 40. 中,将 c LENGTH 40 替换为 string

  17. 返回到全局类标签,然后导航到类 lcl_cargo_flight 的方法 get_description

  18. 在方法调用 cargo_flight->get_description( ) 中,将光标置于方法名称 get_description 上,然后按 F3。或者,您可以按住 Ctrl 键并单击方法名称。

  19. 使用属性 carrier_id 的使用位置清单查找填充该属性的语句。

  20. 将光标置于 carrier_id 上,然后从 eclipse 工具栏中选择获取使用位置清单。或者,您可以按 Ctrl + Shift + G

  21. 在编辑器下方的搜索视图中,双击以 carrier_id = 开头的行。

  22. 在语句 carrier_id = i_carrier_id. 中,将光标置于 i_carrier_id 上,然后按 F2 显示 i_carrier_id 的代码元素信息。

  23. 在代码元素信息中,单击 /dmo/carrier_id 导航到相应的代码元素信息。请注意, /dmo/carrier_id 的类型为 char(3)

  24. 相应重复此操作以分析 carrier_id 的技术类型。在此,您将找到数据类型 numc(4)

  25. 使用转换表达式 EXACT 测试类型转换是否导致信息丢失。激活代码并将其作为控制台应用执行。

  26. 在值分配中,使用 EXACT #( ... ) 将源数据对象 i_carrier_id 括起来,如下所示:

carrier_id = EXACT #( i_carrier_id ).
  1. Ctrl + F3 激活类。

  2. F9 以控制台应用身份执行类。

  3. 您应该会看到运行时错误 CONVT_NO_NUMBER

  4. 对分配 connection_id = i_connection_id 重复类型分析。

  5. 按上一步分析 connection_idi_connection_id 的技术类型。

  6. 使用转换表达式 EXACT 测试分配 connection_id = i_connection_id 中的类型转换是否导致信息丢失。

  7. 如下调整代码:

connection_id = EXACT #( i_connection_id ).
  1. 在这种情况下,语法检查已检测到无法进行无损分配。显示以下错误:"I_CONNECTION_ID" 的类型 "/DMO/CONNECTION_ID" 与 "ME->CONNECTION_ID" 的类型 "/DMO/CARRIER_ID" 不兼容

  2. 更正属性 carrier_idconnection_id 的类型并删除两个 EXACT 表达式。然后激活并重新测试代码。

  3. 在语句 carrier_id = EXACT #( i_carrier_id ). 中,将光标放在 carrier_id 上,然后按 F3 导航到该属性的定义。或者,您可以按住 Ctrl 键并单击属性名称。

  4. 在语句 DATA carrier_id TYPE /dmo/connection_id READ-ONLY. 中,将 /dmo/connection_id 替换为 /dmo/carrier_id

  5. 在语句 DATA connection_id TYPE /dmo/carrier_id READ-ONLY. 中,将 /dmo/carrier_id 替换为 /dmo/connection_id

  6. 然后,属性的声明应如下所示:

*  wrong:
*  DATA carrier_id    TYPE /dmo/connection_id    READ-ONLY.
*  DATA connection_id TYPE /dmo/carrier_id       READ-ONLY.
*  correct:
DATA carrier_id    TYPE /dmo/carrier_id       READ-ONLY.
DATA connection_id TYPE /dmo/connection_id    READ-ONLY.
  1. 通过移除之前添加的 EXACT 表达式修复语法警告类型... 的冗余转换

  2. Ctrl + F3 激活类。

  3. F9 以控制台应用身份执行类。