Was ist der beste Weg, um eine Folge von begrenzten Elementen in Java zu erstellen?


317

Während ich in einer Java-App arbeitete, musste ich kürzlich eine durch Kommas getrennte Liste von Werten zusammenstellen, um sie an einen anderen Webdienst zu übergeben, ohne vorher zu wissen, wie viele Elemente vorhanden sein würden. Das Beste, was ich mir auf den Kopf stellen konnte, war ungefähr so:

public String appendWithDelimiter( String original, String addition, String delimiter ) {
    if ( original.equals( "" ) ) {
        return addition;
    } else {
        return original + delimiter + addition;
    }
}

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Mir ist klar, dass dies nicht besonders effizient ist, da überall Zeichenfolgen erstellt werden, aber ich wollte mehr Klarheit als Optimierung.

In Ruby kann ich stattdessen so etwas tun, was sich viel eleganter anfühlt:

parameterArray = [];
parameterArray << "elementName" if condition;
parameterArray << "anotherElementName" if anotherCondition;
parameterString = parameterArray.join(",");

Da Java jedoch keinen Join-Befehl hat, konnte ich nichts Äquivalentes herausfinden.

Was ist der beste Weg, dies in Java zu tun?


Der StringbBilder ist der richtige Weg - java.lang.StringBuilder.
Yusubov

Für Java 8 haben einen Blick auf diese Antwort: stackoverflow.com/a/22577623/1115554
micha

Antworten:


541

Vor Java 8:

Apaches Commons Lang ist hier Ihr Freund - es bietet eine Join-Methode, die der in Ruby genannten sehr ähnlich ist:

StringUtils.join(java.lang.Iterable,char)


Java 8:

Java 8 bietet sofortiges Beitreten über StringJoinerund String.join(). Die folgenden Ausschnitte zeigen, wie Sie sie verwenden können:

StringJoiner

StringJoiner joiner = new StringJoiner(",");
joiner.add("01").add("02").add("03");
String joinedString = joiner.toString(); // "01,02,03"

String.join(CharSequence delimiter, CharSequence... elements))

String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06"

String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

List<String> strings = new LinkedList<>();
strings.add("Java");strings.add("is");
strings.add("cool");
String message = String.join(" ", strings);
//message returned is: "Java is cool"

2
Ich frage mich - berücksichtigt dies, ob die Zeichenfolgendarstellung eines Objekts in der Sammlung das Trennzeichen selbst enthält?
GreenieMeanie

4
Genau das, wonach ich gesucht habe: StringUtils.join (java.util.Collection, String) aus dem Paket org.apache.commons.lang3.StringUtils, die JAR-Datei ist commons-lang3-3.0.1.jar
Umar

108
Unter Android können Sie auch TextUtils.join () verwenden.
James Wald

3
Es ist nicht der beste Weg. Das Zeichen wird immer hinzugefügt, auch wenn das Objekt in der Sammlung null / leer ist. Es ist schön und sauber, aber manchmal wird ein doppeltes Trennzeichen gedruckt.
Stephan Schielke

52

Sie könnten eine kleine Dienstprogrammmethode im Join-Stil schreiben, die für java.util.Lists funktioniert

public static String join(List<String> list, String delim) {

    StringBuilder sb = new StringBuilder();

    String loopDelim = "";

    for(String s : list) {

        sb.append(loopDelim);
        sb.append(s);            

        loopDelim = delim;
    }

    return sb.toString();
}

Dann benutze es so:

    List<String> list = new ArrayList<String>();

    if( condition )        list.add("elementName");
    if( anotherCondition ) list.add("anotherElementName");

    join(list, ",");

2
Warum sollten Sie Ihre eigene Methode schreiben, wenn bereits mindestens 2 Implementierungen (Apache und Guave) vorhanden sind?
Timofey

29
Dies kann beispielsweise nützlich sein, wenn weniger externe Abhängigkeiten gewünscht werden.
Thomas Zumbrunn

2
Mir gefällt, wie loopDelim anstelle von Bedingung verwendet wird. Es ist eine Art Hack, aber es entfernt eine bedingte Anweisung aus einem Zyklus. Ich bevorzuge immer noch die Verwendung des Iterators und das Hinzufügen des ersten Elements vor dem Zyklus, aber es ist in der Tat ein netter Hack.
Vlasec

Könnten Sie List<String>Ihren Intitializer nicht auf Iterator<?>den gleichen Effekt umstellen ?
k_g

@Tim Eg apache überspringt keine leeren Zeichenfolgen.
BanterCZ


31

Die Guava-Bibliothek von Google enthält com.google.common.base.Joiner Klasse , mit deren Hilfe solche Aufgaben gelöst werden können.

Proben:

"My pets are: " + Joiner.on(", ").join(Arrays.asList("rabbit", "parrot", "dog")); 
// returns "My pets are: rabbit, parrot, dog"

Joiner.on(" AND ").join(Arrays.asList("field1=1" , "field2=2", "field3=3"));
// returns "field1=1 AND field2=2 AND field3=3"

Joiner.on(",").skipNulls().join(Arrays.asList("London", "Moscow", null, "New York", null, "Paris"));
// returns "London,Moscow,New York,Paris"

Joiner.on(", ").useForNull("Team held a draw").join(Arrays.asList("FC Barcelona", "FC Bayern", null, null, "Chelsea FC", "AC Milan"));
// returns "FC Barcelona, FC Bayern, Team held a draw, Team held a draw, Chelsea FC, AC Milan"

Hier ist ein Artikel über die String-Dienstprogramme von Guava .


26

In Java 8 können Sie Folgendes verwenden String.join():

List<String> list = Arrays.asList("foo", "bar", "baz");
String joined = String.join(" and ", list); // "foo and bar and baz"

Schauen Sie sich auch diese Antwort für ein Stream-API-Beispiel an.


20

Sie können es verallgemeinern, aber es gibt keine Verknüpfung in Java, wie Sie gut sagen.

Dies könnte besser funktionieren.

public static String join(Iterable<? extends CharSequence> s, String delimiter) {
    Iterator<? extends CharSequence> iter = s.iterator();
    if (!iter.hasNext()) return "";
    StringBuilder buffer = new StringBuilder(iter.next());
    while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
    return buffer.toString();
}

Ich stimme dieser Antwort zu, aber kann jemand die Signatur so bearbeiten, dass sie Collection <String> anstelle von AbstractCollection <String> akzeptiert? Der Rest des Codes sollte der gleiche sein, aber ich denke, AbstractCollection ist ein Implementierungsdetail, das hier keine Rolle spielt.
Outlaw Programmer

Besser noch, verwenden Sie Iterable<String>und verwenden Sie einfach die Tatsache, dass Sie darüber iterieren können. In Ihrem Beispiel benötigen Sie nicht die Anzahl der Elemente in der Sammlung, daher ist dies noch allgemeiner.
Jason Cohen

1
Oder noch besser, verwenden Sie Iterable <? erweitert Charsequence> und dann können Sie Sammlungen von StringBuilders und Strings sowie Streams und andere stringähnliche Dinge akzeptieren. :)
Jason Cohen

2
Sie möchten StringBuilderstattdessen ein verwenden. Sie sind identisch, StringBufferbieten jedoch unnötige Gewindesicherheit. Kann jemand es bitte bearbeiten!
Casebash

1
Dieser Code weist mehrere Fehler auf. 1. CharSequence hat ein Kapital s. 2. s.iterator () gibt einen Iterator zurück <? erweitert CharSequence>. 3. Ein Iterable hat keine isEmpty()Methode, verwenden Sie next()stattdessen die Methode
Casebash

17

In Java 8 können Sie dies wie folgt tun:

list.stream().map(Object::toString)
        .collect(Collectors.joining(delimiter));

Wenn die Liste Nullen enthält, können Sie Folgendes verwenden:

list.stream().map(String::valueOf)
        .collect(Collectors.joining(delimiter))

Es unterstützt auch Präfix und Suffix:

list.stream().map(String::valueOf)
        .collect(Collectors.joining(delimiter, prefix, suffix));

15

Verwenden Sie einen Ansatz basierend auf java.lang.StringBuilder! ("Eine veränderbare Folge von Zeichen.")

Wie Sie bereits erwähnt haben, erzeugen all diese String-Verkettungen überall Strings. StringBuilderwerde das nicht tun.

Warum StringBuilderstatt StringBuffer? Aus dem StringBuilderJavadoc:

Nach Möglichkeit wird empfohlen, diese Klasse gegenüber StringBuffer zu verwenden, da sie bei den meisten Implementierungen schneller ist.


4
Ja. Außerdem ist StringBuffer threadsicher, StringBuilder hingegen nicht.
Jon Onstott

10

Ich würde Google Collections verwenden. Es gibt eine schöne Join-Einrichtung.
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

Aber wenn ich es selbst schreiben wollte,

package util;

import java.util.ArrayList;
import java.util.Iterable;
import java.util.Collections;
import java.util.Iterator;

public class Utils {
    // accept a collection of objects, since all objects have toString()
    public static String join(String delimiter, Iterable<? extends Object> objs) {
        if (objs.isEmpty()) {
            return "";
        }
        Iterator<? extends Object> iter = objs.iterator();
        StringBuilder buffer = new StringBuilder();
        buffer.append(iter.next());
        while (iter.hasNext()) {
            buffer.append(delimiter).append(iter.next());
        }
        return buffer.toString();
    }

    // for convenience
    public static String join(String delimiter, Object... objs) {
        ArrayList<Object> list = new ArrayList<Object>();
        Collections.addAll(list, objs);
        return join(delimiter, list);
    }
}

Ich denke, es funktioniert besser mit einer Objektsammlung, da Sie Ihre Objekte jetzt nicht mehr in Zeichenfolgen konvertieren müssen, bevor Sie sie verbinden.




3

Sie können hierfür den Java- StringBuilderTyp verwenden. Es gibt auch StringBuffer, aber es enthält zusätzliche Thread-Sicherheitslogik, die oft unnötig ist.


3

Und eine minimale (wenn Sie Apache Commons oder Gauva nicht in Projektabhängigkeiten aufnehmen möchten, nur um Zeichenfolgen zu verbinden)

/**
 *
 * @param delim : String that should be kept in between the parts
 * @param parts : parts that needs to be joined
 * @return  a String that's formed by joining the parts
 */
private static final String join(String delim, String... parts) {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < parts.length - 1; i++) {
        builder.append(parts[i]).append(delim);
    }
    if(parts.length > 0){
        builder.append(parts[parts.length - 1]);
    }
    return builder.toString();
}

Verwenden Sie delim anstelle von File.separator.
Pradeep Kumar

3

Verwenden Sie StringBuilder und class Separator

StringBuilder buf = new StringBuilder();
Separator sep = new Separator(", ");
for (String each : list) {
    buf.append(sep).append(each);
}

Das Trennzeichen umschließt ein Trennzeichen. Das Trennzeichen wird von der Separator- toStringMethode zurückgegeben, es sei denn, beim ersten Aufruf wird die leere Zeichenfolge zurückgegeben!

Quellcode für die Klasse Separator

public class Separator {

    private boolean skipFirst;
    private final String value;

    public Separator() {
        this(", ");
    }

    public Separator(String value) {
        this.value = value;
        this.skipFirst = true;
    }

    public void reset() {
        skipFirst = true;
    }

    public String toString() {
        String sep = skipFirst ? "" : value;
        skipFirst = false;
        return sep;
    }

}

Was ist mit einer Änderung Separator, bei der eine StringBuilderanstelle einer Verkettung verwendet wird Strings?
Mohamed Nuur

@Mohamed gibt Separatornur das Trennzeichen zurück, es verkettet die Zeichenfolge selbst nicht.
Akuhn

Was ist das für eine Klasse Separator??? Warum nicht einfach einen einfachen String verwenden ..?!
Maxxyme

2

Warum schreiben Sie nicht Ihre eigene join () -Methode? Es würde als Parameter Sammlung von Strings und einem Trennzeichen String verwenden. Innerhalb der Methode durchlaufen Sie die Sammlung und bauen Ihr Ergebnis in einem StringBuffer auf.


2

Wenn Sie Spring MVC verwenden, können Sie die folgenden Schritte ausführen.

import org.springframework.util.StringUtils;

List<String> groupIds = new List<String>;   
groupIds.add("a");    
groupIds.add("b");    
groupIds.add("c");

String csv = StringUtils.arrayToCommaDelimitedString(groupIds.toArray());

Es wird dazu führen a,b,c


2

Wenn Sie Eclipse-Sammlungen verwenden , können Sie makeString()oder verwenden appendString().

makeString()gibt eine StringDarstellung zurück, ähnlich wietoString() .

Es hat drei Formen

  • makeString(start, separator, end)
  • makeString(separator) Standardmäßig beginnen und enden leere Zeichenfolgen
  • makeString()Der Standardwert für das Trennzeichen ist ", "(Komma und Leerzeichen).

Codebeispiel:

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[", "/", "]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[", ", ", "]"));

appendString()ist ähnlich makeString(), aber es hängt an ein Appendable(wie StringBuilder) und ist void. Es hat die gleichen drei Formen, mit einem zusätzlichen ersten Argument, dem Anhang.

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable, "[", "/", "]");
assertEquals("[1/2/3]", appendable.toString());

Wenn Sie Ihre Sammlung nicht in einen Eclipse-Sammlungstyp konvertieren können, passen Sie sie einfach mit dem entsprechenden Adapter an.

List<Object> list = ...;
ListAdapter.adapt(list).makeString(",");

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.


2

Java 8 Native Type

List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(","));

Benutzerdefiniertes Java 8-Objekt:

List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(","));

1

Sie sollten wahrscheinlich a StringBuildermit der appendMethode verwenden, um Ihr Ergebnis zu erstellen, aber ansonsten ist dies eine ebenso gute Lösung wie Java.


1

Warum machen Sie in Java nicht dasselbe wie in Ruby, dh die durch Trennzeichen getrennte Zeichenfolge wird erst erstellt, nachdem Sie alle Teile zum Array hinzugefügt haben?

ArrayList<String> parms = new ArrayList<String>();
if (someCondition) parms.add("someString");
if (anotherCondition) parms.add("someOtherString");
// ...
String sep = ""; StringBuffer b = new StringBuffer();
for (String p: parms) {
    b.append(sep);
    b.append(p);
    sep = "yourDelimiter";
}

Möglicherweise möchten Sie diese for-Schleife in einer separaten Hilfsmethode verschieben und anstelle von StringBuffer auch StringBuilder verwenden ...

Bearbeiten : Die Reihenfolge der Anhänge wurde korrigiert.


Ja, warum sollten Sie StringBuffer anstelle von StringBuilder verwenden (da Sie Java 1.5+ verwenden)? Sie haben Ihre Anhänge auch falsch herum.
Tom Hawtin - Tackline

1

Mit Java 5-Variablenargumenten müssen Sie also nicht alle Zeichenfolgen explizit in eine Sammlung oder ein Array einfügen:

import junit.framework.Assert;
import org.junit.Test;

public class StringUtil
{
    public static String join(String delim, String... strings)
    {
        StringBuilder builder = new StringBuilder();

        if (strings != null)
        {
            for (String str : strings)
            {
                if (builder.length() > 0)
                {
                    builder.append(delim).append(" ");
                }
                builder.append(str);
            }
        }           
        return builder.toString();
    }
    @Test
    public void joinTest()
    {
        Assert.assertEquals("", StringUtil.join(",", null));
        Assert.assertEquals("", StringUtil.join(",", ""));
        Assert.assertEquals("", StringUtil.join(",", new String[0]));
        Assert.assertEquals("test", StringUtil.join(",", "test"));
        Assert.assertEquals("foo, bar", StringUtil.join(",", "foo", "bar"));
        Assert.assertEquals("foo, bar, x", StringUtil.join(",", "foo", "bar", "x"));
    }
}

1

Für diejenigen, die sich in einem Frühlingskontext befinden, ihre StringUtils Klasse ebenfalls nützlich:

Es gibt viele nützliche Verknüpfungen wie:

  • collectionToCommaDelimitedString (Sammlung coll)
  • collectionToDelimitedString (Collection coll, String delim)
  • arrayToDelimitedString (Object [] arr, String delim)

und viele andere.

Dies kann hilfreich sein, wenn Sie Java 8 noch nicht verwenden und sich bereits in einem Spring-Kontext befinden.

Ich bevorzuge es gegen die Apache Commons (obwohl auch sehr gut) für die Collection-Unterstützung, die so einfacher ist:

// Encoding Set<String> to String delimited 
String asString = org.springframework.util.StringUtils.collectionToDelimitedString(codes, ";");

// Decoding String delimited to Set
Set<String> collection = org.springframework.util.StringUtils.commaDelimitedListToSet(asString);

0

Sie können so etwas ausprobieren:

StringBuilder sb = new StringBuilder();
if (condition) { sb.append("elementName").append(","); }
if (anotherCondition) { sb.append("anotherElementName").append(","); }
String parameterString = sb.toString();

Es sieht so aus, als würde am Ende der Zeichenfolge ein Komma stehen, das ich vermeiden wollte. (Natürlich, da Sie wissen, dass es da ist, können Sie es dann zuschneiden, aber das riecht auch ein bisschen unelegant.)
Sean McMains

0

Also im Grunde so etwas:

public static String appendWithDelimiter(String original, String addition, String delimiter) {

if (original.equals("")) {
    return addition;
} else {
    StringBuilder sb = new StringBuilder(original.length() + addition.length() + delimiter.length());
        sb.append(original);
        sb.append(delimiter);
        sb.append(addition);
        return sb.toString();
    }
}

Das Problem hierbei ist, dass er anscheinend appendWithDelimiter () allot aufruft. Die Lösung sollte einen und nur einen StringBuffer instanziieren und mit dieser einzelnen Instanz arbeiten.
Stu Thompson

0

Ich weiß nicht, ob dies wirklich besser ist, aber zumindest wird StringBuilder verwendet, was möglicherweise etwas effizienter ist.

Unten finden Sie einen allgemeineren Ansatz, wenn Sie die Liste der Parameter erstellen können, bevor Sie eine Parameterbegrenzung vornehmen.

// Answers real question
public String appendWithDelimiters(String delimiter, String original, String addition) {
    StringBuilder sb = new StringBuilder(original);
    if(sb.length()!=0) {
        sb.append(delimiter).append(addition);
    } else {
        sb.append(addition);
    }
    return sb.toString();
}


// A more generic case.
// ... means a list of indeterminate length of Strings.
public String appendWithDelimitersGeneric(String delimiter, String... strings) {
    StringBuilder sb = new StringBuilder();
    for (String string : strings) {
        if(sb.length()!=0) {
            sb.append(delimiter).append(string);
        } else {
            sb.append(string);
        }
    }

    return sb.toString();
}

public void testAppendWithDelimiters() {
    String string = appendWithDelimitersGeneric(",", "string1", "string2", "string3");
}

0

Ihr Ansatz ist nicht schlecht, aber Sie sollten einen StringBuffer anstelle des + -Zeichens verwenden. Das + hat den großen Nachteil, dass für jede einzelne Operation eine neue String-Instanz erstellt wird. Je länger Ihre Saite wird, desto größer ist der Overhead. Die Verwendung eines StringBuffers sollte also der schnellste Weg sein:

public StringBuffer appendWithDelimiter( StringBuffer original, String addition, String delimiter ) {
        if ( original == null ) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(addition);
                return buffer;
        } else {
                buffer.append(delimiter);
                buffer.append(addition);
                return original;
        }
}

Nachdem Sie Ihre Zeichenfolge erstellt haben, rufen Sie einfach toString () für den zurückgegebenen StringBuffer auf.


1
Wenn Sie hier StringBuffer verwenden, wird es ohne guten Grund langsamer. Wenn Sie den Operator + verwenden, wird intern der schnellere StringBuilder verwendet, sodass er nur Thread-Sicherheit gewinnt, die er nicht benötigt, indem er stattdessen StringBuffer verwendet.
Fredrik

Auch ... Die Aussage "Das + hat den großen Nachteil, dass für jede einzelne Operation eine neue String-Instanz erstellt wird" ist falsch. Der Compiler generiert daraus StringBuilder.append () -Aufrufe.
Fredrik

0

Anstatt die Verkettung von Zeichenfolgen zu verwenden, sollten Sie StringBuilder verwenden, wenn Ihr Code nicht mit einem Thread versehen ist, und StringBuffer, wenn dies der Fall ist.


0

Du machst das etwas komplizierter als es sein muss. Beginnen wir mit dem Ende Ihres Beispiels:

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Mit der kleinen Änderung, einen StringBuilder anstelle eines Strings zu verwenden, wird dies zu:

StringBuilder parameterString = new StringBuilder();
if (condition) parameterString.append("elementName").append(",");
if (anotherCondition) parameterString.append("anotherElementName").append(",");
...

Wenn Sie fertig sind (ich nehme an, Sie müssen auch einige andere Bedingungen überprüfen), stellen Sie einfach sicher, dass Sie das Endkomma mit einem Befehl wie dem folgenden entfernen:

if (parameterString.length() > 0) 
    parameterString.deleteCharAt(parameterString.length() - 1);

Und zum Schluss holen Sie sich den gewünschten String

parameterString.toString();

Sie können das "," im zweiten Aufruf zum Anhängen auch durch eine generische Trennzeichenfolge ersetzen, die auf einen beliebigen Wert festgelegt werden kann. Wenn Sie eine Liste von Dingen haben, von denen Sie wissen, dass Sie sie anhängen müssen (nicht bedingt), können Sie diesen Code in eine Methode einfügen, die eine Liste von Zeichenfolgen enthält.


0
//Note: if you have access to Java5+, 
//use StringBuilder in preference to StringBuffer.  
//All that has to be replaced is the class name.  
//StringBuffer will work in Java 1.4, though.

appendWithDelimiter( StringBuffer buffer, String addition, 
    String delimiter ) {
    if ( buffer.length() == 0) {
        buffer.append(addition);
    } else {
        buffer.append(delimiter);
        buffer.append(addition);
    }
}


StringBuffer parameterBuffer = new StringBuffer();
if ( condition ) { 
    appendWithDelimiter(parameterBuffer, "elementName", "," );
}
if ( anotherCondition ) {
    appendWithDelimiter(parameterBuffer, "anotherElementName", "," );
}

//Finally, to return a string representation, call toString() when returning.
return parameterBuffer.toString(); 

0

Also ein paar Dinge, die Sie tun könnten, um das Gefühl zu bekommen, dass Sie suchen:

1) Erweitern Sie die List-Klasse - und fügen Sie die Join-Methode hinzu. Die Join-Methode würde einfach die Arbeit des Verkettens und Hinzufügens des Trennzeichens erledigen (was ein Parameter für die Join-Methode sein könnte).

2) Es sieht so aus, als würde Java 7 Java Erweiterungsmethoden hinzufügen - mit denen Sie einfach eine bestimmte Methode an eine Klasse anhängen können: Sie können diese Join-Methode also schreiben und als Erweiterungsmethode zu List oder sogar zu hinzufügen Sammlung.

Lösung 1 ist jetzt wahrscheinlich die einzig realistische, da Java 7 noch nicht verfügbar ist :) Aber es sollte gut funktionieren.

Um beide zu verwenden, fügen Sie einfach alle Ihre Elemente wie gewohnt zur Liste oder Sammlung hinzu und rufen dann die neue benutzerdefinierte Methode auf, um sie zu verbinden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.