Método Java Pass como parâmetro

278

Eu estou procurando uma maneira de passar um método por referência. Entendo que Java não passa métodos como parâmetros, no entanto, gostaria de obter uma alternativa.

Disseram-me que as interfaces são a alternativa para passar métodos como parâmetros, mas não entendo como uma interface pode atuar como método por referência. Se bem entendi, uma interface é simplesmente um conjunto abstrato de métodos que não estão definidos. Não quero enviar uma interface que precise ser definida todas as vezes, porque vários métodos diferentes podem chamar o mesmo método com os mesmos parâmetros.

O que eu gostaria de realizar é algo semelhante a isto:

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());
Yojimbo
fonte
No momento, minha solução é passar um parâmetro adicional e usar uma caixa de seleção dentro para selecionar o método apropriado. No entanto, esta solução não leva tempo para reutilizar o código.
Veja também esta resposta stackoverflow.com/a/22933032/1010868 para pergunta semelhante
Tomasz Gawel

Respostas:

233

Edit : a partir do Java 8, as expressões lambda são uma boa solução, como outras respostas apontaram. A resposta abaixo foi escrita para Java 7 e versões anteriores ...


Dê uma olhada no padrão 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");
    }
}

Edit: como Pete Kirkham aponta , há outra maneira de fazer isso usando um Visitante . A abordagem do visitante é um pouco mais envolvida - todos os nós precisam estar cientes do visitante com um acceptVisitor()método - mas se você precisar percorrer um gráfico de objetos mais complexo, vale a pena examinar.

Dan Vinton
fonte
2
@ Mac - bom! este aparece repetidamente em idiomas sem métodos de primeira classe como a maneira de fato de simulá-los, então vale a pena lembrar.
Dan Vinton
7
É o padrão de visitante (separa a ação de iterar sobre uma coleção da função aplicada a cada membro da coleção), não o padrão de comando (encapsula os argumentos para uma chamada de método em um objeto). Você não está especificamente encapsulando o argumento - ele é fornecido pela parte da iteração do padrão do visitante.
Pete Kirkham
Não, você só precisa do método de aceitação se estiver combinando visitas com expedição dupla. Se você tem um visitante monomórfico, é exatamente o código que você tem acima.
Pete Kirkham
No Java 8, pode ser como ex.operS (String :: toLowerCase, "STRING"). Veja o belo artigo: studytrails.com/java/java8/…
Zon
Pete Kirkham está correto: seu código está implementando o padrão Visitor, não o padrão Command (e isso é bom, pois é o que o OP precisa.) Como Pete diz, você não está encapsulando o argumento, portanto não está executando Command - your Command A interface possui uma execução que aceita um parâmetro. Wikipedia não. Isso é fundamental para a intenção do padrão de comando. Como o primeiro parágrafo diz "encapsula todas as informações ... Essas informações incluem o nome do método, o objeto que possui o método e os valores dos parâmetros do método ".
Home
73

No Java 8, agora você pode passar um método mais facilmente usando Expressões e referências de método Lambda . Primeiro, alguns antecedentes: uma interface funcional é uma interface que possui um e apenas um método abstrato, embora possa conter qualquer número de métodos padrão (novos no Java 8) e métodos estáticos. Uma expressão lambda pode implementar rapidamente o método abstrato, sem toda a sintaxe desnecessária necessária se você não usar uma expressão lambda.

Sem expressões lambda:

obj.aMethod(new AFunctionalInterface() {
    @Override
    public boolean anotherMethod(int i)
    {
        return i == 982
    }
});

Com expressões lambda:

obj.aMethod(i -> i == 982);

Aqui está um trecho do tutorial Java sobre Expressões Lambda :

Sintaxe de expressões lambda

Uma expressão lambda consiste no seguinte:

  • Uma lista separada por vírgula de parâmetros formais entre parênteses. O método CheckPerson.test contém um parâmetro, p, que representa uma instância da classe Person.

    Nota : Você pode omitir o tipo de dados dos parâmetros em uma expressão lambda. Além disso, você pode omitir os parênteses se houver apenas um parâmetro. Por exemplo, a seguinte expressão lambda também é válida:

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
  • O token de seta, ->

  • Um corpo, que consiste em uma única expressão ou em um bloco de instruções. Este exemplo usa a seguinte expressão:

    p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25

    Se você especificar uma única expressão, o Java Runtime avaliará a expressão e retornará seu valor. Como alternativa, você pode usar uma declaração de retorno:

    p -> {
        return p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25;
    }

    Uma declaração de retorno não é uma expressão; em uma expressão lambda, você deve colocar instruções entre chaves ({}). No entanto, você não precisa incluir uma chamada de método nulo entre chaves. Por exemplo, a seguir é uma expressão lambda válida:

    email -> System.out.println(email)

Observe que uma expressão lambda se parece muito com uma declaração de método; você pode considerar expressões lambda como métodos anônimos - métodos sem nome.


Aqui está como você pode "passar um método" usando uma expressão 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));
    }
}

A classe Cpode ser reduzida ainda mais com o uso de referências de métodos como:

class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), a::changeColor);
        b.setAllComponents(this.getComponents(), a::changeSize);
    }
}
O cara com o chapéu
fonte
A classe A precisa ser herdada da interface?
Serob_b
1
@Serob_b Nope. A menos que você queira passá-lo como uma referência de método (consulte ::operador), não importa o que seja. a.changeThing(component)pode ser alterado para qualquer instrução ou bloco de código desejado, desde que retorne nulo.
O cara com o chapéu
29

Use o java.lang.reflect.Methodobjeto e chameinvoke

Vinodh Ramasubramanian
fonte
12
Não vejo por que não. A questão é passar um método como parâmetro e essa é uma maneira muito válida de fazê-lo. Isso também pode ser envolvido em qualquer número de padrões bonitos para torná-lo bom. E isso é tão genérico quanto possível, sem a necessidade de interfaces especiais.
Vinodh Ramasubramanian
3
Você digitou segurança em JavaScript fg? Segurança de tipo não é um argumento.
Danubian Sailor
13
Como a segurança de tipo não é um argumento quando o idioma em questão mantém a segurança de tipo como um de seus componentes mais fortes? Java é uma linguagem fortemente tipada, e essa digitação forte é uma das razões pelas quais você a escolhe em relação a outra linguagem compilada.
Adam Parkin
21
"O recurso principal de reflexão foi originalmente projetado para ferramentas do construtor de aplicativos baseadas em componentes. [...] Como regra, os objetos não devem ser acessados ​​reflexivamente em aplicativos normais em tempo de execução." Item 53: Preferir interfaces à reflexão, no Effective Java Second Edition. - Essa é a linha de pensamento dos criadores do Java ;-)
Wilhem Meignan
8
Não é um uso justificável de refletir. Estou horrorizado ao ver todos os votos positivos. o reflect nunca foi concebido para ser utilizado como um mecanismo de programação geral; use-o somente quando não houver outra solução limpa.
Home
22

Desde o Java 8, há uma Function<T, R>interface ( docs ), que possui o método

R apply(T t);

Você pode usá-lo para passar funções como parâmetros para outras funções. T é o tipo de entrada da função, R é o tipo de retorno.

No seu exemplo, você precisa passar uma função que aceita o Componenttipo como entrada e não retorna nada - Void. Nesse caso, Function<T, R>não é a melhor opção, pois não há caixa automática do tipo Void. A interface que você está procurando é chamada Consumer<T>( docs ) com o método

void accept(T t);

Seria assim:

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

E você poderia chamá-lo usando referências de método:

setAllComponents(this.getComponents(), this::changeColor);
setAllComponents(this.getComponents(), this::changeSize); 

Supondo que você definiu os métodos changeColor () e changeSize () na mesma classe.


Se o seu método aceitar mais de um parâmetro, você poderá usar BiFunction<T, U, R>- T e U sendo tipos de parâmetros de entrada e R sendo tipo de retorno. Também há BiConsumer<T, U>(dois argumentos, nenhum tipo de retorno). Infelizmente para 3 ou mais parâmetros de entrada, você deve criar uma interface sozinho. Por exemplo:

public interface Function4<A, B, C, D, R> {

    R apply(A a, B b, C c, D d);
}
JakubM
fonte
19

Primeiro defina uma interface com o método que você deseja passar como parâmetro

public interface Callable {
  public void call(int param);
}

Implementar uma classe com o método

class Test implements Callable {
  public void call(int param) {
    System.out.println( param );
  }
}

// Invoca assim

Callable cmd = new Test();

Isso permite que você passe o cmd como parâmetro e invoque a chamada de método definida na interface

public invoke( Callable callable ) {
  callable.call( 5 );
}
empilhador
fonte
1
Talvez você não precise criar sua própria interface, pois o java definiu muitas delas para você: docs.oracle.com/javase/8/docs/api/java/util/function/…
slim
@slim Ponto interessante, quão estáveis ​​são essas definições, elas devem ser usadas normalmente como você sugere, ou têm probabilidade de quebrar?
Manuel
@slim Na verdade, os documentos respondem que: "As interfaces neste pacote são interfaces funcionais de uso geral usadas pelo JDK e estão disponíveis para serem usadas também pelo código do usuário".
Manuel
14

Embora isso ainda não seja válido para o Java 7 e abaixo, acredito que devemos olhar para o futuro e, pelo menos, reconhecer as mudanças que virão em novas versões, como o Java 8.

Nomeadamente, esta nova versão traz lambdas e referências de método para Java (junto com novas APIs , que são outra solução válida para esse problema. Embora ainda exijam uma interface, nenhum novo objeto foi criado, e arquivos de classe extras não precisam poluir os diretórios de saída devido a diferentes manipulação pela JVM.

Ambos os tipos (referência lambda e método) requerem uma interface disponível com um único método cuja assinatura é usada:

public interface NewVersionTest{
    String returnAString(Object oIn, String str);
}

Os nomes dos métodos não serão importantes daqui em diante. Onde um lambda é aceito, uma referência de método também é. Por exemplo, para usar nossa assinatura aqui:

public static void printOutput(NewVersionTest t, Object o, String s){
    System.out.println(t.returnAString(o, s));
}

Esta é apenas uma invocação simples de interface, até que o lambda 1 seja passado:

public static void main(String[] args){
    printOutput( (Object oIn, String sIn) -> {
        System.out.println("Lambda reached!");
        return "lambda return";
    }
    );
}

Isso produzirá:

Lambda reached!
lambda return

As referências de método são semelhantes. Dado:

public class HelperClass{
    public static String testOtherSig(Object o, String s){
        return "real static method";
    }
}

e principal:

public static void main(String[] args){
    printOutput(HelperClass::testOtherSig);
}

a saída seria real static method. As referências de método podem ser estáticas, instâncias, não estáticas com instâncias arbitrárias e até construtores . Para o construtor, algo semelhante ClassName::newseria usado.

1 Isso não é considerado um lambda por alguns, pois tem efeitos colaterais. Ilustra, no entanto, o uso de um de uma maneira mais fácil de visualizar.

nanofarad
fonte
12

Na última vez que verifiquei, o Java não é capaz de fazer o que você quer; você precisa usar 'soluções alternativas' para contornar essas limitações. Na minha opinião, as interfaces SÃO uma alternativa, mas não uma boa alternativa. Talvez quem tenha lhe dito isso estava significando algo assim:

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
}

Com o qual você invocaria:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
user246091
fonte
6

Se você não precisar desses métodos para retornar algo, poderá fazê-los retornar objetos Runnable.

private Runnable methodName (final int arg) {
    return (new Runnable() {
        public void run() {
          // do stuff with arg
        }
    });
}

Em seguida, use-o como:

private void otherMethodName (Runnable arg){
    arg.run();
}
Smig
fonte
2

Não encontrei nenhum exemplo explícito o suficiente para mim sobre como usar o java.util.function.Functionmétodo simples como função de parâmetro. Aqui está um exemplo simples:

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

Basicamente, você tem um Fooobjeto com um construtor padrão. Um methodque será chamado como um parâmetro do parametrisedMethodqual é do tipo Function<String, Foo>.

  • Function<String, Foo>significa que a função usa a Stringcomo parâmetro e retorna a Foo.
  • O Foo::Methodcorresponde a um lambda comox -> Foo.method(x);
  • parametrisedMethod(Foo::method) poderia ser visto como x -> parametrisedMethod(Foo.method(x))
  • O .apply("from a method")é basicamente fazerparametrisedMethod(Foo.method("from a method"))

O qual retornará na saída:

>> I'm a Foo from a method

O exemplo deve estar sendo executado como está; você pode tentar coisas mais complicadas das respostas acima com diferentes classes e interfaces.

Sylhare
fonte
usar a chamada aplicar no android você precisa de uma api mínimo 24
Ines Belhouchet
1

Java tem um mecanismo para passar o nome e chamá-lo. Faz parte do mecanismo de reflexão. Sua função deve receber parâmetro adicional da classe Method.

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
David Gruzman
fonte
1

Eu não sou um especialista em java, mas resolvo seu problema assim:

@FunctionalInterface
public interface AutoCompleteCallable<T> {
  String call(T model) throws Exception;
}

Eu defino o parâmetro na minha interface especial

public <T> void initialize(List<T> entries, AutoCompleteCallable getSearchText) {.......
//call here
String value = getSearchText.call(item);
...
}

Por fim, implementei o método getSearchText ao chamar o método de inicialização .

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })
Sercan özen
fonte
Na verdade, é a melhor resposta e a maneira correta de fazê-lo. Merece mais +1
amdev 30/10/19
0

Use o padrão Observer (às vezes também chamado de padrão do Listener):

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 um tipo anônimo implementando a interface.

EricSchaefer
fonte
8
Este não é o padrão que você está procurando.
Pete Kirkham
1
O padrão do observador é sobre abstrair a capacidade de responder a uma mudança. O OP deseja abstrair a ação executada em cada item de uma coleção, longe do código que itera sobre a coleção, que é o padrão do visitante.
Pete Kirkham
O padrão Observador / Ouvinte é realmente o mesmo que o padrão Comando. Eles diferem apenas na intenção. O observador trata da notificação, enquanto o comando substitui as funções de primeira classe / lambdas. O visitante, por outro lado, é algo completamente diferente. Eu não acho que isso possa ser explicado em algumas frases, então dê uma olhada em en.wikipedia.org/wiki/Visitor_pattern
EricSchaefer
0

Aqui está um exemplo 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);
    }
}

Resultado:

Do println
Do print
BullyWiiPlaza
fonte
0

Não encontrei aqui nenhuma solução que mostre como passar o método com parâmetros vinculados a ele como parâmetro de um método. Abaixo está um exemplo de como você pode passar um método com valores de parâmetro já vinculados a ele.

  1. Etapa 1: Crie duas interfaces, uma com o tipo de retorno, outra sem. Java possui interfaces semelhantes, mas elas são de pouca utilidade prática porque não suportam o lançamento de exceções.


    public interface Do {
    void run() throws Exception;
    }


    public interface Return {
        R run() throws Exception;
    }
  1. Exemplo de como usamos as duas interfaces para agrupar a chamada de método na transação. Observe que passamos o método com parâmetros reais.


    //example - when passed method does not return any value
    public void tx(final Do func) throws Exception {
        connectionScope.beginTransaction();
        try {
            func.run();
            connectionScope.commit();
        } catch (Exception e) {
            connectionScope.rollback();
            throw e;
        } finally {
            connectionScope.close();
        }
    }

    //Invoke code above by 
    tx(() -> api.delete(6));

Outro exemplo mostra como passar um método que realmente retorna algo



        public  R tx(final Return func) throws Exception {
    R r=null;
    connectionScope.beginTransaction();
    try {
                r=func.run();
                connectionScope.commit();
            } catch (Exception e) {
                connectionScope.rollback();
                throw e;
            } finally {
                connectionScope.close();
            }
        return r;
        }
        //Invoke code above by 
        Object x= tx(() -> api.get(id));
Stan Sokolov
fonte
0

Exemplo de solução com reflexão, o método passado deve ser público

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class Program {
    int i;

    public static void main(String[] args) {
        Program   obj = new Program();    //some object

        try {
            Method method = obj.getClass().getMethod("target");
            repeatMethod( 5, obj, method );
        } 
        catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            System.out.println( e ); 
        }
    }

    static void repeatMethod (int times, Object object, Method method)
        throws IllegalAccessException, InvocationTargetException {

        for (int i=0; i<times; i++)
            method.invoke(object);
    }
    public void target() {                 //public is necessary
        System.out.println("target(): "+ ++i);
    }
}
zemiak
fonte
0

Aprecio as respostas acima, mas fui capaz de atingir o mesmo comportamento usando o método abaixo; uma ideia emprestada de retornos de chamada Javascript. Estou aberto à correção, embora até agora seja bom (em produção).

A idéia é usar o tipo de retorno da função na assinatura, o que significa que o rendimento deve ser estático.

Abaixo está uma função que executa um processo com um tempo limite.

public static void timeoutFunction(String fnReturnVal) {

    Object p = null; // whatever object you need here

    String threadSleeptime = null;

    Config config;

    try {
        config = ConfigReader.getConfigProperties();
        threadSleeptime = config.getThreadSleepTime();

    } catch (Exception e) {
        log.error(e);
        log.error("");
        log.error("Defaulting thread sleep time to 105000 miliseconds.");
        log.error("");
        threadSleeptime = "100000";
    }

    ExecutorService executor = Executors.newCachedThreadPool();
    Callable<Object> task = new Callable<Object>() {
        public Object call() {
            // Do job here using --- fnReturnVal --- and return appropriate value
            return null;
        }
    };
    Future<Object> future = executor.submit(task);

    try {
        p = future.get(Integer.parseInt(threadSleeptime), TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        log.error(e + ". The function timed out after [" + threadSleeptime
                + "] miliseconds before a response was received.");
    } finally {
        // if task has started then don't stop it
        future.cancel(false);
    }
}

private static String returnString() {
    return "hello";
}

public static void main(String[] args) {
    timeoutFunction(returnString());
}
Mwa Joe
fonte