Minha opinião sobre as expressões Lambda em Java está bastante emaranhada:
Eu acho que desta maneira: as expressões lambda reduzem a experiência de leitura dos programas Java. Os programas Java nunca foram excelentes em expressividade. Pelo contrário, um dos fatores que torna Java popular é sua segurança e conservadorismo-mesmo os iniciantes podem escrever um código robusto e fácil de manter, desde que prestem atenção a ele. As expressões Lambda têm requisitos relativamente mais altos para os desenvolvedores, portanto também aumentam algumas dificuldades de manutenção.
Outra coisa que eu acho é: como código de código, é necessário aprender e aceitar novos recursos do idioma. Se você desistir de seus pontos fortes expressivos apenas por causa de sua má experiência de leitura, algumas pessoas acham difícil entender até expressões trinoculares. O idioma também está se desenvolvendo, e aqueles que não conseguem acompanhar serão deixados para trás voluntariamente.
Eu não quero ser deixado para trás. No entanto, se eu tivesse que fazer uma escolha, minha decisão ainda era relativamente conservadora: não há necessidade de usar o Lambda no idioma Java - isso faz com que muitas pessoas no atual círculo Java não acostumadas a ele e causarão um aumento nos custos de mão -de -obra. Se você gosta muito, considere usar o Scala.
De qualquer forma, ainda comecei a tentar dominar o Lambda, afinal, parte do código mantido no trabalho usa Lambda (confie em mim, vou removê -lo gradualmente). Os tutoriais para aprender são tutoriais relacionados no site oficial do Oracle Java.
―
Suponha que um aplicativo de rede social esteja sendo criado atualmente. Um recurso é que os administradores podem executar certas ações em membros que atendem aos critérios especificados, como o envio de mensagens. A tabela a seguir descreve este caso de uso em detalhes:
| Campo | descrever |
| nome | Ações a serem executadas |
| Participantes -chave | administrador |
| Pré -requisitos | Administrador Faça login no sistema |
| Pós-condicionas | Executar apenas ações para membros que atendem aos critérios especificados |
| Cenário de sucesso principal | 1. O administrador define os padrões de filtragem para os membros do destino executarem a operação; 2. O administrador seleciona a ação a ser executada; 3. O administrador clica no botão Enviar; 4. O sistema encontra membros que atendem aos critérios especificados; 5. O sistema executa operações pré-selecionadas em membros que atendem aos critérios especificados. |
| Estendido | Antes de selecionar a operação de execução ou antes de clicar no botão Enviar, o administrador pode escolher se deseja visualizar as informações do membro que atendem aos critérios de filtragem. |
| Frequência de ocorrência | Isso acontece muitas vezes em um dia. |
Use a aula de pessoa a seguir para representar as informações dos membros nas redes sociais:
public class Pessoa {public Enum sexo {masculino, feminino} Nome da string; LocalDate Birthday; Sexo sexo; String emailaddress; public int getage () {// ...} public void printPerson () {// ...}}Suponha que todos os membros sejam salvos em uma lista de <Pessoa>.
Nesta seção, começamos com um método muito simples e, em seguida, tentamos implementá -lo usando classes locais e aulas anônimas e, no final, experimentaremos gradualmente o poder e a eficiência das expressões lambda. O código completo pode ser encontrado aqui.
Solução 1: Crie métodos para encontrar membros que atendam aos critérios especificados um por um
Esta é a solução mais simples e difícil para implementar os casos acima mencionados: é criar vários métodos e cada método verifica um critério (como idade ou sexo). O código a seguir verifica se a idade é mais antiga que um valor especificado:
public static void PrintPerSoSolDerthan (lista <Person> Lista, Int Age) {for (Pessoa P: Lista) {if (P.Getage ()> = Age) {P.PrintPerson (); }}}Esta é uma solução muito frágil, e é muito provável que o aplicativo não seja executado devido a uma pequena atualização. Se adicionarmos novas variáveis de membro à classe Pessoa ou alterarmos o algoritmo para medir a idade no padrão, precisamos reescrever muito código para se adaptar a essa alteração. Além disso, as restrições aqui são muito rígidas. Por exemplo, o que devemos fazer se queremos imprimir membros mais jovens que um valor especificado? Adicionar outro novo método printpersonsyoungerthan? Este é obviamente um método estúpido.
Solução 2: Crie um método mais geral
O método a seguir é mais adaptável que o PrintPerSOlDerthan; Este método imprime as informações do membro dentro da faixa etária especificada:
public static void PrintPerSonsWithinagerange (lista de <Person>, int baixo, int alto) {for (Pessoa P: Lista) {if (Low <= p.getage () && P.Getage () <High) {P.printPerson (); }}}Agora, há uma nova idéia: o que devemos fazer se queremos imprimir informações de membros do gênero especificado ou que atenda ao gênero especificado e está dentro da faixa etária especificada? E se ajustarmos a classe da pessoa e adicionar propriedades como amizade e localização geográfica. Embora os métodos de escrita como esse sejam mais universais que o PrintPersonsyoungerthan, escrever um método para todas as consultas possíveis também pode levar à fragilidade no código. É melhor colocar o código de verificação padrão em uma nova classe.
Solução 3: Implemente a inspeção padrão em uma classe local
O método a seguir imprime as informações do membro que atende aos critérios de pesquisa:
public static void PrintPersons (lista de <Person>, CheckPerson tester) {for (Pessoa P: Lista) {if (tester.test (p)) {P.printPerson (); }}}Um testador de objeto de checkperso é usado no programa para verificar cada instância na lista de parâmetros da lista. Se tester.test () retornar true, o método printperson () será executado. Para definir os critérios de pesquisa, a interface do CheckPerson precisa ser implementada.
A classe a seguir implementa o CheckPerson e fornece uma implementação específica do método de teste. O método de teste nesta classe filtra informações sobre membros que atendem aos requisitos para o serviço militar nos Estados Unidos: ou seja, sexo masculino e idade entre 18 e 25 anos.
classe checkPersonEligibleForSelectiveService implementa o CheckPerson {public boolean Test (Pessoa P) {return }}Para usar esta classe, você precisa criar uma instância e acionar o método PrintPersons:
PrintPersons (lista, novo checkPersonELigibleForSelectiveService ());
O código agora parece menos frágil - não precisamos reescrever o código devido a alterações na estrutura da classe da pessoa. No entanto, ainda há código adicional aqui: uma interface recém -definida que define uma classe interna para cada padrão de pesquisa no aplicativo.
Como o checkPersonELigibleForSelectiveService implementa uma interface, uma classe anônima pode ser usada sem definir uma classe interna para cada padrão.
Solução 4: Use classes anônimas para implementar a inspeção padrão
Um parâmetro no método PrintPersons chamado abaixo é a classe anônima. A função desta classe anônima é a mesma da classe CheckPersoneligibleForSelectiveService no Esquema 3: todos são membros filtrados com gênero masculino e idades entre 18 e 25 anos.
PrintPersons (lista, novo checkPerson () {public boolean teste (Pessoa P) {returnEsse esquema reduz a quantidade de codificação, porque não há mais necessidade de criar novas classes para que cada esquema de pesquisa seja executado. No entanto, ainda é um pouco desconfortável fazer isso: embora a interface do Checkperson tenha apenas um método, a classe anônima implementada ainda é um pouco detalhada e volumosa. Neste momento, você pode usar a expressão do Lambda para substituir as classes anônimas. O seguinte explicará como usar a expressão do Lambda para substituir as classes anônimas.
Solução 5: Use expressões Lambda para implementar a verificação padrão
A interface do CheckPerson é uma interface funcional. A chamada interface funcional refere-se a qualquer interface que contém apenas um método abstrato. (Uma interface funcional também pode ter vários métodos padrão ou métodos estáticos). Como existe apenas um método abstrato na interface funcional, o nome do método do método pode ser omitido ao implementar o método dessa interface funcional. Para implementar essa ideia, você pode substituir expressões de classe anônimas por expressões Lambda. No método PrintPersons reescrito abaixo, o código relevante é destacado:
PrintPersons (Lista, (Pessoa P) -> P.GetGender () == PERSON.SEX.MALE && P.GETAGE ()> = 18 && P.Getage () <= 25);
Aqui você também pode usar uma interface de função padrão para substituir a interface do CheckPerson, simplificando ainda mais o código.
Solução 6: Use interfaces funcionais padrão em expressões lambda
Vamos dar uma olhada na interface do Checkperson:
CheckPerson da interface {teste booleano (Pessoa P); }Esta é uma interface muito simples. Como existe apenas um método abstrato, também é uma interface funcional. Este método abstrato aceita apenas um parâmetro e retorna um valor booleano. Essa interface abstrata é tão simples que consideraremos se é necessário definir essa interface no aplicativo. No momento, você pode considerar o uso de interfaces funcionais padrão definidas pelo JDK e encontrar essas interfaces no pacote java.util.function.
Neste exemplo, podemos usar a interface predicada <t> para substituir o CheckPerson. Existe um método de teste booleano (t) nesta interface:
Predicado da interface <t> {teste booleano (t t); }A interface predicada <t> é uma interface genérica. Uma classe genérica (ou uma interface genérica) especifica um ou mais parâmetros de tipo usando um par de colchetes de ângulo (<>). Existe apenas um parâmetro de tipo nesta interface. Quando você declara ou instancia uma classe genérica usando uma classe de concreto, você obtém uma classe parametrizada. Por exemplo, o predicado de classe parametrizado <sesso> é assim:
Predicado da interface <SOYSE> {teste booleano (pessoa t); }Nesta classe parametrizada, existe um método que é consistente com os parâmetros e os valores de retorno do método de teste de checkPerson.Boolean (Pessoa P). Portanto, você pode usar a interface predicada <t> para substituir a interface do CheckPerson, conforme demonstrado no método a seguir:
public static void PrintPerSonsWithPredicate (lista de <Person>, predicada <sesso> tester) {for (Pessoa P: Lista) {if (tester.test (p)) {P.printPerson (); }}}Em seguida, use o código a seguir para filtrar os membros do serviço militar, como no Plano 3:
PrintPerSonsWithPredicate (Lista, P -> P.GetGender () == PERSON.SEX.MALE && P.GETAGE ()> = 18 && P.GETAGE () <= 25);
Você já notou que, ao usar o predicado <sesso> como tipo de parâmetro, nenhum tipo de parâmetro explícito é especificado. Este não é o único local onde as expressões lambda são aplicadas. O esquema a seguir introduzirá mais uso das expressões lambda.
Solução 7: Use expressões Lambda ao longo do aplicativo
Vamos dar uma olhada no método PrintPerSonsWithPredicate e considerar se você pode usar expressões Lambda aqui:
public static void PrintPerSonsWithPredicate (lista de <Person>, predicada <sesso> tester) {for (Pessoa P: Lista) {if (tester.test (p)) {P.printPerson (); }}}Neste método, cada instância de cada pessoa na lista é verificada usando o testador de instância do predicado. Se a instância de pessoa cumprir os critérios de verificação definidos no testador, o método printperson da instância da pessoa será acionado.
Além de acionar o método printperson, as instâncias de pessoa que atendem ao padrão do testador também podem executar outros métodos. Você pode considerar o uso de uma expressão lambda para especificar o método a ser executado (acho que esse recurso é bom, que resolve o problema que os métodos em Java não podem ser passados como objetos). Agora você precisa de uma expressão de lambda semelhante ao método printperson - uma expressão de lambda que requer apenas um parâmetro e retorna o vazio. Lembre -se de uma coisa: para usar expressões Lambda, você precisa implementar uma interface funcional primeiro. Neste exemplo, é necessária uma interface funcional, que contém apenas um método abstrato. Este método abstrato possui um parâmetro da pessoa do tipo e retorna ao vazio. Você pode dar uma olhada no consumidor de interface funcional padrão <t> fornecida pelo JDK, que tem um método abstrato aceito (t) apenas atende a esse requisito. No código a seguir, use uma instância do consumidor <T> para chamar o método de aceitação em vez de p.printperson ():
public static void ProcessPersons (lista de <Person>, Predicate <Person> testador, consumidor <sesson> bloco) {for (Pessoa P: Lista) {if (tester.test (p)) {block.accept (p); }}}Da mesma forma, você pode usar o seguinte código para filtrar os membros da idade do serviço militar:
ProcessPersons (Lista, P -> P.GetGender () == PERSON.SEX.MALE && P.GETAGE ()> = 18 && P.Getage () <= 25, P -> P.PrintPerson ());
Se queremos fazer coisas que não estão apenas imprimindo as informações do membro, mas mais coisas, como verificar a associação, obter informações de contato do membro etc. Nesse ponto, precisamos de uma interface funcional com um método de valor de retorno. Função de interface funcional padrão do JDK <t, r> possui um método como este R Aplicar (t t). O método a seguir obtém dados do mapeador de parâmetros e executa o comportamento especificado pelo bloco de parâmetros nesses dados:
public static void ProcessPerSonsWithFunction (lista de <Person>, Predicate <Pessoa> Testador, função <Pessoa, String> Mapper, Consumer <String> bloco) {for (Pessoa P: Rolha) {if (tester.test (p)) {string data = mapper.apply (p); block.Acept (dados); }}}O código a seguir obtém as informações de email de todos os membros da idade do serviço militar na lista e o imprime:
ProcessPerSonsWithFunction (Lista, P -> P.GetGender () == PERSON.SEX.MALE && P.GETAGE ()> = 18 && P.Getage () <= 25, P ->
Solução 8: Use genéricos com mais frequência
Vamos revisar o método ProcessPerSonsWithFunction. A seguir, é apresentada uma versão genérica deste método. O novo método requer mais tolerância nos tipos de parâmetros:
public static <x, y> void ProcessElements (iterable <x> fonte, predicado <x> testador, função <x, y> mapeador, consumidor <y> bloco) {for (x p: fonte) {if (tester.test (p)) {y data = mapper.apply (p); block.Acept (dados); }}}Para imprimir as informações do membro para o serviço militar na idade certa, você pode chamar o método do ProcessElements, como o seguinte:
ProcessElements (Lista, P -> P.GetGender () == PERSON.SEX.MALE && P.GETAGE ()> = 18 && P.Getage () <= 25, P -> P.GetemAilAddress (), Email -> System.out.println (email));
Durante o processo de chamada de método, o seguinte comportamento é realizado:
Obtenha as informações do objeto de uma coleção, neste exemplo, obtenha informações de objeto de pessoa da lista de instância de coleta.
Filtrar objetos que podem corresponder ao testador de instância do predicado. Neste exemplo, o objeto de predicado é uma expressão de lambda que especifica as condições para filtrar o serviço militar na idade certa.
O objeto filtrado é entregue a um mapeador de objeto de função para processamento, e o mapeador corresponderá a um valor a esse objeto. Neste exemplo, o mapeador de objeto de função é uma expressão lambda que retorna o endereço de email de cada membro.
Especifica um comportamento pelo bloco de objeto do consumidor para o valor correspondente pelo mapeador. Neste exemplo, o objeto de consumo é uma expressão de lambda, que é a função de imprimir uma string, que é o endereço de email do membro retornado pelo mapeador da instância da função.
Solução 9: Use a operação de agregação usando a expressão lambda como um parâmetro
O código a seguir usa a operação de agregação para imprimir os endereços de email dos membros da idade militar na coleção da lista:
RISTER.STREAM () .Filter (p -> p.getGender () == PERSON.SEX.MALE && p.getage ()> = 18 && p.getage () <= 25) .map (p -> p.getemailaddress ()) .foreach (email -> system.out.println (email));
Analise o processo de execução do código acima e organize a tabela a seguir:
Comportamento | Operação de agregação |
Obtenha o objeto | Stream <E> Stream () |
Filtrar objetos que correspondem aos critérios especificados da instância do predicado | Stream <t> filtro (predicado <? Super t> prevendo) |
Obtenha o valor correspondente do objeto através de uma instância de função | <r> stream <r> mapa (função <? Super t ,? estende r> mapper) |
Executar o comportamento especificado pela instância do consumidor | void foreach (consumidor <? super t> ação) |
O filtro, o mapa e as operações da FOREACH na tabela são operações agregadas. Os elementos processados pela operação de agregação vêm do fluxo, não diretamente da coleção (ou seja, porque o primeiro método chamado neste programa de exemplo é Stream ()). Um fluxo é uma sequência de dados. Ao contrário das coleções, o Stream não armazena dados com uma estrutura específica. Em vez disso, o fluxo obtém dados de uma fonte específica, como de uma coleção, através de um pipeline. O oleoduto é uma sequência de operação de fluxo, neste exemplo, filtro-mapa-foreach. Além disso, as operações de agregação geralmente usam expressões Lambda como parâmetros, o que também nos dá muito espaço personalizado.