Sortieren in Java Tutorial - Feature-Bild

Sortieren in Java [Tutorial]

​​​​von Sven Woltmann – 11. Juni 2020

Dieses Tutorial erklärt – Schritt für Schritt und mit vielen Code-Beispielen – wie man in Java primitive Datentypen (ints, longs, doubles, etc.) und Objekte beliebiger Klassen sortieren kann.

Im einzelnen beantwortet der Artikel folgende Fragen:

  • Wie sortiert man in Java Arrays von primitiven Datentypen?
  • Wie sortiert man in Java Arrays und Listen von Objekten?
  • Wann verwendet man Comparable und wann einen Comparator?
  • Wie erzeugt man einen Comparator am besten?
  • Welche Sortieralgorithmen verwendet das JDK intern?

Der Artikel ist Teil des Ultimate Guides über Sortieralgorithmen, der einen Überblick über die gängigsten Sortierverfahren und deren Eigenschaften, wie z. B. deren Zeit- und Platzkomplexität, gibt.

Alle Quellcodes dieses Artikels findest du in meinem GitLab-Repository.

Was kann man in Java sortieren?

Die folgenden Datentypen lassen sich mit Java-Bordmitteln sortieren:

  • Arrays von primitiven Datentypen (int[], long[], double[], usw.),
  • Arrays und Listen von Objekten, die das Comparable-Interface implementieren,
  • Arrays und Listen von Objekten beliebiger Klassen, mit Angabe eines zusätzlichen Objekts, das das Comparator-Interface implementiert.

Den Unterschied zwischen Comparable und Comparator werde ich im Kapitel über das Sortieren von Objekten im Detail erklären. Dort werde ich auch zeigen, wie man seit Java 8 mit Comparator.comparing() sehr elegant Comparator-Objekte erstellen kann.

Arrays.sort() – primitive Datentypen sortieren

Die Klasse java.util.Arrays stellt Sortiermethoden für alle primitiven Datentypen (außer boolean) bereit:

  • static void sort(byte[] a)
  • static void sort(char[] a)
  • static void sort(double[] a)
  • static void sort(float[] a)
  • static void sort(int[] a)
  • static void sort(long[] a)
  • static void sort(short[] a)

Beispiel: Sortieren eines int-Arrays

Das folgenden Beispiel zeigt, wie ein int-Array sortiert und dann auf der Konsole ausgegeben wird:

int[] a = {4, 8, 5, 9, 2, 3, 1, 7, 6};
Arrays.sort(a);
System.out.println(Arrays.toString(a));

Die Ausgabe dieses kurzen Programs lautet:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Teilbereiche eines Arrays sortieren

Für jeden der o. g. Datentypen gibt es eine überladene Methode, die nur einen Teilbereich des Arrays sortiert, z. B.:

  • static void sort(int[] a, int fromIndex, int toIndex)

Das folgende Beispiel sortiert nur die ersten fünf Elemente des Arrays:

int[] a = {4, 8, 5, 9, 2, 3, 1, 7, 6};
Arrays.sort(a, 0, 5);
System.out.println(Arrays.toString(a));

Das Programm gibt folgendes aus:

[2, 4, 5, 8, 9, 3, 1, 7, 6]

Die ersten fünf Elemente 2, 4, 5, 8, 9 wurden sortiert, die restlichen vier Elemente 3, 1, 7, 6, sind unverändert.

Java-Objekte sortieren

Primitive Datentypen werden nach ihrer natürlichen Ordnung sortiert; dementsprechend wird unser Beispiel-Array [4, 8, 5, 9, 2, 3, 1, 7, 6] nach dem Sortieren zu [1, 2, 3, 4, 5, 6, 7, 8, 9].

Doch in welcher Reihenfolge werden Objekte sortiert?

Integer- und String-Arrays sortieren

Wie ein Integer– oder String-Array sortiert wird, versteht jeder Java-Entwickler intuitiv:

Integer[] a = {4, 8, 5, 9, 2, 3, 1, 7, 6};
Arrays.sort(a);
System.out.println(Arrays.toString(a));

Auch hier bekommen wir:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Sortieren wir ein paar Vornamen:

String[] names = {"Susan", "Thomas", "Judith", "Daniel", "Eva", "Ben",
      "Antonia", "Paul"};
Arrays.sort(names);
System.out.println(Arrays.toString(names));

Das Ergebnis lautet – wie erwartet:

[Antonia, Ben, Daniel, Eva, Judith, Paul, Susan, Thomas]

Integer-Objekte werden also offensichtlich genau so wie int-Primitive sortiert. Und Strings alphabetisch.

Objekte eigener Klassen sortieren

Doch wie sortiert man seine selbstgebaute Customer-Klasse? Oder eine Invoice?

Probieren wir es aus! Hier zunächst unsere Customer-Klasse:

public class Customer {
  private int id;
  private String firstName;
  private String lastName;

  public Customer(int id, String firstName, String lastName) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return "Customer{" +
          "id=" + id +
          ", firstName='" + firstName + '\'' +
          ", lastName='" + lastName + '\'' +
          '}';
  }

Wir versuchen diese mit Arrays.sort() zu sortieren:

Customer[] customers = {
      new Customer(43423, "Elizabeth", "Mann"),
      new Customer(10503, "Phil", "Gruber"),
      new Customer(61157, "Patrick", "Sonnenberg"),
      new Customer(28378, "Marina", "Metz"),
      new Customer(57299, "Caroline", "Albers")
};
Arrays.sort(customers);
System.out.println(Arrays.toString(customers));

Diesen Versuch quittiert Java mit folgender Fehlermeldung:

Exception in thread „main“ java.lang.ClassCastException:
class eu.happycoders.sorting.Customer cannot be cast to class java.lang.Comparable

Java weiß ohne zusätzliche Informationen nicht, wie Customer-Objekte sortiert werden sollen. Wie stellen wir diese Informationen bereit? Das erfährst du im nächsten Kapitel.

Comparable und Comparator

Die Sortier-Instruktionen können wir auf zwei unterschiedliche Arten bereitstellen:

  1. indem wir die Klasse Customer das Interface java.lang.Comparable implementieren lassen (so wie von der Fehlermeldung gefordert) oder
  2. indem wir der Arrays.sort()-Methode eine Implementierung des Interfaces java.util.Comparator mitgeben.

Die beiden Varianten werden in den folgenden zwei Abschnitten beschrieben.

Sortieren mit Comparable

Das Interface java.lang.Comparable definiert eine einzige Methode:

  • public int compareTo(T o)

Diese wird vom Sortieralgorithmus aufgerufen, um zu prüfen, ob ein Objekt kleiner, gleich oder größer als ein anderes Objekt ist. Je nachdem muss die Methode eine negative Zahl, 0 oder eine positive Zahl zurückliefern.

(Wenn du dir die Quellcodes von Integer und String anschaust, wirst du feststellen, dass beide das Comparable-Interface und die compareTo()-Methode implementieren.)

Wir wollen unsere Kunden nach Kundennummer sortieren. Dazu müssen wir die Customer-Klasse wie folgt erweitern (den Konstruktor und die toString()-Methode lasse ich hier der Übersicht halber weg):

public class Customer implements Comparable<Customer> {
  private int id;
  private String firstName;
  private String lastName;

  // Constructor and toString method omitted

  @Override
  public int compareTo(Customer o) {
    return this.id < o.id ? -1 : this.id == o.id ? 0 : 1;
  }
}

Die Funktionsweise aus Perspektive der compareTo()-Methode:

  • Wenn meine Kundennummer kleiner ist als deine, dann gib -1 zurück;
  • wenn unsere Kundennummern gleich sind, gib 0 zurück;
  • ansonsten gib 1 zurück.

Etwas kürzer wird es, wenn man die Methode Integer.compare() verwendet. Diese vergleicht die zwei IDs auf genau die gleiche Art und Weise:

@Override
public int compareTo(Customer o) {
  return Integer.compare(this.id, o.id);
}

Unsere so erweiterte Customer-Klasse können wir nun problemlos sortieren lassen (hier noch mal, damit du nicht scrollen musst, das Customer-Sortier-Beispiel von oben):

Customer[] customers = {
      new Customer(43423, "Elizabeth", "Mann"),
      new Customer(10503, "Phil", "Gruber"),
      new Customer(61157, "Patrick", "Sonnenberg"),
      new Customer(28378, "Marina", "Metz"),
      new Customer(57299, "Caroline", "Albers")
};
Arrays.sort(customers);
System.out.println(Arrays.toString(customers));

Dieses mal läuft das Programm ohne Fehler durch und gibt folgendes aus (die Zeilenumbrüche habe ich der Übersicht halber manuell eingefügt):

[Customer{id=10503, firstName='Phil', lastName='Gruber'},
 Customer{id=28378, firstName='Marina', lastName='Metz'},
 Customer{id=43423, firstName='Elizabeth', lastName='Mann'},
 Customer{id=57299, firstName='Caroline', lastName='Albers'},
 Customer{id=61157, firstName='Patrick', lastName='Sonnenberg'}]

Unsere Kunden sind nun, wie gewünscht, nach Kundernnummer sortiert.

Was aber, wenn wir die Kunden für einen anderen Use Case nicht nach Nummern, sondern nach Namen sortieren wollen? Wir können ja compareTo() nur einmal implementieren. Müssen wir uns für immer und ewig auf eine Reihenfolge festlegen?

Hier kommt das Interface Comparator ins Spiel, das ich im nächsten Abschnitt beschreiben werde.

Achtung Falle: ints subtrahieren

Mitunter sieht man in einer compareTo()-Methode folgendes:

return this.id - o.id;

Das sollte man auf keinen Fall so schreiben, denn es funktioniert nicht, wenn beispielsweise this.id -2.000.000.000 ist und o.id 1.000.000.000. Da in diesem Fall this.id kleiner ist, sollte die compareTo()-Methode eine negative Zahl zurückliefern. Das tut sie aber nicht, da es bei der Subtraktion zu einem arithmetischen Unterlauf kommt – das Ergebnis der Subtraktion ist 1.294.967.296 – eine positive Zahl!

Sortieren mit einem Comparator

Mit der Customer.compareTo()-Methode haben wir die sogenannte „natürliche Ordnung“ der Kunden definiert. Mit dem Interface Comparator können wir beliebig viele weitere Sortierreihenfolgen für eine Klasse definieren.

Analog zur compareTo()-Methode definiert das Comparator-Interface die folgende Methode:

  • int compare(T o1, T o2)

Diese wird aufgerufen, um zu prüfen, ob das Objekt o1 kleiner, gleich oder größer als das Objekt o2 ist. Entsprechend muss auch diese Methode eine negative Zahl, 0 oder eine positive Zahl als Rückgabewert liefern.

Wie erstellt man einen Comparator?

Bis Java 7 konnte man einen Comparator ausschließlich durch Implementierung des Interfaces Comparator erstellen – entweder als öffentliche oder als anonyme Klasse.

Seit Java 8 kann man einen Comparator auch als Lambda notieren oder ihn – ziemlich komfortabel, wie du bald sehen wirst – mit Hilfe der Methoden Comparator.comparing() und thenComparing() erzeugen lassen.

Comparator als anonyme Klasse

Fangen wir mit der Art und Weise an, wie man bis Java 7 einen Comparator erstellt hat: durch Implementierung des Interfaces Comparator, meist in Form einer anonymen Klasse (zur Vorbereitung müssen wir der Customer-Klasse noch Getter hinzufügen; die drucke ich hier nicht extra ab).

 Customer[] customers = {
      new Customer(43423, "Elizabeth", "Mann"),
      new Customer(10503, "Phil", "Gruber"),
      new Customer(61157, "Patrick", "Sonnenberg"),
      new Customer(28378, "Marina", "Metz"),
      new Customer(57299, "Caroline", "Albers")
};

Arrays.sort(customers, new Comparator<Customer>() {
  @Override
  public int compare(Customer o1, Customer o2) {
    int result = o1.getLastName().compareTo(o2.getLastName());
    if (result != 0) return result;

    result = o1.getFirstName().compareTo(o2.getFirstName());
    if (result != 0) return result;

    return Integer.compare(o1.getId(), o2.getId());
  }
});

System.out.println(Arrays.toString(customers));

Das sieht auf den ersten Blick kompliziert aus – aber nur, weil ich die Sortierlogik etwas komplexer gemacht habe ;-)

  • Zuerst werden die Nachnamen verglichen; sind diese unterschiedlich (result != 0), dann wird das entsprechende Ergebnis zurückgegeben.
  • Sind die Nachnamen gleich, werden die Vornamen verglichen und bei Ungleichheit das Ergebnis zurückgegeben.
  • Sind auch die Vornamen gleich, werden schließlich die IDs (Kundennummern) verglichen.

Das heißt: Der Comparator sortiert zuerst nach Nachnamen; wenn diese gleich sind, dann nach Vornamen; und wenn diese auch gleich sind, dann nach IDs.

Hier die Ausgabe des Code-Beispiels:

[Customer{id=57299, firstName='Caroline', lastName='Albers'},
 Customer{id=10503, firstName='Phil', lastName='Gruber'},
 Customer{id=43423, firstName='Elizabeth', lastName='Mann'},
 Customer{id=28378, firstName='Marina', lastName='Metz'},
 Customer{id=61157, firstName='Patrick', lastName='Sonnenberg'}]

Comparator als Konstante in der zu sortierenden Klasse

Den Comparator können wir auch als Konstante extrahieren und in die Customer-Klasse verschieben. Das hat zwei Vorteile:

  1. der Gesamtcode wird lesbarer und
  2. wir können den Comparator wiederverwenden, ohne ihn kopieren zu müssen.

Der folgende Code wird in die Klasse Customer aufgenommen:

public static final Comparator<Customer> NAME_COMPARATOR = new Comparator<>() {
  @Override
  public int compare(Customer o1, Customer o2) {
    int result = o1.getLastName().compareTo(o2.getLastName());
    if (result != 0) return result;

    result = o1.getFirstName().compareTo(o2.getFirstName());
    if (result != 0) return result;

    return Integer.compare(o1.getId(), o2.getId());
  }
};

Der Aufruf von Arrays.sort() vereinfacht sich dadurch auf:

Arrays.sort(customers, Customer.NAME_COMPARATOR);

Comparator als öffentliche Klasse

Da man in den meisten Fällen nur eine einzige Instanz eines Comparators benötigt, bietet sich normalerweise eine anonyme Klasse an.

Eine öffentliche Klasse macht dann Sinn, wenn das Sortierverhalten über Konstruktor-Parameter gesteuert werden soll.

Das folgende Beispiel zeigt einen Comparator für unsere Customer-Klasse, dessen Sortierreihenfolge per Parameter zur Laufzeit eingestellt werden kann:

public class CustomerByIdComparator implements Comparator<Customer> {
  private final boolean ascending;

  public CustomerByIdComparator(boolean ascending) {
    this.ascending = ascending;
  }

  @Override
  public int compare(Customer o1, Customer o2) {
    int result = Integer.compare(o1.getId(), o2.getId());
    return ascending ? result : -result;
  }
}

Verwenden können wir diesen Comparator beispielsweise wie folgt, um die Kunden absteigend nach Kundennummer zu sortieren:

Arrays.sort(customers, new CustomerByIdComparator(false));

Comparator als Lambda notieren

Anstatt einer anonymen Klasse, kann man zur Notation eines Comparators auch ein Lambda verwenden:

Arrays.sort(customers,
      (o1, o2) -> {
        int result = o1.getLastName().compareTo(o2.getLastName());
        if (result != 0) return result;

        result = o1.getFirstName().compareTo(o2.getFirstName());
        if (result != 0) return result;

        return Integer.compare(o1.getId(), o2.getId());
      });

Das Lambda kann man natürlich auch verwenden, um die Konstante Customer.NAME_COMPARATOR zu definieren:

public static final Comparator<Customer> NAME_COMPARATOR =
      (o1, o2) -> {
        int result = o1.getLastName().compareTo(o2.getLastName());
        if (result != 0) return result;

        result = o1.getFirstName().compareTo(o2.getFirstName());
        if (result != 0) return result;

        return Integer.compare(o1.getId(), o2.getId());
      };

Richtig schön wird es allerdings mit der Methode, die ich im folgenden Abschnitt zeigen werde!

Comparator konstruieren mit Comparator.comparing()

Die meiner Meinung nach eleganteste Methode zur Konstruktion eines Comparators, die ebenfalls seit Java 8 zur Verfügung steht, ist die Verwendung von Comparator.comparing() und Comparator.thenComparing() (und deren Variationen für die primitiven Datentypen int, long und double).

Das folgende Beispiel entspricht dem NAME_COMPARATOR und sortiert zuerst nach Nachnamen, dann nach Vornamen und zuletzt nach IDs:

Arrays.sort(customers,
      Comparator.comparing(Customer::getLastName)
            .thenComparing(Customer::getFirstName)
            .thenComparingInt(Customer::getId));

Wie du siehst, kann man hier beinahe in natürlicher Sprache aufschreiben, wie die Kunden sortiert werden sollen. Viel mehr erklären brauche ich an dieser Stelle eigentlich nicht. Probier es einfach mal aus!

Auch hier können wir wieder die Konstante Customer.NAME_COMPARATOR auf die gleiche Art definieren:

  public static final Comparator<Customer> NAME_COMPARATOR =
        Comparator.comparing(Customer::getLastName)
              .thenComparing(Customer::getFirstName)
              .thenComparingInt(Customer::getId);

Sortieren einer Liste in Java

Bis jetzt haben wir ausschließlich die folgenden zwei Methoden der Klasse java.util.Arrays verwendet, um Objekte zu sortieren:

  • static void sort(Object[] a) – zum Sortieren von Objekten entsprechend ihrer natürlichen Ordnung,
  • static void sort(T[] a, Comparator<? super T> c) – zum Sortieren von Objekten anhand des übergebenenen Comparators.

Oft haben wir Objekte nicht in einem Array vorliegen, sondern in einer Liste. Um diese zu sortieren, gibt es (seit Java 8) zwei Möglichkeiten:

Liste sortieren mit Collections.sort()

Bis einschließlich Java 7 musste die Methode Collections.sort() zu Hilfe genommen werden, um eine Liste zu sortieren.

Im folgenden Beispiel sollen wieder unsere Kunden sortiert werden, zunächst nach Kundennummer (also entsprechend ihrer „natürlichen Ordnung“):

ArrayList<Customer> customers = new ArrayList<>(List.of(
      new Customer(43423, "Elizabeth", "Mann"),
      new Customer(10503, "Phil", "Gruber"),
      new Customer(61157, "Patrick", "Sonnenberg"),
      new Customer(28378, "Marina", "Metz"),
      new Customer(57299, "Caroline", "Albers")
));
Collections.sort(customers);
System.out.println(customers);

Das Programm gibt, wie im vorherigen Beispiel auch, die Kunden sortiert nach ihrer Kundennummer aus.

Warum werden im Beispiel zwei Listen erstellt? Eine mit List.of() und dann eine weitere mit new ArrayList<>()?

List.of() ist die eleganteste Art eine Liste zu erstellen. Die ist allerdings unveränderlich (was in den meisten Anwendungsfällen von List.of() auch sinnvoll ist) und kann dementsprechend nicht sortiert werden. Daher übergebe ich diese dann an den Konstruktor von ArrayList, der daraus eine veränderliche Liste macht. Zugegeben: nicht die performanteste Lösung, sie macht den Code aber schön kurz.

Collections.sort() prüft übrigens (im Gegensatz zu Arrays.sort()) schon zur Compile-Zeit, ob die übergebene Liste aus Objekten besteht, die Comparable implementieren.

Liste sortieren mit Collections.sort() und einem Comparator

Auch einen Comparator kann man Collections.sort() mitgeben. In folgender Code-Zeile, um die Kunden nach Namen zu sortieren:

Collections.sort(customers, Customer.NAME_COMPARATOR);

Liste sortieren mit List.sort()

Seit Java 8 gibt es (dank der Default-Methoden in Interfaces) die Möglichkeit eine Liste direkt mit List.sort() zu sortieren. Dabei muss immer ein Comparator angegeben werden:

customers.sort(Customer.NAME_COMPARATOR);

Der Comparator darf allerdings auch null sein, um die Liste entsprechend ihrer natürlichen Ordnung zu sortieren:

customers.sort(null);

Auch hier bekommen wir eine ClassCastException, wenn die übergebene Liste Objekte enthält, die nicht Comparable implementieren.

Arrays parallel sortieren

Seit Java 8 steht jede der Sortiermethoden aus der java.util.Arrays-Klasse auch in einer parallelen Variante zur Verfügung. Diese verteilt den Sortieraufwand ab einer festgelegten Array-Größe (8.192 Elemente von Java 8 bis Java 13; 4.097 Elemente seit Java 14) auf mehrere CPU Cores. Ein Beispiel:

  • static void parallelSort(double[] a)

Das folgende Beispiel misst die benötigte Zeit für das Sortieren von 100 Millionen double-Werten einmal mit Arrays.sort() und einmal mit Arrays.parallelSort():

public class DoubleArrayParallelSortDemo {
  private static final int NUMBER_OF_ELEMENTS = 100_000_000;

  public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
      sortTest("sort", Arrays::sort);
      sortTest("parallelSort", Arrays::parallelSort);
    }
  }

  private static void sortTest(String methodName, Consumer<double[]> sortMethod) {
    double[] a = createRandomArray(NUMBER_OF_ELEMENTS);
    long time = System.currentTimeMillis();
    sortMethod.accept(a);
    time = System.currentTimeMillis() - time;
    System.out.println(methodName + "() took " + time + " ms");
  }

  private static double[] createRandomArray(int n) {
    ThreadLocalRandom current = ThreadLocalRandom.current();
    double[] a = new double[n];
    for (int i = 0; i < n; i++) {
      a[i] = current.nextDouble();
    }
    return a;
  }
}

Mein System (DELL XPS 15 mit Core i7-8750H) gibt folgende Messwerte aus:

sort() took 9596 ms
parallelSort() took 2186 ms
sort() took 9232 ms
parallelSort() took 1835 ms
sort() took 8994 ms
parallelSort() took 1917 ms
sort() took 9152 ms
parallelSort() took 1746 ms
sort() took 8899 ms
parallelSort() took 1757 ms

Die jeweils ersten Aufrufe dauern etwas länger, da der HotSpot-Compiler etwas Zeit braucht, um den Code zu optimieren.

Danach ist gut zu sehen, wie das parallele Sortieren etwa fünf mal schneller ist als das sequentielle. Für sechs Cores ist das ein sehr gutes Ergebnis, da die Parallelisierung natürlich auch einen gewissen Overhead mit sich bringt.

Sortieralgorithmen im Java Development Kit (JDK)

Im JDK werden je nach Aufgabenstellung verschiedene Sortieralgorithmen angewendet:

  • Counting Sort für byte[], short[] und char[], wenn mehr als 64 Bytes bzw. mehr als 1750 Shorts oder Characters sortiert werden.
  • Dual-Pivot Quicksort für das Sortieren primitiver Datentypen mit Arrays.sort(). Hierbei handelt es sich um eine optimierte Variante von Quicksort, kombiniert mit Insertion Sort und Counting Sort. Der Algorithmus erreicht eine Zeitkomplexität von O(n log n) bei vielen Eingabedaten, für die andere Quicksort-Implementierungen in der Regel auf O(n²) zurückfallen.
  • Timsort (ein optimiertes Merge Sort kombiniert mit Insertion Sort) für alle anderen Objekte.

Beim parallelen Sortieren werden folgende Algorithmen angewendet:

  • Bytes, Shorts, Characters werden niemals parallel sortiert.
  • Für andere primitive Datentypen wird eine Kombination aus Quicksort, Merge Sort, Insertion Sort und Heap Sort eingesetzt.
  • Für Objekte wird ebenfalls Timsort eingesetzt – die parallele Variante allerdings erst bei einer Listengröße von mehr als 8.192 Elementen; darunter wird die Single-Threaded Variante verwendet, da ansonsten der Overhead größer ist als der Gewinn.

Zusamenfassung

Du hast in diesem Artikel gelernt (oder aufgefrischt), wie du in Java primitive Datentypen und Objekte sortieren kannst, wie und wann man Comparable und Comparator anwendet und welche Sortierverfahren das JDK intern anwendet.

Wenn dir der Artikel gefallen hat, teile ihn gerne über einen der Share-Buttons am Ende. Möchtest du per E-Mail informiert werden, wenn ich einen neuen Artikel veröffentliche? Dann trage dich über das folgende Formular in meinen E-Mail-Verteiler ein.

  •  
  •  
  •  
  •  
  •  
  •  

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Pflichtfelder sind markiert.

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}