Delegados Java?


194

A linguagem Java possui recursos de delegação, semelhante à maneira como o C # oferece suporte a delegados?


20
@Suma Como isso pode ser duplicado se a pergunta que você mencionou foi postada um ano após esta?
Ani

1
O Java 8 tem um recurso semelhante aos delegados. Chama-se lambdas.
tbodt

4
@ tbodt para ser mais correto, o Java 8 tem um recurso como os delegados. Chama-se interfaces funcionais . Os lambdas são uma maneira de criar essas instâncias delegadas (anonimamente).
Nawfal

3
As respostas abaixo são do Pré-Java 8. Após o Java 8, veja as respostas neste segmento: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, mas, para ser ainda mais correto, o Java 7 e abaixo já possuem um recurso semelhante aos delegados. Chama-se interfaces simples.
Pacerier 5/11

Respostas:


152

Não, realmente não.

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

Você também pode achar este artigo interessante / útil: Um programador Java analisa delegados em C # (@ archive.org)


3
A solução com invoke () como a única função membro de uma interface é muito boa.
precisa

1
Mas veja meu exemplo aqui do que alguém faria, mesmo em Java 7, para obter o equivalente a um delegado em C #.
usar o seguinte

63

Dependendo exatamente do que você quer dizer, é possível obter um efeito semelhante (passando por um método) usando o Padrão de Estratégia.

Em vez de uma linha como esta declarar uma assinatura de método nomeado:

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

Em qualquer lugar em que você tivesse um SomeFunctiondelegado em C #, use uma ISomeBehaviourreferência:

// 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 é muito específica para o contexto atual e não se beneficiaria da reutilização.

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

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

6
+1. essa é uma solução decente e a verbosidade pode ser compensada pela capacidade de manutenção no futuro.
Nawfal

isso é ótimo ... Atualmente trabalhando em um projeto onde eu sou incapaz de usar a reflexão devido a restrições do projeto e esta solução alternativa começa o trabalho feito muito bem :)
Jonathan Camarena

O Java possui uma convenção de nomenclatura com prefixo I para interfaces? Eu não vi isso antes
Kyle Delaney

36

História curta: não .

Introdução

A versão mais recente do ambiente de desenvolvimento do Microsoft Visual J ++ oferece suporte a uma construção de linguagem chamada delegados ou referências de método vinculadas . Essa construção, e as novas palavras delegate- chave e multicastintroduzidas para suportá-la, não fazem parte da linguagem de programação Java TM , especificada pela Java Language Specification e emendada pela Inner Classes Specification incluída na documentação do software JDKTM 1.1 .

É improvável que a linguagem de programação Java inclua essa construção. A Sun já considerou cuidadosamente adotá-lo em 1996, a ponto de criar e descartar protótipos em funcionamento. Concluímos que as referências aos métodos encadernados são desnecessárias e prejudiciais à linguagem. Esta decisão foi tomada em consulta com a Borland International, que possuía experiência anterior com referências a métodos vinculados no Delphi Object Pascal.

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

Acreditamos que as referências aos métodos encadernados são prejudiciais porque prejudicam a simplicidade da linguagem de programação Java e o caráter abrangente das APIs orientadas a objetos. As referências de método vinculado também introduzem irregularidades na sintaxe do idioma e nas regras de escopo. Por fim, eles diluem o investimento em tecnologias de VM porque são necessárias para lidar com tipos adicionais e díspares de referências e vincular métodos com eficiência.


Como diz o que Patrick vinculou, você deseja usar classes internas.
SCDF 04/09/08

16
Ótimo artigo. EU AMO a definição deles de "simples": Java é projetado para ser uma linguagem simples, como em "Java deve ser simples de escrever compilador / VM para" enquanto ignora "Java deve ser simples de escrever / ler por uma pessoa" . Explica muito.
Juozas Kontvainis

5
Eu só acho que a SUN cometeu um grande erro. Eles simplesmente não foram convencidos pelo paradigma funcional, e isso é tudo.
Stephane Rolland

7
@ Juozas: Python é explicitamente criado para ser simples de escrever / ler por uma pessoa e implementa funções / delegados lambda .
Stephane Rolland

5
Substituído por um link archive.org. Além disso, isso é realmente estúpido, Oracle.
Patrick

18

Você leu isto :

Delegados são uma construção útil em sistemas baseados em eventos. Essencialmente, delegados são objetos que codificam um envio de método em um objeto especificado. Este documento mostra como as classes internas do java fornecem uma solução mais genérica para esses problemas.

O que é um Delegado? Realmente, é muito semelhante a um ponteiro para função de membro, como 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 no ano em que algum evento específico fosse recebido.

É isso que a estrutura de delegados alcança.

Classes internas de Java

Foi argumentado que o Java fornece essa funcionalidade por meio de classes internas anônimas e, portanto, não precisa da construção Delegate adicional.

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

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

Manipulador Geral

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

Em uma situaçã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 dentro do 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 podem ser acessadas nas definições de método de classe anônima. Certifique-se de estudar este código cuidadosamente para entender as ramificações. Esta é potencialmente uma técnica muito poderosa. Por exemplo, pode ser usado com bom efeito ao registrar manipuladores no MiniDOM e em situações mais gerais.

Por outro lado, a construção Delegate não fornece uma solução para esse requisito mais geral e, como tal, deve ser rejeitada como um idioma no qual os designs podem se basear.



5

Não, mas é possível usar 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 seja no tempo de compilação, infelizmente, embora um plug-in FindBugs possa ajuda aqui) e use-o com segurança para delegar em várias instâncias.

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


2

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

Há uma classe de princípio chamada de retorno de chamada 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 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.

A seguir, é apresentado um exemplo usando um retorno de chamada para processar os arquivos em uma árvore de diretórios. Este é um passe de validação inicial que conta apenas os arquivos para processar e garante que nenhum exceda um tamanho máximo predeterminado. Nesse caso, apenas criamos o retorno de chamada em linha com a chamada da API. No entanto, refletimos o método de destino 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 árduo de percorrer recursivamente uma árvore de diretórios fica bem escondido 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. Obviamente, o argumento para uma interface é que ela é muito mais explícita sobre o que implementar (é aplicada, e não simplesmente documentada) - mas, na prática, não achei um problema obter a definição de retorno de chamada correta.

Definir e implementar uma interface não é tão ruim (a menos que você esteja distribuindo applets, como eu, onde é importante evitar a criação de classes extras), mas o que realmente brilha é quando você tem vários retornos de chamada em uma única classe. Não apenas está sendo forçado a colocá-los em uma classe interna separada, adicionada no aplicativo implementado, mas também é tedioso de programar, e todo esse código da placa da caldeira é realmente apenas "ruído".


1

Sim e não, mas o padrão de delegação em Java pode ser pensado dessa maneira. Este tutorial em vídeo é sobre troca de dados entre atividade - fragmentos e possui uma grande essência de delegar o padrão sorta usando interfaces.

Interface Java


1

Ele não possui uma delegatepalavra-chave explícita como C #, mas você pode obter similar no Java 8 usando uma interface funcional (ou seja, qualquer interface com exatamente um método) e lambda:

private interface SingleFunc {
    void printMe();
}

public static void main(String[] args) {
    SingleFunc sf = () -> {
        System.out.println("Hello, I am a simple single func.");
    };
    SingleFunc sfComplex = () -> {
        System.out.println("Hello, I am a COMPLEX single func.");
    };
    delegate(sf);
    delegate(sfComplex);
}

private static void delegate(SingleFunc f) {
    f.printMe();
}

Todo novo objeto do tipo SingleFuncdeve ser implementado printMe(), portanto é seguro passá-lo para outro método (por exemplo delegate(SingleFunc)) para chamar o printMe()método.


0

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


2
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. As respostas somente para links podem se tornar inválidas se a página vinculada for alterada.
StackFlowed

2
@StackFlowed Retire o link e o que você ganha? Um post com informações. Vote para manter.
Scimonster

1
@ Scimonster, o que aconteceria se o link não fosse mais válido?
StackFlowed

@StackFlowed Ele ainda fornece uma resposta - use um Java Proxy.
Scimonster

então poderia ser deixado como um comentário?
StackFlowed

0

Não, mas tem um comportamento semelhante, internamente.

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

Em java, não há ponteiro de função (em uma visão superior), mas internamente o Java precisa fazer a mesma coisa para atingir esses objetivos.

Por exemplo, a criação de encadeamentos em Java requer uma classe estendendo o Thread ou implementando Runnable, porque uma variável de objeto de classe pode ser usada como um ponteiro de local da memória.



0

O código descrito oferece muitas das vantagens dos delegados em C #. Métodos, estáticos ou dinâmicos, podem ser tratados de maneira uniforme. A complexidade de chamar métodos por meio de reflexão é reduzida e o código é reutilizável, no sentido de não exigir classes adicionais no código do usuário. Observe que estamos chamando uma versão alternativa conveniente de invoke, em que um método com um parâmetro pode ser chamado sem criar uma matriz de objetos.

  class Class1 {
        public void show(String s) { System.out.println(s); }
    }

    class Class2 {
        public void display(String s) { System.out.println(s); }
    }

    // allows static method as well
    class Class3 {
        public static void staticDisplay(String s) { System.out.println(s); }
    }

    public class TestDelegate  {
        public static final Class[] OUTPUT_ARGS = { String.class };
        public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);

        public void main(String[] args)  {
            Delegate[] items = new Delegate[3];

            items[0] = DO_SHOW .build(new Class1(),"show,);
            items[1] = DO_SHOW.build (new Class2(),"display");
            items[2] = DO_SHOW.build(Class3.class, "staticDisplay");

            for(int i = 0; i < items.length; i++) {
                items[i].invoke("Hello World");
            }
        }
    }

-11

Java não tem delegados e se orgulha disso :). Pelo que li aqui, encontrei em essência 2 maneiras de falsificar delegados: 1. reflexão; 2. classe interna

Reflexões são slooooow! A classe interna não cobre a função mais simples de caso de uso: classificação. Não quero entrar em detalhes, mas a solução com a classe interna é basicamente criar uma classe de wrapper para que uma matriz de números inteiros seja classificada em ordem crescente e uma classe para uma matriz de números inteiros para ordem decrescente.


O que a classe interna tem a ver com a classificação? E o que a classificação tem a ver com a pergunta?
Nawfal
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.