É possível transmitir um fluxo no Java 8?

160

É possível transmitir um fluxo no Java 8? Digamos que eu tenha uma lista de objetos, posso fazer algo assim para filtrar todos os objetos adicionais:

Stream.of(objects).filter(c -> c instanceof Client)

Após isso, porém, se eu quiser fazer algo com os clientes, precisarei converter cada um deles:

Stream.of(objects).filter(c -> c instanceof Client)
    .map(c -> ((Client) c).getID()).forEach(System.out::println);

Isso parece um pouco feio. É possível converter um fluxo inteiro para um tipo diferente? Como elenco Stream<Object>para um Stream<Client>?

Por favor, ignore o fato de que fazer coisas como essa provavelmente significaria um design ruim. Como fazemos coisas na minha aula de ciências da computação, eu estava analisando os novos recursos do java 8 e fiquei curioso para saber se isso era possível.

Phiction
fonte
3
Do ponto de vista do tempo de execução Java, os dois tipos de Stream já são os mesmos, portanto, nenhuma conversão é necessária. O truque é passar pelo compilador. (Ou seja, supondo que ele faz qualquer sentido fazê-lo.)
Hot Licks

Respostas:

283

Eu não acho que exista uma maneira de fazer isso imediatamente. Uma solução possivelmente mais limpa seria:

Stream.of(objects)
    .filter(c -> c instanceof Client)
    .map(c -> (Client) c)
    .map(Client::getID)
    .forEach(System.out::println);

ou, como sugerido nos comentários, você pode usar o castmétodo - o primeiro pode ser mais fácil de ler:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);
assylias
fonte
Isso é basicamente o que eu estava procurando. Acho que esqueci que a transmissão para o Client mapretornaria a Stream<Client>. Obrigado!
Phiction 19/03/14
+1 novas formas interessantes, embora eles arriscam a entrar em spaghetti-code de um tipo de nova geração (horizontal, e não vertical)
robermann
@LordOfThePigs Sim, funciona, embora não tenha certeza se o código fica mais claro. Eu adicionei a ideia à minha resposta.
assylias
38
Você pode "simplificar" a instância do filtro com:Stream.of(objects).filter(Client.class::isInstance).[...]
Nicolas Labrot
A parte sem flecha é realmente bonita <3
Fabich 16/01
14

Na linha da resposta de ggovan , faço o seguinte:

/**
 * Provides various high-order functions.
 */
public final class F {
    /**
     * When the returned {@code Function} is passed as an argument to
     * {@link Stream#flatMap}, the result is a stream of instances of
     * {@code cls}.
     */
    public static <E> Function<Object, Stream<E>> instancesOf(Class<E> cls) {
        return o -> cls.isInstance(o)
                ? Stream.of(cls.cast(o))
                : Stream.empty();
    }
}

Usando esta função auxiliar:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);
Brandon
fonte
10

Tarde para a festa, mas acho que é uma resposta útil.

flatMap seria a maneira mais curta de fazer isso.

Stream.of(objects).flatMap(o->(o instanceof Client)?Stream.of((Client)o):Stream.empty())

Se ofor um Client, crie um fluxo com um único elemento, caso contrário, use o fluxo vazio. Esses fluxos serão achatados em a Stream<Client>.

ggovan
fonte
Tentei implementar isso, mas recebi um aviso dizendo que minha classe "usa operações não verificadas ou inseguras" - isso é de se esperar?
aweibell
Infelizmente sim. Se você usasse um operador if/elseem vez do ?:operador, não haveria aviso. Tenha certeza de que você pode suprimir com segurança o aviso.
ggovan
3
Na verdade, isso é mais longo Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)ou uniforme Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast).
Didier L
4

Isso parece um pouco feio. É possível converter um fluxo inteiro para um tipo diferente? Como elenco Stream<Object>para um Stream<Client>?

Não, isso não seria possível. Isso não é novo no Java 8. Isso é específico para genéricos. A List<Object>não é um super tipo de List<String>, então você não pode simplesmente converter de a List<Object>para a List<String>.

Semelhante é o problema aqui. Você não pode lançar Stream<Object>para Stream<Client>. Claro que você pode transmiti-lo indiretamente assim:

Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;

mas isso não é seguro e pode falhar no tempo de execução. A razão subjacente para isso é que os genéricos em Java são implementados usando apagamento. Portanto, não há informações de tipo disponíveis sobre qual tipo Streamestá em tempo de execução. Tudo é justo Stream.

BTW, o que há de errado com sua abordagem? Parece bom para mim.

Rohit Jain
fonte
2
O @DR Generics in C#é implementado usando reificação, enquanto que em Java é implementado usando apagamento. Ambos são implementados de maneira diferente subjacente. Portanto, você não pode esperar que funcione da mesma maneira nos dois idiomas.
Rohit Jain
1
@DR Entendo que o apagamento coloca muitos problemas para os iniciantes entenderem o conceito de genéricos em Java. E como não uso C #, não posso entrar em muitos detalhes sobre comparação. Mas toda a motivação por trás da implementação dessa maneira da IMO foi evitar grandes mudanças na implementação da JVM.
Rohit Jain
1
Por que "certamente falha no tempo de execução"? Como você diz, não há informações do tipo (genérico), portanto, nada para o tempo de execução verificar. Pode , eventualmente, falhar em tempo de execução, se os tipos errados são alimentados através, mas não há "certeza" sobre isso tudo.
Hot Licks
1
@RohitJain: Eu não estou criticando conceito genérico de Java, mas esta única conseqüência ainda cria código feio ;-)
DR
1
@DR - os genéricos Java são feios desde o git-go. Principalmente apenas C ++ apresenta luxúria.
Hot Licks