programacion - Método de paso de Java como parámetro
programacion ats java poo (14)
Aquí hay un ejemplo básico:
public class TestMethodPassing
{
private static void println()
{
System.out.println("Do println");
}
private static void print()
{
System.out.print("Do print");
}
private static void performTask(BasicFunctionalInterface functionalInterface)
{
functionalInterface.performTask();
}
@FunctionalInterface
interface BasicFunctionalInterface
{
void performTask();
}
public static void main(String[] arguments)
{
performTask(TestMethodPassing::println);
performTask(TestMethodPassing::print);
}
}
Salida:
Do println
Do print
Estoy buscando una manera de pasar un método por referencia. Entiendo que Java no pasa los métodos como parámetros, sin embargo, me gustaría obtener una alternativa.
Me han dicho que las interfaces son la alternativa a pasar los métodos como parámetros, pero no entiendo cómo una interfaz puede actuar como un método por referencia. Si entiendo correctamente, una interfaz es simplemente un conjunto abstracto de métodos que no están definidos. No quiero enviar una interfaz que deba definirse cada vez porque varios métodos diferentes podrían llamar al mismo método con los mismos parámetros.
Lo que me gustaría lograr es algo similar a esto:
public void setAllComponents(Component[] myComponentArray, Method myMethod) {
for (Component leaf : myComponentArray) {
if (leaf instanceof Container) { //recursive call if Container
Container node = (Container) leaf;
setAllComponents(node.getComponents(), myMethod);
} //end if node
myMethod(leaf);
} //end looping through components
}
invocado como:
setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
Desde Java 8 hay una interfaz de Function<T, R>
( docs ), que tiene el método
R apply(T t);
Puede usarlo para pasar funciones como parámetros a otras funciones. T es el tipo de entrada de la función, R es el tipo de retorno.
En su ejemplo, necesita pasar una función que toma el tipo de Component
como una entrada y no devuelve nada: Void
. En este caso, la Function<T, R>
no es la mejor opción, ya que no hay autoajuste del tipo Void. La interfaz que está buscando se llama Consumer<T>
( docs ) con el método
void accept(T t);
Se vería así:
public void setAllComponents(Component[] myComponentArray, Consumer<Component> myMethod) {
for (Component leaf : myComponentArray) {
if (leaf instanceof Container) {
Container node = (Container) leaf;
setAllComponents(node.getComponents(), myMethod);
}
myMethod.accept(leaf);
}
}
Y lo llamarías usando referencias de métodos:
setAllComponents(this.getComponents(), this::changeColor);
setAllComponents(this.getComponents(), this::changeSize);
Suponiendo que ha definido los métodos changeColor () y changeSize () en la misma clase.
Si su método acepta más de un parámetro, puede usar BiFunction<T, U, R>
- T y U son tipos de parámetros de entrada y R es el tipo de retorno. También hay BiConsumer<T, U>
(dos argumentos, sin tipo de retorno). Desafortunadamente para 3 y más parámetros de entrada, debes crear una interfaz por ti mismo. Por ejemplo:
public interface Function4<A, B, C, D, R> {
R apply(A a, B b, C c, D d);
}
En Java 8, ahora puede pasar un método más fácilmente utilizando Lambda Expressions y Method References. Primero, algunos antecedentes: una interfaz funcional es una interfaz que tiene uno y solo un método abstracto, aunque puede contener cualquier número de métodos predeterminados (nuevos en Java 8) y métodos estáticos. Una expresión lambda puede implementar rápidamente el método abstracto, sin toda la sintaxis innecesaria necesaria si no usa una expresión lambda.
Sin expresiones lambda:
obj.aMethod(new AFunctionalInterface() {
@Override
public boolean anotherMethod(int i)
{
return i == 982
}
});
Con expresiones lambda:
obj.aMethod(i -> i == 982);
Aquí hay un extracto del tutorial de Java sobre Lambda Expressions :
Sintaxis de las expresiones Lambda
Una expresión lambda consiste en lo siguiente:
Una lista separada por comas de parámetros formales encerrados entre paréntesis. El método CheckPerson.test contiene un parámetro, p, que representa una instancia de la clase Person.
Nota : Puede omitir el tipo de datos de los parámetros en una expresión lambda. Además, puede omitir los paréntesis si solo hay un parámetro. Por ejemplo, la siguiente expresión lambda también es válida:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
La ficha de flecha,
->
Un cuerpo, que consiste en una sola expresión o un bloque de declaración. Este ejemplo usa la siguiente expresión:
p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25
Si especifica una sola expresión, entonces el tiempo de ejecución de Java evalúa la expresión y luego devuelve su valor. Alternativamente, puede utilizar una declaración de retorno:
p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; }
Una declaración de retorno no es una expresión; en una expresión lambda, debe incluir instrucciones entre llaves ({}). Sin embargo, no tiene que incluir una invocación de método void entre llaves. Por ejemplo, la siguiente es una expresión lambda válida:
email -> System.out.println(email)
Tenga en cuenta que una expresión lambda se parece mucho a una declaración de método; puede considerar las expresiones lambda como métodos anónimos, métodos sin nombre.
Aquí es cómo puede "pasar un método" usando una expresión lambda:
interface I {
public void myMethod(Component component);
}
class A {
public void changeColor(Component component) {
// code here
}
public void changeSize(Component component) {
// code here
}
}
class B {
public void setAllComponents(Component[] myComponentArray, I myMethodsInterface) {
for(Component leaf : myComponentArray) {
if(leaf instanceof Container) { // recursive call if Container
Container node = (Container)leaf;
setAllComponents(node.getComponents(), myMethodInterface);
} // end if node
myMethodsInterface.myMethod(leaf);
} // end looping through components
}
}
class C {
A a = new A();
B b = new B();
public C() {
b.setAllComponents(this.getComponents(), component -> a.changeColor(component));
b.setAllComponents(this.getComponents(), component -> a.changeSize(component));
}
}
La clase C
puede reducirse incluso un poco más mediante el uso de referencias de métodos como las siguientes:
class C {
A a = new A();
B b = new B();
public C() {
b.setAllComponents(this.getComponents(), a::changeColor);
b.setAllComponents(this.getComponents(), a::changeSize);
}
}
Java tiene un mecanismo para pasar el nombre y llamarlo. Es parte del mecanismo de reflexión. Su función debe tomar el parámetro adicional de la clase Método.
public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
La última vez que lo comprobé, Java no es capaz de hacer de forma nativa lo que quieres; tiene que usar ''soluciones'' para evitar tales limitaciones. Por lo que veo, las interfaces son una alternativa, pero no una buena alternativa. Tal vez quien te dijo que eso significaba algo como esto:
public interface ComponentMethod {
public abstract void PerfromMethod(Container c);
}
public class ChangeColor implements ComponentMethod {
@Override
public void PerfromMethod(Container c) {
// do color change stuff
}
}
public class ChangeSize implements ComponentMethod {
@Override
public void PerfromMethod(Container c) {
// do color change stuff
}
}
public void setAllComponents(Component[] myComponentArray, ComponentMethod myMethod) {
for (Component leaf : myComponentArray) {
if (leaf instanceof Container) { //recursive call if Container
Container node = (Container) leaf;
setAllComponents(node.getComponents(), myMethod);
} //end if node
myMethod.PerfromMethod(leaf);
} //end looping through components
}
Que luego invocarías con:
setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
No creo que las lambdas estén destinadas a esto ... Java no es un lenguaje de programación funcional y nunca lo será, no pasamos métodos como parámetros. Dicho esto, recuerde que Java está orientado a objetos y con eso en mente podemos hacer lo que queramos. La primera idea es simplemente pasar un "objeto que contiene un método" como parámetro. Entonces, cuando necesite "pasar" el método, simplemente pase una instancia de esa clase. Tenga en cuenta que cuando defina el método, debe agregar como parámetro una instancia de la clase que contiene el método. Esto debería funcionar pero no es lo que queremos porque no puede redefinir el método a menos que tenga acceso al código de clase y eso no es posible en muchos casos; además, creo que si alguien necesita pasar un método como parámetro, es porque el comportamiento del método debería ser dinámico. Lo que quiero decir es que el programador que usa sus clases debería poder elegir qué método debería devolver pero no su tipo. Por suerte para nosotros, Java tiene una solución hermosa y simple: clases abstractas. Las clases abstractas, en pocas palabras, se usan cuando conoce la "firma" de un método "pero no su comportamiento ... Puede incluir el nombre y el tipo del método en una clase abstracta y pasar una instancia de esa clase como una parámetro para el método ... Espere ... ¿no es lo mismo que antes? ¿Y puede tener una instancia de una clase abstracta? No y No ... pero también sí ... cuando crea un método abstracto También TIENE QUE redefinirlo en una clase que extienda la clase abstracta y debido al enlace dinámico de Java, Java siempre (a menos que lo declare estático, privado y algunas otras cosas) usará la versión redefinida de la misma. Este es un ejemplo ... Supongamos que queremos aplicar una función a una serie de números: si queremos cuadrar la entrada-salida debería tener este aspecto [1,2,3,4, ...] -> [1,4,9,16 , ...] (en un lenguaje de programación funcional como haskell esto es fácil gracias a algunas herramientas como ''map'', ...). Tenga en cuenta que no hay nada especial en cuadrar números, podríamos aplicar cualquier función que queramos t Entonces el código debería ser algo como esto [args], f -> [f (args)]. Back to java => una función es solo un método, así que lo que queremos es una función que aplique otra función a una matriz. En pocas palabras, necesitamos pasar un método como parámetro. Así es como lo haría ==>
1) DEFINIR LA CLASE ABSTRACTA DEL ENVOLVENTE Y EL MÉTODO
public abstract class Function
{
public abstract double f(double x);
}
2) DEFINE LA CLASE CON EL MÉTODO APPLY_TO_ARRAY
public class ArrayMap
{
public static double[] apply_to_array(double[] arr, Function fun)
{
for(int i=0; i<arr.length;i++)
{
arr[i]=fun.f(arr[i]);
}
return arr;
}
}
3) CREAR UNA CLASE DE PRUEBA Y TENER ALGO DE DIVERSIÓN
public class Testclass extends Function
{
public static void main(String[] args)
{
double[] myarr = {1,2,3,4};
ArrayMap.apply_to_array(myarr, new Testclass());
for (double k : myarr)
{
System.out.println(k);
}
}
@Override
public double f(double x)
{
return Math.log(x);
}
}
Tenga en cuenta que debemos pasar un objeto de tipo Function y como Testclass amplía la clase de Función que podemos usar, el cast es automático.
No encontré ningún ejemplo lo suficientemente explícito para mí sobre cómo usar java.util.function.Function
para un método simple como función de parámetro. Aquí hay un ejemplo simple:
import java.util.function.Function;
public class Foo {
private Foo(String parameter) {
System.out.println("I''m a Foo " + parameter);
}
public static Foo method(final String parameter) {
return new Foo(parameter);
}
private static Function parametrisedMethod(Function<String, Foo> function) {
return function;
}
public static void main(String[] args) {
parametrisedMethod(Foo::method).apply("from a method");
}
}
Básicamente tienes un objeto Foo
con un constructor por defecto. Un method
que se llamará como un parámetro desde el method
parametrisedMethod
que es de tipo Function<String, Foo>
.
-
Function<String, Foo>
significa que la función toma unaString
como parámetro y devuelve unFoo
. - El
Foo::Method
corresponde a una lambda comox -> Foo.method(x);
-
parametrisedMethod(Foo::method)
podría verse comox -> parametrisedMethod(Foo.method(x))
- El
.apply("from a method")
es básicamente para realizarparametrisedMethod(Foo.method("from a method"))
Que luego volverá en la salida:
>> I''m a Foo from a method
El ejemplo debería ejecutarse tal como está, luego puede probar cosas más complicadas de las respuestas anteriores con diferentes clases e interfaces.
No soy un experto en java pero resuelvo tu problema de esta manera:
@FunctionalInterface
public interface AutoCompleteCallable<T> {
String call(T model) throws Exception;
}
Defino el parámetro en mi interfaz especial
public <T> void initialize(List<T> entries, AutoCompleteCallable getSearchText) {.......
//call here
String value = getSearchText.call(item);
...
}
Finalmente, implemento el método getSearchText al llamar al método de inicialización .
initialize(getMessageContactModelList(), new AutoCompleteCallable() {
@Override
public String call(Object model) throws Exception {
return "custom string" + ((xxxModel)model.getTitle());
}
})
Primero defina una interfaz con el método que desea pasar como parámetro
public interface Callable {
public void call(int param);
}
Implementar una clase con el método.
class Test implements Callable {
public void call(int param) {
System.out.println( param );
}
}
// invocar asi
Callable cmd = new Test();
Esto le permite pasar cmd como parámetro e invocar la llamada de método definida en la interfaz
public invoke( Callable callable ) {
callable.call( 5 );
}
Si bien esto aún no es válido para Java 7 y versiones anteriores, creo que deberíamos mirar hacia el futuro y al menos reconocer los cambios que se presentarán en nuevas versiones, como Java 8.
A saber, esta nueva versión trae lambdas y referencias de métodos a Java (junto con las nuevas API , que son otra solución válida para este problema. Aunque aún requieren una interfaz, no se crean nuevos objetos, y los archivos de clase adicionales no necesitan contaminar los directorios de salida debido a diferentes Manejo por parte de la JVM.
Ambos sabores (lambda y referencia de método) requieren una interfaz disponible con un solo método cuya firma se utiliza:
public interface NewVersionTest{
String returnAString(Object oIn, String str);
}
Los nombres de los métodos no importarán de aquí en adelante. Cuando se acepta un lambda, una referencia de método es también. Por ejemplo, para usar nuestra firma aquí:
public static void printOutput(NewVersionTest t, Object o, String s){
System.out.println(t.returnAString(o, s));
}
Esto es solo una simple invocación de interfaz, hasta que se pasa la lambda 1 :
public static void main(String[] args){
printOutput( (Object oIn, String sIn) -> {
System.out.println("Lambda reached!");
return "lambda return";
}
);
}
Esto dará como resultado:
Lambda reached!
lambda return
Las referencias de los métodos son similares. Dado:
public class HelperClass{
public static String testOtherSig(Object o, String s){
return "real static method";
}
}
y principal:
public static void main(String[] args){
printOutput(HelperClass::testOtherSig);
}
La salida sería real static method
. Las referencias de los métodos pueden ser estáticas, de instancia, no estáticas con instancias arbitrarias e incluso de constructores . Para el constructor se usaría algo similar a ClassName::new
.
1 Esto no se considera un lambda por algunos, ya que tiene efectos secundarios. Sin embargo, ilustra el uso de uno de una manera más sencilla de visualizar.
Si no necesita estos métodos para devolver algo, podría hacer que devuelvan objetos Ejecutables.
private Runnable methodName (final int arg){
return new Runnable(){
public void run(){
// do stuff with arg
}
}
}
Entonces úsalo como:
private void otherMethodName (Runnable arg){
arg.run();
}
Use el patrón de observador (a veces también llamado patrón de oyente):
interface ComponentDelegate {
void doSomething(Component component);
}
public void setAllComponents(Component[] myComponentArray, ComponentDelegate delegate) {
// ...
delegate.doSomething(leaf);
}
setAllComponents(this.getComponents(), new ComponentDelegate() {
void doSomething(Component component) {
changeColor(component); // or do directly what you want
}
});
new ComponentDelegate()...
declara un tipo anónimo que implementa la interfaz.
Utilice el objeto java.lang.reflect.Method
y llame a invoke
Edición : a partir de Java 8, las expresiones lambda son una buena solución, como han señalado other answers . La respuesta a continuación fue escrita para Java 7 y anteriores ...
Echa un vistazo al patrón de comando .
// NOTE: code not tested, but I believe this is valid java...
public class CommandExample
{
public interface Command
{
public void execute(Object data);
}
public class PrintCommand implements Command
{
public void execute(Object data)
{
System.out.println(data.toString());
}
}
public static void callCommand(Command command, Object data)
{
command.execute(data);
}
public static void main(String... args)
{
callCommand(new PrintCommand(), "hello world");
}
}
Edición: como señala Pete Kirkham , hay otra forma de hacerlo utilizando un Visitor . El enfoque de los visitantes es un poco más complicado (todos los nodos deben estar al tanto de los visitantes con un método acceptVisitor()
, pero si necesita recorrer un gráfico de objetos más complejo, vale la pena examinarlo.