ABAP - Type Casting
Wie kommst du eigentlich an den ursprünglichen Typ einer Klasse bzw. Instanz, wenn diese in einer generischen Tabelle übergeben wird? In diesem Artikel prüfen wir die Möglichkeiten.
Inhaltsverzeichnis
Bei der Übergabe einer generischen Tabelle mit vielen Instanzen, möchte man in manchen Situationen vielleicht auch weitere Informationen aus dem Objekt erhalten. In solchen Fällen muss man einen CAST durchführen, um den Typ und damit die Möglichkeiten des Zugriffs zu ändern.
Einleitung
In der objektorientierten Entwicklung arbeitet man oft mit Objekten, die von einem Interface erben, aber dann auch wieder spezifische Informationen enthalten. Solche Objekte werden meist in Tabellen mit "unspezifischem" Typ verwaltet, damit alle Arten von Objekten gespeichert werden können und damit man nicht für jedes Objekt eine eigene Tabelle benötigt. In diesem Beispiel gehen wir auf das neue Application Log ein und versuchen bei den Meldungen an die verschiedenen Inhalte zu gelangen.
Vorbereitung
Im ersten Schritt legen wir ein Log an, dazu verwenden wir das Beispiel aus dem Artikel für das neue Application Log, um ein Message Handle zu erzeugen. Dieses können wir nun lesen, um an die Nachrichten zu gelangen.
DATA(lt_messages) = cl_bali_log_db=>get_instance( )->load_log( c_handle )->get_all_items( ).
Nachdem wir die Nachrichten gelesen haben, erhalten wir die Tabelle LT_MESSAGES mit verschiedenen Referenzen auf unterschiedliche Objekte:
Interface finden
Bevor wir mit der Verarbeitung beginnen können, sollten wir das richtige Interface finden. Sobald wir ein Objekt haben, müssen wir diese nicht erneut im passenden Typ erzeugen, sondern wir müssen lediglich einen Cast auf den entsprechenden Typen durchführen. In den meisten Fällen verwenden wir dazu das Interface, da es die öffentliche Darstellung der Klasse nach außen ist, mit allen Methoden und Attributen, die wir verwenden können. Schauen wir uns dazu einmal die Klasse CL_BALI_MESSAGE_GETTER genauer an:
Die Klasse besitzt zwei Interfaces, welche verschiedene Attribute und Methoden zur Verfügung stellt. In der Klasse steht uns aber die Gesamtheit beider Interfaces zur Verfügung. Das Interface IF_BALI_ITEM_GETTER scheint das generische Interface zu sein, da es allgemeine Informationen zum Logitem zur Verfügung stellt, wie SEVERITY, TIMESTAMP oder die Methode GET_MESSAGE_TEXT.
Schauen wir uns nun das Interface IF_BALI_MESSAGE_GETTER an, dann sehen wir hier spezifische Attribute, die eine T100 Nachricht beschreiben, wie die Message Variablen oder die Message ID.
Das heißt für die folgende Verarbeitung, benötigen wir die spezifischen Interfaces, um an die abhängigen Informationen der Meldungen zu kommen und nicht nur den "Text" zu erhalten.
Variante 0 - Klassisch
Die klassische Variante gibt es in verschiedenen Ausprägungen, du könntest versuchen über die Typbeschreibung (RTTI) an die Informationen zur Ausprägung zu kommen oder du könntest einen CAST auf den entsprechenden Typen versuchen. In diesem Beispiel versuchen wir es erst einmal mit einem einfachen CAST, verwenden dabei aber nicht die alte Variante "?=" der Zuweisung.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
Da wir hier mit mehreren Typen arbeiten, kann es auch passieren, dass der Cast nicht funktioniert, ist dies der Fall wird eine Exception vom Typ CX_SY_MOVE_CAST_ERROR erzeugt. Diese Ausnahme sollten wir abfangen und verwenden dazu den TRY/CATCH.
TRY.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
CATCH cx_sy_move_cast_error.
ENDTRY.
Hat dies funktioniert, können wir den Schleifendurchlauf mit CONTINUE beenden, da ein weiterer Cast keinen Sinn macht. Dies wiederholen wir entsprechend für die anderen Typen, um so an die spezifischen Informationen zu kommen. der finale Loop könnte damit so aussehen:
LOOP AT it_messages INTO DATA(ls_message).
TRY.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
ENDLOOP.
Variante 1 - IS INSTANCE
Einen etwas einfacheren Weg und ohne Exception Handling, bietet die Variante mit IS INSTANCE. Dabei können wir in einer IF Anweisung abfragen, ob das Objekt einer entsprechenden Klasse oder Interface zugeordnet werden kann. Ist dies der Fall können wir einen Cast durchführen.
IF ls_message-item IS INSTANCE OF if_bali_message_getter.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ENDIF.
Die Abfragen können wir Verketten und sparen damit die Fehlerbehandlung, sowie das Continue, die Verarbeitung sieht zum Abschluss einmal viel aufgeräumter aus:
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
IF ls_message-item IS INSTANCE OF if_bali_message_getter.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_free_text_getter.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_exception_getter.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDIF.
ENDLOOP.
Variante 2 - CASE TYPE
Im letzten Beispiel einmal die bisher einfachste Variante, da sie leicht zu überblicken ist und unnötigen Code erspart, dabei verwenden wir die CASE TYP Anweisung. Diese Anweisung ist eine Erweiterung des klassischen CASE und kann auch Typen erzeugen. Im WHEN fragen wir das entsprechende Interface ab und weisen es ein spezifischen Variable zu.
CASE TYPE OF ls_message-item.
WHEN TYPE if_bali_message_getter INTO DATA(lo_message).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ENDCASE.
Nun rufen wir die Konvertierung in der Schleife auf und erhalten das gleiche Ergebnis, wie bei den anderen Varianten. Diese Variante ist noch einmal kürzer und etwas übersichtlicher. Du wirst auch keinen CAST mehr finden, da dieser durch den CASE TYPE übernommen wird.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
CASE TYPE OF ls_message-item.
WHEN TYPE if_bali_message_getter INTO DATA(lo_message).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
WHEN TYPE if_bali_free_text_getter INTO DATA(lo_free_text).
io_out->write( lo_free_text->get_message_text( ) ).
WHEN TYPE if_bali_exception_getter INTO DATA(lo_exception).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDCASE.
ENDLOOP.
Vollständiges Beispiel
Hier einmal die komplette Klasse aus den verschiedenen Beispielen. Das Handle Objekt musst du wahrscheinlich anpassen, wenn du sie bei dir laufen lassen möchtest, da auf deinem System eine eigene ID erzeugt wurde.
CLASS zcl_bs_demo_type_casting DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
CONSTANTS c_handle TYPE if_bali_log_db=>ty_handle VALUE '<YOUR_HANDLE>'.
METHODS classic_casting
IMPORTING it_messages TYPE if_bali_log=>ty_item_table
io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS if_casting
IMPORTING it_messages TYPE if_bali_log=>ty_item_table
io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS case_casting
IMPORTING it_messages TYPE if_bali_log=>ty_item_table
io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_type_casting IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_messages) = cl_bali_log_db=>get_instance( )->load_log( c_handle )->get_all_items( ).
out->write( `--> CLASSIC` ).
classic_casting( it_messages = lt_messages
io_out = out ).
out->write( |-| ).
out->write( `--> IS INSTANCE` ).
if_casting( it_messages = lt_messages
io_out = out ).
out->write( |-| ).
out->write( `--> CASE TYPE` ).
case_casting( it_messages = lt_messages
io_out = out ).
ENDMETHOD.
METHOD classic_casting.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
TRY.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD if_casting.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
IF ls_message-item IS INSTANCE OF if_bali_message_getter.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_free_text_getter.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_exception_getter.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD case_casting.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
CASE TYPE OF ls_message-item.
WHEN TYPE if_bali_message_getter INTO DATA(lo_message).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
WHEN TYPE if_bali_free_text_getter INTO DATA(lo_free_text).
io_out->write( lo_free_text->get_message_text( ) ).
WHEN TYPE if_bali_exception_getter INTO DATA(lo_exception).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDCASE.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Verfügbarkeit
Ab welchem Release sind die neuen Statements verfügbar, dazu findest du die entsprechenden Informationen in der ABAP Feature Matrix (AFM):
- IS INSTANCE OF - S/4 HANA 1511 (7.50)
- CASE TYPE OF - S/4 HANA 1511 (7.50)
Fazit
In der objektorientierten ABAP Welt gehört das Casting von Objekten zum Standard, um an die nötigen Informationen in der Verarbeitung zu kommen und Objekte generisch in deiner Tabelle zu verwalten. Wir hoffen das du mit der Hilfe des Artikels dies nun besser beherrschst.