Domanda

Ho un metodo che comprende una decina di righe di codice. Voglio creare più metodi che facciano esattamente la stessa cosa, ad eccezione di un piccolo calcolo che cambierà una riga di codice. Questa è un'applicazione perfetta per passare un puntatore a funzione per sostituire quella riga, ma Java non ha puntatori a funzione. Qual è la mia migliore alternativa?

È stato utile?

Soluzione

Classe interna anonima

Supponi di voler passare una funzione con un parametro String che restituisce un int .
Per prima cosa devi definire un'interfaccia con la funzione come unico membro, se non puoi riutilizzarne una esistente.

interface StringFunction {
    int func(String param);
}

Un metodo che utilizza il puntatore accetterebbe l'istanza StringFunction in questo modo:

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

E si chiamerebbe così:

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

EDIT: In Java 8, potresti chiamarlo con un'espressione lambda:

ref.takingMethod(param -> bodyExpression);

Altri suggerimenti

Per ogni "puntatore a funzione", creerei una piccola classe di funzioni che implementa il tuo calcolo. Definisci un'interfaccia che verranno implementate da tutte le classi e passa le istanze di quegli oggetti nella tua funzione più grande. Questa è una combinazione di " modello di comando " ;, e " modello di strategia " ;.

L'esempio di @ sblundy è buono.

Quando esiste un numero predefinito di calcoli diversi che è possibile eseguire in quella riga, l'utilizzo di un enum è un modo rapido ma chiaro per implementare un modello di strategia.

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

Ovviamente, la dichiarazione del metodo di strategia, così come esattamente un'istanza di ogni implementazione, sono tutti definiti in una singola classe / file.

Devi creare un'interfaccia che fornisca le funzioni che vuoi passare. ad esempio:

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

}

Quindi, quando è necessario passare una funzione, è possibile implementare tale interfaccia:

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

Infine, la funzione map utilizza il passato in Function1 come segue:

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

Spesso puoi usare Runnable al posto della tua interfaccia se non hai bisogno di passare parametri, oppure puoi usare varie altre tecniche per rendere il conteggio dei parametri meno "riparato". ma di solito è un compromesso con la sicurezza del tipo. (Oppure puoi sovrascrivere il costruttore affinché il tuo oggetto funzione passi i parametri in quel modo .. ci sono molti approcci e alcuni funzionano meglio in determinate circostanze.)

Riferimenti ai metodi utilizzando l'operatore ::

È possibile utilizzare i riferimenti di metodo negli argomenti del metodo in cui il metodo accetta una interfaccia funzionale . Un'interfaccia funzionale è qualsiasi interfaccia che contiene solo un metodo astratto. (Un'interfaccia funzionale può contenere uno o più metodi predefiniti o metodi statici.)

IntBinaryOperator è un'interfaccia funzionale. Il suo metodo astratto, applyAsInt , accetta due int come parametri e restituisce un int . Math.max accetta anche due int se restituisce un int . In questo esempio, A.method (Math :: max); fa in modo che parameter.applyAsInt invii i suoi due valori di input a Math.max e ritorni il risultato di quel 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);
    }
}

In generale, puoi usare:

method1(Class1::method2);

anziché:

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

che è l'abbreviazione di:

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

Per ulteriori informazioni, vedere :: operatore (due punti) in Java 8 e Specifica del linguaggio Java §15.13 .

Puoi anche farlo (che in alcune occasioni RARE ha senso). Il problema (ed è un grosso problema) è che perdi tutta la sicurezza tipica dell'uso di una classe / interfaccia e devi affrontare il caso in cui il metodo non esiste.

Ha il "vantaggio" che è possibile ignorare le restrizioni di accesso e chiamare metodi privati ??(non mostrato nell'esempio, ma è possibile chiamare metodi che il compilatore normalmente non consente di chiamare).

Ancora una volta, è un caso raro che questo abbia un senso, ma in quelle occasioni è uno strumento piacevole da avere.

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

Se hai solo una linea diversa, puoi aggiungere un parametro come un flag e un'istruzione if (flag) che chiama una linea o l'altra.

New Java 8 Interfacce funzionali e Riferimenti ai metodi utilizzando l'operatore :: .

Java 8 è in grado di mantenere i riferimenti ai metodi (MyClass :: new) con " @ Interfaccia funzionale " puntatori. Non è necessario lo stesso nome del metodo, è richiesta solo la stessa firma del metodo.

Esempio:

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

Quindi, cosa abbiamo qui?

  1. Interfaccia funzionale: si tratta di un'interfaccia, annotata o meno con @FunctionalInterface , che contiene solo una dichiarazione del metodo.
  2. Riferimenti ai metodi - questa è solo una sintassi speciale, assomiglia a questa, objectInstance :: methodName , niente di più niente di meno.
  3. Esempio di utilizzo - solo un operatore di assegnazione e quindi interfaccia chiamata al metodo.

DEVI UTILIZZARE LE INTERFACCE FUNZIONALI SOLO PER GLI ASCOLTI E SOLO PER QUELLO!

Perché tutti gli altri puntatori a tali funzioni sono davvero dannosi per la leggibilità del codice e per la capacità di comprensione. Tuttavia, i riferimenti ai metodi diretti a volte sono utili, ad esempio foreach.

Esistono diverse interfacce funzionali predefinite:

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

Per le versioni precedenti di Java, dovresti provare Guava Libraries, che ha funzionalità e sintassi simili, come Adrian Petrescu ha menzionato sopra.

Per ulteriori ricerche, consulta Cheatsheet di Java 8

e grazie a The Guy with The Hat per Link alle specifiche del linguaggio Java §15.13 .

La risposta di @ sblundy è ottima, ma le classi interne anonime hanno due piccoli difetti, il principale è che tendono a non essere riutilizzabili e il secondario è una sintassi voluminosa.

La cosa bella è che il suo modello si espande in classi complete senza alcun cambiamento nella classe principale (quella che esegue i calcoli).

Quando crei un'istanza di una nuova classe puoi passare parametri in quella classe che può agire come costanti nella tua equazione, quindi se una delle tue classi interne appare così:

f(x,y)=x*y

ma a volte hai bisogno di uno che sia:

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

e forse un terzo che è:

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

anziché creare due classi interne anonime o aggiungere un "passthrough" parametro, puoi creare una singola classe ACTUAL che crei un'istanza come:

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

Memorizzerebbe semplicemente la costante nella classe e la userebbe nel metodo specificato nell'interfaccia.

In effetti, se CONOSCI che la tua funzione non verrà memorizzata / riutilizzata, potresti farlo:

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

Ma le classi immutabili sono più sicure - non posso trovare una giustificazione per rendere una classe come questa mutabile.

Pubblico davvero questo solo perché mi sento rabbrividito ogni volta che sento una classe interna anonima - ho visto un sacco di codice ridondante che era " Richiesto " perché la prima cosa che ha fatto il programmatore è diventata anonima quando avrebbe dovuto usare una classe reale e non ha mai ripensato alla sua decisione.

Le librerie di Guava di Google, che stanno diventando molto popolari, hanno un Funzione e Predicato oggetto che hanno lavorato in molte parti della loro API.

Mi sembra un modello strategico. Dai un'occhiata ai modelli Java di fluffycat.com.

Una delle cose che mi manca davvero quando si programma in Java sono i callback di funzioni. Una situazione in cui la necessità di questi continuava a presentarsi era nell'elaborazione ricorsiva di gerarchie in cui si desidera eseguire alcune azioni specifiche per ciascun elemento. Come camminare su un albero di directory o elaborare una struttura di dati. Il minimalista dentro di me odia dover definire un'interfaccia e quindi un'implementazione per ogni caso specifico.

Un giorno mi sono ritrovato a chiedermi perché no? Abbiamo puntatori a metodi: l'oggetto Method. Con l'ottimizzazione dei compilatori JIT, l'invocazione riflessiva in realtà non comporta più un'enorme penalità di prestazione. E oltre a, per esempio, copiare un file da una posizione all'altra, il costo dell'invocazione del metodo riflesso impallidisce.

Mentre ci pensavo di più, mi sono reso conto che un callback nel paradigma OOP richiede l'associazione di un oggetto e un metodo insieme - inserire l'oggetto Callback.

Scopri la mia soluzione basata sulla riflessione per Callbacks in Java . Gratuito per qualsiasi uso.

ok, questa discussione è già abbastanza vecchia, quindi molto probabilmente la mia risposta non è utile per la domanda. Ma poiché questo thread mi ha aiutato a trovare la mia soluzione, la metterò comunque qui.

Avevo bisogno di usare un metodo statico variabile con input noto e output noto (entrambi doppio ). Quindi, conoscendo il pacchetto e il nome del metodo, ho potuto lavorare come segue:

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

per una funzione che accetta un doppio come parametro.

Quindi, nella mia situazione concreta, l'ho inizializzato con

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

e lo ha invocato successivamente in una situazione più complessa con

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

dove attività è un doppio valore arbitrario. Sto forse pensando di farlo in modo un po 'più astratto e di generalizzarlo, come ha fatto SoftwareMonkey, ma attualmente sono abbastanza contento di come è. Tre righe di codice, senza classi e interfacce necessarie, non è poi così male.

Per fare la stessa cosa senza interfacce per una serie di funzioni:

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

Dai un'occhiata a lambdaj

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

e in particolare la sua nuova funzione di chiusura

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

e troverai un modo molto leggibile per definire il puntatore di chiusura o funzione senza creare un'interfaccia insignificante o usare brutte classi interne

Caspita, perché non creare semplicemente una classe delegata che non è poi così difficile dato che l'ho già fatto per Java e usarla per passare il parametro dove T è di tipo restituito. Mi dispiace, ma come programmatore C ++ / C # in generale solo imparando Java, ho bisogno di puntatori di funzione perché sono molto utili. Se hai familiarità con qualsiasi classe che si occupa delle informazioni sul metodo, puoi farlo. Nelle librerie Java sarebbe java.lang.reflect.method.

Se usi sempre un'interfaccia, devi sempre implementarla. Nella gestione degli eventi non c'è davvero un modo migliore per registrare / annullare la registrazione dall'elenco dei gestori ma per i delegati in cui è necessario passare funzioni e non il tipo di valore, rendendo una classe delegata per gestirla per surclassare un'interfaccia.

Nessuna delle risposte di Java 8 ha fornito un esempio completo e coerente, quindi ecco che arriva.

Dichiara il metodo che accetta il " puntatore a funzione " come segue:

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

Chiamalo fornendo alla funzione un'espressione lambda:

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

Se qualcuno fa fatica a passare una funzione che accetta un set di parametri per definirne il comportamento ma un altro set di parametri su cui eseguire, come quello di Scheme:

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

vedi Funzione pass con comportamento definito da parametri in Java

Da Java8, puoi usare lambdas, che ha anche librerie nell'API SE 8 ufficiale.

Utilizzo: È necessario utilizzare un'interfaccia con un solo metodo astratto. Creane un'istanza (potresti voler usare quella java SE 8 già fornita) in questo modo:

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

Per ulteriori informazioni, consulta la documentazione: https://docs.oracle .com / JavaSE / tutorial / java / javaOO / lambdaexpressions.html

Prima di Java 8, il sostituto più vicino alla funzionalità simile a un puntatore a funzione era una classe anonima. Ad esempio:

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

Ma ora in Java 8 abbiamo un'alternativa molto chiara conosciuta come lambda espressione , che può essere utilizzata come:

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

dove isBiggerThan è un metodo in CustomClass . Possiamo anche usare i riferimenti ai metodi qui:

list.sort(MyClass::isBiggerThan);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top