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

在此的第一个示例中,我们正在尝试将字符串的值分配给整数字段。由于字符串 12345 的值是有效整数,因此分配成功。第二个分配也成功。
试用 - 成功任务
创建实施接口 IF_OO_ADT_CLASSRUN 的新类。
将以下代码复制到 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 }| ).
按 Ctrl + F3 激活类。
在代码周围播放以熟悉主题。
未成功分配
值分配中的类型转换并不总是成功的。有时,源值与目标字段的类型不兼容。此处显示的两个分配都会导致异常,如果保持不变,则会导致运行时错误。

当我们尝试将字符串值 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。
试用 - 截断和舍入
创建实施接口 IF_OO_ADT_CLASSRUN 的新类。
将以下代码复制到 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 }| ).
按 Ctrl + F3 激活类。
在代码周围播放以熟悉主题。
分配的意外结果
有时,两种不同数据类型之间的分配结果似乎是意外的。例如,如果将日期字段分配给整数字段,则值为自 0001 年 1 月 1 日起的天数。同样,如果将时间字段指派给整数,则结果是自午夜起的秒数。

如果将字符字段或字符串分配给类型为 N 的变量,系统将放弃任何不是数字的字符,将字段中的剩余数字右对齐排列,并使用前导零填充剩余空格。
从技术角度来看,日期字段是字符字段。因此,您可以将值分配到与有效日期不对应的 ABAP 中的日期字段(请注意,当用户通过 UI 输入日期时无法执行此操作,因为 UI 层会验证日期)。
内嵌声明的陷阱

在算术表达式中使用内联声明时,系统会从表达式的右侧派生新变量的类型。由于数字文字 5 和 10 是整数,因此变量 result1 和 result2 也创建为整数。虽然乘法结果正确,但除法结果向上舍入为 1。
试用 - 意外的分配结果
创建实施接口 IF_OO_ADT_CLASSRUN 的新类。
将以下代码复制到 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 ).
按 Ctrl + F3 激活类。
在代码周围播放以熟悉主题。
强制类型的转换
观看此视频,了解如何显式和隐式强制类型转换。
视频:本节含 SAP 官方视频(视频 ID
1_8nsh3go1),需在 learning.sap.com 在线观看。
试用 - 强制类型的转换
创建实施接口 IF_OO_ADT_CLASSRUN 的新类。
将以下代码复制到 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 ).
按 Ctrl + F3 激活类。
在代码周围播放以熟悉主题。
防止截断和舍入
观看此视频,了解如何防止截断和舍入。
视频:本节含 SAP 官方视频(视频 ID
1_08pt2l0w),需在 learning.sap.com 在线观看。
试用 - 防止截断和舍入
创建实施接口 IF_OO_ADT_CLASSRUN 的新类。
将以下代码复制到 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.
按 Ctrl + F3 激活类。
在代码周围播放以熟悉主题。
避免有问题的类型转换
仔细查看 ABAP 类的控制台输出,并注意到承运商名称已被截断。您还会注意到,为货物航班显示的承运商标识和转机标识存在问题。识别有问题的类型转换作为这些问题的源,并相应地更正代码。
模板:
/LRN/CL_S4D401_ATS_SQL_TRACE(全局类)
解决方案:
/LRN/CL_S4D401_TCS_TYPE_CONV(全局类)
任务 1: 复制模板(可选)
复制模板类 /LRN/CL_S4D401_ATS_SQL_TRACE。如果已完成上一练习,则可以跳过此任务并继续编辑类 ZCL_##_SOLUTION。
步骤
将类
/LRN/CL_S4D401_ATS_SQL_TRACE复制到您自己的包中的类(建议名称:ZCL_##_SOLUTION,其中 ## 代表组编号)。在项目浏览器中,右键单击类
/LRN/CL_S4D401_ATS_SQL_TRACE以打开上下文菜单。从上下文菜单中,选择复制....
在包字段中输入包的名称。在名称字段中,输入
ZCL_##_SOLUTION,其中 ## 代表组编号。选择下一步。
确认传输请求并选择完成。
激活复制。
按
Ctrl + F3激活类。
任务 2: 检测问题类型转换
以控制台应用形式执行类。检查控制台输出,尤其要注意货物航班的承运商名称、承运商标识和转机标识。查找产生此输出的代码并将数据追溯到源 - 请注意潜在的问题类型转换。使用 EXACT 表达式验证是否存在数据丢失的类型转换。
步骤
执行 ABAP 类
ZCL_##_SOLUTION作为控制台应用。在编辑器中打开类,然后按
F9。检查控制台输出。
在控制台视图中向下滚动。
找到以*承运商名称:*开头的行。
进一步向下滚动到以 找到合适的货物航班 开头的行并检查下一行。
返回到 ABAP 类的源代码并分析方法
if_oo_adt_classrun~main的实施。导航到方法
get_output。在方法调用
carrier->get_output( )中,将光标置于方法名称get_output上,然后按F3。或者,您可以按住Ctrl键并单击方法名称。在表达式
me->name中,将光标置于**名称上,然后按F2显示属性名称** 的代码元素信息。将光标置于
r_result上,然后按F2显示 get_output 方法的返回参数r_result的代码元素信息。在代码元素信息中,单击 tt_output 以显示此类型的代码元素信息。
单击 t_output 导航到 tt_output 表类型的行类型的代码元素信息。
更改参数
r_result的行类型。使用可变长度的字符型行类型。从元素信息底部的工具栏中,选择在编辑器中打开。或者,您可以按
F3导航到类型t_output的定义。在语句
TYPES t_output TYPE c LENGTH 40.中,将c LENGTH 40替换为string。返回到全局类标签,然后导航到类
lcl_cargo_flight的方法get_description。在方法调用
cargo_flight->get_description( )中,将光标置于方法名称get_description上,然后按F3。或者,您可以按住Ctrl键并单击方法名称。使用属性
carrier_id的使用位置清单查找填充该属性的语句。将光标置于
carrier_id上,然后从 eclipse 工具栏中选择获取使用位置清单。或者,您可以按Ctrl + Shift + G。在编辑器下方的搜索视图中,双击以 carrier_id = 开头的行。
在语句
carrier_id = i_carrier_id.中,将光标置于i_carrier_id上,然后按F2显示i_carrier_id的代码元素信息。在代码元素信息中,单击
/dmo/carrier_id导航到相应的代码元素信息。请注意,/dmo/carrier_id的类型为 char(3)。相应重复此操作以分析
carrier_id的技术类型。在此,您将找到数据类型 numc(4) 。使用转换表达式 EXACT 测试类型转换是否导致信息丢失。激活代码并将其作为控制台应用执行。
在值分配中,使用
EXACT #( ... )将源数据对象i_carrier_id括起来,如下所示:
carrier_id = EXACT #( i_carrier_id ).
按
Ctrl + F3激活类。按
F9以控制台应用身份执行类。您应该会看到运行时错误
CONVT_NO_NUMBER。对分配
connection_id = i_connection_id重复类型分析。按上一步分析
connection_id和i_connection_id的技术类型。使用转换表达式 EXACT 测试分配
connection_id = i_connection_id中的类型转换是否导致信息丢失。如下调整代码:
connection_id = EXACT #( i_connection_id ).
在这种情况下,语法检查已检测到无法进行无损分配。显示以下错误:"I_CONNECTION_ID" 的类型 "/DMO/CONNECTION_ID" 与 "ME->CONNECTION_ID" 的类型 "/DMO/CARRIER_ID" 不兼容。
更正属性
carrier_id和connection_id的类型并删除两个 EXACT 表达式。然后激活并重新测试代码。在语句
carrier_id = EXACT #( i_carrier_id ).中,将光标放在carrier_id上,然后按F3导航到该属性的定义。或者,您可以按住Ctrl键并单击属性名称。在语句
DATA carrier_id TYPE /dmo/connection_id READ-ONLY.中,将/dmo/connection_id替换为/dmo/carrier_id。在语句
DATA connection_id TYPE /dmo/carrier_id READ-ONLY.中,将/dmo/carrier_id替换为/dmo/connection_id。然后,属性的声明应如下所示:
* 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.
通过移除之前添加的 EXACT 表达式修复语法警告类型... 的冗余转换。
按
Ctrl + F3激活类。按
F9以控制台应用身份执行类。