[Pesquisar este blog]

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 foi 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

quarta-feira, 9 de novembro de 2016

Java 9::mais um atraso no lançamento

O lançamento do Java 9 foi adiado uma outra vez. Com essa notícia, um tanto frustante para a enorme comunidade Java, vem um questionamento bastante natural: por quê o prolongamento da fase final de desenvolvimento da nova versão?

Segundo o arquiteto-chefe da plataforma Java na Oracle, Mark Reinhold, o time de desenvolvimento não está onde deveria em relação o cronograma, pois o Jigsaw precisa mais tempo. Além disso também observou que o número de problemas relatados no JDK 9 é maior que a quantidade existente no JDK 8 quando se encontrava em fase idêntica do desenvolvimento. A partir destas declarações, o atraso se tornou oficial:em 18/10 a data de entrega da versão 9 passou de 27/03/2017 para 27/07/2017. Quatro longos meses de atraso, que se somam aos outros seis meses da data original que era 22/09/2016.

A notícia gerou muitos comentários na comunidade e na imprensa especializada (InfoWorld, 404TS, The Register, Jaxenter, eWeek, ADTmag, Takipi). Nada muito diferente do que temos aqui, o que é pouco animador. 

Jigsaw se refere ao novo mecanismo de modularização da API, característica que é carro chefe da versão. Seu objetivo é possibilitar a divisão do runtime em componentes interoperáveis que possibilitem a criação de versões otimizadas para atender necessidade específicas. Atualmente a estrutura do Java Runtime Enviroment é praticamente monolítica, sendo que o arquivo rt.jar contém quase 20.000 classes, muitas delas usadas em poucos programas e poucas usadas por quase todos. Assim, seu tamanho torna-se um peso para a JVM e, com isso, compromete o desempenho das aplicações Java. Ao particionar a API, cada programa Java poderá carregar apenos os módulos necessários, o que trará um melhor desempenho. Além disso, aplicações poderão ser construídas de maneira idêntica, ou seja, com estrutura modular, estendendo tal flexibilidade aos programas construídos com a plataforma.

Apesar da importância do projeto Jigsaw, este atraso é bastante desanimador e, de certa maneira, lança uma incerteza quanto ao cumprimento das novas datas, pois que já errou mais de uma vez, pode continuar errando. Por outro lado, a busca de qualidade ao invés de velocidade, com mero cumprimento de prazo, é algo visto como positivo pelos desenvolvedores em geral. A esperança, então, é que esta data seja cumprida.

Mesmo com tudo isso, a plataforma Java continua liderando a preferência dos programadores. No TIOBE Index de novembro/2016 o Java figura como #1 (18.755%), C como #2 (9,203%), C++ é #3 (5.415%) e C# o #4 (3.659%). Observe que o percentual exibido pelo Java é maior que a soma de C, C++ e C#. Isso diz muita coisa!

O Novo Plano


Com o adiamento da data final de lançamento, todo o cronograma da versão 9 foi alterado, na verdade prolongado, conforme a página do projeto. No momento temos:

  • 2016/12/22 Feature Extension Complete
  • 2017/01/05 Rampdown Start
  • 2017/02/09 All Tests Run
  • 2017/02/16 Zero Bug Bounce
  • 2017/03/16 Rampdown Phase 2
  • 2017/07/06 Final Release Candidate
  • 2017/07/27 General Availability
Assim, apenas em dezembro deste ano teremos a confirmação de todas as características inclusas na versão 9, ponto em que se congela a lista de características da plataforma, sem possibilidade de novas inclusões (até esta data existe até um risco do Jigsaw ser removido da versão 9 e empurrado para a 10, o que já ocorreu no passado!). A fase de testes que segue pretende produzir um candidato a primeira versão oficial do Java 9 no início de julho/2017. Com tudo isso, contando com a inspiração do time de desenvolvimento, no final de julho/2017 deverá ocorrer o General Avaliability, ou seja, quando o JDK 9 é liberado para produção. Maktub!

Para os Curiosos, Ansiosos & Afoitos

As versões preliminares do Java 9 podem ser experimentadas. Basta efetuar o download de uma versão early access no link indicado para JDK 9 Early Access Releases na seção Para Saber Mais. É só conferir!

Para saber mais

sábado, 5 de novembro de 2016

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

Uma das melhores novidades do Java 8 foi a adição da API Streams que trouxe novas ferramentas para a manipulação de conjuntos, listas e outras coleções. Sua característica mais marcante é possibilitar o tratamento de cada elemento de uma coleção sem a necessidade de construção explícita de laços de repetição para tal processamento, ou seja, a realização de operações em massa sobre as coleções. Esta série de quatro artigos pretende apresentar e detalhar esta importante API.
Hierarquia básica das interfaces do pacote java.util.stream.

Definição

A interface java.util.stream.BaseStream, que define sequencias de elementos que podem suportar operações agregadas executadas em modo sequencial ou paralelo, dá origem a quatro subinterfaces muito importantes do pacote java.util.stream.

A primeira é interface java.util.stream.Stream<T> representa uma sequência de elementos sobre os quais uma ou mais operações podem ser executadas. De fato, Stream<T> provê uma cadeia sequencial de referências de objetos, enquanto as demais subinterfaces (IntStream, LongStream e DoubleStream) são especializações voltadas para os tipos primitivos mais comuns, contendo algumas funcionalidades extras.

Para utilizar um stream é necessário organizar um stream pipeline. Tal pipeline 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).



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).

 O fragmento de código que segue exibe um stream pipeline com três estágios:
// StreamFragm101.java
long cont = nomes.stream();
                 .filter(n -> n.startsWith("K"))
                 .count();

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 count(), que é terminal, pois produz um resultado long que não permite continuar encadeamento do stream pipeline.

Resumidamente, este pipeline filtra os nomes iniciados com "K", contando a quantidade de ocorrências. A coleção nomes permanece inalterada.

Um aspecto muito importante, que diferencia os streams das coleções, é que os streams são consumíveis, ou seja, só podem ser utilizados uma única vez por uma operação intermediária ou terminal. Caso sejam reutilizados será lançada a exceção IllegalStateException.

O encadeamento de métodos usado na construção de um pipeline poderia ser reescrito de maneira mais convencional como segue:
// StreamFragm101.java
Stream<String> stream_0 = nomes.stream();
Stream<String> stream_1 = stream_0.filter(n -> n.startsWith("K"));
long cont = stream_1.count();

O resultado proporcionado é o mesmo, mas a custa de código extra que não melhora a legibilidade das operações efetuadas. Por isso a construção dos pipelines é incentivada, evitando que variáveis intermediárias sejam incorretamente utilizadas.

Para podemos detalhar um pouco mais as operações de tipo fonte, intermediária e terminal; além da aplicação geral desta nova API, o material foi dividido em quatro artigos:

Operações Fonte

São aquelas capazes de iniciar um stream pipeline a partir de uma fonte, ou seja, que originam um stream que poderá ser utilizado posteriormente numa sequência de operações intermediárias e por uma última operação terminal.

As fontes são tipicamente coleções (subclasses de java.util.Collection), ou arrays; mas podem ser definidas como uma função geradora ou um canal de I/O. Observe que os mapas não são diretamente suportados com fontes.

Operações Fonte
static Stream<T> concat (Stream<T> a, Stream<T> b)
Retorna um stream cujos elementos são todos os existentes no stream a seguidos de todos os existentes no stream b.
static Stream<T> empty ( )
Retorna um stream sequencial vazio.
static Stream<T> generate (Supplier<T> s)
Retorna um stream sequencial infinito e não ordenado cujos elementos são gerados pela função provida.
static Stream<T> of (T... values)
Retorna um stream sequencial cujos elementos são aqueles fornecidos (na mesma ordem).
static Stream<T> of (T value)
Retorna um stream sequencial contendo um único elemento.

Dada uma coleção qualquer do tipo T, é possível tomá-la como fonte, ou seja, obter seu stream Stream<T> associado com:
// StreamFragm102.java
ArrayList<Integer> colecao = new ArrayList<Integer>();
// Adiciona conteúdo: números de 0 a 50, de 2 em 2
for (int i = 0; i < 50; i+=2) {
    colecao.add(i);
}
// Obtém stream
Stream<Integer> streamI = colecao.stream();

Também é possível obter um stream a partir de um array de qualquer tipo, tal como segue:
// StreamFragm102.java
// Obtém stream a partir de array de elementos do tipo Dimension
Dimension[] dimemsionArray = {
new Dimension(19, 64), new Dimension(68, 19), 
new Dimension(19, 31), new Dimension(95, 19),
new Dimension(13, 20), new Dimension(20, 16)
}; 

Stream<?> stream = Stream.of(dimemsionArray);

Outra possibilidade é concatenar dois streams existentes, na forma de um terceiro stream como abaixo:
// StreamFragm102.java
// Obtém stream de String a partir de outras streams de Strings

Stream<String> sAll = Stream.concat(nomes.stream(),
                                    outrosNomes.stream());

Finalmente é possível criar um stream, com infinitos elementos, com base numa função geradora, como o exemplo que segue:

// StreamFragm102.java
// Obtém stream a partir de gerador infinito
// de Double entre [0, 100)

Stream<Double> streamD = Stream.generate(() -> 100*Math.random());

A partir das operações fonte, obtemos streams que podem ser utilizadas por zero, uma ou mais operações intermediárias; e zero ou uma operação terminal. Mas isto fica para os próximos posts!



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



Para saber mais



domingo, 30 de outubro de 2016

V Workshop de Tecnologia de Informação::WTI2016::Apresentações

WTI2016, quinta edição do evento anual dos cursos de graduação em Análise e Desenvolvimento de Sistemas, Ciência da Computação e Sistemas de Informação do Centro Universitário Padre Anchieta (Jundiaí-SP), foi realizado no período de 24 a 26 de outubro de 2016 com temática centrada nos projetos de software.
Abaixo seguem os links para download dos materiais do evento.

segunda-feira, 24 de outubro de 2016

V Workshop de Tecnologia de Informação::WTI2016

O WTI2016, quinta edição do evento anual dos cursos de graduação em Análise e Desenvolvimento de Sistemas, Ciência da Computação e Sistemas de Informação do Centro Universitário Padre Anchieta (Jundiaí-SP), foi realizado no período de 24 a 26 de outubro de 2016 com temática centrada nos projetos de software.

O objetivo do evento foi a divulgação de novas tecnologias, metodologias, práticas, serviços e oportunidades de carreira na área de Tecnologia de Informação; além da avaliação e do debate das influências da tecnologia na organização da sociedade, considerando aspectos sociais, econômicos e culturais, incluindo as questões da inclusão e exclusão digital, da igualdade de gênero, raça e credo; além de oferecer motivação para os estudantes no sentido do prosseguimento de seus estudos e desenvolvimento de suas carreiras.

Nesta edição, o evento foi composto exclusivamente de palestras e dirigido ao público interno do UniAnchieta, em especial para os estudantes dos cursos de graduação em Análise e Desenvolvimento de Sistemas, Ciência da Computação e Sistemas de Informação. A programação envolveu três temas chave da Tecnologia da Informação: a arquitetura de software, o gerenciamento de projetos e a domótica.

Programação

Abaixo segue a programação do evento.
  • [24 de Outubro de 2016 | Sala 65 | Prédio 2]
    19h15 | Abertura
    19h30 | O Papel do Arquiteto de Software
    Prof. Ms. Peter Jandl Junior | UniAnchieta
  • [25 de Outubro de 2016 | Sala 65 | Prédio 2]
    19h15 | Abertura
    19h30 | A Importância das Práticas de Gerenciamento de Projeto
    Prof. Ms. Antônio Rogério Tabalipa | UniAnchieta
  • [26 de Outubro de 2016 | Sala 65 | Prédio 2]
    19h15 | Abertura
    19h30 | Aplicações da Domótica
    Prof. Esp. Leandro Pottes | UniAnchieta
Links para download dos materiais compartilhados pelos apresentadores estão disponibilizados em outro post deste blog.