Qual é o substituto mais próximo de um ponteiro de função em Java?

299

Eu tenho um método que é cerca de dez linhas de código. Quero criar mais métodos que façam exatamente a mesma coisa, exceto um pequeno cálculo que alterará uma linha de código. Este é um aplicativo perfeito para passar um ponteiro de função para substituir essa linha, mas o Java não possui ponteiros de função. Qual é a minha melhor alternativa?

Bill the Lizard
fonte
5
O Java 8 terá expressões lambda . Você pode ler mais sobre expressões lambda aqui .
Marius
4
@ Marius Eu não acho que expressões lambda contam como ponteiros de função. O ::operador, por outro lado ...
O cara com o chapéu
Desculpe pelo comentário tardio;) - geralmente, você não precisa de um ponteiro de função para isso. Basta usar um método de modelo! ( pt.wikipedia.org/wiki/Template_method_pattern )
isnot2bad
2
@ isnot2bad - olhando para esse artigo, parece um exagero - mais complexo do que as respostas fornecidas aqui. Especificamente, o método de modelo requer a criação de uma subclasse para cada cálculo alternativo. Não vejo o OP ter declarado nada que exija subclasses ; ele simplesmente deseja criar vários métodos e compartilhar a maior parte da implementação. Como a resposta aceita mostra, isso é feito facilmente "no local" (dentro de cada método), mesmo antes do Java 8 com suas lambdas.
Página
@ToolmakerSteve A solução aceita também exige classe por cálculo (mesmo que seja apenas uma classe interna anônima). E o padrão do método de modelo também pode ser realizado usando classes internas anônimas, portanto, não difere muito da solução aceita em relação à sobrecarga (antes do Java 8). Portanto, trata-se mais do padrão de uso e dos requisitos detalhados, que não sabemos. Agradeço a resposta aceita e só queria acrescentar outra possibilidade para pensar.
Isnot2bad

Respostas:

269

Classe interna anônima

Digamos que você queira que uma função seja passada com um Stringparâmetro que retorne um int.
Primeiro, você precisa definir uma interface com a função como seu único membro, se não puder reutilizar uma existente.

interface StringFunction {
    int func(String param);
}

Um método que pega o ponteiro apenas aceita a StringFunctioninstância da seguinte maneira:

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

E seria chamado assim:

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

EDIT: No Java 8, você poderia chamá-lo com uma expressão lambda:

ref.takingMethod(param -> bodyExpression);
esplêndido
fonte
13
Este é um exemplo do "Command Patern", a propósito. en.wikipedia.org/wiki/Command_Pattern
Ogre Psalm33
3
@Ogre Psalm33 Essa técnica também pode ser o Padrão de Estratégia, dependendo de como você a usa. A diferença entre o padrão de estratégia e o padrão de comando .
Rory O'Kane
2
Aqui está uma implementação de fechamento para Java 5, 6 e 7 mseifed.blogspot.se/2012/09/… Ele contém tudo o que se pode pedir ... Eu acho que é incrível!
mmm
@ SecretService: Esse link está morto.
Lawrence Dol
@LawrenceDol Sim, é. Aqui está uma pasta da classe que estou usando. pastebin.com/b1j3q2Lp
mmm
32

Para cada "ponteiro de função", eu criaria uma pequena classe de functor que implementa seu cálculo. Defina uma interface que todas as classes implementarão e passe instâncias desses objetos para sua função maior. Esta é uma combinação do " padrão de comando " e " padrão de estratégia ".

O exemplo do @ sblundy é bom.

Blair Conrad
fonte
28

Quando há um número predefinido de cálculos diferentes que você pode fazer nessa linha, o uso de uma enumeração é uma maneira rápida e clara de implementar um padrão de estratégia.

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, a declaração do método de estratégia, bem como exatamente uma instância de cada implementação, são definidas em uma única classe / arquivo.

javashlook
fonte
24

Você precisa criar uma interface que forneça as funções que você deseja transmitir. por exemplo:

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

}

Então, quando você precisar passar uma função, poderá implementar essa interface:

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

Por fim, a função map usa o passado na Function1 da seguinte maneira:

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

Geralmente, você pode usar o Runnable em vez de sua própria interface, se não precisar passar parâmetros, ou pode usar várias outras técnicas para tornar a contagem de parâmetros menos "fixa", mas geralmente é uma troca com a segurança de tipo. (Ou você pode substituir o construtor do objeto de função para passar os parâmetros dessa maneira. Existem muitas abordagens e algumas funcionam melhor em determinadas circunstâncias.)

rcreswick
fonte
4
Esta "resposta" refere-se mais ao conjunto de problemas do que ao conjunto de soluções. T
tchrist 30/11/2010
18

Referências de método usando o ::operador

Você pode usar referências de método em argumentos de método em que o método aceita uma interface funcional . Uma interface funcional é qualquer interface que contenha apenas um método abstrato. (Uma interface funcional pode conter um ou mais métodos padrão ou métodos estáticos.)

IntBinaryOperatoré uma interface funcional. Seu método abstrato applyAsInt, aceita dois ints como parâmetros e retorna an int. Math.maxtambém aceita dois se intretorna um int. Neste exemplo, A.method(Math::max);make parameter.applyAsIntenvia seus dois valores de entrada Math.maxe retorna o resultado disso 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);
    }
}

Em geral, você pode usar:

method1(Class1::method2);

ao invés de:

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

que é a abreviação de:

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

Para obter mais informações, consulte Operador :: (dois pontos) no Java 8 e Java Language Specification §15.13 .

O cara com o chapéu
fonte
15

Você também pode fazer isso (o que em algumas ocasiões RARAS faz sentido). O problema (e é um grande problema) é que você perde toda a segurança de tipo ao usar uma classe / interface e precisa lidar com o caso em que o método não existe.

Ele tem o "benefício" de que você pode ignorar as restrições de acesso e chamar métodos privados (não mostrados no exemplo, mas pode chamar métodos que o compilador normalmente não deixaria chamar).

Novamente, é raro que isso faça sentido, mas nessas ocasiões é uma boa ferramenta.

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);
    }
}
TofuBeer
fonte
1
Às vezes, eu uso isso para manipulação de menus / GUIs porque a sintaxe do método é muito mais simples que a sintaxe anônima da classe interna. É legal, mas você está adicionando a complexidade da reflexão em que algumas pessoas não querem se aprofundar, por isso, certifique-se de acertar e de ter erros de texto claros para todas as condições de erro possíveis.
Bill K
Você pode digitar com segurança usando genéricos e não precisa de reflexão.
Luigi Plinge
1
Não vejo como o uso de genéricos e não o reflexo permitiria chamar um método com um nome contido em uma String?
TofuBeer 5/09/11
1
@LuigiPlinge - você pode fornecer um trecho de código do que você quer dizer?
Página
13

Se você tiver apenas uma linha diferente, poderá adicionar um parâmetro como uma flag e uma instrução if (flag) que chame uma linha ou outra.

Peter Lawrey
fonte
1
A resposta de javaslook parece uma maneira mais limpa de fazer isso, se houver mais de duas variantes de cálculo. Ou, se desejar incorporar o código ao método, uma enumeração para os diferentes casos que o método trata e uma opção.
Página
1
@ToolmakerSteve verdade, embora hoje você usaria lambdas em Java 8.
Peter Lawrey
11

Novas interfaces funcionais do Java 8 e referências de método usando o ::operador.

O Java 8 é capaz de manter referências de método (MyClass :: new) com ponteiros " @ Functional Interface ". Não há necessidade do mesmo nome de método, apenas a mesma assinatura de método necessária.

Exemplo:

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

Então, o que temos aqui?

  1. Interface funcional - essa é a interface, anotada ou não com @FunctionalInterface , que contém apenas uma declaração de método.
  2. Referências de método - esta é apenas uma sintaxe especial, tem esta aparência, objectInstance :: methodName , nada mais, nada menos.
  3. Exemplo de uso - apenas um operador de atribuição e, em seguida, chamada de método de interface.

VOCÊ DEVE USAR INTERFACES FUNCIONAIS APENAS PARA OUVIR OUVIDOS POR ISSO!

Porque todos os outros indicadores de função são muito ruins para a legibilidade do código e a capacidade de entender. No entanto, referências diretas a métodos às vezes são úteis, com foreach, por exemplo.

Existem várias interfaces funcionais 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 versões anteriores do Java, tente o Guava Libraries, que possui funcionalidade e sintaxe semelhantes, como Adrian Petrescu mencionou acima.

Para pesquisas adicionais, consulte o Java 8 Cheatsheet

e obrigado a The Guy with The Hat pelo link §15.13 da especificação da linguagem Java .

user3002379
fonte
7
" Porque todos os outros ... são realmente ruins para a legibilidade do código " é uma afirmação completamente infundada, e errada, além disso.
Lawrence Dol
9

A resposta do @ sblundy é ótima, mas as classes internas anônimas têm duas pequenas falhas, a principal é que elas tendem a não ser reutilizáveis ​​e a secundária é uma sintaxe volumosa.

O bom é que o padrão dele se expande para classes completas sem nenhuma alteração na classe principal (aquela que executa os cálculos).

Quando você instancia uma nova classe, pode passar parâmetros para essa classe que podem atuar como constantes em sua equação - portanto, se uma de suas classes internas se parecer com isso:

f(x,y)=x*y

mas às vezes você precisa de um que seja:

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

e talvez um terceiro que seja:

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

em vez de criar duas classes internas anônimas ou adicionar um parâmetro "passthrough", você pode criar uma única classe ACTUAL que instancia 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);

Simplesmente armazenaria a constante na classe e a usaria no método especificado na interface.

De fato, se SABE que sua função não será armazenada / reutilizada, você poderá fazer o seguinte:

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

Mas classes imutáveis ​​são mais seguras - não posso apresentar uma justificativa para tornar uma classe como essa mutável.

Eu realmente só posto isso porque me encolho sempre que ouço uma classe interna anônima - eu vi muitos códigos redundantes que eram "Necessários" porque a primeira coisa que o programador fez foi ficar anônimo quando ele deveria ter usado uma classe real e nunca repensou sua decisão.

Bill K
fonte
Hã? OP está falando sobre diferentes cálculos (algoritmos; lógica); você está mostrando valores diferentes (dados). Você mostra um caso específico em que a diferença pode ser incorporada a um valor, mas isso é uma simplificação injustificável do problema apresentado.
Página Inicial>
6

As bibliotecas do Google Guava , que estão se tornando muito populares, têm um objeto genérico de Function and Predicate que eles trabalharam em várias partes de sua API.

Adrian Petrescu
fonte
Esta resposta seria mais útil se desse detalhes do código. Pegue o código mostrado na resposta aceita e mostre como ficaria usando a Função.
Página
4

Parece um padrão de estratégia para mim. Confira os padrões Java do fluffycat.com.

Dennis S
fonte
4

Uma das coisas que realmente sinto falta ao programar em Java é o retorno de chamada de função. Uma situação em que a necessidade delas continuava se apresentando era nas hierarquias de processamento recursivo, nas quais você deseja executar alguma ação específica para cada item. Como caminhar em uma árvore de diretórios ou processar uma estrutura de dados. O minimalista dentro de mim odeia ter que definir uma interface e depois uma implementação para cada caso específico.

Um dia eu me perguntei por que não? Temos ponteiros de método - o objeto Method. Com a otimização de compiladores JIT, a invocação reflexiva realmente não acarreta uma grande penalidade de desempenho. E além de, digamos, copiar um arquivo de um local para outro, o custo da invocação do método refletido é insignificante.

Enquanto pensava mais sobre isso, percebi que um retorno de chamada no paradigma OOP requer a vinculação de um objeto e um método - insira o objeto de retorno de chamada.

Confira minha solução baseada em reflexão para retornos de chamada em Java . Grátis para qualquer uso.

Lawrence Dol
fonte
4

OK, este tópico já tem idade suficiente, então provavelmente minha resposta não é útil para a pergunta. Mas como esse tópico me ajudou a encontrar minha solução, eu o colocarei aqui de qualquer maneira.

Eu precisava usar um método estático variável com entrada e saída conhecidas (ambas duplas ). Então, sabendo o pacote e o nome do método, eu poderia trabalhar da seguinte maneira:

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

para uma função que aceita um duplo como parâmetro.

Então, na minha situação concreta, eu o inicializei com

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

e invocou-o mais tarde em uma situação mais complexa com

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

onde atividade é um valor duplo arbitrário. Estou pensando em talvez fazer isso um pouco mais abstrato e generalizá-lo, como o SoftwareMonkey fez, mas atualmente estou feliz o suficiente com o jeito que está. Três linhas de código, sem classes e interfaces necessárias, isso não é muito ruim.

yogibimbi
fonte
graças Rob para adicionar a coderemarcação, eu estava muito impaciente e estúpido para encontrá-lo ;-)
yogibimbi
4

Para fazer a mesma coisa sem interfaces para uma matriz de funções:

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); } }
vwvan
fonte
1
Por quê? Isso é mais complicado do que as soluções propostas anteriormente, que simplesmente precisam de uma definição de função anônima por alternativa. Por alternativa, você cria uma classe e uma definição de função anônima. Pior, isso é feito em dois locais diferentes no código. Você pode fornecer algumas justificativas para usar essa abordagem.
Página
3

Uau, por que não apenas criar uma classe Delegate que não é tão difícil, já que eu já fiz para java e usá-la para passar no parâmetro onde T é o tipo de retorno. Sinto muito, mas como um programador de C ++ / C # em geral apenas aprendendo java, preciso de ponteiros de função porque eles são muito úteis. Se você estiver familiarizado com alguma classe que lida com informações de método, você pode fazê-lo. Nas bibliotecas java, isso seria java.lang.reflect.method.

Se você sempre usa uma interface, sempre precisa implementá-la. Na manipulação de eventos, não há realmente uma maneira melhor de registrar / cancelar o registro da lista de manipuladores, mas para os delegados onde você precisa passar as funções e não o tipo de valor, criando uma classe delegada para lidar com isso para ultrapassar uma interface.

Robert
fonte
Não é uma resposta útil, a menos que você mostre detalhes do código. Como a criação de uma classe Delegate ajuda? Qual código é necessário por alternativa?
Home
2

Nenhuma das respostas do Java 8 deu um exemplo completo e coeso, então aqui está.

Declare o método que aceita o "ponteiro de função" da seguinte maneira:

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

Chame-o fornecendo à função uma expressão lambda:

doCalculation((i) -> i.toString(), 2);
Mrts
fonte
1

Desde o Java8, você pode usar lambdas, que também possuem bibliotecas na API oficial do SE 8.

Uso: Você precisa usar uma interface com apenas um método abstrato. Faça uma instância dele (você pode usar o java SE 8 já fornecido) como este:

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

Para obter mais informações, consulte a documentação: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

Alex
fonte
1

Antes do Java 8, o substituto mais próximo da funcionalidade de ponteiro de função era uma classe anônima. Por exemplo:

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

Mas agora no Java 8, temos uma alternativa muito interessante, conhecida como expressão lambda , que pode ser usada como:

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

onde isBiggerThan é um método em CustomClass. Também podemos usar referências de método aqui:

list.sort(MyClass::isBiggerThan);
akhil_mittal
fonte