Existe uma alternativa para instanceof ao filtrar um fluxo Java por classe?

8

Eu tenho uma situação inesperada em um projeto no qual todos os tipos que estendem uma classe são compactados em uma coleção Java; mas apenas uma extensão específica dessa classe contém um método adicional. Vamos chamar de "also ()"; e deixe-me esclarecer que nenhuma outra extensão possui. Antes de executar uma tarefa em cada item dessa coleção, preciso chamar também () todos os itens que a implementam.

O caminho mais fácil é o seguinte:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));
stuff.stream().forEach(item -> item.method());

Funciona bem, mas não me sinto confortável com a "instânciaof" lá. Isso geralmente é um marcador de mau cheiro de código. É muito possível que eu refatore essa classe apenas para me livrar dela. Antes de fazer algo tão dramático, pensei em verificar com a comunidade e ver se alguém com mais experiência no Streams ou no Collections tinha uma solução mais simples.

Como exemplo (certamente não exclusivo), é possível obter uma visão de uma coleção que filtra entradas por classe?

Michael Eric Oberlin
fonte
2
Se você precisa filtrar SpecificItem, e esta é a maneira correta de especificar o predicado, então por que você é sensível sobre o fato de instanceofestar lá?
Robert Harvey
Os objetos no fluxo implementam alguma interface comum que permite discriminar entre classes? Alguns .getKind()ou .isSpecific()?
9000
@ 9000 Infelizmente não, e acredito que a adição de uma coisa dessas o deixaria um pouco mais confuso.
Michael Eric Oberlin
8
Existe uma razão SpecificItempara a implementação não chamar apenas also()primeiro?
Tristan Burnside
1
@ LuísGuilherme Iterablenão tem um stream()método, mas você pode chamar forEach()diretamente em um Iterable.
Shmosel

Respostas:

3

Eu sugiro .mapque faça uma ligação para fazer o elenco para você. Então seu código posterior pode usar a 'coisa real'.

Antes:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));

Depois de:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .map(item -> (SpecificItem)item)
            .forEach(specificItem -> specificItem.also()));

Isso não é perfeito, mas parece limpar um pouco as coisas.

Jon Onstott
fonte
É meu interesse remover o maior número possível de transmissões do código de trabalho, e você está certo, o mapa destinado exatamente para isso.
22615 Michael Michael Oberlin
Mais funcionalmente: stuff.stream().filter(item -> item instanceof SpecificItem).map(SpecificItem.class::cast).forEach(SpecificItem::also);
strickli 6/12/19
3

Eu tenho uma situação inesperada em um projeto no qual todos os tipos que estendem uma classe são compactados em uma coleção Java; mas apenas uma extensão dessa classe implementa um método. Vamos chamá-lo de "also ()". Antes de executar uma tarefa em cada item dessa coleção, preciso chamar também () todos os itens que a implementam.

Obviamente, esse é um design defeituoso. Pelo que você escreveu, não está claro, o que significa, que uma classe não implementa o método. Se simplesmente não fizer nada , não importaria, então presumo que exista um efeito colateral indesejado.

Funciona bem, mas não me sinto confortável com a "instânciaof" lá.

Suas entranhas estão certas. Um bom design orientado a objetos funcionaria sem mais conhecimento do que exatamente um objeto, ou se é uma instância de um tipo especial.

Antes de fazer algo tão dramático, pensei em verificar com a comunidade e ver se alguém com mais experiência no Streams ou no Collections tinha uma solução mais simples.

A refatoração não é dramática. Melhora a qualidade do código.

Com sua base de código fornecida, a solução mais simples seria, para garantir, você ter duas coleções separadas, uma com a classe pai e outra com a classe filho. Mas isso não é muito limpo.

Thomas Junk
fonte
Editei minha pergunta para ser mais específico. Para esclarecer mais, este é um trabalho de crack para uma mudança necessária imediatamente; uma extensão da classe deve ser levada em consideração. Quanto à refatoração, vamos apenas dizer que é uma parte essencial de ser um programador. No entanto, estou procurando o refator menos dramático possível.
Michael Eric Oberlin
Para sua pergunta, há uma resposta clara: se o filtro precisa saber, com qual instância ele deve lidar, não há como contornar a instância (exceto algum tipo de mágica de reflexão esquisita, que é uma maneira mais enigmática de perguntar). instanceof ). Porém, o tempo que você economiza agora para uma solução rápida e suja é gasto mais tarde no ciclo de vida do produto, corrigindo bugs devido a um design ruim.
Thomas Junk
@ MichaelEricOberlin: Então, se eu ouço Thomas corretamente, suas escolhas parecem ser: mudar o design ou deixá-lo como está.
Robert Harvey
@RobertHarvey coloca dessa maneira, parece tautológico;) se ele não quiser refatorar, não há outra opção a não ser seguir a estrada atual.
Thomas Junk
2

É difícil dar recomendações específicas sem saber o que SpecificIteme also()realmente são, mas:

Defina also()na superclasse de SpecificItem. Dê a ele uma implementação padrão que não faça nada (dê um corpo de método vazio). Subclasses individuais podem substituí-lo, se desejado. Dependendo do que alsorealmente é, pode ser necessário renomeá-lo para algo que faça sentido para todas as classes envolvidas.

Alex D
fonte
Isso é sensato, e eu posso acabar refatorando para fazer isso.
Michael Eric Oberlin
1

uma alternativa:

  1. faça SpecificItem implementar uma interface (diga "Filtrável")
  2. faça uma nova classe que estenda o Stream
  3. crie um novo método de 'filtro' que aceite objetos que implementem sua interface
  4. substitua o método de fluxo original e redirecione-o para sua implementação (lançando o parâmetro predicado em sua interface)

Dessa forma, apenas objetos que implementam sua interface poderiam passar temas para o seu método ... não há necessidade de usar instanceof

public class MyStream extends Stream
{
    //...

    Stream<Filterable> filter(Predicate<? implements Filterable> predicate)
    {
         return super.filter(predicate);
    }
}
ymz
fonte
3
Não é esse o caminho: melhorar o design ruim, tornando-o pior.
Thomas Junk
Eu tenho que concordar com Thomas Junk aqui. O terceiro passo basicamente faz o que eu já estou fazendo; e sem usar novamente instanceof, não consigo ver como seu método resolveria isso.
Michael Eric Oberlin
1
@ MichaelEricOberlin: Era exatamente com isso que eu estava preocupado; criando um Rube Goldberg apenas para satisfazer a ideia de alguém de uma "melhor prática".
Robert Harvey
1

Espero não estar perdendo nada óbvio aqui (também como sugerido pelo comentário de @Tristan Burnside ), mas por que não posso SpecificItem.method()ligar also()primeiro?

public class SpecificItem extends Item {
    ...
    public void method() {
        also();
        super.method();
    }
}

Como exemplo (certamente não exclusivo), é possível obter uma visão de uma coleção que filtra entradas por classe?

Um fluxo-y maneira que eu posso pensar, à custa de talvez algum impacto no desempenho (YMMV), é a collect() via Collectors.groupingBy()na classe como a chave, em seguida, escolher o que você quer do resultado Map. Os valores são armazenados como a List; portanto, se você esperava fazer essa filtragem em um Sete espera filtrá- Setlo, precisará de uma etapa adicional para inserir ainda mais os valores em um resultado Set.

hjk
fonte
-1

Aqui está a minha abordagem:

stuff.stream().filter(item -> SpecificItem.class.isInstance(item)).map(item  -> SpecificItem.class.cast(item)).forEach(item -> item.also());
stuff.stream().forEach(item -> item.method());

Nenhuma instância de , nenhuma conversão explícita (na verdade, é uma conversão explícita, mas mascarada por uma chamada de método). Eu acho que isso é o melhor possível.

beto
fonte
1
Não é apenas uma versão menos legível do original?
grahamparks
Bem, ele realmente faz o mesmo de uma maneira segura. Pode ser mais simples, dependendo do contexto do código. Eu uso isso para filtrar certos tipos dentro de uma lista genérica, exceto a parte forEach.
beto