Pregunta

Tengo un método que consta de aproximadamente diez líneas de código.Quiero crear más métodos que hagan exactamente lo mismo, excepto por un pequeño cálculo que cambiará una línea de código.Esta es una aplicación perfecta para pasar un puntero de función para reemplazar esa línea, pero Java no tiene punteros de función.¿Cuál es mi mejor alternativa?

¿Fue útil?

Solución

Clase interna anónima

Supongamos que desea que se pase una función con un String parámetro que devuelve un int.
Primero debe definir una interfaz con la función como su único miembro, si no puede reutilizar una existente.

interface StringFunction {
    int func(String param);
}

Un método que toma el puntero simplemente aceptaría StringFunction ejemplo así:

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

Y se llamaría así:

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

EDITAR: En Java 8, puedes llamarlo con una expresión lambda:

ref.takingMethod(param -> bodyExpression);

Otros consejos

Para cada "puntero de función", crearía un pequeño clase funtor que implementa su cálculo.Defina una interfaz que implementarán todas las clases y pase instancias de esos objetos a su función más amplia.Esta es una combinación de "patrón de comando", y "patrón de estrategia".

El ejemplo de @sblundy es bueno.

Cuando hay una cantidad predefinida de cálculos diferentes que puede realizar en esa línea, usar una enumeración es una forma rápida pero clara de implementar un patrón de estrategia.

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

Obviamente, la declaración del método de estrategia, así como exactamente una instancia de cada implementación, están definidas en una única clase/archivo.

Debe crear una interfaz que proporcione las funciones que desea transmitir.p.ej:

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

}

Luego, cuando necesites pasar una función, puedes implementar esa interfaz:

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

Finalmente, la función de mapa utiliza lo pasado en la Función1 de la siguiente manera:

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

A menudo puedes usar Runnable en lugar de tu propia interfaz si no necesitas pasar parámetros, o puedes usar varias otras técnicas para hacer que el parámetro cuente menos "fijo", pero generalmente es una compensación con la seguridad de tipos.(O puede anular el constructor de su objeto de función para pasar los parámetros de esa manera...Hay muchos enfoques y algunos funcionan mejor en determinadas circunstancias).

Referencias de métodos utilizando el :: operador

Puede utilizar referencias a métodos en los argumentos del método donde el método acepta una interfaz funcional.Una interfaz funcional es cualquier interfaz que contiene sólo un método abstracto.(Una interfaz funcional puede contener uno o más métodos predeterminados o métodos estáticos).

IntBinaryOperator es una interfaz funcional.Su método abstracto, applyAsInt, acepta dos ints como sus parámetros y devuelve un int. Math.max también acepta dos ints y devuelve un int.En este ejemplo, A.method(Math::max); marcas parameter.applyAsInt enviar sus dos valores de entrada a Math.max y devolver el resultado de eso 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);
    }
}

En general, puedes utilizar:

method1(Class1::method2);

en lugar de:

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

que es la abreviatura de:

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

Para más información, ver ::Operador (dos dos puntos) en Java 8 y Especificación del lenguaje Java §15.13.

También puedes hacer esto (que en algunos EXTRAÑO ocasiones tiene sentido).El problema (y es un gran problema) es que se pierde toda la seguridad de tipos al usar una clase/interfaz y hay que lidiar con el caso en el que el método no existe.

Tiene el "beneficio" de que puede ignorar las restricciones de acceso y llamar a métodos privados (no se muestran en el ejemplo, pero puede llamar a métodos que el compilador normalmente no le permitiría llamar).

Nuevamente, es raro que esto tenga sentido, pero en esas ocasiones es una buena herramienta.

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

Si tiene solo una línea que es diferente, puede agregar un parámetro como una bandera y una declaración if (bandera) que llame a una línea u otra.

Quizás también le interese conocer el trabajo que se está realizando para Java 7 que involucra cierres:

¿Cuál es el estado actual de los cierres en Java?

http://gafter.blogspot.com/2006/08/closures-for-java.html
http://tech.puredanger.com/java7/#closures

Nuevo Java 8 Interfaces funcionales y Referencias de métodos utilizando el :: operador.

Java 8 es capaz de mantener referencias de métodos ( MyClass::new ) con "@ Interfaz funcional"punteros.No es necesario el mismo nombre de método, solo se requiere la misma firma de método.

Ejemplo:

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

Entonces, ¿qué tenemos aquí?

  1. Interfaz funcional: esta es la interfaz, anotada o no con @Interfaz funcional, que contiene sólo una declaración de método.
  2. Referencias de métodos: esta es solo una sintaxis especial, se ve así, instancia de objeto::nombremétodo, nada más y nada menos.
  3. Ejemplo de uso: solo un operador de asignación y luego una llamada al método de interfaz.

¡DEBES UTILIZAR INTERFACES FUNCIONALES SÓLO PARA OYENTES Y SÓLO PARA ESO!

Porque todos los demás punteros de funciones son realmente malos para la legibilidad y la capacidad de comprensión del código.Sin embargo, las referencias directas a métodos a veces resultan útiles, con foreach por ejemplo.

Hay varias interfaces funcionales predefinidas:

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

Para versiones anteriores de Java, debería probar Guava Libraries, que tiene una funcionalidad y una sintaxis similares, como Adrian Petrescu mencionó anteriormente.

Para investigaciones adicionales consulte Hoja de referencia de Java 8

y gracias a El chico del sombrero por Especificación del lenguaje Java §15.13 enlace.

La respuesta de @sblundy es excelente, pero las clases internas anónimas tienen dos pequeños defectos: el principal es que tienden a no ser reutilizables y el secundario es una sintaxis voluminosa.

Lo bueno es que su patrón se expande a clases completas sin ningún cambio en la clase principal (la que realiza los cálculos).

Cuando creas una instancia de una nueva clase, puedes pasar parámetros a esa clase que pueden actuar como constantes en tu ecuación, por lo que si una de tus clases internas tiene este aspecto:

f(x,y)=x*y

pero a veces necesitas uno que sea:

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

y tal vez un tercero que es:

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

en lugar de crear dos clases internas anónimas o agregar un parámetro de "transferencia", puede crear una única clase REAL que instanciará como:

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

Simplemente almacenaría la constante en la clase y la usaría en el método especificado en la interfaz.

De hecho, si SABE que su función no se almacenará/reutilizará, puede hacer esto:

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

Pero las clases inmutables son más seguras; no puedo encontrar una justificación para hacer que una clase como esta sea mutable.

Realmente solo publico esto porque me estremezco cada vez que escucho una clase interna anónima. He visto mucho código redundante que era "Obligatorio" porque lo primero que hizo el programador fue volverse anónimo cuando debería haber usado una clase real y nunca. replanteó su decisión.

El Google bibliotecas de guayaba, que se están volviendo muy populares, tienen un carácter genérico Función y Predicado objetan que han trabajado en muchas partes de su API.

Me parece un patrón estratégico.Consulte los patrones Java de Fluffycat.com.

Una de las cosas que realmente extraño cuando programo en Java son las devoluciones de llamadas de funciones.Una situación en la que la necesidad de estos seguía presentándose era en el procesamiento recursivo de jerarquías en las que se desea realizar alguna acción específica para cada elemento.Como recorrer un árbol de directorios o procesar una estructura de datos.El minimalista que llevo dentro odia tener que definir una interfaz y luego una implementación para cada caso específico.

Un día me pregunté ¿por qué no?Tenemos punteros a métodos: el objeto Método.Con la optimización de los compiladores JIT, la invocación reflexiva ya no conlleva una gran penalización de rendimiento.Y además de, por ejemplo, copiar un archivo de una ubicación a otra, el costo de la invocación del método reflejado resulta insignificante.

Mientras pensaba más en ello, me di cuenta de que una devolución de llamada en el paradigma de programación orientada a objetos requiere vincular un objeto y un método: ingrese el objeto de devolución de llamada.

Consulte mi solución basada en reflexiones para Devoluciones de llamada en Java.Gratis para cualquier uso.

Vale, este hilo ya tiene bastante edad, así que muy probablemente mi respuesta no es útil para la pregunta.Pero como este hilo me ayudó a encontrar la solución, lo publicaré aquí de todos modos.

Necesitaba usar un método estático variable con entrada y salida conocidas (ambas doble).Entonces, conociendo el paquete del método y su nombre, podría trabajar de la siguiente manera:

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

para una función que acepta un doble como parámetro.

Entonces, en mi situación concreta lo inicialicé con

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

y lo invocó más tarde en una situación más compleja 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);

donde la actividad es un valor doble arbitrario.Estoy pensando en hacer esto un poco más abstracto y generalizarlo, como lo ha hecho SoftwareMonkey, pero actualmente estoy bastante contento con la forma en que está.Tres líneas de código, no se necesitan clases ni interfaces, eso no está tan mal.

Para hacer lo mismo sin interfaces para una variedad de funciones:

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

Echa un vistazo a lambdaj

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

y en particular su nueva característica de cierre

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

y encontrará una forma muy legible de definir el cierre o el puntero de función sin crear una interfaz sin sentido ni utilizar clases internas desagradables.

Vaya, ¿por qué no simplemente crear una clase Delegate que no es tan difícil dado que ya lo hice para Java y usarla para pasar el parámetro donde T es el tipo de retorno?Lo siento, pero como programador de C++/C# en general estoy aprendiendo Java, necesito punteros de función porque son muy útiles.Si está familiarizado con alguna clase que trate con información de métodos, puede hacerlo.En las bibliotecas de Java, eso sería java.lang.reflect.method.

Si siempre usas una interfaz, siempre tienes que implementarla.En el manejo de eventos, realmente no hay una mejor manera de registrarse o cancelar el registro de la lista de controladores, pero para los delegados donde necesita pasar funciones y no el tipo de valor, crear una clase de delegado para manejarlo supera a una interfaz.

Ninguna de las respuestas de Java 8 ha dado un ejemplo completo y coherente, así que aquí viene.

Declare el método que acepta el "puntero de función" de la siguiente manera:

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

Llámelo proporcionando a la función una expresión lambda:

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

Si alguien tiene dificultades para pasar una función que toma un conjunto de parámetros para definir su comportamiento pero otro conjunto de parámetros para ejecutar, como el de Scheme:

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

ver Función de paso con comportamiento definido por parámetros en Java

Desde Java8, puedes usar lambdas, que también tienen bibliotecas en la API oficial de SE 8.

Uso:Necesita utilizar una interfaz con un solo método abstracto.Haga una instancia de él (es posible que desee utilizar el que Java SE 8 ya proporciona) como este:

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

Para más información consulte la documentación: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

Antes de Java 8, el sustituto más cercano a la funcionalidad similar a un puntero de función era una clase anónima.Por ejemplo:

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

Pero ahora en Java 8 tenemos una alternativa muy interesante conocida como expresión lambda, que se puede utilizar como:

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

donde isBiggerThan es un método en CustomClass.También podemos usar referencias de métodos aquí:

list.sort(MyClass::isBiggerThan);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top