webentwicklung-frage-antwort-db.com.de

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

Ich habe eine Methode, die etwa zehn Codezeilen ist. 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 hat keine Funktionszeiger. Was ist meine beste Alternative?

299
Bill the Lizard

Anonyme innere Klasse

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

interface StringFunction {
    int func(String param);
}

Eine Methode, die den Zeiger verwendet, akzeptiert die Instanz StringFunction wie folgt:

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önnte man es mit einem Lambda-Ausdruck aufrufen:

ref.takingMethod(param -> bodyExpression);
269
sblundy

Für jeden "Funktionszeiger" würde ich ein kleines Funktorklasse erstellen, das 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 " Strategie-Muster ".

@sblundys Beispiel ist gut.

32
Blair Conrad

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 übersichtliche 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);
}

Die Strategie-Methodendeklaration sowie genau eine Instanz jeder Implementierung sind offensichtlich alle in einer einzelnen Klasse/Datei definiert.

28
javashlook

Sie müssen eine Schnittstelle erstellen, die die Funktionen 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 Zuordnungsfunktion die in Function1 übergebene Funktion 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 oft Runnable anstelle Ihrer eigenen Schnittstelle verwenden, wenn Sie keine Parameter übergeben müssen, oder Sie können verschiedene andere Techniken verwenden, um die Parameteranzahl weniger "fest" zu machen, aber dies ist normalerweise ein Kompromiss mit der Typensicherheit. (Oder Sie können den Konstruktor für Ihr Funktionsobjekt überschreiben, um die Parameter auf diese Weise zu übergeben. Es gibt viele Ansätze, und einige funktionieren unter bestimmten Umständen besser.)

24
rcreswick

Methodenreferenzen mit dem Operator ::

Sie können Methodenreferenzen in Methodenargumenten verwenden, in denen die Methode eine Funktionsschnittstelle 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.)

IntBinaryOperator ist eine funktionale Schnittstelle. Die abstrakte Methode applyAsInt akzeptiert zwei ints als Parameter und gibt ein int zurück. Math.max akzeptiert auch zwei ints und gibt ein int zurück. In diesem Beispiel veranlasst A.method(Math::max);, dass parameter.applyAsInt Seine beiden Eingabewerte an Math.max Sendet und das Ergebnis dieses Math.max Zurückgibt.

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));

was ist kurz für:

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

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

17

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 gesamte Typensicherheit der Verwendung einer Klasse/Schnittstelle verlieren und sich mit dem Fall befassen müssen, in dem die Methode nicht existiert.

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).

Wiederum ist dies selten sinnvoll, aber in solchen 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);
    }
}
15
TofuBeer

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

13
Peter Lawrey
12
Dave L.

New Java 8 Functional Interfaces and Method References using the :: Operator.

Java 8 kann Methodenreferenzen (MyClass :: new) mit " @ Functional Interface" - Zeigern 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. Functional Interface - Dies ist eine Schnittstelle, die mit @ FunctionalInterface versehen ist und nur eine Methodendeklaration enthält.
  2. Methodenreferenzen - das ist nur eine spezielle Syntax, sieht so aus, objectInstance :: methodName, nicht mehr und nicht weniger.
  3. Anwendungsbeispiel - nur ein Zuweisungsoperator und dann ein Schnittstellenmethodenaufruf.

SIE SOLLTEN FUNKTIONALE SCHNITTSTELLEN NUR FÜR ZUHÖRER UND NUR FÜR DIESE BENUTZEN!

Denn alle anderen derartigen Funktionszeiger sind für die Lesbarkeit des Codes und für die Verständlichkeit sehr schlecht. Manchmal sind jedoch direkte Methodenreferenzen hilfreich, beispielsweise für foreach.

Es gibt mehrere vordefinierte Funktionsschnittstellen:

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 Libraries ausprobieren, die ähnliche Funktionen und Syntax haben, wie Adrian Petrescu oben erwähnt hat.

Weitere Informationen finden Sie unter Java 8 Cheatsheet

und danke an The Guy with The Hat für den Java Language Specification §15.1 Link.

11
user3002379

Die Antwort von @sblundy ist großartig, aber anonyme innere Klassen weisen zwei kleine Fehler auf. Der erste besteht darin, dass sie in der Regel nicht wiederverwendbar sind, und der zweite besteht aus einer umfangreichen Syntax.

Das Schöne daran ist, dass sich sein Muster zu vollständigen Klassen erweitert, ohne dass sich die Hauptklasse ändert (die, die die Berechnungen durchführt).

Wenn Sie eine neue Klasse instanziieren, können Sie dieser Klasse Parameter ü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 braucht man eines, das ist:

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

und vielleicht ein dritter das ist:

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);

Die Konstante wird einfach in der Klasse gespeichert und in der in der Schnittstelle angegebenen Methode verwendet.

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 mir keine Rechtfertigung dafür ausdenken, eine Klasse wie diese veränderlich zu machen.

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

9
Bill K

Die Google Guava-Bibliotheken , die immer beliebter werden, haben ein generisches Funktion und Prädikat Objekt, mit dem sie in vielen Teilen ihrer API gearbeitet haben.

6
Adrian Petrescu

oK, dieser Thread ist bereits alt genug, daher ist sehr wahrscheinlich meine Antwort für die Frage nicht hilfreich. 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 verwenden (beide double). 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 doppelter Wert 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 der Art und Weise, wie es ist, zufrieden genug. Drei Codezeilen, keine Klassen und Interfaces notwendig, das ist nicht schlecht.

4
yogibimbi

Eines der Dinge, die ich beim Programmieren in Java) wirklich vermisse, sind Funktionsrückrufe. Eine Situation, in der sich diese immer wieder zeigten, war die rekursive Verarbeitung von Hierarchien, in denen Sie für jedes Element eine bestimmte Aktion ausführen möchten Als würde man einen Verzeichnisbaum durchgehen oder eine Datenstruktur bearbeiten. “Der Minimalist in mir hasst es, eine Schnittstelle und dann eine Implementierung für jeden spezifischen Fall zu definieren.

Eines Tages fragte ich mich, warum nicht? Wir haben Methodenzeiger - das Method-Objekt. Durch die Optimierung von JIT-Compilern ist das Aufrufen von Reflections nicht mehr mit einer enormen Leistungseinbuße verbunden. Und abgesehen davon, dass beispielsweise eine Datei von einem Speicherort an einen anderen kopiert wird, werden die Kosten für den Aufruf der reflektierten Methode unwichtig.

Als ich darüber nachdachte, stellte ich fest, dass für einen Rückruf im Paradigma OOP ein Objekt und eine Methode zusammengebunden werden müssen - geben Sie das Callback-Objekt ein.

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

4
Lawrence Dol

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); } }
4
vwvan

Klingt für mich nach einem Strategiemuster. Schauen Sie sich fluffycat.com Java patterns an.

4
Dennis S

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

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

3
Robert

Schauen Sie sich Lambdaj an

http://code.google.com/p/lambdaj/

und insbesondere das neue Verschlussmerkmal

http://code.google.com/p/lambdaj/wiki/Closures

und Sie werden eine sehr gut lesbare Möglichkeit finden, Schließungs- oder Funktionszeiger zu definieren, ohne bedeutungslose Schnittstellen zu erstellen oder hässliche innere Klassen zu verwenden

3
Mario Fusco

Keine der Java 8 Antworten hat ein vollständiges, zusammenhängendes Beispiel gegeben, also kommt es hier.

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);
2
mrts

Vor Java 8 war der nächste Ersatz für funktionszeigerähnliche Funktionen eine anonyme Klasse. 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 in Java 8 haben wir eine sehr nette Alternative, bekannt als Lambda-Ausdruck , die 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);
1
i_am_zero

Wenn jemand Schwierigkeiten hat, eine Funktion zu übergeben, die einen Satz von Parametern zur Definition ihres Verhaltens benötigt, aber einen anderen Satz von Parametern, auf denen sie ausgeführt werden soll, wie z.

(define (function scalar1 scalar2)
  (lambda (x) (* x scalar1 scalar2)))

siehe Funktion mit parametrisiertem Verhalten in Java übergeben

1
Scott Emmons

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):

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
Alex