有朋友向我咨询:
jerry老师,我需要用se37开发一个函数,要求就是函数的入参是一个动态的内表,就是入参的内表结构不定,数据量也不定。这种函数的入参定义啊?我需要的效果就是在这个函数里面,对这个动态的入参内表进行一个LOOP循环,在循环里面进行数据变更保存。
![]()
在一般情况下,我们在 SE37 创建 Function Module 时,对于其输入参数的数据结构,都是确认已知的。但实际项目中也会遇到,需要编写一个通用的
Function Module,能处理具有各种各样行项目结构
(table line structure)的内表。
本文就来解答朋友提出的这个疑问,在 ABAP 里应该如何编写。
第一步也是最重要的一步。这个 Function Module 的输入参数,应该如何指定?
如下图所示,我定义一个名叫 IT_TABLE
的输入参数,I 代表 Input 即输入,T 代表 Table Type 即 Table 类型。这个是 ABAP 编程的命名规范。这个输入参数的名称也可以改成其他的,只要符合大家开发团队指定的规范即可。
最关键的一步,就是将这个输入参数的类型,指定成 ANY TABLE
.
![](https://img.haomeiwen.com/i2085791/1e1f39191d3feabe.png)
这是一个泛型,即 ABAP 帮助文档里所谓的 Generic Type,可以代表一切类型的 ABAP 内表。
下一步,我们要开始实现这个函数体了,这里要用到 ABAP RTTI 的理论知识。有过 Java 编程语言开发经验的朋友,可以把 ABAP RTTI 类比成 Java 的反射机制,二者虽然是不同的编程语言,但这两个名词实际上描述的都是同一种机制,即在运行时,通过编程语言提供的 API,来获取数据对象的各种信息。
ABAP RTTI 的全称是 Runtime Type Identification. 使用 RTTI,ABAP 开发人员可以在运行时获取关于数据对象的各种信息,包括其数据类型、字段列表(如果数据是Structure 或者内表的话)以及这些字段的技术属性,比如字段名称,字段数据类型,长度等等。对于这些字段信息,ABAP 提供了一组特殊的类和接口,包括 CL_ABAP_TYPEDESCR、CL_ABAP_STRUCTDESCR、CL_ABAP_TABLEDESCR 等等。
我们来动手实践一下。
这个函数的全部源代码:
FUNCTION zdynamic_table_input.
FIELD-SYMBOLS:<item> TYPE any,
<value> TYPE any.
DATA: lo_table_descr TYPE REF TO cl_abap_tabledescr,
lo_struc_descr TYPE REF TO cl_abap_structdescr,
lt_fields TYPE abap_compdescr_tab,
ls_field TYPE abap_compdescr,
lv_field_name TYPE abap_compname.
lo_table_descr ?= cl_abap_tabledescr=>describe_by_data( it_table ).
lo_struc_descr ?= lo_table_descr->get_table_line_type( ).
lt_fields = lo_struc_descr->components.
LOOP AT it_table ASSIGNING <item>.
LOOP AT lt_fields INTO ls_field.
ASSIGN COMPONENT ls_field-name OF STRUCTURE <item> TO <value>.
WRITE:/ 'field name:', ls_field-name, ' value: ', <value>.
ENDLOOP.
ENDLOOP.
ENDFUNCTION.
关键点有 4 处,我们逐一讲解。
- 代码第 17 行:
lo_table_descr ?= cl_abap_tabledescr=>describe_by_data( it_table ).
这里调用 API cl_abap_tabledescr=>describe_by_data
,将输入参数,即内表 it_table 的描述符取出来,存储到变量 lo_table_descr
中去。
何为描述符(descriptor)
?描述符是一个对象引用,封装了被描述的 ABAP 数据对象的所有技术类型信息。我们拿到了这个对象应用,就可以用其暴露的各种 get
开头的公有方法,拿到被描述的 ABAP 数据对象的各种信息。
注意 cl_abap_tabledescr=>describe_by_data
返回的描述符,类型是 cl_abap_typedescr
,它是所有 ABAP RTTI API 里各种其他描述符 cl_abap_XXXdescr
共同的父类
。在这一行代码里,因为我们很肯定,it_table 一定是个内表,因为它受 Function Module 输入参数类型 ANY TABLE
所约束,所以我们在代码里声明了一个类型为 cl_abap_tabledescr
的变量,lo_table_descr
, 去接收 cl_abap_tabledescr=>describe_by_data
的返回结果。
这里我们使用了一个类型转换符号 ?=
, 符号的左边是子类对象类型,符号的右边是父类对象类型。
![](https://img.haomeiwen.com/i2085791/148462ba558394f3.png)
-
代码第 19 行,拿到了内表的类型描述符之后,我们调用其
get_table_line_type
, 拿到内表行项目
的类型描述符,存储到类型为cl_abap_structdescr
的结构体类型描述符,lo_struc_descr
中去。 -
代码第 20 行,通过结构体类型描述符,我们就能在运行时,知道这个 structure 详细的字段信息了。
我们动手写个报表来测试一下,看运行时 structure 的字段信息长啥样。
REPORT zst.
TYPES: BEGIN OF ty_line,
name TYPE string,
age TYPE int4,
END OF ty_line.
TYPES: tt_table TYPE TABLE OF ty_line.
DATA: ls_line TYPE ty_line,
lt_table TYPE tt_table,
lt_tadir TYPE TABLE OF tadir.
ls_line-name = 'Jerry'.
ls_line-age = 41.
APPEND ls_line TO lt_table.
ls_line-name = 'Tom'.
ls_line-age = 50.
APPEND ls_line TO lt_table.
CALL FUNCTION 'ZDYNAMIC_TABLE_INPUT'
EXPORTING
it_table = lt_table.
上面这段代码,我自定义了一个 structure 类型,包含 name 和 age 两个字段。基于这个行项目结构我创建了一个内表,插入两条记录到内表里,然后调用我们还没有编写完毕的函数。
单步调试:
![](https://img.haomeiwen.com/i2085791/2bbe4edd1fa0f7a8.png)
从这个内表里,我们拿到了 Function Module 输入参数的内表行项目的字段名称列表:NAME 和 AGE.
![](https://img.haomeiwen.com/i2085791/dc84fc469b734e9d.png)
有了这些名称,剩下一步就很简单了:使用 ASSIGN COMPONENT <字段名称> OF STRUCTURE <item> TO <value>.
将内表行项目对应字段的实际值
,赋给 Field Symbol 变量。
因为在编写代码的时候,我们是不知道这些实际值的数据类型的,因此代码第 9 行定义的 Field Symbol,必须使用另一种泛型 any
,这里的语言是代表一切 ABAP 数据类型。
![](https://img.haomeiwen.com/i2085791/21846280f0e9adaf.png)
最后测试结果,可以把这个内表里两条记录的每个字段名称和字段值全部打印出来。
![](https://img.haomeiwen.com/i2085791/2e47ffbcf77af1ea.png)
为了验证这个 Function Module 是否真的支持任意内表类型,我们把输入内表变量更换成一个更复杂的结构,比如行项目为 TADIR 这张表对应的字段:
SELECT * INTO TABLE lt_tadir FROM tadir UP TO 2 ROWS .
CALL FUNCTION 'ZDYNAMIC_TABLE_INPUT'
EXPORTING
it_table = lt_tadir.
看看运行时传入 Function Module 的这个内表:
![](https://img.haomeiwen.com/i2085791/c30b45945fb2f89a.png)
最后的输出结果:
![](https://img.haomeiwen.com/i2085791/1edf449a8cecd015.png)
证明咱们这个函数编写成功了。
网友评论