BT

Experimente a nova interface visual do InfoQ! Veja o novo design do InfoQ 3.0 e nos diga o que você achou.

GS Collections através de exemplos – Parte 2

| por Donald Raab Seguir 0 Seguidores , traduzido por Pedro Boschi Seguir 0 Seguidores em 09 fev 2015. Tempo estimado de leitura: 15 minutos |

Na parte 1 de GS Collections via Exemplos demonstrei diferentes maneiras de filtrar uma coleção em GS Collections utilizando select e selectWith.

Para a chamada do select, passamos um Predicate, geralmente como um lambda. Para a chamada do selectWith, passamos um Predicate2, normalmente como uma referência para método.

Existem vários métodos em GS Collections que recebem um Predicate, incluindo select, reject, detect, anySatisfy, noneSatisfy, count e partition.

Também há formas que recebem um Predicate2, como selectWith, rejectWith, detectWith, anySatisfyWith, allSatisfyWith, noneSatisfyWith, countWith e partitionWith.

Nesta parte 2 vamos olhar com mais detalhes para chamadas com Predicates e ver alguns exemplos de "métodos de transformação" (collect, flatCollect, groupBy, groupByEach), que todos recebem o tipo Function como argumento.

Agora mostrarei exemplos que aproveitam a API da GS Collections para mover de containers de objetos para containers de primitivos e como utilizar as APIs disponíveis nos containers de tipos primitivos. Alguns dos exemplos usarão um domínio simples mostrado abaixo.

Os exemplos são testes jUnit e necessitam de Java 8 para rodar.

[Clique na imagem para ampliar]

fig1-large.jpg

Espero que ao final dos exemplos você esteja interessado em explorar mais a fundo a rica e completa API que desenvolvemos em GS Collections.

Exemplo 2: Existe algum dos elementos de uma coleção que satisfaça um dado Predicate?

Use anySatisfy:

@Test
public void doAnyPeopleHaveCats()
{

    Predicate<Person> predicate = person -> person.hasPet(PetType.CAT);
    boolean result =
       this.people.anySatisfy(predicate);
    Assert.assertTrue(result);
    boolean result1 =
       this.people.anySatisfyWith(Person::hasPet, PetType.CAT);
    Assert.assertTrue(result1);

}

Exemplo 3: Todos os elementos de uma coleção satisfazem um dado Predicate?

Use allSatisfy:

@Test
public void doAllPeopleHaveCats()
{
    boolean result =        
       this.people.allSatisfy(person -> person.hasPet(PetType.CAT));    
   Assert.assertFalse(result);    

    boolean result1 =        
       this.people.allSatisfyWith(Person::hasPet, PetType.CAT);    
    Assert.assertFalse(result1);
}

Exemplo 4: Nenhum dos elementos de uma coleção satisfaz um dado Predicate?

Use noneSatisfy:

@Test
public void doNoPeopleHaveCats()
{
    boolean result =
        this.people.noneSatisfy(person -> person.hasPet(PetType.CAT));
    Assert.assertFalse(result);
    boolean result1 =
        this.people.noneSatisfyWith(Person::hasPet, PetType.CAT);
    Assert.assertFalse(result1);
}

Exemplo 5: Contar o número de elementos de uma coleção que satisfazem um dado Predicate

Use count:

@Test
public void howManyPeopleHaveCats()
{
    int count =
        this.people.count(person -> person.hasPet(PetType.CAT));
    Assert.assertEquals(2, count);
    int count1 =
        this.people.countWith(Person::hasPet, PetType.CAT);
    Assert.assertEquals(2, count1);
}

Exemplo 6: Encontre o primeiro elemento de uma coleção que satisfaça um dado Predicate

Use detect:

@Test
public void findPersonNamedMarySmith()
{
    Person result =
        this.people.detect(person -> person.named("Mary Smith"));
    Assert.assertEquals("Mary", result.getFirstName());
    Assert.assertEquals("Smith", result.getLastName());
   
 Person result1 =

        this.people.detectWith(Person::named, "Mary Smith");
    Assert.assertEquals("Mary", result1.getFirstName());
    Assert.assertEquals("Smith", result1.getLastName());
}

Os métodos any/all/noneSatisfy e detect são exemplos de métodos que provocam curto circuito, ou seja, que podem retornar o resultado sem ter que percorrer toda a coleção. Por exemplo, anySatisfy se comporta como um ou lógico, retornando true ou verdadeiro assim que ele encontra um elemento que satisfaça o Predicate, ou que então passe por toda a coleção e retorne falso.

Os exemplos a seguir também recebem Predicate mas não curto-circuitam.

Exemplo 7: Selecionar os elementos de uma coleção

Mostrei muitos exemplos de filtragem na parte 1 dessa série. Aqui está apenas uma recordação, usando o domínio Person/Pet (Pessoa / Animal de Estimação). O método para filtrar a coleção é chamado select.

@Test
public void getPeopleWithCats()
{
    MutableList<Person> peopleWithCats =
        this.people.select(person -> person.hasPet(PetType.CAT))
    Verify.assertSize(2, peopleWithCats);
    MutableList<Person> peopleWithCats1 =
        this.people.selectWith(Person::hasPet, PetType.CAT);
    Verify.assertSize(2, peopleWithCats1);
}

Exemplo 8: Encontre os elementos de uma coleção que não satisfazem um dado Predicate

Use reject:

@Test
public void getPeopleWhoDontHaveCats()
{
    MutableList<Person> peopleWithNoCats =
        this.people.reject(person -> person.hasPet(PetType.CAT));

    Verify.assertSize(5, peopleWithNoCats);
    MutableList<Person> peopleWithNoCats1 =
        this.people.rejectWith(Person::hasPet, PetType.CAT);
    Verify.assertSize(5, peopleWithNoCats1);
}

Exemplo 9: Separar os elementos de uma coleção entre aqueles que satisfazem um dado Predicate e os que não

Use partition:

@Test
public void partitionPeopleByCatOwnersAndNonCatOwners()
{
    PartitionMutableList<Person> catsAndNoCats =
        this.people.partition(person -> person.hasPet(PetType.CAT));
    Verify.assertSize(2, catsAndNoCats.getSelected());
    Verify.assertSize(5, catsAndNoCats.getRejected());
    PartitionMutableList<Person> catsAndNoCats1 =
        this.people.partitionWith(Person::hasPet, PetType.CAT);
    Verify.assertSize(2, catsAndNoCats1.getSelected());
    Verify.assertSize(5, catsAndNoCats1.getRejected());
}

Neste exemplo um tipo especial, PartitionMutableList, foi retornado. O pai dessa interface é PartitionIterable. Esta interface possui dois métodos: getSelected e getRejected. Esses métodos são covariantes nos subtipos de PartitionIterable. Então em PartitionMutableList, o tipo de retorno de ambos os métodos é MutableList. No tipo pai, PartitionIterable, o tipo de retorno seria definido como RichIterable.

Isso conclui a sessão sobre API recebendo parâmetros do tipo Predicate/Predicate2. Vamos seguir para API que recebe parâmetros do tipo Function/Function2.

Exemplo 10: Transformar a coleção de um tipo para outro

Use collect:

@Test
public void getTheNamesOfBobSmithPets()
{
    Person person =
        this.people.detectWith(Person::named, "Bob Smith");
    MutableList<Pet> pets = person.getPets();
    MutableList<String> names =
        pets.collect(Pet::getName);
    Assert.assertEquals("Dolly, Spot", names.makeString());
}

No exemplo acima encontramos uma pessoa chamada "Bob Smith", obtemos sua lista de pets, e a convertemos para MutableList<String>. Nós coletamos a lista de nomes de pet da coleção,

Exemplo 11: Achatar (flatten) a coleção baseado em um atributo

Se quiser coletar um atributo de uma coleção de coleções para uma única coleção achatada (flattened), use flatCollect.

@Test
public void getAllPets()
{
    Function<Person, Iterable<PetType>> function = person ->
person.getPetTypes();
    Assert.assertEquals(
        UnifiedSet.newSetWith(PetType.values()),
        this.people.flatCollect(function).toSet()
    );
    Assert.assertEquals(
        UnifiedSet.newSetWith(PetType.values()),
        this.people.flatCollect(Person::getPetTypes).toSet()
    );
}

No primeiro exemplo extraí o lambda para uma variável separada para ilustrar o tipo que flatCollect espera. O método collect recebe um Function<? super T, ? extends V>. O método flatCollect recebe Function<? super T, ? extends Iterable<V>>. Em outras palavras, a Function passada para o flatCollect deve retornar algum tipo iterável.

Exemplo 12: Agrupar uma coleção baseado em uma Function

Use groupBy:

@Test
public void groupPeopleByLastName()
{
    Multimap<String, Person> byLastName =
this.people.groupBy(Person::getLastName);
    Verify.assertIterableSize(3, byLastName.get("Smith"));
}

No exemplo acima, as pessoas foram agrupadas de acordo com seu sobrenome. O teste mostra que existem 3 pessoas com sobrenome Smith. O resultado de groupBy é um Multimap. Um Multimap pode ser encarado como relativamente equivalente a Map<K, Iterable<V>>. Existem muitas especializações de Multimap em GS Collections (List, Set, Bag, SortedBag, SortedSet) e existem ambas as formas Mutable e Immutable. Neste exemplo usei apenas o tipo pai, chamado Multimap, mas poderia tê-lo especializado para usar ListMultimap ou MutableListMultimap.

Exemplo 13: Agrupar uma coleção baseado em uma Function que retorne múltiplas chaves

Use groupByEach:

@Test
public void groupPeopleByTheirPets()
{
    Multimap<PetType, Person> peopleByPets =
        this.people.groupByEach(Person::getPetTypes);
    RichIterable<Person> catPeople = peopleByPets.get(PetType.CAT);
    Assert.assertEquals(
        "Mary, Bob",
        catPeople.collect(Person::getFirstName).makeString()
    );
    RichIterable<Person> dogPeople = peopleByPets.get(PetType.DOG);
    Assert.assertEquals(
        "Bob, Ted",
        dogPeople.collect(Person::getFirstName).makeString()
    );
}

De maneira similar a flatCollect, o método groupByEach recebe um Function<? super T, ? extends Iterable<V>>. O Multimap<K, T> que é retornado é efetivamente um indice múltipo onde cada valor pode ter múltiplas chaves. Neste exemplo agrupamos pessoas pelos tipos de pets que elas possuem. Uma pessoa pode ter mais do que um pet, e é por isso que "Bob" aparece em ambas as Strings. Utilizei collect para converter cada Person para seu primeiro nome, e então usei makeString para convertê-los em uma String separada por vírgulas. Existem sobrecargas (overloads) de makeString que recebem um separador como parâmetro, bem como um prefixo e um sufixo para o resultado.

Exemplo 14: Somar os valores de um atributo primitivo retornado por um método

Use um dos quatro métodos sumOf disponíveis em RichIterable: sumOfInt, sumOfFloat, sumOfLong e sumOfDouble.

@Test
public void getTotalNumberOfPets()
{
    long numberOfPets = this.people.sumOfInt(Person::getNumberOfPets);
    Assert.assertEquals(9, numberOfPets);
}

No exemplo acima somamos os valores dos números de pets que cada pessoa possui, o que nos dá o número total de pets considerando todas as pessoas. Quando somados ints e floats, um tipo maior é retornado, como long ou double. O método sumOfInt recebe uma forma especializada de Function chamada IntFunction.

public interface IntFunction<T>
        extends Serializable
{
    int intValueOf(T anObject);
}

Todos Procedures, Functions, e Predicates em GS Collections estendem Serializable. Isso permite que, de forma segura, sejam serializados para disco ou enviados pela rede remotamente sem a necessidade de os desenvolvedores criarem suas próprias extensões de Serializable.

Exemplo 15: Fluência entre coleções de objetos e coleções de primitivos

Se quiser converter de uma coleção de objetos para uma coleção de primitivos, você pode utilizar uma das 8 formas primitivas especializadas de collect (collectInt/Float/Long/Double/Byte/Short/Char/Boolean). Uma coleção de primitivos pode ser convertida de volta para uma coleção de objetos bastando apenas usar collect. Essas facilidades permitem muita fluência quando usando a API.

@Test
public void getAgesOfPets()
{
    IntList sortedAges =
        this.people
            .asLazy()
            .flatCollect(Person::getPets)
            .collectInt(Pet::getAge)
            .toSortedList();
    IntSet uniqueAges = sortedAges.toSet();
    IntSummaryStatistics stats = new IntSummaryStatistics();
    sortedAges.forEach(stats::accept);
    Assert.assertTrue(sortedAges.allSatisfy(IntPredicates.greaterThan(0)));
    Assert.assertTrue(sortedAges.allSatisfy(i -> i > 0));
    Assert.assertFalse(sortedAges.anySatisfy(i -> i == 0));
    Assert.assertTrue(sortedAges.noneSatisfy(i -> i < 0));
    Assert.assertEquals(IntHashSet.newSetWith(1, 2, 3, 4), uniqueAges);
    Assert.assertEquals(2.0d, sortedAges.median(), 0.0);
    Assert.assertEquals(stats.getMin(), sortedAges.min());
    Assert.assertEquals(stats.getMax(), sortedAges.max());
    Assert.assertEquals(stats.getSum(), sortedAges.sum());

    Assert.assertEquals(stats.getAverage(), sortedAges.average(), 0.0);
    Assert.assertEquals(stats.getCount(), sortedAges.size());
}

Neste exemplo respondo algumas perguntas sobre as idades dos pets possuídos por uma lista de pessoas. Primeiro uso o método asLazy(). Essa foi uma decisão consciente pois quis reduzir a quantidade de coleções transientes. Eu poderia remover a chamada para asLazy() que o código ainda funcionaria. Neste caso, asLazy() é apenas uma otimização de memória. A seguir chamo flatCollect, que junta todos os Pets das pessoas em uma única coleção achatada (flattened). Então chamo collectInt, que converte o LazyIterable<Pet> para um IntIterable. Cada Pet é convertido para sua idade usando o método getAge. Se eu não tivesse usado o método asLazy() anteriormente, eu estaria convertendo uma MutableList<Pet> para uma IntList. Finalmente, chamo toSortedList, que recebe um IntIterable e o converte para uma IntList e ordena os ints. Eu então chamo toSet na IntList que armazena no conjunto de inteiros único de idades.

Após isso demonstro a riqueza de nossa coleção de primitivos em GS Collections. Métodos estatísticos como min, max, sum, average, median (para cálculo de mínimo, máximo, média e mediana) estão disponíveis diretamente nas coleções. Também aproveito uma das novas classes estatísticas do Java 8 chamada IntSummaryStatistics com a IntList usando referência a método para o IntSumaryStatistics::accept, que recebe um int. E mostro métodos já vistos nos exemplos anteriores como any/all/noneSatisfy, sendo que eles agora estão sendo usados em uma coleção de primitivos.

Exemplo 16: Contando o número de ocorrências de um item em uma coleção

Se você quiser contar rapidamente o número de itens, você pode converter a coleção para um Bag. Um Bag é aproximadamente equivalente a um Map<K, Integer> no qual o Integer é o número de ocorrências do item K. Um Bag se parece com qualquer outra coleção no fato que suporta add, remove, etc. Ele também possui métodos especializados para lidar eficientemente com consultas, adição ou remoção de ocorrências de um item. Um Bag é como um Set que permite duplicatas e mantém a conta das ocorrências.

@Test
public void getCountsByPetType()
{
    Bag<PetType> counts =
        this.people
            .asLazy()
            .flatCollect(Person::getPets)
            .collect(Pet::getType)
            .toBag();
    Assert.assertEquals(2, counts.occurrencesOf(PetType.CAT));
    Assert.assertEquals(2, counts.occurrencesOf(PetType.DOG));
    Assert.assertEquals(2, counts.occurrencesOf(PetType.HAMSTER));
    Assert.assertEquals(1, counts.occurrencesOf(PetType.SNAKE));
    Assert.assertEquals(1, counts.occurrencesOf(PetType.TURTLE));
    Assert.assertEquals(1, counts.occurrencesOf(PetType.BIRD));
}

Não existe equivalente ao Bag na JDK hoje, da mesma forma que não há equivalentes a PartitionIterable ou Multimap. Nos métodos Java 8 da classe Collectors esses tipos são simulados usando Map<K, Integer> (Collectors.counting), Map<Boolean, List<V>> (Collectors.partitioning), e Map<K, List<V>> (Collectors.groupingBy).

Exemplo 17: Contando o número de ocorrências de um valor primitivo em uma coleção

Se quiser obter a contagem de um valor primitivo você pode usar um Bag de primitivos.

@Test
public void getCountsByPetAge()
{
    IntBag counts =
        this.people
            .asLazy()
            .flatCollect(Person::getPets)
            .collectInt(Pet::getAge)
            .toBag();
    Assert.assertEquals(4, counts.occurrencesOf(1));
    Assert.assertEquals(3, counts.occurrencesOf(2));
    Assert.assertEquals(1, counts.occurrencesOf(3));
    Assert.assertEquals(1, counts.occurrencesOf(4));
    Assert.assertEquals(0, counts.occurrencesOf(5));
}

Neste exemplo crio um IntBag a partir da idade de todos os pets. Isso me permite usar o método occurrencesOf de IntBag para encontrar a contagem de cada idade. Este é o último dos exemplos da parte 2.

Esses exemplos visam passar uma amostra do que você consegue realizar com a API em GS Collections. Existem mais de cem métodos disponíveis em RichIterable hoje, o que provê um conjunto de funcionalidades extremamente rico para os desenvolvedores Java quando lidando com coleções.

No JavaOne 2014, Craig Motlin e eu cobrimos vários exemplos comparando como fazer as coisas em Java 8 com Streams versus como fazer as coisas usando GS Collections em uma sessão intitulada: "GS Collections and Java8: Functional, Fluent, Friendly and Fun!" ("GS Collections e Java8: Funcional, Fluente, Amigável e Divertida!"). Os slides da apresentação estão disponíveis no site do JavaOne 2014 e também podem ser encontrados no GitHub wiki do GS Collections.

Para referência, aqui está o código que usei para a configuração dos testes e das classes de domínio.

import com.gs.collections.api.RichIterable;
import com.gs.collections.api.bag.Bag;
import com.gs.collections.api.bag.MutableBag;
import com.gs.collections.api.bag.primitive.IntBag;
import com.gs.collections.api.block.function.Function;
import com.gs.collections.api.block.predicate.Predicate;
import com.gs.collections.api.list.MutableList;
import com.gs.collections.api.list.primitive.IntList;
import com.gs.collections.api.multimap.Multimap;
import com.gs.collections.api.partition.list.PartitionMutableList;
import com.gs.collections.api.set.primitive.IntSet;
import com.gs.collections.impl.bag.mutable.HashBag;
import com.gs.collections.impl.block.factory.Predicates2;
import com.gs.collections.impl.block.factory.primitive.IntPredicates;
import com.gs.collections.impl.list.mutable.FastList;
import com.gs.collections.impl.set.mutable.UnifiedSet;
import com.gs.collections.impl.set.mutable.primitive.IntHashSet;
import com.gs.collections.impl.test.Verify
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.IntSummaryStatistics;

public class PersonTest
{
    MutableList<Person> people;

    @Before
    public void setUp() throws Exception
    {
        this.people = FastList.newListWith(
            new Person("Mary", "Smith").addPet(PetType.CAT, "Tabby", 2),
            new Person("Bob", "Smith").addPet(PetType.CAT, "Dolly",
3).addPet(PetType.DOG, "Spot", 2),
            new Person("Ted", "Smith").addPet(PetType.DOG, "Spike", 4),
            new Person("Jake", "Snake").addPet(PetType.SNAKE, "Serpy", 1),
            new Person("Barry", "Bird").addPet(PetType.BIRD, "Tweety", 2),
            new Person("Terry", "Turtle").addPet(PetType.TURTLE, "Speedy",
1)
            new Person("Harry", "Hamster").addPet(PetType.HAMSTER, "Fuzzy",
1).addPet(PetType.HAMSTER, "Wuzzy", 1)
        );
    }

    public class Person
    {
        private String firstName;
        private String lastName;
        private MutableList<Pet> pets = FastList.newList();

        private Person(String firstName, String lastName)
        {
            this.firstName = firstName;
            this.lastName = lastName;

        public String getFirstName()
        {
            return this.firstName;
        }

        public String getLastName()
        {
            return this.lastName;
        }

        public boolean named(String name)
        {
            return name.equals(this.getFirstName() + " " +
this.getLastName());
        }

        public boolean hasPet(PetType petType)
        {
            return
this.pets.anySatisfyWith(Predicates2.attributeEqual(Pet::getType), petType);
        }

        public MutableList<Pet> getPets()
        {
            return this.pets;
        }

        public MutableBag<PetType> getPetTypes()
        {
            return this.pets.collect(Pet::getType, HashBag.newBag());
        }

        public Person addPet(PetType petType, String name, int age)
        {
            this.pets.add(new Pet(petType, name, age));
            return this;
        }

        public int getNumberOfPets()
        {
            return this.pets.size();
        }
    }

        public class Pet
    {
        private PetType type;
        private String name;
        private int age;

        public Pet(PetType type, String name, int age)
  
            this.type = type;
            this.name = name;
            this.age = age;
        }

        public PetType getType()
        {
            return this.type;
        }

        public String getName()
        {
            return this.name;
        }

        public int getAge()
        {
            return this.age;
        }
    }

    public enum PetType
    {
        CAT, DOG, HAMSTER, TURTLE, BIRD, SNAKE
    }
}

Sobre o Autor

raab.jpgDonald Raab gerencia o time de Arquitetura (JVM Architecture Team), que é parte do grupo de Plataformas Empresariais (Enterprise Platforms) da Divisão de Tecnologia no Goldman, Sachs / Co. ("Goldman Sachs"). Raab foi membro do JSR 335 Expert Group (Expressões Lambda para a Linguagem de Programação Java) e é um dos suplentes do Goldman Sachs no Comitê Executivo do JCP. Ele se juntou ao Goldman Sachs em 2001 como arquiteto técnico no time PARA. Foi nomeado "technology fellow" em 2007 e "managing director" em 2013.

Para mais informações sobre GS Collections e Engenharia no Goldman Sachs visite este link.

Aviso

Este artigo reflete informação referente somente à Divisão de Tecnologia do Goldman, Sachs / Co. ("Goldman Sachs") e de nenhuma outra divisão ou entidade afiliada do Goldman Sachs. Não deve ser interpretado nem considerado como aconselhamento de investimento. As opiniões no artigo não são do Goldman Sachs, a menos que expressamente especificadas. Goldman Sachs não se responsabiliza nem garante a exatidão, completitude ou eficácia deste artigo, e os destinatários não devem contar com isso exceto por própria conta e risco. Este artigo não pode ser encaminhado ou divulgado sem este aviso.

Avalie esse artigo

Relevância
Estilo/Redação

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

Dê sua opinião

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão
Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

Dê sua opinião
BT