Frage

Ich habe eine Methode, die etwa zehn Zeilen Code. Ich möchte mehr Methoden erstellen, die genau das gleiche tun, bis auf eine kleine Berechnung, die eine Zeile Code ändern wird. Dies ist eine perfekte Anwendung für in einem Funktionszeiger vorbei, dass eine Zeile zu ersetzen, aber Java nicht Funktionszeiger hat. Was ist meine beste Alternative?

War es hilfreich?

Lösung

Anonyme innere Klasse

Angenommen, Sie möchten eine Funktion haben, mit einem String param übergeben, die eine int zurückgibt.
Zuerst müssen Sie eine Schnittstelle mit der Funktion als einziges Mitglied definieren, wenn Sie einen vorhandenen nicht wiederverwenden können.

interface StringFunction {
    int func(String param);
}

Ein Verfahren, das den Zeiger nimmt würde nur StringFunction Instanz akzeptiert wie folgt:

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

Und würde wie so genannt werden:

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

EDIT: In Java 8, können Sie es mit einem Lambda-Ausdruck nennen könnte:

ref.takingMethod(param -> bodyExpression);

Andere Tipps

Für jede „Funktionszeiger“, würde ich eine kleine Funktor Klasse dass implementiert Ihre Berechnung. Definieren Sie eine Schnittstelle, die alle Klassen implementieren, und Instanzen dieser Objekte in Ihre größere Funktion übergeben. Dies ist eine Kombination aus den " Befehlsmustern " und "strategy Muster “.

@ sblundy Das Beispiel ist gut.

Wenn es eine vorgegebene Anzahl von verschiedenen Berechnungen ist man in dieser einer Zeile zu tun, eine ENUM verwendet, ist eine schnelle und doch klare Art und Weise ein Strategie-Muster 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 ist die Strategie Methodendeklaration, als auch genau eine Instanz jeder Implementierung alle in einer einzigen Klasse / Datei definiert werden.

Sie müssen eine Schnittstelle schaffen, die die Funktion bereitstellt (s), die Sie um übergeben wollen. zB:

/**
 * 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 zu ü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 Function1 bestanden 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 der eigenen Schnittstelle, wenn Sie nicht in Parametern übergeben müssen, oder können Sie verschiedene andere Techniken verwenden, die param Zählung weniger „fest“ zu machen, aber es ist in der Regel ein Kompromiss mit Typ-Sicherheit . (Sie können auch den Konstruktor für Ihre Funktionsobjekt außer Kraft setzen in den params auf diese Weise zu passieren .. es gibt viele Ansätze, und einige besser funktionieren unter bestimmten Umständen.)

Methodenreferenzen mit dem :: Operator

Sie können Methode Referenzen in Methodenargumente verwenden, bei denen das Verfahren eine funktionale Schnittstelle akzeptiert . Eine funktionale Schnittstelle ist jede Oberfläche, die nur eine abstrakte Methode enthält. (A funktionale Schnittstelle enthalten kann, eine oder mehr Standardmethoden oder statische Methoden.)

IntBinaryOperator ist eine funktionelle Schnittstelle. Seine abstrakte Methode, applyAsInt , akzeptiert zwei ints als Parameter und gibt einen int. Math.max auch nimmt zwei ints und gibt eine int. In diesem Beispiel macht A.method(Math::max); parameter.applyAsInt seine beiden Eingangswerte sendet das Ergebnis dieser Math.max Math.max und zurück.

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

In der Regel können Sie:

method1(Class1::method2);

statt:

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 :: (Doppelpunkt) Operator in Java 8 und Java Language Specification §15.13 .

Sie können dies auch tun (was in einigen RARE Gelegenheiten macht Sinn). Das Problem (und es ist ein großes Problem) ist, dass Sie alle Typsicherheit der Verwendung einer Klasse / Schnittstelle verlieren und Sie haben mit dem Fall befassen, wo die Methode nicht existiert.

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

Auch hier ist es ein seltener Fall, dass dies Sinn macht, aber bei diesen Gelegenheiten ist es ein nettes Tool zu haben.

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

Wenn Sie nur eine Zeile, die unterschiedlich ist ein Parameter hinzufügen könnte wie eine Fahne und ein if (Flag) Aussage, die eine Zeile aufruft oder das andere.

Neue Java 8 Funktionelle Grenzflächen und Methode Referenzen mit dem :: Operator.

Java 8 sind in der Lage Methode Referenzen (MyClass :: neu) mit " @ Functional Schnittstelle " Zeiger zu halten. Es gibt keine Notwendigkeit für gleiche Methodennamen, nur gleiche Methode Unterschrift erforderlich ist.

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. Funktions Schnittstelle -. Dies ist Schnittstelle, mit Anmerkungen versehen oder nicht mit @FunctionalInterface , die nur eine Methode Erklärung enthält
  2. Methode Referenzen -. Dies ist nur eine spezielle Syntax, sieht wie folgt aus, ObjectInstance :: methoden , nicht mehr und nicht weniger
  3. Anwendungsbeispiel -. Nur ein Zuweisungsoperator und dann Schnittstelle Methodenaufruf

Sie sollten funktionalen Schnittstellen für die Zuhörer NUR UND NUR FÜR DAS!

Da alle anderen derartigen Funktionszeiger sind wirklich schlecht für die Lesbarkeit des Codes und für die Fähigkeit zu verstehen. Aber manchmal direkte Methode Referenzen kommen praktisch, mit foreach zum Beispiel.

Es gibt mehr vordefinierte Funktions 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 ältere Java-Versionen sollten Sie Guava Bibliotheken versuchen, die ähnliche Funktionalität und Syntax, wie Adrian Petrescu oben erwähnt hat.

Für weitere Forschung Blick auf Java 8 Spickzettel

und dank der Typ mit dem Hut für die Java Language Specification §15.13 Link.

@ sblundy Antwort ist groß, aber anonyme innere Klassen haben zwei kleine Fehler, die primären sind, dass sie nicht wiederverwendbar zu neigen und die Sekundär ist eine sperrige Syntax.

Das Schöne daran ist, dass sein Muster ohne irgendeine Änderung in der Hauptklasse in volle Klassen erweitert (die die Berechnungen durchführen).

Wenn Sie instanziiert eine neue Klasse von Parametern in dieser Klasse übergeben kann, die als Konstanten in der Gleichung wirken kann - so, wenn einer Ihrer inneren Klassen wie folgt aussehen:

f(x,y)=x*y

aber manchmal müssen Sie eine, die lautet:

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

und vielleicht ein drittes, das ist:

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

statt zwei anonyme innere Klassen zu machen oder einen „Pass-Through“ Parameter hinzufügen, können Sie eine einzige IST- Klasse, die Sie instanziieren wie:

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äre einfach speichern Sie die Konstante in der Klasse und in der Methode in der Schnittstelle angegeben verwenden.

In der Tat, wenn wissen, dass Ihre Funktion wird nicht gespeichert / wiederverwendet, könnten Sie dies 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 nicht mit einer Begründung aufwarten kann eine Klasse wie dieses wandelbar machen

ich wirklich nur diese Posten, weil ich zurückschrecken, wenn ich anonyme innere Klasse hören - ich habe eine Menge redundanten Code zu sehen, die „erforderlich“, weil das erste, was der Programmierer tat, war anonym war gehen, wenn er ein tatsächliches verwendet haben sollte Klasse und nie überdachte seine Entscheidung.

Die Google Guava Bibliotheken , die sind immer sehr beliebt, haben eine generische Funktion und Prädikats einwenden, dass sie in viele Teile ihrer API gearbeitet haben.

Klingt wie ein Strategie-Muster für mich. Schauen Sie sich fluffycat.com Java-Muster.

Eines der Dinge, die ich wirklich vermissen, wenn in Java programmiert ist Funktion Rückrufe. Eine Situation, in der die Notwendigkeit für diese selbst gehalten zu präsentieren war in rekursiven Verarbeitung Hierarchien, wo Sie einige spezifische Aktion für jedes Element ausgeführt werden sollen. Wie ein Verzeichnisbaum zu Fuß, oder die Verarbeitung einer Datenstruktur. Die minimalistisch in mir hasse mit einer Schnittstelle zu definieren und dann einer Implementierung für jeden spezifischen Fall.

Eines Tages fand ich frage mich, warum nicht? Wir haben Methodenzeiger - das Method-Objekt. Mit der Optimierung mehr JIT-Compiler, reflektierendes Aufruf nicht wirklich eine große Leistungseinbuße führen. Und außerdem neben, sagen wir, eine Datei von einem Ort zum anderen zu kopieren, sind die Kosten des reflektierten Methodenaufruf verblasst.

Wie ich mehr darüber nachgedacht, erkennen ich, dass ein Rückruf in dem OOP Paradigma erfordert ein Objekt binden und ein Verfahren zusammen -. Das Callback-Objekts eingeben

Überprüfen Sie meine Reflexion basierte Lösung aus für Rückrufe in Java . Kostenlos für jeden Einsatz.

oK, dieser Thread ist schon alt genug, so dass sehr wahrscheinlich ist meine Antwort auf die Frage nicht hilfreich. Aber da dieses Themas mir geholfen, meine Lösung zu finden, werde ich es hier sowieso löschte.

I benötigt, um eine variable statische Methode mit bekannten Eingangs zu verwenden und bekannten Ausgangs (beide double ). Also dann, wohl wissend, die Methode Paket und den Namen, ich konnte arbeiten wie folgt:

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

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

Also, in meiner konkreten Situation, die ich initialisiert es mit

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

und rief sie 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);

, wo die Aktivität ist ein beliebiger doppelter Wert. Ich denke an die vielleicht dieses etwas abstraktere tun und es zu verallgemeinern, wie SoftwareMonkey getan hat, aber zur Zeit bin ich glücklich genug, um mit dem so ist. Drei Zeilen Code, keine Klassen und Schnittstellen notwendig, das ist nicht so schlimm.

das Gleiche zu tun, 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); } }

Überprüfen Sie heraus lambdaj

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

und insbesondere die neue Verschlussmerkmal

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

und Sie werden eine sehr lesbare Art und Weise zu definieren, Schließung oder Funktionszeiger ohne die Schaffung bedeutungs Schnittstelle oder verwenden hässliche innere Klassen

finden

Wow, warum nicht nur eine Delegate-Klasse erstellen, die alle nicht so schwer, da ich bereits für Java haben und verwenden Sie es in Parameter zu übergeben, wo T Rückgabetyp ist. Es tut mir leid, aber als C ++ / C # Programmierer in der Regel nur zu lernen, Java, ich brauche Funktionszeiger, weil sie sehr handlich sind. Wenn Sie mit einer beliebigen Klasse vertraut sind, die mit Methode Informationen beschäftigt können Sie es tun. In Java-Bibliotheken, die Methode von java.lang.reflect würde.

Wenn Sie immer eine Schnittstelle verwenden, haben Sie immer, sie umzusetzen. In Ereignisbehandlung gibt es wirklich keinen besseren Weg, um die Registrierung / Deregistrierung von der Liste der Handler, aber für die Teilnehmer, wo Sie in Funktionen übergeben müssen und nicht der Werttyp, einen Delegierten Klasse macht es zu handhaben für eine Schnittstelle deklassiert.

Keine der Java 8 Antworten hat ein vollständiges, zusammenhängendes Beispiel gegeben, so ist es hier geht.

Deklarieren die Methode für die „Funktionszeiger“ annimmt, wie folgt:

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

Nennen Sie es, indem Sie die Funktion mit einem Lambda-Ausdruck bereitstellt:

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

Wenn jemand kämpft, um eine Funktion zu übergeben, die einen Satz von Parametern nimmt sein Verhalten zu definieren, aber einen anderen Satz von Parametern, auf denen auszuführen, wie Scheme:

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

finden Sie unter Pass-Funktion mit Parameter-Defined Verhalten in Java

Da Java8 können Sie lambdas verwenden, die auch Bibliotheken in der offiziellen SE 8 API haben.

Verwendung: Sie müssen eine Schnittstelle mit nur einem abstrakten Methode verwenden. Machen Sie eine Instanz davon (vielleicht möchten Sie die eine Java SE 8 bereits nutzen) wie folgt aus:

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

Weitere Informationen Kasse in der Dokumentation: https://docs.oracle .com / JavaSE / tutorial / java / javaOO / lambdaexpressions.html

Vor der Java 8, nächster Ersatz für Funktionszeiger-ähnliche Funktionalität war eine anonyme Klasse. 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 in Java 8 haben wir eine sehr saubere Alternative bekannt als Lambda Ausdruck , die als verwendet werden können:

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

wo isBiggerThan ist ein Verfahren, in CustomClass. Wir können auch Verfahren Referenzen verwenden hier:

list.sort(MyClass::isBiggerThan);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top