Was ist der nächste Ersatz für einen Funktionszeiger in Java?


299

Ich habe eine Methode, die ungefähr zehn Codezeilen umfasst. Ich möchte mehr Methoden erstellen, die genau dasselbe tun, mit Ausnahme einer kleinen Berechnung, die eine Codezeile ändert. Dies ist eine perfekte Anwendung zum Übergeben eines Funktionszeigers, um diese eine Zeile zu ersetzen, aber Java verfügt nicht über Funktionszeiger. Was ist meine beste Alternative?


5
Java 8 verfügt über Lambda-Ausdrücke . Weitere Informationen zu Lambda-Ausdrücken finden Sie hier .
Marius

4
@Marius Ich glaube nicht, dass Lambda-Ausdrücke als Funktionszeiger zählen. Der ::Betreiber auf der anderen Seite ...
Der Kerl mit dem Hut

Entschuldigung für den späten Kommentar;) - normalerweise benötigen Sie dafür keinen Funktionszeiger. Verwenden Sie einfach eine Vorlagenmethode! ( en.wikipedia.org/wiki/Template_method_pattern )
isnot2bad

2
@ isnot2bad - ein Blick auf diesen Artikel scheint übertrieben - komplexer als die hier gegebenen Antworten. Insbesondere erfordert die Vorlagenmethode das Erstellen einer Unterklasse für jede alternative Berechnung. Ich sehe nicht, dass OP etwas angegeben hat, das Unterklassen erfordert ; Er möchte einfach mehrere Methoden erstellen und den größten Teil der Implementierung gemeinsam nutzen. Wie die akzeptierte Antwort zeigt, ist dies leicht "an Ort und Stelle" (innerhalb jeder Methode) möglich, noch vor Java 8 mit seinen Lambdas.
ToolmakerSteve

@ToolmakerSteve Die akzeptierte Lösung erfordert auch eine Klasse pro Berechnung (auch wenn es sich nur um eine anonyme innere Klasse handelt). Das Muster der Vorlagenmethode kann auch mit anonymen inneren Klassen realisiert werden, sodass es sich nicht wesentlich von der akzeptierten Lösung für den Overhead (vor Java 8) unterscheidet. Es geht also eher um das Nutzungsmuster und die detaillierten Anforderungen, die wir nicht kennen. Ich schätze die akzeptierte Antwort und wollte nur eine weitere Möglichkeit hinzufügen, an die ich denken kann.
isnot2bad

Antworten:


269

Anonyme innere Klasse

StringAngenommen, Sie möchten eine Funktion mit einem Parameter übergeben, der ein zurückgibt int.
Zuerst müssen Sie eine Schnittstelle mit der Funktion als einzigem Mitglied definieren, wenn Sie eine vorhandene nicht wiederverwenden können.

interface StringFunction {
    int func(String param);
}

Eine Methode, die den Zeiger verwendet, würde nur eine StringFunctionInstanz wie folgt akzeptieren :

public void takingMethod(StringFunction sf) {
   int i = sf.func("my string");
   // do whatever ...
}

Und würde so heißen:

ref.takingMethod(new StringFunction() {
    public int func(String param) {
        // body
    }
});

EDIT: In Java 8 können Sie es mit einem Lambda-Ausdruck aufrufen:

ref.takingMethod(param -> bodyExpression);

13
Dies ist übrigens ein Beispiel für das "Command Patern". en.wikipedia.org/wiki/Command_Pattern
Ogre Psalm33

3
@Ogre Psalm33 Diese Technik kann auch das Strategiemuster sein, je nachdem, wie Sie sie verwenden. Der Unterschied zwischen dem Strategiemuster und dem Befehlsmuster .
Rory O'Kane

2
Hier ist eine Abschlussimplementierung für Java 5, 6 und 7 mseifed.blogspot.se/2012/09/… Sie enthält alles, was man sich wünschen kann ... Ich finde es ziemlich großartig!
mmm

@SecretService: Dieser Link ist tot.
Lawrence Dol

@ LawrenceDol Ja, das ist es. Hier ist ein Pastebin der Klasse, die ich benutze. pastebin.com/b1j3q2Lp
mmm

32

Für jeden "Funktionszeiger" würde ich eine kleine Funktorklasse erstellen , die Ihre Berechnung implementiert. Definieren Sie eine Schnittstelle, die von allen Klassen implementiert wird, und übergeben Sie Instanzen dieser Objekte an Ihre größere Funktion. Dies ist eine Kombination aus " Befehlsmuster " und " Strategiemuster ".

@ sblundys Beispiel ist gut.


28

Wenn es eine vordefinierte Anzahl verschiedener Berechnungen gibt, die Sie in dieser einen Zeile ausführen können, ist die Verwendung einer Aufzählung eine schnelle und dennoch klare Möglichkeit, ein Strategiemuster zu implementieren.

public enum Operation {
    PLUS {
        public double calc(double a, double b) {
            return a + b;
        }
    },
    TIMES {
        public double calc(double a, double b) {
            return a * b;
        }
    }
     ...

     public abstract double calc(double a, double b);
}

Offensichtlich sind die Strategie-Methodendeklaration sowie genau eine Instanz jeder Implementierung in einer einzigen Klasse / Datei definiert.


24

Sie müssen eine Schnittstelle erstellen, die die Funktion (en) bereitstellt, die Sie weitergeben möchten. z.B:

/**
 * A simple interface to wrap up a function of one argument.
 * 
 * @author rcreswick
 *
 */
public interface Function1<S, T> {

   /**
    * Evaluates this function on it's arguments.
    * 
    * @param a The first argument.
    * @return The result.
    */
   public S eval(T a);

}

Wenn Sie dann eine Funktion übergeben müssen, können Sie diese Schnittstelle implementieren:

List<Integer> result = CollectionUtilities.map(list,
        new Function1<Integer, Integer>() {
           @Override
           public Integer eval(Integer a) {
              return a * a;
           }
        });

Schließlich verwendet die Kartenfunktion die in Funktion1 übergebene wie folgt:

   public static <K,R,S,T> Map<K, R> zipWith(Function2<R,S,T> fn, 
         Map<K, S> m1, Map<K, T> m2, Map<K, R> results){
      Set<K> keySet = new HashSet<K>();
      keySet.addAll(m1.keySet());
      keySet.addAll(m2.keySet());

      results.clear();

      for (K key : keySet) {
         results.put(key, fn.eval(m1.get(key), m2.get(key)));
      }
      return results;
   }

Sie können Runnable häufig anstelle Ihrer eigenen Schnittstelle verwenden, wenn Sie keine Parameter übergeben müssen, oder Sie können verschiedene andere Techniken verwenden, um die Anzahl der Parameter weniger "fest" zu machen, aber dies ist normalerweise ein Kompromiss mit der Typensicherheit. (Oder Sie können den Konstruktor überschreiben, damit Ihr Funktionsobjekt die Parameter auf diese Weise übergibt. Es gibt viele Ansätze, und einige funktionieren unter bestimmten Umständen besser.)


4
Diese „Antwort“ bezieht sich mehr auf die Problemmenge als auf die Lösungsmenge.
Tchrist

18

Methodenreferenzen mit dem ::Operator

Sie können Methodenreferenzen in Methodenargumenten verwenden, bei denen die Methode eine funktionale Schnittstelle akzeptiert . Eine funktionale Schnittstelle ist eine Schnittstelle, die nur eine abstrakte Methode enthält. (Eine funktionale Schnittstelle kann eine oder mehrere Standardmethoden oder statische Methoden enthalten.)

IntBinaryOperatorist eine funktionale Schnittstelle. Seine abstrakte Methode applyAsIntakzeptiert zwei ints als Parameter und gibt ein zurück int. Math.maxakzeptiert auch zwei ints und gibt ein zurück int. In diesem Beispiel werden A.method(Math::max);die parameter.applyAsIntbeiden Eingabewerte an gesendet Math.maxund das Ergebnis zurückgegeben Math.max.

import java.util.function.IntBinaryOperator;

class A {
    static void method(IntBinaryOperator parameter) {
        int i = parameter.applyAsInt(7315, 89163);
        System.out.println(i);
    }
}
import java.lang.Math;

class B {
    public static void main(String[] args) {
        A.method(Math::max);
    }
}

Im Allgemeinen können Sie Folgendes verwenden:

method1(Class1::method2);

anstatt:

method1((arg1, arg2) -> Class1.method2(arg1, arg2));

das ist die Abkürzung für:

method1(new Interface1() {
    int method1(int arg1, int arg2) {
        return Class1.method2(arg1, agr2);
    }
});

Weitere Informationen finden Sie unter Operator :: (Doppelpunkt) in Java 8 und Java Language Specification §15.13 .


15

Sie können dies auch tun (was in einigen seltenen Fällen sinnvoll ist). Das Problem (und es ist ein großes Problem) ist, dass Sie die Sicherheit einer Klasse / Schnittstelle verlieren und sich mit dem Fall befassen müssen, in dem die Methode nicht vorhanden ist.

Es hat den "Vorteil", dass Sie Zugriffsbeschränkungen ignorieren und private Methoden aufrufen können (im Beispiel nicht gezeigt, aber Sie können Methoden aufrufen, die der Compiler normalerweise nicht aufrufen lässt).

Auch hier ist es selten, dass dies sinnvoll ist, aber in diesen Fällen ist es ein gutes Werkzeug.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final String methodName;
        final Method method;
        final Main   main;

        main = new Main();

        if(argv.length == 0)
        {
            methodName = "foo";
        }
        else
        {
            methodName = "bar";
        }

        method = Main.class.getDeclaredMethod(methodName, int.class);

        main.car(method, 42);
    }

    private void foo(final int x)
    {
        System.out.println("foo: " + x);
    }

    private void bar(final int x)
    {
        System.out.println("bar: " + x);
    }

    private void car(final Method method,
                     final int    val)
        throws IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        method.invoke(this, val);
    }
}

1
Ich benutze dies manchmal für die Menübehandlung / GUIs, weil die Methodensyntax so viel einfacher ist als die anonyme innere Klassensyntax. Es ist ordentlich, aber Sie fügen die Komplexität der Reflexion hinzu, mit der sich einige Leute nicht befassen möchten. Seien Sie also verdammt sicher, dass Sie es richtig machen und klare Textfehler für jede mögliche Fehlerbedingung haben.
Bill K

Sie können es sicher mit Generika tippen und brauchen keine Reflexion.
Luigi Plinge

1
Ich verstehe nicht, wie Sie mit Generika und ohne Reflektion eine Methode mit einem Namen aufrufen können, der in einem String enthalten ist.
TofuBeer

1
@LuigiPlinge - können Sie einen Code-Ausschnitt dessen bereitstellen, was Sie meinen?
ToolmakerSteve

13

Wenn Sie nur eine andere Zeile haben, können Sie einen Parameter wie ein Flag und eine if (flag) -Anweisung hinzufügen, die die eine oder andere Zeile aufruft.


1
Die Antwort von javaslook scheint eine sauberere Möglichkeit zu sein, wenn mehr als zwei Berechnungsvarianten vorhanden sind. Oder wenn man den Code in die Methode einbetten möchte, dann eine Aufzählung für die verschiedenen Fälle, die die Methode behandelt, und einen Schalter.
ToolmakerSteve

1
@ToolmakerSteve wahr, obwohl Sie heute Lambdas in Java 8 verwenden würden.
Peter Lawrey


11

Neue Java 8- Funktionsschnittstellen und Methodenreferenzen mit dem ::Operator.

Java 8 kann Methodenreferenzen (MyClass :: new) mit Zeigern " @ Functional Interface " verwalten. Es ist nicht derselbe Methodenname erforderlich, sondern nur die gleiche Methodensignatur.

Beispiel:

@FunctionalInterface
interface CallbackHandler{
    public void onClick();
}

public class MyClass{
    public void doClick1(){System.out.println("doClick1");;}
    public void doClick2(){System.out.println("doClick2");}
    public CallbackHandler mClickListener = this::doClick;

    public static void main(String[] args) {
        MyClass myObjectInstance = new MyClass();
        CallbackHandler pointer = myObjectInstance::doClick1;
        Runnable pointer2 = myObjectInstance::doClick2;
        pointer.onClick();
        pointer2.run();
    }
}

Also, was haben wir hier?

  1. Funktionsschnittstelle - Dies ist eine Schnittstelle, die mit @FunctionalInterface kommentiert ist oder nicht , die nur eine Methodendeklaration enthält.
  2. Methodenreferenzen - Dies ist nur eine spezielle Syntax. Sieht so aus: objectInstance :: methodName , nicht mehr und nicht weniger.
  3. Anwendungsbeispiel - nur ein Zuweisungsoperator und dann ein Aufruf der Schnittstellenmethode.

SIE SOLLTEN NUR FÜR LISTENER FUNKTIONALE SCHNITTSTELLEN UND NUR FÜR DIESE VERWENDEN!

Weil alle anderen derartigen Funktionszeiger für die Lesbarkeit des Codes und die Fähigkeit zum Verstehen wirklich schlecht sind. Direkte Methodenreferenzen sind jedoch manchmal nützlich, beispielsweise bei foreach.

Es gibt mehrere vordefinierte funktionale Schnittstellen:

Runnable              -> void run( );
Supplier<T>           -> T get( );
Consumer<T>           -> void accept(T);
Predicate<T>          -> boolean test(T);
UnaryOperator<T>      -> T apply(T);
BinaryOperator<T,U,R> -> R apply(T, U);
Function<T,R>         -> R apply(T);
BiFunction<T,U,R>     -> R apply(T, U);
//... and some more of it ...
Callable<V>           -> V call() throws Exception;
Readable              -> int read(CharBuffer) throws IOException;
AutoCloseable         -> void close() throws Exception;
Iterable<T>           -> Iterator<T> iterator();
Comparable<T>         -> int compareTo(T);
Comparator<T>         -> int compare(T,T);

Für frühere Java-Versionen sollten Sie Guava-Bibliotheken ausprobieren, die ähnliche Funktionen und Syntax haben, wie Adrian Petrescu oben erwähnt hat.

Weitere Informationen finden Sie im Java 8 Cheatsheet

und danke an The Guy with The Hat für die Java-Sprachspezifikation §15.13 Link.


7
" Weil alle anderen ... wirklich schlecht für die Lesbarkeit von Code sind " ist eine völlig unbegründete Behauptung und außerdem falsch.
Lawrence Dol

9

Die Antwort von @ sblundy ist großartig, aber anonyme innere Klassen weisen zwei kleine Fehler auf, wobei der primäre darin besteht, dass sie nicht wiederverwendbar sind und der sekundäre eine umfangreiche Syntax ist.

Das Schöne ist, dass sein Muster ohne Änderung in der Hauptklasse (die die Berechnungen durchführt) zu vollständigen Klassen erweitert wird.

Wenn Sie eine neue Klasse instanziieren, können Sie Parameter an diese Klasse übergeben, die als Konstanten in Ihrer Gleichung fungieren können. Wenn also eine Ihrer inneren Klassen so aussieht:

f(x,y)=x*y

aber manchmal brauchen Sie eine, die ist:

f(x,y)=x*y*2

und vielleicht ein dritter:

f(x,y)=x*y/2

Anstatt zwei anonyme innere Klassen zu erstellen oder einen "Passthrough" -Parameter hinzuzufügen, können Sie eine einzelne ACTUAL-Klasse erstellen, die Sie instanziieren als:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f=new InnerFunc(2.0);// for the second
calculateUsing(f);
f=new InnerFunc(0.5);// for the third
calculateUsing(f);

Es würde einfach die Konstante in der Klasse speichern und sie in der in der Schnittstelle angegebenen Methode verwenden.

Wenn Sie wissen, dass Ihre Funktion nicht gespeichert / wiederverwendet wird, können Sie Folgendes tun:

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f.setConstant(2.0);
calculateUsing(f);
f.setConstant(0.5);
calculateUsing(f);

Aber unveränderliche Klassen sind sicherer - ich kann keine Rechtfertigung dafür finden, eine Klasse wie diese veränderlich zu machen.

Ich poste das wirklich nur, weil ich zusammenzucke, wenn ich anonyme innere Klasse höre - ich habe viel redundanten Code gesehen, der "Erforderlich" war, weil der Programmierer als erstes anonym ging, wenn er eine tatsächliche Klasse hätte verwenden sollen und niemals überlegte seine Entscheidung.


Huh? OP spricht von verschiedenen Berechnungen (Algorithmen; Logik); Sie zeigen unterschiedliche Werte (Daten). Sie zeigen einen bestimmten Fall, in dem die Differenz in einen Wert einbezogen werden kann, dies ist jedoch eine nicht zu rechtfertigende Vereinfachung des gestellten Problems.
ToolmakerSteve

6

Die Google Guava-Bibliotheken , die immer beliebter werden, verfügen über ein generisches Funktions- und Prädikatobjekt , das sie in vielen Teilen ihrer API bearbeitet haben.


Diese Antwort wäre nützlicher, wenn sie Codedetails enthalten würde. Nehmen Sie den in der akzeptierten Antwort gezeigten Code und zeigen Sie, wie er mit Function aussehen würde.
ToolmakerSteve

4

Klingt für mich nach einem Strategiemuster. Schauen Sie sich die Java-Muster von fluffycat.com an.


4

Eines der Dinge, die ich beim Programmieren in Java wirklich vermisse, sind Funktionsrückrufe. Eine Situation, in der sich die Notwendigkeit für diese immer wieder zeigte, war die rekursive Verarbeitung von Hierarchien, in denen Sie für jedes Element eine bestimmte Aktion ausführen möchten. Zum Beispiel durch einen Verzeichnisbaum gehen oder eine Datenstruktur verarbeiten. Der Minimalist in mir hasst es, eine Schnittstelle und dann eine Implementierung für jeden speziellen Fall definieren zu müssen.

Eines Tages fragte ich mich, warum nicht? Wir haben Methodenzeiger - das Methodenobjekt. Mit der Optimierung von JIT-Compilern bringt der reflektierende Aufruf keine großen Leistungseinbußen mehr mit sich. Abgesehen davon, dass beispielsweise eine Datei von einem Speicherort an einen anderen kopiert wird, sind die Kosten für den Aufruf der reflektierten Methode unbedeutend.

Als ich mehr darüber nachdachte, wurde mir klar, dass ein Rückruf im OOP-Paradigma das Zusammenbinden eines Objekts und einer Methode erfordert - geben Sie das Rückrufobjekt ein.

Schauen Sie sich meine reflexionsbasierte Lösung für Rückrufe in Java an . Kostenlos für jede Verwendung.


4

OK, dieser Thread ist bereits alt genug, daher ist meine Antwort sehr wahrscheinlich nicht hilfreich für die Frage. Aber da dieser Thread mir geholfen hat, meine Lösung zu finden, werde ich ihn trotzdem hier veröffentlichen.

Ich musste eine variable statische Methode mit bekannter Eingabe und bekannter Ausgabe (beide doppelt ) verwenden. Wenn ich also das Methodenpaket und den Namen kenne, könnte ich wie folgt arbeiten:

java.lang.reflect.Method Function = Class.forName(String classPath).getMethod(String method, Class[] params);

für eine Funktion, die ein Double als Parameter akzeptiert.

Also habe ich es in meiner konkreten Situation mit initialisiert

java.lang.reflect.Method Function = Class.forName("be.qan.NN.ActivationFunctions").getMethod("sigmoid", double.class);

und rief es später in einer komplexeren Situation mit

return (java.lang.Double)this.Function.invoke(null, args);

java.lang.Object[] args = new java.lang.Object[] {activity};
someOtherFunction() + 234 + (java.lang.Double)Function.invoke(null, args);

wobei Aktivität ein beliebiger Doppelwert ist. Ich denke darüber nach, dies vielleicht etwas abstrakter zu machen und zu verallgemeinern, wie es SoftwareMonkey getan hat, aber derzeit bin ich mit dem, was es ist, zufrieden genug. Drei Codezeilen, keine Klassen und Schnittstellen erforderlich, das ist nicht schlecht.


danke Rob für das Hinzufügen des codeAbschlags, ich war zu ungeduldig und dumm, um ihn zu finden
;-)

4

So machen Sie dasselbe ohne Schnittstellen für eine Reihe von Funktionen:

class NameFuncPair
{
    public String name;                // name each func
    void   f(String x) {}              // stub gets overridden
    public NameFuncPair(String myName) { this.name = myName; }
}

public class ArrayOfFunctions
{
    public static void main(String[] args)
    {
        final A a = new A();
        final B b = new B();

        NameFuncPair[] fArray = new NameFuncPair[]
        {
            new NameFuncPair("A") { @Override void f(String x) { a.g(x); } },
            new NameFuncPair("B") { @Override void f(String x) { b.h(x); } },
        };

        // Go through the whole func list and run the func named "B"
        for (NameFuncPair fInstance : fArray)
        {
            if (fInstance.name.equals("B"))
            {
                fInstance.f(fInstance.name + "(some args)");
            }
        }
    }
}

class A { void g(String args) { System.out.println(args); } }
class B { void h(String args) { System.out.println(args); } }

1
Warum? Dies ist komplizierter als zuvor vorgeschlagene Lösungen, für die lediglich eine anonyme Funktionsdefinition pro Alternative erforderlich ist. Alternativ erstellen Sie eine Klasse und eine anonyme Funktionsdefinition. Schlimmer noch, dies geschieht an zwei verschiedenen Stellen im Code. Möglicherweise möchten Sie eine Begründung für die Verwendung dieses Ansatzes liefern.
ToolmakerSteve


3

Wow, warum nicht einfach eine Delegate-Klasse erstellen, die nicht allzu schwer ist, da ich dies bereits für Java getan habe, und damit Parameter übergeben, bei denen T der Rückgabetyp ist. Es tut mir leid, aber als C ++ / C # -Programmierer im Allgemeinen, der gerade Java lernt, brauche ich Funktionszeiger, weil sie sehr praktisch sind. Wenn Sie mit einer Klasse vertraut sind, die sich mit Methodeninformationen befasst, können Sie dies tun. In Java-Bibliotheken wäre das java.lang.reflect.method.

Wenn Sie immer eine Schnittstelle verwenden, müssen Sie diese immer implementieren. Bei der Ereignisbehandlung gibt es keinen besseren Weg, um sich von der Liste der Handler zu registrieren / die Registrierung aufzuheben, als für Delegaten, bei denen Sie Funktionen und nicht den Werttyp übergeben müssen, sodass eine Delegatenklasse diese für Outclasses einer Schnittstelle handhabt.


Keine nützliche Antwort, es sei denn, Sie zeigen Codedetails an. Wie hilft das Erstellen einer Delegate-Klasse? Welcher Code wird pro Alternative benötigt?
ToolmakerSteve

2

Keine der Java 8-Antworten hat ein vollständiges, zusammenhängendes Beispiel gegeben.

Deklarieren Sie die Methode, die den "Funktionszeiger" akzeptiert, wie folgt:

void doCalculation(Function<Integer, String> calculation, int parameter) {
    final String result = calculation.apply(parameter);
}

Rufen Sie es auf, indem Sie der Funktion einen Lambda-Ausdruck geben:

doCalculation((i) -> i.toString(), 2);


1

Seit Java8 können Sie Lambdas verwenden, die auch Bibliotheken in der offiziellen SE 8-API enthalten.

Verwendung: Sie müssen eine Schnittstelle mit nur einer abstrakten Methode verwenden. Erstellen Sie eine Instanz davon (möglicherweise möchten Sie die bereits bereitgestellte Java SE 8 verwenden) wie folgt:

Function<InputType, OutputType> functionname = (inputvariablename) {
... 
return outputinstance;
}

Weitere Informationen finden Sie in der Dokumentation: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html


1

Vor Java 8 war eine anonyme Klasse der nächste Ersatz für funktionszeigerähnliche Funktionen. Zum Beispiel:

Collections.sort(list, new Comparator<CustomClass>(){
    public int compare(CustomClass a, CustomClass b)
    {
        // Logic to compare objects of class CustomClass which returns int as per contract.
    }
});

Aber jetzt haben wir in Java 8 eine sehr nette Alternative, die als Lambda-Ausdruck bekannt ist und verwendet werden kann als:

list.sort((a, b) ->  { a.isBiggerThan(b) } );

Dabei ist isBiggerThan eine Methode in CustomClass. Wir können hier auch Methodenreferenzen verwenden:

list.sort(MyClass::isBiggerThan);
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.