Praktikum Grundlage der Programmierung - Woche 10 Yudhistira Arief Wibowo Materialien aus Folien von Adrian Stein und Julian Koch mit Edit - LRZ ...
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
Praktikum Grundlage der Programmierung Woche 10 Yudhistira Arief Wibowo Materialien aus Folien von Adrian Stein und Julian Koch mit Edit
Anmerkung Aus gegebenem Anlass, bitte niemals eure Lösung für die Hausaufgabe oder die Aufgabestellung veröffentlichen. Die ÜL (und auch Plagiat KI natürlich) können einfach googlen. Man darf sich freiwillig bei Michael Steipe melden, falls man sein Gewissen bereinigen will. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 2
Agenda • Additionen • Umlaut-Concat • Chat • Telefonbuch Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 3
Aufgabe 1 | Additionen • Exceptions • try-catch Syntax • catch im Speziellen • Java Throwable-Hierarchie • Unchecked und Checked Exceptions • Eigene Exceptions schreiben • Exceptions als Objekte • try-catch-finally Syntax • Good Practices Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 4
Aufgabe 1 | Additionen (Exceptions) Bei der Ausführung eines Programms kann es dazu kommen, dass ein Fehler oder unerwartetes Event passiert, welches den normalen Programmfluss stören und im schlimmsten Fall zum Absturz bringen kann. Solche Fehler können überall auftreten, auch bei sehr simplen Methoden. Sehen Sie sich das folgende Code-Beispiel an und überlegen Sie, welche Parameter die Ausführung des Programms gefährden könnten. public static int intDiv(int dividend, int divisor){ return dividend / divisor; } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 5
Aufgabe 1 | Addition (try-catch Syntax) Die try-catch-Syntax besteht aus einem try-Teil und mind. einem catch-Teil. • try: Hier steht der Code, der planmäßig ausgeführt werden soll. • catch: Hier steht der Code, der ausgeführt werden soll, wenn der try-Teil fehlschlägt. Der catch-Teil sieht ein bisschen aus wie eine Methode mit einem Argument. Dieses Argument muss vom statischen Typen der Klasse Throwable oder einer beliebigen Unterklasse angehören. try { // Try to execute the Code within the try {} System.out.println(intDiv(dividend, divisor)); } catch (Exception e) { // If an Exception occurred, run the Code within the catch {} System.out.println("An Exception while dividing occurred"); } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 6
Aufgabe 1 | Addition (catch im Speziellen) Wie bereits angedeutet können mehrere catch-Teile geschrieben werden. Dies ist besonders hilfreich, wenn man die Fehlerbehandlung bestimmter Exceptions unterscheiden möchte. Sehen Sie sich das folgende Code-Beispiel an, in dem versucht wird, ein Socket zu öffnen. Falls das fehlschlagen sollte, wird bei einer ConnectException das erste catch ausgeführt, bei einer IOException das zweite catch. public ChatClient() { try { client = new Socket(InetAddress.getLocalHost(), 3000); } catch (ConnectException e) { System.out.println("Connection was declined by Server. Please retry. Terminating Process."); return; } catch (IOException e) { System.out.println("IO Error. Please retry. Terminating Process."); return; } prepareChat(); } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 7
Aufgabe 1 | Addition (catch im Speziellen) Es ist auch möglich, mehrere Exceptions in einem catch abzufangen (multi-catch). Person yudhis = new Person(); try { yudhis.propose(yudhis.getGirlfriend()); } catch (RejectedProposalException | GirlfriendNotFoundException e) { yudhis.cry(); } Bemerkung: Die Exceptions müssen dijoint sein! Also eine Exception darf nicht Unterklasse oder Oberklasse von anderen sein. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 8
Aufgabe 1 | Addition (catch im Speziellen) Achtung: Die Reihenfolge, in der die catch Blöcke stehen, ist relevant! Sollte eine Exception innerhalb des try Blocks geworfen werden, so werden die catch Blöcke von oben nach unten durchgegangen bis einer gefunden wurde, der die geworfene Exception fängt (also der statische Typ kompatibel ist). Sollte das erste catch als Argument ein Throwable deklarieren, wird dieses immer im Fehlerfall ausgeführt, da alle Exceptions und Errors von Throwable erben. Um einen Überblick über die gängigsten Fehler-Klassen zu bekommen, werden wir uns im Folgenden die Throwable-Hierarchie ansehen und anschließend in 2 Kategorien unterteilen. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 9
Aufgabe 1 | Addition (Java Throwable- Hierarchie) Quelle: EIDI VL von Prof. Dr. Helmut Seidl Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 10
Aufgabe 1 | Addition (Unchecked Exceptions) Zu den sogenannten unchecked Exceptions gehören alle Runtime-Exceptions sowie Errors. Typischerweise werden unchecked Exceptions nicht gefangen! • Eine Runtime-Exception ist meist ein programminterner Fehler, welcher normalerweise nicht behandelt wird. Diese Art von Fehler tritt meistens durch einen Programmierfehler (Bug) auf und sollte daher eher nicht gefangen werden (es kann Ausnahmen geben). Stattdessen sollte dem Ursprung des Bugs auf den Grund gegangen werden, um diesen anschließend zu beheben. Ein Beispiel: NullPointerException • Ein Error ist meist ein programmexterner Fehler, welcher nicht erwartet werden kann und meist auch nicht behandelt werden kann. Im Error-Fall ist es oft sinnvoll, den Nutzer über den Fehler zu informieren und das Programm zu beenden. Ein Beispiel: OutOfMemoryError • Eine unchecked Exception muss daher nicht von einem try-catch Block behandelt werden! Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 11
Aufgabe 1 | Addition (Checked Exceptions) Zu den sogenannten checked Exceptions gehören alle anderen Exceptions. Im Gegensatz zu einer unchecked Exception muss jede Methode, die eine solche checked Exception werfen kann, das auch angeben. Solche Fehler sind im allgemeinen leicht behandelbar, da genau bekannt ist, was fehlschlagen kann und warum. Sehen Sie sich das folgende Beispiel an; die kickUser-Methode eines Chatprogramms wirft eine IllegalAccessException, wenn der Aufrufende nicht die nötigen Rechte hat, um die gewünschte Aktion auszuführen. Da eine IllegalAccessException eine checked Exception ist, muss die Möglichkeit, dass diese Exception geworfen wird, im Methodenkopf bekannt gegeben werden. public void kickUser(int userID, Rights rights) throws IllegalAccessException { switch (rights) { case USER -> throw new IllegalAccessException(); case ADMIN, MODERATOR -> kickUser(userID); } } private void kickUser(int userID) {/* CODE */} Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 12
Aufgabe 1 | Addition (Checked Exceptions) Eine Methode, die eine checked Exception wirft, muss das also bekannt geben (declare). Dies hat zur Folge, dass dort, wo eine solche Methode gerufen wird, ein try-catch Block erzwungen wird (Alternativ kann natürlich die aufrufende Methode die selbe Exception declaren. Das verschiebt das Problem aber nur zum vorherigen Aufrufer und löst es nicht). Eine unchecked Exception kann theoretisch überall entstehen, weswegen hier weder Zwang zu einem try-catch Block noch einer Exception declaration besteht. try { chatService.kickUser(4, rights); } catch (IllegalAccessException e) { System.out.println("Insufficient rights for kicking. Your rights: " + rights.toString()); e.printStackTrace(); // This prints the Stack Trace of the catched Exception } Jede Exception verfügt über die Methode printStackTrace(), die Informationen darüber enthält, welche Funktionen alles in welcher Zeile durch diese Exception abgebrochen wurden. Um diese Methode aufrufen zu können, muss die Exception zuerst gefangen werden. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 13
Aufgabe 1 | Addition (eigene Exceptions schreiben) Bei dem bereits bekannten Beispiel haben wir eine IllegalAccessException geworfen, wenn der User eine Aktion ausführen möchte, für die dieser keine Rechte hat. Die gewählte Exception ist jedoch namentlich momentan nicht ganz passend, also schreiben wir nun eine eigene InsufficientRightsException, die von der Klasse Exception erbt, also eine checked Exception ist. Zudem soll die toString dieser neuen Exception Aufschluss darüber geben, welche Action versucht wurde. public class InsufficientRightsException extends Exception { private final String action; public InsufficientRightsException(String action) { this.action = action; } @Override public String toString() { return "Insufficient Rights to perform the \"" + action + "\" action!"; } } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 14
Aufgabe 1 | Addition (Exception als Objkekte) Exceptions (egal welcher Art) sind also auch nur Objekte, die wie jede andere Klasse erstellt und benutzt werden können. Eine Exception ist also kein „Gift“ für euren Code, der diesen sofort zum Absturz bringt. Erst, wenn eine Exception (bzw. ein Throwable) mit dem Keyword throw explizit (bzw. implizit durch einen fehlschlagenden Aufruf) geworfen wird, geht die Programmausführung in einen kritischen Zustand über. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 15
Aufgabe 1 | Addition (try-catch-finally Syntax) Ein try-catch Block kann zudem noch um ein finally-Teil erweitert werden. Dieser enthält Code, welcher immer ausgeführt wird egal ob try erfolgreich war oder nicht (Meist werden hier Ressourcen, die im try benutzt wurden, wieder freigegeben). try{ // try normal program execution }catch(Exception e){ // handle Exceptions if try failed }finally{ // clean up } Ein Intuition aus dem echten Leben: „Wenn man versucht, einen Kuchen zu backen, dann kann das entweder funktionieren (try) oder fehlschlagen (catch). Egal was passiert ist, am Ende des Tages muss man den Ofen trotzdem ausschalten (finally).“ Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 16
Aufgabe 1 | Addition (Good Practices) • Eine Exception sollte immer dann explizit geworfen werden, wenn das fortsetzen des Codes (aufgrund z.B. falscher Parameter) keinen Sinn hat. • Eine Methode, die eine Exception wirft, sollte diese nicht selber fangen (Die Exception wird meist deswegen geworfen, da die Methode die Ausführung nicht sinnvoll fortsetzten kann) • Eine Methode, die eine andere Methode ruft, welche eine checked Exception deklariert hat, sollte immer selbst eine Fehlerbehandlung bereitstellen (Immer so früh wie möglich Fehler behandeln) • Unchecked Exceptions deuten eher auf einen Programmierfehler (z.B. IndexOutOfBoundsException, NullPointerException, etc.) hin und sollten daher nicht behandelt werden, sondern logisch verhindert werden. Errors deuten meist auf einen kritischen Fehler hin, von welchem man sich nicht erholen kann. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 17
Aufgabe 1 | Addition (Good Practices) • Exceptions sollten immer so gut wie möglich vermieden werden (durch Aufrufe von isEmpty(), isPresent(), etc.) • Insbesondere sollten Exceptions niemals genutzt werden, um den regulären Kontrollfluss abzubilden! • Um den Nutzer besser über Fehler zu informieren und/oder um eine bessere Fehlerbehandlung durchzuführen, sollte man eher mehrere spezifische catch-stmts verwenden, als ein allgemeines catch-stmt. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 18
Aufgabe 1 | Additionen • Bearbeiten Sie nun die Aufgabe „Additionen“ (Woche 10 P 01) auf Artemis • Arbeitszeit: ca. 20min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 19
Aufgabe 2 | Umlaut-Concat • Dateisystem • OpenOption • Ressourcenmanagement und try-with-resources • Kommandozeilenargumente Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 20
Aufgabe 2 | Umlaut-Concat (Dateisystem) Java bietet betriebssystemübergreifende Möglichkeiten, mit Ordnern und Dateien zu interagieren. Vor Java 1.7 wurde hierzu die File-Klasse benutzt, die in modernem Code jedoch keine Anwendung mehr findet. Stattdessen nutzen wir die Klassen in java.nio.file. Path repräsentiert eine einfache Namenssequenz, die einen Ordner oder eine Datei über deren Ordnerpfad referenziert. Ein Path kann relativ zu einem anderen sein, oder absolut (auf Linux ist z.B. /a/b absolut, aber a/b relativ). // a/b/c.txt var p = Path.of("a", "b", "c.txt"); // a/b/d.txt var q = p.getParent().resolve("d.txt"); Auffällig ist, dass die Existenz eines Path-Objekts noch nichts darüber aussagt, ob die referenzierte Datei existiert, oder ob diese offen ist bzw. wie diese zu öffnen sei. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 21
Aufgabe 2 | Umlaut-Concat (Dateisystem) Die Klasse Files bietet statische Hilfsmethoden, um mit Path-Objekten zu arbeiten. Die wichtigsten Methoden sind folgend aufgelistet: Files.exists(Path): boolean testet, ob die referenzierte Datei/Ordner existiert. isRegularFile gibt true zurück, wenn der gegebene Path auf eine Datei zeigt. Files.createDirectories(Path): Path erstellt rekursiv alle Ordner, in denen der gegebene Path liegt, sowie Path als Ordner selbst. Bereits existierende Ordner werden beibehalten. Files.readString(Path): String liest die referenzierte Datei komplett ein und gibt den Inhalt zurück. Files.lines(Path): Stream gibt die Zeilen einer Datei in Form eines Streams wieder. Schreibmethoden stehen ebenfalls zur Verfügung und sind im JavaDoc beschrieben. Einige Methoden in Files werfen im Fehlerfall eine IOException, die abgefangen werden muss. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 22
Aufgabe 2 | Umlaut-Concat (OpenOption) Alle Methoden in Files, die eine Datei lesen oder schreiben müssen, nehmen optional eine Menge an OpenOptions an, die einfach kommagetrennt angegeben werden können. Diese Optionen spezifizieren das Verhalten des Dateisystems. Die folgenden Optionen liegen alle statisch in der Klasse StandardOpenOption. • CREATE: Erstelle die Datei, falls sie nicht exisitiert. • CREATE_NEW: Erstelle die Datei oder wirf eine IOException, falls diese bereits existiert. • APPEND: Schreibzugriffe hängen Inhalt automatisch an das Ende der Datei an. • TRUNCATE_EXISTING: Löscht beim Öffnen mit einer der Schreibmethoden den Inhalt der Datei. // Schreibe an das Ende der Datei Files.writeString(p, "Hallo", StandardOpenOption.CREATE, StandardOpenOption.APPEND); Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 23
Aufgabe 3 | Chat (Resourcenmanagement) Sie kennen bereits die try/catch-Struktur im Zusammenhang mit Exceptions. Ein Problem dabei ist, dass in Java oft Ressourcen geöffnet werden (z.B. eine Datei), die wieder geschlossen werden müssen, um für andere Programme verfügbar zu sein. Der naive Ansatz sieht folgendermaßen aus: try { var output = Files.newBufferedWriter(Path.of("a.txt")); output.write("Hallo, Welt!"); output.close(); } catch (IOException ioe) { ioe.printStackTrace(); } Das Problem entsteht, wenn eine Instruktion zwischen dem Öffnen und Schließen der Ressource zu einer Exception führt. In diesem Fall springt Java aus dem try-Teil heraus, wodurch die Ressource nicht mehr geschlossen wird. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 24
Aufgabe 3 | Chat (try-catch-finally) Hierzu existiert eine einfache Lösung: Einem try/catch-Statement kann noch ein finally-Teil angehangen werden, welcher immer am Ende der try/catch-Struktur ausgeführt wird—unabhängig davon, ob eine Exception geworfen wurde. BufferedWriter output = null; try { output = Files.newBufferedWriter(Path.of("a.txt")); output.write("Hallo, Welt!"); } catch (IOException ioe) { ioe.printStackTrace(); } finally { if (output != null) output.close(); } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 25
Aufgabe 3 | Chat (try-with-resources) Die in der vorgegangenen Folie gezeigte Lösung ist allerdings sehr umständlich zu schreiben und führt zu unleserlichem Code. Besser ist es daher, in solchen Fällen ein try-with-resources anzuwenden: try (var output = Files.newBufferedWriter(Path.of("a.txt"))) { // Work with resource output.write("Hallo, Welt!"); } catch (IOException ioe) { // Handle Exceptions ioe.printStackTrace(); } In der allgemeinsten Form kann try-with-resources für alle Ressourcen benutzt werden, die AutoCloseable implementieren. Dieses Interface spezifiziert nur eine Methode: public void close() throws Exception; Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 26
Aufgabe 2 | Umlaut-Concat (Kommandozeilenargumente) Jede main-Methode bekommt als Argument ein String-Array. Bis jetzt haben Sie dieses noch nie verwendet. Es beinhaltet etwaige Argumente, mit denen das Programm gestartet wird. Diese werden meistens über die Kommandozeile übergeben. Nehmen wir an, folgendes Beispielprogramm liegt in Main.java: import java.util.*; import java.util.stream.*; public class Main { public static void main(String[] args){ System.out.println(args.length + " Arguments: " + Arrays.stream(args) .collect(Collectors.joining(", "))); } } Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 27
Aufgabe 2 | Umlaut-Concat (Kommandozeilenargumente) Programme, die aus nur einer Datei bestehen, können folgendermaßen über die Konsole kompiliert und ausgeführt werden: java Main.java Führen wir diese Zeile aus, so erscheint der Text „0 Argumente:“ auf der Konsole. Um nun Argumente anzugeben, können wir diese einfach leerzeichengetrennt an java übergeben: < java Main.java Hallo Welt > 2 Argumente: Hallo, Welt Um ein Argument, das Leerzeichen enthält, zu übergeben, muss dieses mit Anführungszeichen umrundet werden: < java Main.java "Hallo Welt" > 1 Argumente: Hallo Welt Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 28
Aufgabe 2 | Umlaut-Concat (Kommandozeilenargumente) Das eben gezeigte Beispiel, Java in der Kommandozeile auszuführen, ist auf nur eine Quelldatei beschränkt. Sie haben schon weitaus komplexere Programme geschrieben; es gibt also natürlich auch die Möglichkeit, mehrere Dateien miteinander zu verknüpfen. java dient eigentlich nur der Ausführung eines Programms mit der JVM (Java Virtual Machine). Dazu muss das Programm vorher in das von der JVM interpretierbare Format des Bytecodes umgewandelt werden—es muss kompiliert werden. Ein kompiliertes Programm ahmt stark die Struktur der Quelldateien nach: Jede Klasse spiegelt sich in einer eigenen .class-Datei wider (innere Klassen bekommen auch ihre eigenen Dateien). Um diese Dateien zu generieren, bietet sich javac (Java Compiler) an. In seiner simpelsten Form lautet die Syntax wie folgt: javac A.java B.java Es wird auch Globbing unterstützt: javac *.java Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 29
Aufgabe 2 | Umlaut-Concat (Kommandozeilenargumente) Der vollständige Workflow sieht also folgendermaßen aus: Gehen wir von drei Quelldateien aus. Diese werden zuerst mit javac kompiliert: javac Main.java A.java B.java Nun liegt neben jeder Quelldatei eine zugehörige .class-Datei. Um das Programm auszuführen, muss java der Name der Hauptklasse (also der Klasse mit der main-Methode) übergeben werden: java Main Man sollte das Programm aus src Ordner aufrüfen. Falls die Klasse in einer Package liegt, dann kann man so machen src> java package1.package1-1.Main Neben den gezeigten primitiven Tools gibt es auch weitere, komplexere Build-Tools wie Maven oder Gradle. Falls Sie tiefer in die Thematik gehen möchten, können Sie mehr zum Java Ecosystem unter folgenden Links nachlesen: Maven: https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html Gradle: https://spring.io/guides/gs/gradle/ Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 30
Aufgabe 2 | Umlaut-Concat Bearbeiten Sie nun die Aufgabe „Umlaut-Concat“ (Woche 10 P 02) auf Artemis Arbeitszeit: ca. 45min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 31
Aufgabe 3 | Chat • Sockets Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 32
Aufgabe 3 | Chat (Sockets) Sockets sind eine Möglichkeit, Client-Server-Architekturen zu implementieren bzw. überhaupt irgendwie das Internet zu nutzen. Schauen Sie sich folgendes Beispiel zur Anwendung von Sockets an: Server: var ss = new ServerSocket(1234); try (var csSocket = ss.accept()) { csSocket.getOutputStream().write("Hi!".getBytes()); } /* CODE */ Client: try (var connection = new Socket("localhost", 1234)) { byte[] message = connection.getInputStream() .readAllBytes(); System.out.println(new String(message)); } Durch die Verwendung von try-with-resources wird die Verbindung am Ende automatisch geschlossen. Eine tiefergehende Erklärung finden Sie in den Vorlesungsfolien ab S. 681. Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 33
Aufgabe 3 | Chat Bearbeiten Sie nun die Aufgabe „Chat“ (Woche 10 P 03) auf Artemis Arbeitszeit: ca. 45min Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 34
Aufgabe 4 | Telefonbuch Bearbeiten Sie die Aufgabe „Telefonbuch“ (Woche 10 P 04) auf Artemis. Vertiefen Sie hier Ihr Wissen über File-IO. Schlagen Sie Ihnen unbekannte Operationen in der auf Artemis verlinkten Java- API nach Arbeitszeit: bis 15.45 Uhr Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 35
Fragen? Yudhistira Arief Wibowo | PGdP 2020/21 | http://zulip.in.tum.de 26.01.2021 36
Sie können auch lesen