Cheat Sheet Modernes ABAP
Dieses Plakat ist die perfekte Ergänzung zu den Schulungen
von Brandeis Consulting
Die Sprache ABAP hat sich seit NetWeaver ABAP 7.40 enorm weiterentwickelt. Viele neue Sprachkonstrukte sind hinzugekommen. Vor allem natürlich viele neue Ausdrücke. Damit kann man sauberen Code schreiben und sich viele Hilfsvariablen und Deklarationen sparen. Die neuen Sprachelemente sollten genutzt werden, um den Code robuster und lesbarer zu machen. Eine schnellere Ausführung ist jedoch nicht zu erwarten.
Dieses Plakat zeigt die wichtigsten und nützlichsten Neuerungen. Ausgelassen wurden die neue ABAP-SQL-Syntax und die ABAP Built-In Functions.
Inline-Deklarationen
Mit Inline-Deklarationen kann eine Variable dort definiert werden, wo sie zum ersten Mal verwendet wird, d.h. wo ihr zum ersten Mal ein Wert zugewiesen wird. An vielen Stellen im ABAP-Code ergibt sich der Datentyp einer Variablen aus dem Kontext.
Mit DATA(<variablenname>)
kann eine Variable dort definiert werden, wo sie benötigt wird.
Modernes ABAP
LOOP AT ResultTab INTO DATA(ResultLine).
...
ENDLOOP.
Klassisches ABAP
DATA ResultLine LIKE LINE OF ResultTab.
LOOP AT ResultTab INTO ResultLine.
...
ENDLOOP.
Die Vorteile der Inline-Deklaration sind neben dem kompakteren Code auch die Flexibilität. Wenn sich im Beispiel die Feldliste ändert, wird der Datentyp von RESULT
automatisch angepasst. Gerade bei JOIN
Operationen hat man oft auch keine passende Strukturdefinition.
Modernes ABAP
SELECT *
FROM zbc_users
INTO TABLE @DATA(Result).
Klassisches ABAP
DATA Result TYPE TABLE OF zbc_users.
SELECT *
FROM zbc_users
INTO TABLE Result.
Auch Feldsymbole können mit Inline-Deklaration einem existierenden Speicherbereich zugewiesen werden. Entweder mit einem ASSIGN
oder mit dem den passenden Anweisungen für den Zugriff auf interne Tabellen wie zum Beispiel
LOOP AT ... ASSIGNING FIELD-SYMOBL(<fs-name>).
oder
READ TABLE ... ASSIGNING FIELD-SYMBOL(<fs-name>) ...
Verkettungsoperator &&
Zwei Zeichenketten können mit dem Operator &&
verkettet werden. Dies ist wesentlich eleganter als mit CONCATENATE
und funktioniert auch an Operandenpositionen ohne Hilfsvariable.
Modernes ABAP
out->write( `Es ist das Jahr `
&& sy-datum(4) ).
Klassisches ABAP
DATA tmp TYPE c LENGTH 100.
CONCATENATE `Es ist das Jahr `
sy-datum(4) INTO tmp.
out->write( tmp ).
Berechnungszuweisung mit &&=
Beim Zusammensetzen von Zeichenketten ist es oft notwendig, 'Anhängen' zu verwenden. Zum Beispiel bei der Generierung von HTML.
Modernes ABAP
html &&= `<b>Hallo</b>`
Klassisches ABAP
CONCATENATE html `<b>Hallo</b>`
INTO html.
String-Templates
String-Templates sind Ausdrücke, die eine Zeichenkette erzeugen. Sie bestehen aus Literalen, die eingebettete Ausdrücke, Formatierungen und Steuerzeichen enthalten können. Ein String-Template beginnt und endet mit einem senkrechten Strich |
. Alles dazwischen ist konstanter Text (=Literal), außer er ist ein von geschweiften Klammern { ... }
umgebener, eingebetter Ausdruck.
Die Zeichen {
, }
, |
und \
müssen im Literal durch einen Backslash \
escaped werden.
Modernes ABAP
out->write( |Heute: {
sy-datum DATE = USER }| ).
Klassisches ABAP
DATA tmp TYPE string.
DATA datum TYPE c LENGTH 10.
WRITE sy-datum TO datum.
CONCATENATE `Heute: ` datum INTO tmp.
out->write( tmp ).
Ausdrücke in String Templates
In geschweiften Klammern { ... }
können Ausdrücke direkt in das String-Template eingebettet werden. Damit kann z.B. eine funktionale Methode aufgerufen oder eine Berechnung durchgeführt werden. Schöne Beispiele dafür sind bei dem Konstruktor-Ausdrücken COND
und SWITCH
.
Formatierung
Die Darstellung der Ausdrücke kann über die Formatierungsoptionen angepasst werden. Die wichtigsten sind
WIDTH = <Länge>
- Breite in ZeichenALIGN = <Ausrichtung>
- EntwederLEFT
,RIGHT
oderCENTER
DATE = USER
- Formatierung des Datums gemäß Benutzerstammsatz, siehe obenTIME = USER
- Dasselbe für die ZeitALPHA = IN/OUT
- Alphakonvertierung
Steuerzeichen in String Templates
Die Steuerzeichen
\n
- Zeilenvorschub/Line Feed\r
- Wagenrücklauf/Return und\t
- Tabulator
können direkt in String Templates verwendet werden. Damit können also ohne weiteres auch mehrzeilige Texte ohne die Verwendung von CL_ABAP_CHAR_UTILS=>CR_LF
erzeugt werden:
out->write( |Hallo\r\nWelt| ).
Aufzählungstypen mit Enumerations
Mit den Enumerations haben wir typsichere Aufzählungstypen. Das bedeutet, dass mit der Enumeration auch ein Datentyp definiert wird. Und es wird zur Designzeit geprüft wird, ob der Datentyp bei Zuweisungen und Methodenaufrufen korrekt ist. Somit können nur die Werte verwendet werden, die definiert wurden.
Definition einer Aufzählung
TYPES: BEGIN OF ENUM eColour,
Red,
...
Orange,
Violet,
END OF ENUM eColour.
Verwendung der Aufzählung
DATA MyColour TYPE eColor.
" MyColour = 0. "Gibt einen Fehler
MyColour = Red .
Gruppierung in einer Struktur
Da manchmal viele konstante Werte definiert werden müssen, ist es möglich, die einzelnen Komponenten einer Enumeration in einer Struktur zusammenzufassen. Dies erleichtert das Auffinden der richtigen Werte. Der Zugriff erfolgt wie bei einer konstanten Struktur:
MyColour = Colour-Red
Definition mit Struktur
TYPES:
BEGIN OF ENUM eColour STRUCTURE Colour,
Red,
...
Orange,
Violet,
END OF ENUM eColour STRUCTURE Colour.
Feste Werte und andere Datentypen
Normalerweise wird intern der Datentyp I
verwendet und die Werte werden von 0 bis N zugewiesen. Dies kann aber auch manuell geändert werden. Der Datentyp kann bis zu 8 Zeichen lang sein. Wichtig ist auch, dass für eine Konstante immer der Wert IS INITIAL
verwendet wird.
TYPES ColourDef TYPE c LENGTH 7.
TYPES: BEGIN OF ENUM eColour
STRUCTURE Colour
BASE TYPE ColourDef,
Black VALUE IS INITIAL,
Red VALUE '#ff0000',
Green VALUE '#00ff00',
Blue VALUE '#0000ff',
END OF ENUM eColour
STRUCTURE Colour.
Wert und Enum-Typ
Da eine direkte Wertzuweisung nicht möglich ist bzw. in einer Richtung zum falschen Ergebnis führt, ist für die Speicherung der Werte in der Datenbank (bzw. für alle Schnittstellen) immer eine Konvertierung mit dem Konstruktor-Operator CONV
erforderlich:
DATA MyColour TYPE eColour.
DATA ColourHex TYPE ColourDef.
"Falscher Inhalt 'GREEN':
ColourHex = Colour-green.
"Richtiger Inhalt '#00ff00' :
ColourHex = conv #( Colour-Green ).
"Syntaxfehler:
" MyColour = '#00ff00'.
"Korrekte Zuweisung:
MyColour = conv #( '#00ff00' ).
Konstruktor-Ausdrücke
Konstruktor-Ausdrücke erzeugen ein neues (Daten-)Objekt eines bestimmten (Daten-)Typs. Dazu werden sogenannte Konstruktor-Operatoren verwendet. Diese decken eine Vielzahl sehr unterschiedlicher Anwendungsfälle ab. Gemeinsam ist ihnen jedoch immer folgende Syntax:
<Konstruktor-Operator> <Datentyp>( <Parameter> ).
Ein einfaches Beispiel: Mit dem Operator VALUE
eine interne Tabelle mit festem Inhalt erzeugen.
DATA tt_tadir TYPE STANDARD TABLE OF tadir.
DATA ObjectCatalog TYPE tt_tadir.
ObjectCatalog = VALUE tt_tadir( ( obj_name = 'ZCL_CS' object = 'CLAS' )
( obj_name = 'MARA' object = 'TABL' ) ).
Der Datentyp und das Hash-Zeichen #
Je nach Situation wird der passende Datentyp angegeben, z.B. ein Datenelement, ein Tabellentyp, ein Strukturtyp oder ein Klassenname.
Wenn der Datentyp aus dem Kontext heraus abgeleitet werden kann, dann muss er nicht mehr angegeben werden. Stattdessen wird das #
Zeichen verwendet. Das ist z.B. bei einer Zuweisung der Fall.
Lesbarkeit von Konstruktor-Ausdrücken
Einige Operatoren können sehr komplexe Logik ausführen. Dazu können Konstruktor-Ausdrücke auch ineinander verschachtelt werden. Der Code wird dabei schnell unübersichtlich. Es empfiehlt sich daher, nur einfache Logik in Konstruktor-Ausdrücken zu berechnen. Bei komplexer Logik ist klassisches ABAP oft besser lesbar. In den ADTs gibt es keine Möglichkeit, Konstruktor-Ausdrücke zu debuggen.
Daten und Objekte erzeugen mit VALUE
und NEW
Die beiden Operatoren erzeugen Daten. Mit VALUE
erhält man die Daten direkt, mit NEW
die Referenz darauf. Wenn keine Parameter übergeben werden, erzeugen beide Operatoren leere Datenobjekte.
Strukturen erzeugen
Wenn Strukturen erzeugt werden, kann man die einzelnen Komponenten als Parameter mitgeben. Nicht zugewiesene Komponenten werden mit ihrem Initialwert belegt.
DATA(line) = VALUE tadir( obj_name = 'ZCL_CS' object = 'CLAS' ).
Interne Tabellen erzeugen
Die einzelnen Zeilen einer internen Tabelle werden wiederum in runde Klammern eingeschlossen.
DATA DateRange TYPE RANGE OF dats.
DateRange = VALUE #( ( sign = 'I' option = 'EQ' low = '20221031' )
( sign = 'I' option = 'EQ' low = '20220406' ) ).
Gemeinsame Bestandteile der einzelnen Zeilen können auch vor den runden Klammern definiert werden:
DateRange = VALUE #( sign = 'I' option = 'EQ' ( low = '20221031' )
( low = '20230406' ) ).
BASE
gibt einen Initialwert vor
Bei Strukturen kann vor der ersten Komponente mit BASE
eine kompatible Struktur angegeben werden, die die nicht zugewiesenen Komponenten mit Werten füllt.
Bei internen Tabellen kann mit BASE
eine interne Tabelle mitgegeben werden, die dann durch die folgenden Zeilen ergänzt wird. Das entspricht also einem APPEND
.
NewDateRange = VALUE #( BASE DateRange
( sign = 'I' option = 'EQ' low = '20230101' ) ).
Im VALUE
-Operator sind auch FOR
-Schleifen möglich. Diese sind beim REDUCE
-Operator beschrieben.
Objekte erzeugen mit NEW
Mit NEW
können auch Instanzen von Klassen erzeugt werden:
NEW <Klassenname>( <Konstruktorparameter> )
Datentypänderung mit CONV
Dieser Operator wird verwendet, um einfach Datentypen zu konvertieren. Dies ist z.B. bei Methodenparametern ein häufiges Problem: Der Inhalt einer Variablen passt, der Datentyp aber nicht.
Modernes ABAP
my_method( TEXT = CONV #( sy-datum ) ).
Klassisches ABAP
DATA DateText TYPE char8.
DateText = sy-datum.
my_method( Text = DateText )
Weitere Beispiel finden Sie bei den Enumerations.
Down- und Upcast von Referenzvariablen mit CAST
Im klassischen ABAP erfolgt der Upcast durch einfache Zuweisung mit =
, der Downcast mit dem Cast-Operator ?=
. Dafür wird immer eine Hilfsvariable vom passenden Typ benötigt. Mit dem CAST
-Operator kann diese in vielen Fällen entfallen:
Modernes ABAP
DATA Task TYPE zbc_tasks.
DATA(Components) = cast cl_abap_structdescr(
cl_abap_typedescr=>describe_by_data(
Task ) )->get_components( ).
Klassisches ABAP
DATA Task TYPE zbc_tasks.
DATA StructDescr TYPE REF TO cl_abap_structdescr.
DATA(TypeDescr) = cl_abap_typedescr=>describe_by_data( Task ).
StructDescr ?= TypeDescr.
DATA(Components) = StructDescr->get_components( ).
Fallunterscheidungen mit COND
und SWITCH
Diese Konstruktor-Ausdrücke entsprechen im SQL dem CASE
Ausdruck. Einfache Fallunterscheidungen in Abhängigkeit von einem Feld werden mit SWITCH
implementiert:
Modernes ABAP
out->write(
|Hallo { SWITCH #( User-Gender
WHEN 'F' THEN 'Frau'
WHEN 'M' THEN 'Herr'
ELSE '' )
} { User-Lastname } | )..
Klassisches ABAP
DATA Salutation TYPE string.
CASE User-Gender.
WHEN 'F'. Salutation = 'Frau'.
WHEN 'M'. Salutation = 'Herr'.
WHEN OTHERS. Salutation = ''.
ENDCASE.
out->write( |Hallo { Salutation
} { User-Lastname }| ).
Komplexere Unterscheidungen mit beliebigen Bedingungen erfolgen mit COND
:
out->write( |Der Status ist { COND #( WHEN Priority > 3
AND DueDate <= SY-DATUM THEN 'Critical'
WHEN Priority > 2 THEN 'Medium'
ELSE 'Low' ) }|.
Mit REDUCE
einen Wert aus einer internen Tabelle berechnen
Dieser Operator berechnet durch Schleifen über interne Tabellen einen einzelnen Ergebniswert. Auf komplexe Varianten wurde bewusst verzichtet, da dieser Operator schnell die Lesbarkeit einschränkt.
out->write( REDUCE string( INIT res TYPE string
sep TYPE string
FOR User IN Users
NEXT
res &&= sep && User-Firstname
sep = ', ' ) ).
Interne Tabellen mit dem FILTER
-Operator erzeugen
Mit dem FILTER
-Operator können interne Tabellen auf Basis einer anderen internen Tabelle durch Filterung erzeugt werden. Entweder über eine einfache WHERE
-Klausel oder anhand einer anderen Tabelle. Grundsätzlich muss der Tabellentyp und -Schlüssel für die Filterung optimiert sein, sonst gibt es Syntaxfehler.
FILTER
mit WHERE
-Klausel
DATA lt_data TYPE sorted TABLE OF I_CountryText WITH UNIQUE KEY LANGUAGE COUNTRY.
SELECT * FROM i_countrytext INTO TABLE @lt_data.
out->WRITE( FILTER #( lt_data WHERE LANGUAGE = 'D' ) ).
FILTER
mit IN ... WHERE
Hier wird nach einer anderen internen Tabelle gefiltert. Dies entspricht einem INNER JOIN
in SQL.
DATA lt_data TYPE sorted TABLE OF I_CountryText WITH UNIQUE KEY COUNTRY.
DATA(lt_filter) = VALUE tt_demo( ( COUNTRY = 'DE' )
( COUNTRY = 'US' ) ).
SELECT * FROM i_countrytext WHERE LANGUAGE = 'D' INTO TABLE @lt_data.
out->WRITE( FILTER #( lt_data IN lt_filter WHERE COUNTRY = COUNTRY ) ).
Der Operator CORRESPONDING
Dieser Operator erinnert an die Anweisung MOVE-CORRESPONDING
. Er kann sowohl für Strukturen als auch für interne Tabellen verwendet werden.
Mit dem CORRESPONDING
-Operator kann man aus einem strukturierten Datenobjekt, also einer internen Tabellen oder einer Struktur, ein anderes Datenobjekt erzeugen und dabei die Werte gleichlautender Felder übernehmen. Das ähnelt der Anweisung MOVE-CORRESPONDING
.
Grundform von CORRESPONDING
Die Daten werden in gleichlautende Felder kopiert. Felder, die in der Quelle nicht vorhanden sind, bleiben im Ziel leer.
TasksSmall = corresponding #( TasksOriginal ).
Explizites MAPPING
und EXCEPT
Wenn die Komponenten nicht exakt den gleichen Namen haben und trotzdem aufeinander kopiert werden sollen oder wenn einzelne Komponenten eben nicht kopiert werden sollen, so kann dies explizit mit den Zusätzen MAPPING
und EXCEPT
angegeben werden:
TaskSmall = corresponding #( TaskOriginal
MAPPING id = task_id
title = summary
EXCEPT assignee ).
Mit BASE
werden Initialwerte vorgegebenen, siehe VALUE
-Operator. Damit werden bei Strukturen die Werte vorgefüllt oder bei internen Tabellen zusätzliche Zeilen an den Anfgang gesetzt.
CORRESPONDING
mit Lookup Tabelle
Diese Variante führt ein Lookup auf eine andere interne Tabelle durch. Das entspricht einem LEFT OUTER JOIN
mit einer :1 Kardinalität. Wie beim FILTER
-Operator funktioniert dies nur dann, wenn der Tabellentyp und die Schlüsseldefinition zu der Join-Bedingung in der USING
-Klausel passt.
DATA Lookup TYPE HASHED TABLE OF I_CountryText WITH UNIQUE KEY Country.
DATA(Original) = VALUE tt_demo( ( Country = 'DE' )
( Country = 'US' ) ).
SELECT * FROM I_CountryText WHERE LANGUAGE = 'D' INTO TABLE @Lookup.
DATA(Result) = CORRESPONDING tt_demo( Original FROM Lookup
USING Country = Country
MAPPING country_text = CountryName ).
Prädikativer Methodenaufruf
Hinter dem sperrigen Begriff Prädikativer Methodenaufruf verbirgt sich ein einfaches Konzept, das in (fast) allen anderen Programmiersprachen Standard ist: Ein Methodenaufruf als Prädikat, z.B. direkt in einer IF
Anweisung. Da im ABAP die Werte TRUE
und FALSE
nicht eindeutig definiert sind, gilt die folgende Definition:
Wenn der Rückgabewert einer funktionalen Methode (d.h. mit RETURNING
) initial ist, ist das Prädikat logisch FALSE
, ansonsten TRUE
. Es entspricht also IF Methode( ) IS NOT INITIAL
.
Modernes ABAP
IF isRelevant( ).
...
ENDIF.
Klassisches ABAP
IF isRelevant( ) EQ abap_true.
...
ENDIF.
Zugriff auf Tabellenzeilen mit Tabellenausdrücken
Auch wenn der Name etwas anderes vermuten lässt, so liefern uns Tabellenausdrücke Zeilen einer Tabelle. In SQL würde man sie daher Zeilenausdrücke nennen.
Sie sind keine Kopien der Zeile, sondern die Zeile in der Tabelle. Deshalb verändern Schreiboperationen auch die Tabelle. Sie ersetzen also READ ... INTO
und READ ... ASSIGNING
Anweisungen.
Aufbau von Tabellenausdrücken: <Tabelle>[ <Zeilenspezifikation> ]
Die Tabelle kann dabei eine beliebige interne Tabelle sein. Die Zeilenspezifikation steht in eckigen Klammern und kann auf verschiedene Arten erfolgen: Über die Angabe der Zeilennummer bei Standardtabellen, eines freien Schlüssels oder des Tabellenschlüssels.
Modernes ABAP
* Kopie der ersten Zeile
DATA(row) = users[ 1 ].
* Ändern des Vornamens 1. Zeile
users[ 1 ]-firstname = 'Edgar'.
* Ändern von Peters Nachname
users[ firstname = 'Peter'
]-lastname = 'Pan'.
Klassisches ABAP
DATA row LIKE LINE OF users.
READ TABLE users INTO row INDEX 1.
FIELD-SYMBOLS <row> LIKE LINE OF users.
READ TABLE users ASSIGNING <row> INDEX 1.
<row>-firstname = 'Edgar'.
FIELD-SYMBOLS <row> LIKE LINE OF users.
READ TABLE users ASSIGNING <row>
WITH firstname = 'Peter'.
<row>-lastname = 'Pan'.
Ein Zugriff auf nicht existierende Zeilen erzeugt eine Ausnahme CX_SY_ITAB_LINE_NOT_FOUND
.
Modernes ABAP
* Ändern von Peters Nachname
TRY.
users[ firstname = 'Peter'
]-lastname ='Pan'.
CATCH CX_SY_ITAB_LINE_NOT_FOUND.
ENDTRY.
Klassisches ABAP
* Ändern von Peters Nachname
FIELD-SYMBOLS <row> LIKE LINE OF users.
READ TABLE users ASSIGNING <row>
WITH firstname = 'Peter'.
IF sy-subrc = 0.
<row>-lastname = 'Pan'.
ENDIF.
Bei lesendem Zugriff kann alternativ auch mit dem VALUE
-Operator ein Defaultwert erzeugt werden oder der Zugriff als OPTIONAL
markiert werden.
...VALUE #( <Tabelle>[<Zeilenspezifikation>]
DEFAULT <Alternativer Wert> | OPTIONAL )
Neue Gruppenstufenverarbeitung
Die klassische Gruppenstufenverarbeitung mit AT ...
ist abhängig von der Sortierung der Daten und der Reihenfolge der Spalten. Beides muss exakt passen. Damit ist sie fehleranfällig und manchmal kaum nutzbar. Die neue Gruppenstufenverarbeitung löst dieses Problem durch verschachtelte LOOP
s.
Modernes ABAP
LOOP AT PlantMats
INTO DATA(Grouping)
GROUP BY ( Plant = Grouping-Plant )
INTO DATA(Grp).
" A
LOOP AT GROUP Grp
INTO DATA(PlantMat).
" B
ENDLOOP.
" C
ENDLOOP.
Klassisches ABAP
LOOP AT PlantMats
INTO DATA(PlantMat).
AT NEW Plant.
" A
ENDAT.
" B
AT END OF Plant.
" C
ENDAT.
ENDLOOP.
Brandeis Consulting
Massgeschneiderte Schulungen und Beratung von Buchautoren und SAP Champions! Auf unserer Website www.brandeis.de finden Sie unser Angebot!
(C) Brandeis Consulting GmbH