Pergunta

A linguagem Java possui recursos de delegação, semelhantes a como o C# tem suporte para delegados?

Foi útil?

Solução

Não, realmente não.

Você pode conseguir o mesmo efeito usando reflexão para obter objetos Method que você pode invocar, e a outra maneira é criar uma interface com um único método 'invoke' ou 'execute' e, em seguida, instanciá-los para chamar o método você está interessado em (ou seja,usando uma classe interna anônima).

Você também pode achar este artigo interessante/útil: Um programador Java analisa delegados C#

Outras dicas

Dependendo exatamente do que você quer dizer, você pode obter um efeito semelhante (passando um método) usando o Strategy Pattern.

Em vez de uma linha como esta declarando uma assinatura de método nomeada:

// C#
public delegate void SomeFunction();

declare uma interface:

// Java
public interface ISomeBehaviour {
   void SomeFunction();
}

Para implementações concretas do método, defina uma classe que implemente o comportamento:

// Java
public class TypeABehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeA behaviour
   }
}

public class TypeBBehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeB behaviour
   }
}

Então, onde quer que você tivesse um SomeFunction delegar em C#, use um ISomeBehaviour referência em vez disso:

// C#
SomeFunction doSomething = SomeMethod;
doSomething();
doSomething = SomeOtherMethod;
doSomething();

// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();

Com classes internas anônimas, você pode até evitar declarar classes nomeadas separadas e quase tratá-las como funções delegadas reais.

// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
   ...
}

...

SomeMethod(new ISomeBehaviour() { 
   @Override
   public void SomeFunction() {
      // your implementation
   }
});

Provavelmente, isso só deve ser usado quando a implementação for muito específica para o contexto atual e não se beneficiaria com a reutilização.

E é claro que no Java 8, elas se tornam basicamente expressões lambda:

// Java 8
SomeMethod(() -> { /* your implementation */ });

História curta:não.

Introdução

A versão mais recente do ambiente de desenvolvimento do Microsoft Visual J ++ suporta um construto de linguagem chamado delegados ou referências de método vinculado.Esta construção e as novas palavras-chave delegate e multicast introduzidos para apoiá-lo, não fazem parte do JavaMTlinguagem de programação, que é especificada pelo Especificação da linguagem Java e alterado pelo Especificação de classes internas incluído em documentação para o software JDKTM 1.1.

É improvável que a linguagem de programação Java inclua esse construto.Sun já considerou cuidadosamente adotá -lo em 1996, na extensão da construção e descarte de protótipos de trabalho.Nossa conclusão foi que as referências ao método vinculado são desnecessárias e prejudiciais ao idioma.Esta decisão foi tomada em consulta com a Borland International, que teve experiência anterior com referências de método vinculado no objeto Delphi Pascal.

Acreditamos que as referências de métodos vinculados são desnecessário Porque outra alternativa de design, classes internas, fornece funcionalidade igual ou superior.Em particular, as classes internas suportam totalmente os requisitos do manuseio de eventos de interface do usuário e foram usados ​​para implementar uma API de interface do usuário pelo menos tão abrangente quanto as classes da Fundação do Windows.

Acreditamos que as referências de métodos vinculados são prejudicial Porque eles prejudicam a simplicidade da linguagem de programação Java e o caráter generalizado orientado a objetos das APIs.As referências de método vinculado também introduzem irregularidade nas regras de sintaxe e escopo da linguagem.Finalmente, eles diluem o investimento em tecnologias de VM porque as VMs são necessárias para lidar com tipos adicionais e díspares de referências e vínculo com o método com eficiência.

Você leu esse :

Os delegados são uma construção útil em sistemas baseados em eventos.Delegados essencialmente são objetos que codificam um método despacho em um objeto especificado.Este documento mostra como as classes internas da Java fornecem uma solução mais genérica para esses problemas.

O que é um delegado?Realmente é muito semelhante a um ponteiro à função de membro, conforme usado em C ++.Mas um delegado contém o objeto de destino junto com o método a ser chamado.Idealmente, seria bom poder dizer:

obj.registerHandler(ano.methodOne);

..e que o método methodOne seria chamado ano quando algum evento específico fosse recebido.

Isto é o que a estrutura Delegada consegue.

Classes internas de Java

Argumentou -se que o Java fornece essa funcionalidade por meio de classes internas anônimas e, portanto, não precisa da construção de delegados adicionais.

obj.registerHandler(new Handler() {
        public void handleIt(Event ev) {
            methodOne(ev);
        }
      } );

À primeira vista isto parece correto, mas ao mesmo tempo um incômodo.Porque, para muitos exemplos de processamento de eventos, a simplicidade da sintaxe dos delegados é muito atraente.

Manipulador Geral

No entanto, se a programação baseada em eventos for usada de maneira mais difundida, digamos, por exemplo, como parte de um ambiente de programação assíncrono geral, há mais em jogo.

Em uma situação tão geral, não é suficiente incluir apenas o método de destino e a instância do objeto de destino.Em geral, pode haver outros parâmetros necessários, que são determinados no contexto quando o manipulador de eventos é registrado.

Nesta situação mais geral, a abordagem Java pode fornecer uma solução muito elegante, principalmente quando combinada com o uso de variáveis ​​finais:

void processState(final T1 p1, final T2 dispatch) { 
  final int a1 = someCalculation();

  m_obj.registerHandler(new Handler() {
    public void handleIt(Event ev) {
     dispatch.methodOne(a1, ev, p1);
    }
  } );
}

final * final * final

Chamou sua atenção?

Observe que as variáveis ​​finais são acessíveis a partir das definições do método de classe anônima.Certifique -se de estudar esse código cuidadosamente para entender as ramificações.Esta é potencialmente uma técnica muito poderosa.Por exemplo, pode ser usado com um bom efeito ao registrar manipuladores no Minidom e em situações mais gerais.

Por outro lado, o delegado construto não fornece uma solução para esse requisito mais geral e, como tal, deve ser rejeitado como um idioma no qual os projetos podem ser baseados.

Sei que este post é antigo, mas o Java 8 adicionou lambdas e o conceito de interface funcional, que é qualquer interface com apenas um método.Juntos, eles oferecem funcionalidade semelhante aos delegados C#.Veja aqui para mais informações, ou apenas pesquise no Google Java Lambdas.http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

Não, mas eles podem ser falsificados usando proxies e reflexão:

  public static class TestClass {
      public String knockKnock() {
          return "who's there?";
      }
  }

  private final TestClass testInstance = new TestClass();

  @Test public void
  can_delegate_a_single_method_interface_to_an_instance() throws Exception {
      Delegator<TestClass, Callable<String>> knockKnockDelegator = Delegator.ofMethod("knockKnock")
                                                                   .of(TestClass.class)
                                                                   .to(Callable.class);
      Callable<String> callable = knockKnockDelegator.delegateTo(testInstance);
      assertThat(callable.call(), is("who's there?"));
  }

O bom desse idioma é que você pode verificar se o método delegado existe e tem a assinatura necessária no ponto em que você cria o delegador (embora não em tempo de compilação, infelizmente, embora um plug-in FindBugs possa ajuda aqui) e use-o com segurança para delegar a várias instâncias.

Veja o código karg no github para mais testes e implementação.

Implementei suporte de retorno de chamada/delegado em Java usando reflexão.Detalhes e fonte de trabalho são disponível no meu site.

Como funciona

Existe uma classe principal chamada Callback com uma classe aninhada chamada WithParms.A API que necessita do callback pegará um objeto Callback como parâmetro e, se necessário, criará um Callback.WithParms como variável de método.Como muitas das aplicações deste objeto serão recursivas, isso funciona de maneira muito limpa.

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 invocação - afinal, em uma grande estrutura de dados, poderia haver milhares de elementos, e em um processamento de mensagens Nesse cenário, poderíamos acabar processando milhares de estruturas de dados por segundo.

Para ser threadsafe, o array de parâmetros precisa existir exclusivamente para cada invocação do método API e, para maior eficiência, o mesmo deve ser usado para cada invocação do retorno de chamada;Eu precisava de um segundo objeto que fosse barato de 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, o array de parâmetros não pertence ao objeto Callback.Além disso, a escolha da invocação (passar os parâmetros como um array ou como objetos individuais) pertence à API usando o retorno de chamada, permitindo-lhe usar qualquer invocação que seja mais adequada ao seu funcionamento interno.

A classe aninhada WithParms, então, é opcional e serve a dois propósitos: contém a matriz de objetos de parâmetro necessária para as invocações 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 então invocar o destino de retorno de chamada.

A seguir está um exemplo de uso de retorno de chamada para processar os arquivos em uma árvore de diretórios.Esta é uma passagem de validação inicial que apenas conta os arquivos a serem processados ​​e garante que nenhum exceda um tamanho máximo predeterminado.Neste caso, apenas criamos o retorno de chamada em linha com a invocação da API.No entanto, refletimos o método alvo como um valor estático para que a reflexão não seja feita sempre.

static private final Method             COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);

...

IoUtil.processDirectory(root,new Callback(this,COUNT),selector);

...

private void callback_count(File dir, File fil) {
    if(fil!=null) {                                                                             // file is null for processing a directory
        fileTotal++;
        if(fil.length()>fileSizeLimit) {
            throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
            }
        }
    progress("Counting",dir,fileTotal);
    }

IoUtil.processDirectory():

/**
 * Process a directory using callbacks.  To interrupt, the callback must throw an (unchecked) exception.
 * Subdirectories are processed only if the selector is null or selects the directories, and are done
 * after the files in any given directory.  When the callback is invoked for a directory, the file
 * argument is null;
 * <p>
 * The callback signature is:
 * <pre>    void callback(File dir, File ent);</pre>
 * <p>
 * @return          The number of files processed.
 */
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
    return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
    }

static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
    int                                 cnt=0;

    if(!dir.isDirectory()) {
        if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
        }
    else {
        cbk.invoke(dir,(Object[])null);

        File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
        if(lst!=null) {
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(!ent.isDirectory()) {
                    cbk.invoke(dir,ent);
                    lst[xa]=null;
                    cnt++;
                    }
                }
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
                }
            }
        }
    return cnt;
    }

Este exemplo ilustra a beleza dessa abordagem - a lógica específica do aplicativo é abstraída no retorno de chamada, e o trabalho penoso de percorrer recursivamente uma árvore de diretórios é bem guardado em um método utilitário estático completamente reutilizável.E não precisamos pagar repetidamente o preço de definir e implementar uma interface para cada novo uso.Claro, o argumento para uma interface é muito mais explícita sobre o que implementar (é aplicada, não simplesmente documentada) - mas na prática não achei um problema acertar a definição de retorno de chamada.

Definir e implementar uma interface não é tão ruim (a menos que você esteja distribuindo miniaplicativos, como eu, onde evitar a criação de classes extras realmente importa), mas isso realmente brilha quando você tem vários retornos de chamada em uma única classe.Não apenas ser forçado a empurrá-los para uma classe interna separada adiciona sobrecarga ao aplicativo implantado, mas também é totalmente tedioso programar e todo aquele código padrão é, na verdade, apenas "ruído".

Embora não seja tão limpo, você pode implementar algo como delegados C# usando um Java Procurador.

Não, mas tem comportamento semelhante, internamente.

Em C#, os delegados são usados ​​para criar um ponto de entrada separado e funcionam como um ponteiro de função.

Em java não existe ponteiro de função (olhando de cima), mas internamente Java precisa fazer a mesma coisa para atingir esses objetivos.

Por exemplo, a criação de threads em Java requer uma classe que estende Thread ou implementa Runnable, porque uma variável de objeto de classe pode ser usada como um ponteiro de localização de memória.

Sim e não, mas o padrão de delegação em Java pode ser pensado desta forma. Este tutorial em vídeo trata-se de troca de dados entre fragmentos de atividades e tem grande essência de padrão de delegação usando interfaces.

Java Interface

Java não tem delegados e tem orgulho disso :).Pelo que li aqui, encontrei basicamente 2 maneiras de falsificar delegados:1.reflexão;2.classe interna

As reflexões são lentas!A classe interna não cobre o caso de uso mais simples:função de classificação.Não quero entrar em detalhes, mas a solução com a classe interna basicamente é criar uma classe wrapper para um array de inteiros ser classificado em ordem crescente e uma classe para um array de inteiros ser classificado em ordem decrescente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top