Punteros de función en Java

148

Esto puede ser algo común y trivial, pero parece que tengo problemas para encontrar una respuesta concreta. En C # hay un concepto de delegados, que se relaciona fuertemente con la idea de punteros de función de C ++. ¿Existe una funcionalidad similar en Java? Dado que los punteros están algo ausentes, ¿cuál es la mejor manera de hacerlo? Y para ser claros, estamos hablando de primera clase aquí.

grito de terror
fuente
1
Tengo curiosidad por saber por qué querrías esto donde los oyentes u otras construcciones de OOP harían la misma tarea mientras conservan su naturaleza de objeto. Quiero decir, entiendo la necesidad de la funcionalidad que ofrece el concepto, solo que se puede lograr con un objeto simple ... a menos que, por supuesto, me falte algo, ¡de ahí que haga esta pregunta! :-)
Newtopian
2
Java 8 tiene expresiones lambda: docs.oracle.com/javase/tutorial/java/javaOO/… Es posible que desee comprobar eso. No es exactamente un puntero de función, pero aún podría ser más útil.
FrustratedWithFormsDesigner
66
Las referencias del método Java 8 es exactamente lo que está pidiendo.
Steven
8
Las referencias del método Java 8 es exactamente lo que está pidiendo. this::myMethodes semánticamente lo mismo que crear una lambda paramA, paramB -> this.myMethod(paramA, paramB).
Steven

Respuestas:

126

El lenguaje de Java para la funcionalidad de puntero de función es una clase anónima que implementa una interfaz, por ejemplo

Collections.sort(list, new Comparator<MyClass>(){
    public int compare(MyClass a, MyClass b)
    {
        // compare objects
    }
});

Actualización: lo anterior es necesario en las versiones de Java anteriores a Java 8. Ahora tenemos alternativas mucho mejores, a saber, lambdas:

list.sort((a, b) -> a.isGreaterThan(b));

y referencias de métodos:

list.sort(MyClass::isGreaterThan);
Michael Borgwardt
fuente
2
El patrón de diseño de la estrategia. No es tan feo como un puntero de función C o C ++ cuando desea que la función use algunos datos no globales que no se proporcionan como argumento para la función.
Raedwald
75
@Raedwald C ++ tiene functores, y C ++ 0x tiene cierres y lambdas construidos sobre los functores. Incluso tiene std::bind, que une los parámetros a las funciones y devuelve un objeto invocable. No puedo defender C por estos motivos, pero C ++ es realmente mejor que Java para esto.
zneak
21
@zneak, @Raedwald: puede hacer esto en C pasando un puntero a los datos del usuario (por ejemplo: struct). (y puede encapsularlo con cada nuevo nivel de indirección de devolución de llamada) En realidad, es bastante limpio.
brice
25
Sí, en realidad tomaré punteros de función C sobre clases crudas de envoltura cualquier día
BT
3
Java 8 tiene una mejor sintaxis para esto.
Thorbjørn Ravn Andersen
65

Puede sustituir un puntero de función con una interfaz. Digamos que desea ejecutar una colección y hacer algo con cada elemento.

public interface IFunction {
  public void execute(Object o);
}

Esta es la interfaz que podríamos pasar a algunos, por ejemplo, CollectionUtils2.doFunc (Collection c, IFunction f).

public static void doFunc(Collection c, IFunction f) {
   for (Object o : c) {
      f.execute(o);
   }
}

Como ejemplo, digamos que tenemos una colección de números y le gustaría agregar 1 a cada elemento.

CollectionUtils2.doFunc(List numbers, new IFunction() {
    public void execute(Object o) {
       Integer anInt = (Integer) o;
       anInt++;
    }
});
Björn Raupach
fuente
42

Puedes usar la reflexión para hacerlo.

Pase como parámetro el objeto y el nombre del método (como una cadena) y luego invoque el método. Por ejemplo:

Object methodCaller(Object theObject, String methodName) {
   return theObject.getClass().getMethod(methodName).invoke(theObject);
   // Catch the exceptions
}

Y luego úsalo como en:

String theDescription = methodCaller(object1, "toString");
Class theClass = methodCaller(object2, "getClass");

Por supuesto, verifique todas las excepciones y agregue los moldes necesarios.

zoquete
fuente
77
La reflexión no es la respuesta correcta a nada, excepto "¿Cómo escribo código Java lento y poco fiable": D
Jacob
55
Puede ser "feo" y lento, pero creo que la reflexión permite hacer cosas que la solución basada en la interfaz en la respuesta aceptada no puede hacer (a menos que me equivoque), es decir, llamar a varios métodos del mismo objeto dentro del mismo código parte.
Eusebio el
@zoquete ... Estaba leyendo esta publicación y tenía una duda ... así que en su enfoque, si accedo a la variable "theDescription", ¿se llamará a la función cada vez que se acceda a la variable?
karthik27
20

No, las funciones no son objetos de primera clase en Java. Puede hacer lo mismo implementando una clase de controlador: así es como se implementan las devoluciones de llamada en Swing, etc.

Sin embargo, hay propuestas de cierres (el nombre oficial de lo que estás hablando) en futuras versiones de Java: Javaworld tiene un artículo interesante.

jwoolard
fuente
15

Esto nos recuerda la Ejecución de Steve Yegge en el Reino de los sustantivos . Básicamente establece que Java necesita un objeto para cada acción y, por lo tanto, no tiene entidades "solo verbales" como punteros de función.

Yuval F
fuente
8

Para lograr una funcionalidad similar, puede usar clases internas anónimas.

Si tuviera que definir una interfaz Foo:

interface Foo {
    Object myFunc(Object arg);
}

Cree un método barque recibirá un 'puntero de función' como argumento:

public void bar(Foo foo) {
    // .....
    Object object = foo.myFunc(argValue);
    // .....
}

Finalmente llame al método de la siguiente manera:

bar(new Foo() {
    public Object myFunc(Object arg) {
        // Function code.
    }
}
Kingamajick
fuente
7

Java8 ha introducido lambdas y referencias de métodos . Entonces, si su función coincide con una interfaz funcional (puede crear la suya propia), puede usar una referencia de método en este caso.

Java proporciona un conjunto de interfaces funcionales comunes . mientras que podrías hacer lo siguiente:

public class Test {
   public void test1(Integer i) {}
   public void test2(Integer i) {}
   public void consumer(Consumer<Integer> a) {
     a.accept(10);
   }
   public void provideConsumer() {
     consumer(this::test1);   // method reference
     consumer(x -> test2(x)); // lambda
   }
}
Alex
fuente
¿Existe el concepto de un alias: por ejemplo public List<T> collect(T t) = Collections::collect
javadba
@javadba por lo que no sé.
Alex
5

No existe tal cosa en Java. Deberá ajustar su función en algún objeto y pasar la referencia a ese objeto para pasar la referencia al método en ese objeto.

Sintácticamente, esto se puede facilitar en cierta medida mediante el uso de clases anónimas definidas en el lugar o clases anónimas definidas como variables miembro de la clase.

Ejemplo:

class MyComponent extends JPanel {
    private JButton button;
    public MyComponent() {
        button = new JButton("click me");
        button.addActionListener(buttonAction);
        add(button);
    }

    private ActionListener buttonAction = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // handle the event...
            // note how the handler instance can access 
            // members of the surrounding class
            button.setText("you clicked me");
        }
    }
}
VoidPointer
fuente
3

He implementado el soporte de devolución de llamada / delegado en Java usando la reflexión. Los detalles y la fuente de trabajo están disponibles en mi sitio web .

Cómo funciona

Tenemos una clase principal llamada Callback con una clase anidada llamada WithParms. La API que necesita la devolución de llamada tomará un objeto de devolución de llamada como parámetro y, si es necesario, creará un Callback.WithParms como una variable de método. Dado que muchas de las aplicaciones de este objeto serán recursivas, esto funciona de manera muy limpia.

Dado que el rendimiento sigue siendo una gran prioridad para mí, no quería que se me pidiera que creara una matriz de objetos desechables para contener los parámetros para cada invocación; después de todo, en una estructura de datos grande podría haber miles de elementos y en un procesamiento de mensajes escenario podríamos terminar procesando miles de estructuras de datos por segundo.

Para ser seguro para subprocesos, la matriz de parámetros debe existir de manera única para cada invocación del método API, y para mayor eficacia, se debe usar el mismo para cada invocación de la devolución de llamada; Necesitaba un segundo objeto que sería barato de crear para vincular la devolución de llamada con una matriz de parámetros para la invocación. Pero, en algunos escenarios, el invocador ya tendría una matriz de parámetros por otros motivos. Por estas dos razones, la matriz de parámetros no pertenecía al objeto Callback. Además, la elección de la invocación (pasando los parámetros como una matriz o como objetos individuales) pertenece a la API utilizando la devolución de llamada que le permite utilizar la invocación que mejor se adapte a su funcionamiento interno.

La clase anidada WithParms, entonces, es opcional y tiene dos propósitos: contiene la matriz de objetos de parámetros necesarios para las invocaciones de devolución de llamada, y proporciona 10 métodos invoke () sobrecargados (con 1 a 10 parámetros) que cargan la matriz de parámetros y luego invocar el objetivo de devolución de llamada.

Lawrence Dol
fuente
-1

En relación con la mayoría de las personas aquí, soy nuevo en Java, pero como no he visto una sugerencia similar, tengo otra alternativa para sugerir. No estoy seguro de si es una buena práctica o no, o incluso sugerí antes y simplemente no lo entendí. Simplemente me gusta porque creo que es descriptivo.

 /*Just to merge functions in a common name*/
 public class CustomFunction{ 
 public CustomFunction(){}
 }

 /*Actual functions*/
 public class Function1 extends CustomFunction{
 public Function1(){}
 public void execute(){...something here...}
 }

 public class Function2 extends CustomFunction{
 public Function2(){}
 public void execute(){...something here...}
 }

 .....
 /*in Main class*/
 CustomFunction functionpointer = null;

luego, dependiendo de la aplicación, asigne

 functionpointer = new Function1();
 functionpointer = new Function2();

etc.

y llama por

 functionpointer.execute();
ozgeneral
fuente