Ponteiros de Função em Java

148

Isso pode ser algo comum e trivial, mas parece que estou tendo problemas para encontrar uma resposta concreta. No C #, existe um conceito de delegados, que se relaciona fortemente com a idéia de ponteiros de função do C ++. Existe uma funcionalidade semelhante em Java? Dado que os indicadores estão um pouco ausentes, qual é a melhor maneira de fazer isso? E para ser claro, estamos falando de primeira classe aqui.

dreadwail
fonte
1
Estou curioso para saber por que você desejaria isso onde ouvintes ou outra construção de OOP faria a mesma tarefa, mantendo a natureza de seus objetos. Quero dizer, eu entendo a necessidade da funcionalidade oferecida pelo conceito, apenas para que ela possa ser alcançada com um objeto comum. :-)
Newtopian 02/07/2009
2
O Java 8 possui expressões lambda: docs.oracle.com/javase/tutorial/java/javaOO/… Você pode verificar isso. Não é um ponteiro de função, mas ainda pode ser mais útil.
FrustratedWithFormsDesigner
6
As referências do método Java 8 são exatamente o que você está solicitando.
Steven
8
As referências do método Java 8 são exatamente o que você está solicitando. this::myMethodé semanticamente o mesmo que criar um lambda paramA, paramB -> this.myMethod(paramA, paramB).
Steven

Respostas:

126

O idioma Java para a funcionalidade de ponteiro de função é uma classe anônima que implementa uma interface, por exemplo

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

Atualização: o acima é necessário nas versões Java anteriores ao Java 8. Agora temos alternativas muito melhores, ou seja, lambdas:

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

e referências de método:

list.sort(MyClass::isGreaterThan);
Michael Borgwardt
fonte
2
O padrão de design da estratégia. Não é tão feio quanto um ponteiro de função C ou C ++ quando você deseja que a função use alguns dados não globais que não são fornecidos como argumento para a função.
Raedwald 13/01
75
O @Raedwald C ++ possui functors, e o C ++ 0x possui fechamentos e lambdas construídos sobre os functors. Ele ainda possui std::bind, que liga parâmetros a funções e retorna um objeto que pode ser chamado. Não posso defender C por esse motivo, mas C ++ é realmente melhor que Java para isso.
zneak
21
@zneak, @Raedwald: Você pode fazer isso em C passando um ponteiro para os dados do usuário (por exemplo: struct). (e pode encapsulá-lo a cada novo nível de indireção de retorno de chamada) Na verdade, é bastante limpo.
Brice
25
Sim, eu vou realmente tomar C ponteiros de função ao longo classes de mensagens publicitárias intrincada qualquer dia
BT
3
O Java 8 tem uma sintaxe melhor para isso.
Thorbjørn Ravn Andersen
65

Você pode substituir um ponteiro de função por uma interface. Digamos que você deseja executar uma coleção e fazer algo com cada elemento.

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

Essa é a interface que poderíamos passar para alguns, como CollectionUtils2.doFunc (Coleção c, IFunction f).

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

Como exemplo, digamos que temos uma coleção de números e você gostaria de adicionar 1 a cada elemento.

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

Você pode usar a reflexão para fazer isso.

Passe como parâmetro o objeto e o nome do método (como uma sequência) e, em seguida, chame o método. Por exemplo:

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

E depois use-o como em:

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

Obviamente, verifique todas as exceções e adicione os lançamentos necessários.

zoquete
fonte
7
Reflexão não é a resposta certa para qualquer coisa, exceto "Como faço para escrever lenta código java desonesto": D
Jacob
5
Pode ser "feio" e lento, mas acredito que a reflexão permite fazer coisas que a solução baseada em interface na resposta aceita não pode fazer (a menos que eu esteja enganado), ou seja, chamar vários métodos do mesmo objeto no mesmo código parte.
Eusébio
@zoquete .. Eu estava passando por esse post e tinha uma dúvida .. então, na sua abordagem, se eu acessar a variável "theDescription", a função será chamada a cada vez que a variável for acessada?
precisa saber é o seguinte
20

Não, funções não são objetos de primeira classe em java. Você pode fazer o mesmo implementando uma classe de manipulador - é assim que os retornos de chamada são implementados no Swing etc.

No entanto, existem propostas de fechamento (o nome oficial do que você está falando) em versões futuras do java - o Javaworld tem um artigo interessante.

jwoolard
fonte
15

Isso lembra a Execução de Steve Yegge no Reino dos Substantivos . Ele basicamente afirma que Java precisa de um objeto para cada ação e, portanto, não possui entidades "somente verbais", como ponteiros de função.

Yuval F
fonte
8

Para obter funcionalidade semelhante, você pode usar classes internas anônimas.

Se você definir uma interface Foo:

interface Foo {
    Object myFunc(Object arg);
}

Crie um método barque receberá um 'ponteiro de função' como argumento:

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

Por fim, chame o método da seguinte maneira:

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

O Java8 introduziu lambdas e referências de método . Portanto, se sua função corresponder a uma interface funcional (você pode criar sua própria), poderá usar uma referência de método neste caso.

Java fornece um conjunto de interfaces funcionais comuns . considerando que você pode fazer o seguinte:

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
fonte
existe o conceito de um alias: por exemplo public List<T> collect(T t) = Collections::collect
javadba 5/01
@javadba, tanto quanto eu não sei.
Alex
5

Não existe tal coisa em Java. Você precisará agrupar sua função em algum objeto e passar a referência a esse objeto para passar a referência ao método nesse objeto.

Sintaticamente, isso pode ser facilitado até certo ponto, usando classes anônimas definidas no local ou classes anônimas definidas como variáveis ​​de membro da classe.

Exemplo:

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
fonte
3

Implementei o retorno de chamada / delegar suporte em Java usando reflexão. Detalhes e fonte de trabalho estão disponíveis no meu site .

Como funciona

Temos uma classe de princípio chamada Callback com uma classe aninhada chamada WithParms. A API que precisa do retorno de chamada terá um objeto de retorno de chamada como parâmetro e, se necessário, criará um Callback.WithParms como uma variável de método. Como muitas das aplicações desse objeto serão recursivas, isso funciona muito bem.

Com o desempenho ainda sendo uma alta prioridade para mim, eu não queria ser obrigado a criar uma matriz de objetos descartáveis ​​para armazenar os parâmetros de cada chamada - afinal, em uma grande estrutura de dados, poderia haver milhares de elementos e no processamento de mensagens Nesse cenário, poderíamos acabar processando milhares de estruturas de dados por segundo.

Para ser thread-safe, a matriz de parâmetros precisa existir exclusivamente para cada chamada do método API e, para eficiência, o mesmo deve ser usado para cada chamada do retorno de chamada; Eu precisava de um segundo objeto que seria barato criar para vincular o retorno de chamada a uma matriz de parâmetros para invocação. Mas, em alguns cenários, o invocador já teria uma matriz de parâmetros por outros motivos. Por esses dois motivos, a matriz de parâmetros não pertence ao objeto de retorno de chamada. Além disso, a escolha da chamada (passar os parâmetros como uma matriz ou como objetos individuais) pertence às mãos da API usando o retorno de chamada, permitindo que ela use a chamada que melhor se adequar ao seu funcionamento interno.

A classe aninhada WithParms, portanto, é opcional e serve para dois propósitos, contém a matriz de objetos de parâmetro necessária para as chamadas de retorno de chamada e fornece 10 métodos invoke () sobrecarregados (com de 1 a 10 parâmetros) que carregam a matriz de parâmetros e, em seguida, invocar o destino de retorno de chamada.

Lawrence Dol
fonte
2

Verifique os fechamentos como eles foram implementados na biblioteca lambdaj. Eles realmente têm um comportamento muito semelhante aos delegados em C #:

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

Mario Fusco
fonte
-1

Em relação à maioria das pessoas aqui, sou novo no java, mas como não vi uma sugestão semelhante, tenho outra alternativa a sugerir. Não tenho certeza se é uma boa prática ou não, ou mesmo sugerida antes e eu simplesmente não entendi. Eu gosto desde que eu acho que é auto-descritivo.

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

dependendo da aplicação, atribua

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

etc.

e ligue por

 functionpointer.execute();
ozgeneral
fonte