[Pesquisar este blog]

domingo, 27 de novembro de 2016

Carreira em TI

Estudo recente realizado pela IDC, empresa de inteligência de mercado, estima que o déficit de profissionais de TI no Brasil será menor em 2019 do que em 2015, quando sobraram quase 200 mil vagas na área, que não puderam ser preenchidas devido a falta de profissionais qualificados.

Esta notícia pode ser interpretada de maneiras diferentes, mas complementares:
  • a demanda por mão-de-obra com formação tecnológica é, hoje, bastante alta, e permanecerá elevada nos próximos anos;
  • os esforços do setor privado (substanciais) e do governo (em queda), não conseguiram suprir a demanda das empresas de tecnologia, que vivem, conforme muitos gerentes de RH, uma guerra por talentos;
  • a lacuna de profissionais preparados faz com que os salários oferecidos pelo mercado sejam maiores.
Existe hoje uma necessidade enorme de novos aplicativos móveis e no projeto de sistemas de alta usabilidade, além de sistemas de apoio server-side, com uso intensivo de arquiteturas orientadas à serviço, distribuídas e em nuvem. Alguns números:
  • US$59.9 bilhões de investimento na área de TI em 2015 no Brasil (líder disparado na América Latina).
  • R$19.6 bilhões movimentados pelo e-Commerce no Brasil, apenas no 1o trimestre de 2016; 
  • 170% de crescimento de e-Commerce via smartphones no Brasil;
  • 50% das empresas brasileiras pretendem manter ou aumentar seu projetos em TI;
  • 63% do mercado brasileiro de TI se concentra na região sudeste;
  • Previsão de aumento nos gastos com nuvem em 2017.
Daí serem muito procurados os analistas de sistemas, os cientistas da computação, os engenheiros de computação e outros profissionais com competências para o desenvolvimento de software. A demanda é tão grande que surgiram muitas iniciativas para promover o ensino de programação entre crianças e adolescentes, como forma de incentivar e direcionar um número maior de pessoas para área.

Além dos conhecimentos tradicionais de técnicas de programação, engenharia de software e linguagens como Java e C#, existem oportunidades para aqueles que dominem o Python e o PHP. Já tendeências mais recentes, como aplicativos para cloud computing, big data e web analytics são diferenciais enormes, pois existe um pequeno número de pessoas que dominam estes assuntos. Mas não podem ser esquecidos os conteúdos de outras áreas, bem como (um mínimo) de proficiência em inglês e, quem sabe, outro idioma. 

E não é apenas no segmento de Tecnologia da Informação. A área de  Telecomunicações passa pelas mesmas dificuldades, principalmente no nível técnico, envolvendo a integração de sistemas, incluindo sua segurança. Há quem diga que serão 400 mil vagas em aberto nas áreas de TI & TELECOM até 2020.
Embora o número de cursos ofertados nas instituições de ensino superior tenha aumentado muito nos últimos 10 anos, a procura tem, surpreendentemente, diminuído. As razões para isso são muitas: os problemas enfrentados pelo ensino médio no Brasil; as exigências dos currículos que parecem desarticuladas com as necessidades do mercado; e o desinteresse pelas carreiras em TI, apesar das oportunidades existentes. Isto acaba mantendo ainda mais aquecido este mercado.

E quem já é graduado, ou está prestes a concluir sua graduação, não pode nem pensar em parar de estudar. O caminho do sucesso requer fazer uma pós-graduação ou, no mínimo, buscar certificações profissionais.

Em resumo, quem estiver bem preparado, com conhecimento técnico substancial, e também possuir perfil proativo, vontade de trabalhar e de crescer, não terá dificuldades em encontrar seu lugar!


domingo, 20 de novembro de 2016

Coleções e Streams outra vez::Parte III

Neste terceiro artigo da série sobre Coleções e Streams, o assunto são as operações terminais. A API Streams, permite a manipulação do conteúdo das coleções de maneira muito flexível e elegante, possibilitando a execução de operações em massa sobre seus elementos, aproveitando a simplicidade das construções funcionais.

Revisão Rápida

A ideia de stream é representar uma sequência de elementos sobre os quais uma ou mais operações podem ser executadas. Um stream associado à uma coleção fornece uma sequência de seus elementos, onde cada uma figura uma única vez. Mas, diferentemente das coleções, cujo conteúdo é estável e pode ser reutilizado; ou dos iterators, que podem permitir a navegação entre os elementos das coleçõesos streams são consumíveis, ou seja, só podem ser utilizados uma única vez por uma operação intermediária ou terminal. Sua reutilização provoca a exceção IllegalStateException.


A proposta da API de Streams é organizar um stream pipeline, como o ilustrado na figura anterior, iniciado  com uma fonte (ou origem) dos dados; uma sequência de zero ou mais operações intermediárias; e uma operação terminal (ou final).


O fragmento de código que segue mostra um stream pipeline com cinco estágios que, resumidamente, filtra os nomes da lista convertidos para maiúsculas com quatro caracteres que são terminados por "RO", contando a quantidade de ocorrências, mas mantendo a coleção lista inalterada:

// StreamFragm301.java
long c = lista.stream()
.map(s -> s.toUpperCase())
.filter(s -> s.length()==4)
.filter(s -> s.endsWith("RO"))
.count();

A coleção coleção de objetos String lista é acionada pelo método stream(), operação fonte que constitui o primeiro estágio onde é criado um stream que inicia o pipeline. No segundo estágio cada elemento é mapeado com map(Function<T,R>) para sua versão em maiúsculas. O dois estágios seguintes usam método filter(Predicate), uma operação intermediária (que toma um stream como entrada, resultando em outro), cuja expressões lambda fornecidas determinam o predicado da filtragem, isto é, o critério de aceitação dos elementos do stream de entrada que serão enviados para o stream de saída. No último estágio temos a operação terminal count() (que toma um stream como entrada e produz um tipo não encadeável) cujo resultado long não permite a continuação do encadeamento do stream pipeline.
Nos fragmentos de código que seguem, o comentário inicial indica o nome arquivo-fonte Java onde estão contidos, permitindo que sejam testados com maior facilidade. Todos exemplos estão disponíveis no GitHub (projeto pjandl/stream_again).

Neste artigo serão comentadas as operações de tipo terminal, lembrando que este material será dividido em quatro artigos:


Operações Terminais

As operações terminais processam o conteúdo de um stream produzindo um objeto/valor único ou aplicando uma operação sobre todos os seus elementos. Em ambos os casos, o resultado não é um novo stream, o que impede a continuação do stream pipeline, encerrando-o (daí a terminologia de operação terminal).

As operações que produzem um resultado único, sob alguns aspectos são operações de redução dos elementos do stream no sentido de obter um valor especial que representa um aspecto específico do conjunto de elementos, como sua contagem, seu máximo, seu mínimo, uma ocorrência específica ou o resultado de sua redução.

Um outro tipo de operação terminal é aquela que aplica uma mesma operação a cada um dos elementos do stream, ou seja, efetuando uma operação em massa (ou bulk operation).

Também existem operações terminais que convertem um stream em um array, finalizando o pipeline.

Redução

A redução é uma operação terminal que processa, avalia ou agrega os elementos do stream resultando num valor ou objeto final que representa um aspecto específico do conjunto de elementos contidos no stream.

Redução
booleanallMatch (Predicate<T> p)
Retorna um resultado lógico que indica se todos os elementos do stream atendem o predicado p dado.
booleananyMatch (Predicate<T> p)
Retorna um resultado lógico que indica se ao menos um dos elementos do stream atendem o predicado p dado.
booleannoneMatch (Predicate<T> p)
Retorna um resultado lógico que indica se nenhum os elementos do stream atendem o predicado p dado.
longcount ( )
Conta o número de elementos contidos neste stream.
Optional<T>findAny ()
Retorna um objeto Optional<T> contendo um elemento qualquer do stream; ou um objeto Optional<T> vazio quando o stream não contém elementos.
Optional<T>findFirst ()
Retorna um objeto Optional<T> contendo o primeiro elemento do stream; ou um objeto Optional<T> vazio quando o stream não contém elementos.
Optional<T>max (Comparator<T> c)
Retorna um objeto Optional<T> contendo o maior elemento do stream segundo o comparador c fornecido; ou um Optional<T> vazio se o stream não contém elementos.
Optional<T>min (Comparator<T> c)
Retorna um objeto Optional<T> contendo o menor elemento do stream segundo o comparador c fornecido; ou um Optional<T> vazio se o stream não contém elementos.
Optional<T>reduce (BinaryOperator<T> acum)
Realiza a redução do stream por meio da função acumuladora-associativa acum, retornando um objeto Optional<T> com o resultado.
Treduce (T iden, BinaryOperator<T> acum)
Realiza a redução do stream, tomando iden como valor-identidade e acum como uma função acumuladora-associativa, retornando um objeto Optional<T> com o resultado.

No fragmento que segue, uma coleção de String, definida na variável lista, tem seu stream associado obtido e depois avaliado com os métodos allMatch(Predicate<T>)anyMatch(Predicate<T>)noneMatch(Predicate<T>), os quais respectivamente verificarão para um dado predicado se todos os elementos o atendem, se algum elemento o atende e se nenhum elemento o atende.
// StreamFrag301.java
List<String> lista = Arrays.asList("zEro", "Um", "DoIs", "TRes", "QuaTRo", "CiNcO", "SEIS", "setE", "oITo", "noVe", "DeZ");

// verifica se todas as strings são iniciadas com 'Q'
System.out.println("lista[ todos|inicial 'Q']? " +
lista.stream().
allMatch(s -> s.toUpperCase().startsWith("Q")));

// verifica se alguma string tem comprimento 3
System.out.println("lista[ algum|compr.==3]? " +
lista.stream().
anyMatch(s -> s.length()==3));

// verifica se nenhuma string é iniciada com 'x'
System.out.println("lista[nenhum|final 'x']? " +
lista.stream().
noneMatch(s -> s.toLowerCase().endsWith("x")));

O método findAny() retorna um elemento do stream (tipicamente o primeiro, mas sem garantia), enquanto findFirst() retorna o primeiro elemento do stream. Considerando as situações em que o stream está vazio, ao invés de retornar um valor null, que potencialmente poderia provocar o lançamento de NullPointerException, estes métodos sempre retornam um objeto de tipo Optional<T>. Um Optional<T> encapsula um outro objeto de qualquer tipo, ou mesmo um resultado null, simplificando o tratamento do resultado de operações que podem ou não retornar um valor.

O trecho que código abaixo mostra o uso de uma lista de nomes construída na classe Colecoes, onde se obtém um valor qualquer com findAny() e o primeiro valor com findFirst(). Observe as diferentes formas de uso do objeto Optional<String> retornado.
// StreamFragm302.java
List<String> lista = Colecoes.outrosNomes;

Optional<?> opt1 = null;
opt1 = lista.stream()
.findAny(); // obtém qualquer elemento
System.out.println(opt1);
lista.stream()
.findFirst() // obtém primeiro elemento
.ifPresent(opt2 -> System.out.println(opt2));

lista.stream()
.filter(s -> s.equals("Peter Jandl Junior"))
.findFirst() // obtém primeiro elemento
.ifPresent(opt2 -> System.out.println(opt2));

Os métodos max(Comparator<T>) e min(Comparator<T>) permitem obter o maior ou o menor elemento de um stream, retornando o resultado como um objeto Optional<T>, pois o stream pode estar vazio. O comparador fornecido deve atender a interface Comparator<T>. Segue um outro exemplo:
// StreamFrag303.java
List<String> lista = Colecoes.nomes;
Optional<?> opt = null;
opt = lista.stream()
.map(s -> s) // adição de operação intermediária dummy
.min(Comparator.comparing(s -> s.length()));
System.out.println(opt);

Colecoes.cores.stream()
.map(c -> c.getRed()) // mapeia cor RGB em seu valor Red
.max(Comparator.comparing(s -> s)) // comparador elementar
.ifPresent(v -> System.out.println(v));

Finalmente um exemplo do método de redução utilizado para obter a soma de uma série de valores (o componente azul) obtido a partir de uma coleção de objetos (uma lista de cores):
// StreamFrag304.java
long somaBlue = Colecoes.cores.stream()
.map(c -> c.getBlue()) // mapeia cor RGB em seu valor Blue
.reduce(0, (x,y) -> x + y); // redução (soma dos elementos)
System.out.println(somaBlue);

O uso de arrays ou listas de valores é ainda mais simples, pois não requer o mapeamento intermediário:
// StreamFrag304.java
Integer array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int soma = 0; 

soma = Stream.of(array) // stream obtida de array
.reduce(0, (x,y) -> x + y); // redução (soma dos elementos)
System.out.println(soma);

List<Integer> lista = Arrays.asList(array); // lista obtida de array
soma = lista.stream() // stream obtida de lista
.reduce(0, Integer::sum); // redução (soma dos elementos)
System.out.println(soma);

Operação em Massa

São operações terminais onde uma operação específica é aplicada individualmente a todos os elementos presentes no stream, sem retorno de valor.

Operação em Massa
voidforEach (Consumer<T> c)
Aplica a função consumidora a cada elemento presente no stream, sem observar a ordem em que se encontram seus elementos (não-determinístico).
voidforEachOrdered (Consumer<T> c)
Aplica a função consumidora a cada elemento presente no stream, observando a ordem em que se encontram seus elementos (determinístico).

O método forEach() fará a aplicação a função consumidora dada em observar a ordem em que os elementos se encontram no stream. Como consequência não se pode prever como os elementos serão processados em conjunto (situação não-determinística), embora seja garantidos que a função Consumer<T> dada será aplicada a todos. O método forEachOrdered() observará a sequência de elementos presente no stream para aplicação da função, proporcionando um efeito determinístico, mas que não aproveitará da possível paralelização. Segue um fragmento ilustrando o :
// StreamFrag305.java
List<String> lista = Colecoes.nomes;
lista.stream().parallel() // stream paralela
.forEach(s->System.out.println(s));

System.out.println();

Colecoes.nomes.stream() // stream sequencial
.forEachOrdered(System.out::println);

Conversão

Permite transformar um stream em um array de elementos.

Conversão
Object[]toArray ()
Retorna um array contendo os elementos deste stream.

Um exemplo de uso da operação terminal de conversão é:
Object[] o = Colecoes.nomes.stream()
.filter(s -> s.startsWith("K"))
.toArray();

As operações terminais são um importante recursos no tratamento dos streams, pois permitem a realização de operações de redução, em massa ou sua conversão. No próximo post veremos aplicações interessantes da API Stream!


Este artigo faz parte de uma pequena série Coleções e Streams outra vez:

Para saber mais

sexta-feira, 11 de novembro de 2016

Coleções e Streams outra vez::Parte II

A API Streams foi uma das melhores adições do Java 8. Por meio dela podemos manipular as coleções de maneira muito flexível, simples e elegante, realizando operações em massa sobre seus elementos, mas com uso de construções funcionais. Neste segundo artigo da série sobre Coleções e Streams, concentramos nossa atenção nas operações intermediárias.

Revisão Rápida

Um stream representa uma sequência de elementos sobre os quais uma ou mais operações podem ser executadas. Para utilizar um stream é necessário organizar um stream pipeline, como o ilustrado na figura anterior, que se inicia com uma fonte (ou origem) dos dados; uma sequência de zero ou mais operações intermediárias; e uma operação terminal (ou final).

Além disso, os streams se diferenciam das coleções pelo fato de serem consumíveis, ou seja, só podem ser utilizados uma única vez por uma operação intermediária ou terminal. Sua reutilização provoca a exceção IllegalStateException.

Nos fragmentos de código que seguem, o comentário inicial indica o nome arquivo-fonte Java onde estão contidos, permitindo que sejam testados com maior facilidade. Todos exemplos estão disponíveis no GitHub (projeto pjandl/stream_again).

No fragmento de código que segue foi construído um stream pipeline com três estágios que, resumidamente, filtra os nomes da coleção iniciados com "K", contando a quantidade de ocorrências, mas mantendo a coleção nomes inalterada:
// StreamFragm101.java
long cont = nomes.stream() // obtém stream fonte
 .filter(n -> n.startsWith("K"))// filtragem
 .count(); // contagem


No primeiro, nomes é uma coleção de objetos String (por exemplo, List<String>), cujo método stream() é a  operação fonte que cria um stream e inicia o pipeline. O segundo estágio se constitui pelo método filter(Predicate), uma operação intermediária (que toma um stream como entrada, resultando em outro), cuja expressão lambda fornecida determina o predicado da filtragem, isto é, o critério de aceitação dos elementos do stream de entrada que serão enviados para o stream de saída. No último estágio temos a operação terminal count() (que toma um stream como entrada e produz um tipo não encadeável) cujo resultado long não permite a continuação do encadeamento do stream pipeline.

Neste artigo serão detalhadas um pouco mais as operações de tipo intermediária, lembrando que este material será dividido em quatro artigos:


Operações Intermediárias

As operações intermediárias transformam um stream em outro, isto é, resultam um outro stream modificado, o que possibilita o encadeamento de operações, ou seja, tomando um stream como partida, uma operação intermediária produz um outro stream, ao qual pode ser aplicada outra operação intermediária e assim por diante, possibilitando o encadeamento desejado pelo stream pipeline.

As operações intermediárias (que retornam um outro stream) possíveis são:
  • filtragem (filtering),
  • ordenação (sorting),
  • mutação (mutation) e
  • mapeamento (maping).


Filtragem

A filtragem é uma operação intermediária que seleciona elementos do stream baseado num critério (i.e., em um predicado). A filtragem não modifica o tipo de elemento contido no stream.

Filtragem
Stream<T>filter (Predicate<T> p)
Retorna um stream consistindo dos elementos deste stream que atendem o predicado p dado.
Stream<T>distinct ( )
Retorna um stream consistindo dos elementos distintos deste stream (diferentes segundo Object.equals(Object)).
Stream<T>limit (long n)
Retorna um stream consistindo dos n primeiros elementos deste stream.
Stream<T>skip (long n)
Retorna um stream consistindo elementos restantes deste stream após o descarte dos n primeiros elementos.

Com uma coleção de Double definida na variável colecao é possível filtrar elementos que atendam um critério específico, separando uma amostra de tamanho determinado:
// StreamFrag201.java
Stream<Double> stream =
    colecao.stream()              // obtém stream fonte
.filter(e -> e >= 5.0) // filtra valores >= 5
.limit(3);             // ret. stream c/ (até) 3 elem.

Aqui a stream resultante proverá acesso para até três elementos maiores do que 5.0 dentre aqueles contidos na coleção. Uma construção equivalente, sem uso de streams, seria algo como:
List<Double> subcolecao = new ArrayList<>();
int tam = 3;
for(Double e:colecao) {
if (e>=5.0) {
subcolecao.add(e);
tam--;
if (tam==0) break;
}
}

Este código não é complexo, mas é maior e é menos direto que a construção em pipeline.

Outro exemplo é a obtenção da stream de caracteres de uma String, tratados como valores inteiros, que podem ser filtrados para obtenção dos elementos distintos. O método count() é uma operação terminal, de redução (que será abordada no próximo post) que conta a quantidade de elementos de um stream retornando um valor long.
// StreamFrag202.java
// obtém stream de char (valores int) de String
IntStream charList = string.chars();
// conta caracteres distintos
long count = charList.distinct().count();
System.out.println("Caracteres distintos: " + count);

Ordenação

É outra operação intermediária que possibilita classificar os elementos do stream segundo um critério. A ordenação não altera o tipo de elemento contido no stream.

Ordenação
Stream<T>sorted ( )
Retorna um stream contendo os elementos deste stream classificados em ordem natural.
Stream<T>sorted (Comparator comparator)
Retorna um stream contendo os elementos deste stream classificados conforme o comparator fornecido.

Com a mesma coleção de Double definida anteriormente, podemos criar um stream pipeline de cinco estágios, onde a ordenação é um deles:
// StreamFrag203.java
Stream<Double> stream =
  colecao.stream()              // obtém stream fonte
.filter(e -> e < 6.0)  // filtra valores < 6.0
.sorted()              // ordena elementos
           .skip(1);              // descarta primeiro
.limit(5);             // ret. stream com (até) 5 elem.

Nesta situação a stream resultante proverá acesso aos cinco menores elementos, descartado o primeiro, da stream ordenada resultada da filtragem da stream original para valores menores que 6.0 existentes na coleção.

Mutação

Permite alterar os elementos do stream, conforme a função indicada, embora o tipo do elemento não seja alterado.
Mutação
Stream<T>peek (Consumer<> action)
Retorna um stream contendo os elementos deste stream, executando a ação provida em cada elemento.

Um exemplo de uso da operação intermediária de mutação seria a obtenção de um stream com os quadrados do elementos contidos na coleção:
Stream<Double> pipe1 = colecao.stream()
.peek(e -> Math.pow(e, 2));

Poderia ser obtida um stream com os quadrado maiores que 10:
Stream<Double> pipe2 = colecao.stream()
.peek(e -> Math.pow(e, 2))
.filter(e -> e > 10);

Várias operações intermediárias podem ser encadeadas, em qualquer ordem, assim podemos criar um stream com os quadrados dos elementos menores do que 1:
Stream<Double> pipe3 = colecao.stream()
.filter(e -> e < 1)
.peek(e -> Math.pow(e, 2));

Os streams resultantes (pipe1pipe2 e pipe3) podem ser utilizados para outras operações intermediárias ou uma operação terminal.


Mapeamento

O mapeamento é uma operação intermediária mais sofisticada, que transforma os elementos do stream (considerandos como uma entrada do tipo T), com base em uma função de mapeamento  (ou de transformação), e retorna um stream contendo elementos do tipo R, ou seja, esta é uma operação pode alterar o tipo dos elementos contidos no stream.

A função de mapeamento é responsável por tomar, individualmente, elementos do tipo T transformando-os em elementos do tipo R. É bastante comum que a função de mapeamento não altere os elementos de entrada, produzindo novos objetos com os dados ou características desejadas.

Mapeamento
Stream<R>flatMap (Function<T, Stream<R>> mapper)
Retorna um stream consistindo dos resultados da substituição de cada elemento deste stream com o conteúdo do stream de mapeamento produzido pela aplicação da função de mapeamento a cada elemento.
DoubleStreamflatMapToDouble (ToDoubleFunction<T> mapper)
Retorna um DoubleStream consistindo dos resultados da substituição de cada elemento deste stream com o conteúdo do stream de mapeamento produzido pela aplicação da função de mapeamento a cada elemento.
IntStreamflatMapToInt (ToIntFunction<T> mapper)
Retorna um IntStream consistindo dos resultados da substituição de cada elemento deste stream com o conteúdo do stream de mapeamento produzido pela aplicação da função de mapeamento a cada elemento.
LongStreamflatMapToLong (ToLongFunction<T> mapper)
Retorna um LongStream consistindo dos resultados da substituição de cada elemento deste stream com o conteúdo do stream de mapeamento produzido pela aplicação da função de mapeamento a cada elemento.
Stream<R>map (Function<T, R> mapper)
Retorna um stream consistindo dos resultados da aplicação da função a cada elemento deste stream.
DoubleStreammapToDouble (ToDoubleFunction<T> mapper)
Retorna um stream especializado no tipo primitivo double consistindo dos resultados da aplicação da função a cada elemento deste stream.
IntStreammapToInt (ToIntFunction<T> mapper)
Retorna um stream especializado no tipo primitivo int consistindo dos resultados da aplicação da função a cada elemento deste stream.
LongStreammapToLong (ToLongFunction<T> mapper)
Retorna um stream especializado no tipo primitivo long consistindo dos resultados da aplicação da função a cada elemento deste stream.


Considere o método estático que segue criado para ser um gerador de coleções de String. Embora retorne uma coleção de tipo indefinido, internamente este método instancia um ArrayList de String contendo uma centena de objetos String no formato CCDc, ou seja, dois caracteres maiúsculos, seguidos de um dígito e um caractere minúsculo.
// Generator.java
public class Generator {
    public static int SIZE = 100;
    public static Collection<String> newStringCollection() {
        List<String> stringList = new ArrayList<>();
        char[] charArray = new char[4];
        for (int i = 0; i < SIZE; i++) {
            charArray[0] = (char) (65 + (int) (Math.random() * 26));
            charArray[1] = (char) (65 + (int) (Math.random() * 26));
            charArray[2] = (char) (48 + (int) (Math.random() * 10));
            charArray[3] = (char) (97 + (int) (Math.random() * 26));
            stringList.add(new String(charArray));
        }
        return stringList;
    }
}


A partir deste stream é possível a realização de várias operações intermediárias e terminais.

Uma nova coleção pode criada com:
// StreamFrag203.java
Collection<String> colecao = Generator.newStringCollection();

A quantidade de objetos desta coleção pode ser controlada por meio da definição de um outro valor para o campo estático SIZE.
// StreamFrag203.java
Generator.SIZE = 20;
Collection<String> colecao = Generator.newStringCollection();

No trecho que segue, o stream obtido é filtrado para separar os elementos iniciados por 'J'. Uma operação de mapeamento conectada ao pipeline transforma os elementos, inserindo do sufixo "ava" após o 'J' inicial. Um último estágio para prover a ordenação do stream é incluso no pipeline.
// StreamFrag204.java
Stream<String> stream = colecao.stream()
  .filter(e -> e.charAt(0)=='J')
  .map(e -> e.substring(0, 1) + "ava" + e.substring(1))
  .sorted();


Assim o uso de operações intermediárias permite processar os streams, transformando-os conforme desejado e produzindo novos streams que podem ser utilizados por zero, uma ou mais operações intermediárias; e zero ou uma operação terminal. As operações terminais é assunto do próximo post desta série!


Este artigo faz parte de uma pequena série Coleções e Streams outra vez:

Para saber mais