Pergunta

Eu quero filtrar um java.util.Collection com base em um predicado.

Foi útil?

Solução

Java 8 ( 2014 ) resolve este problema usando riachos e lambdas em uma linha de código:

List<Person> beerDrinkers = persons.stream()
    .filter(p -> p.getAge() > 16).collect(Collectors.toList());

Aqui está uma tutorial .

Use Collection#removeIf para modificar a coleção no lugar. (Aviso: Neste caso, o predicado irá remover objetos que satisfazem o predicado):

persons.removeIf(p -> p.getAge() <= 16);

lambdaj permite filtrar coleções sem escrever loops ou classes internas:

List<Person> beerDrinkers = select(persons, having(on(Person.class).getAge(),
    greaterThan(16)));

Você pode imaginar algo mais legível?

Disclaimer: Eu sou um contribuinte em lambdaj

Outras dicas

Assumindo que você está usando Java 1.5 , e que você não pode adicionar Google Collections , eu faria algo muito semelhante ao que os caras do Google fizeram . Esta é uma pequena variação sobre os comentários de Jon.

Primeiro adicione esta interface para sua base de código.

public interface IPredicate<T> { boolean apply(T type); }

Seus implementadores pode responder quando um determinado predicado é verdadeiro de um certo tipo. Por exemplo. Se T foram User e AuthorizedUserPredicate<User> implementos IPredicate<T>, em seguida, retorna AuthorizedUserPredicate#apply se o passado em User está autorizado.

Depois de alguma classe de utilitário, você poderia dizer

public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
    Collection<T> result = new ArrayList<T>();
    for (T element: target) {
        if (predicate.apply(element)) {
            result.add(element);
        }
    }
    return result;
}

Assim, supondo que você tem o uso do acima pode ser

Predicate<User> isAuthorized = new Predicate<User>() {
    public boolean apply(User user) {
        // binds a boolean method in User to a reference
        return user.isAuthorized();
    }
};
// allUsers is a Collection<User>
Collection<User> authorizedUsers = filter(allUsers, isAuthorized);

Se o desempenho no cheque linear é motivo de preocupação, então eu poderia querer ter um objeto de domínio que tem a coleção de destino. O objeto de domínio que tem a coleção de destino teria filtragem lógica para os métodos que inicializar, adicionar e definir a coleção de destino.

UPDATE:

Na classe de utilitário (digamos predicado), eu adicionei um método de seleção com uma opção para o valor padrão quando o predicado não devolver o valor esperado, e também uma propriedade estática para params para ser usado dentro do novo IPredicate .

public class Predicate {
    public static Object predicateParams;

    public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
        Collection<T> result = new ArrayList<T>();
        for (T element : target) {
            if (predicate.apply(element)) {
                result.add(element);
            }
        }
        return result;
    }

    public static <T> T select(Collection<T> target, IPredicate<T> predicate) {
        T result = null;
        for (T element : target) {
            if (!predicate.apply(element))
                continue;
            result = element;
            break;
        }
        return result;
    }

    public static <T> T select(Collection<T> target, IPredicate<T> predicate, T defaultValue) {
        T result = defaultValue;
        for (T element : target) {
            if (!predicate.apply(element))
                continue;
            result = element;
            break;
        }
        return result;
    }
}

O exemplo a seguir procura por objetos perdido entre coleções:

List<MyTypeA> missingObjects = (List<MyTypeA>) Predicate.filter(myCollectionOfA,
    new IPredicate<MyTypeA>() {
        public boolean apply(MyTypeA objectOfA) {
            Predicate.predicateParams = objectOfA.getName();
            return Predicate.select(myCollectionB, new IPredicate<MyTypeB>() {
                public boolean apply(MyTypeB objectOfB) {
                    return objectOfB.getName().equals(Predicate.predicateParams.toString());
                }
            }) == null;
        }
    });
O exemplo seguinte, procura uma instância em uma coleção, e retorna o primeiro elemento da coleção como valor padrão quando a instância não for encontrada:

MyType myObject = Predicate.select(collectionOfMyType, new IPredicate<MyType>() {
public boolean apply(MyType objectOfMyType) {
    return objectOfMyType.isDefault();
}}, collectionOfMyType.get(0));

UPDATE (após Java 8 release):

Tem sido vários anos desde I (Alan) publicado pela primeira vez esta resposta, e eu ainda não posso acreditar que eu estou coletando pontos SO para esta resposta. De qualquer forma, agora que Java 8 introduziu o fecho a língua, a minha resposta seria agora consideravelmente diferente, e mais simples. Com Java 8, não há necessidade de uma classe de utilitário estático distinta. Então, se você quiser encontrar o 1º elemento que corresponde ao seu predicado.

final UserService userService = ... // perhaps injected IoC
final Optional<UserModel> userOption = userCollection.stream().filter(u -> {
    boolean isAuthorized = userService.isAuthorized(u);
    return isAuthorized;
}).findFirst();

O JDK 8 API para opcionais tem a capacidade de get(), isPresent(), orElse(defaultUser), orElseGet(userSupplier) e orElseThrow(exceptionSupplier), bem como outros 'monádicas' funções como map, flatMap e filter.

Se você quiser simplesmente recolher todos os usuários que combinam com o predicado, então use o Collectors para terminar o fluxo na coleção desejada.

final UserService userService = ... // perhaps injected IoC
final List<UserModel> userOption = userCollection.stream().filter(u -> {
    boolean isAuthorized = userService.isAuthorized(u);
    return isAuthorized;
}).collect(Collectors.toList());

aqui para mais exemplos de como Java 8 fluxos de trabalho.

"melhor" forma é muito grande um pedido. É "mais curto"? "O mais rápido"? "Legível"? Filtro no lugar ou em outra coleção?

mais simples (mas não mais legível) maneira é iterar-lo e usá Iterator.remove () método:

Iterator<Foo> it = col.iterator();
while( it.hasNext() ) {
  Foo foo = it.next();
  if( !condition(foo) ) it.remove();
}

Agora, para torná-lo mais legível, você pode envolvê-lo em um método de utilitário. Em seguida, inventar uma interface IPredicate, criar uma implementação anônima de que a interface e fazer algo como:

CollectionUtils.filterInPlace(col,
  new IPredicate<Foo>(){
    public boolean keepIt(Foo foo) {
      return foo.isBar();
    }
  });

onde filterInPlace () iterate a recolha e chama Predicate.keepIt () para saber se a instância de ser mantido na coleção.

Eu realmente não vejo justificativa para trazer uma biblioteca de terceiros apenas para essa tarefa.

Google Collections para um quadro coleções atualizado que suporta os genéricos.

Atualizar : A biblioteca de coleções Google agora está obsoleta. Você deve usar a última versão do Goiaba . Ele ainda tem todas as mesmas extensões do quadro coleções incluindo um mecanismo para filtrar com base em um predicado.

Wait for Java 8:

List<Person> olderThan30 = 
  //Create a Stream from the personList
  personList.stream().
  //filter the element to select only those with age >= 30
  filter(p -> p.age >= 30).
  //put those filtered elements into a new List.
  collect(Collectors.toList());

Desde o lançamento inicial do Java 8, você pode tentar algo como:

Collection<T> collection = ...;
Stream<T> stream = collection.stream().filter(...);

Por exemplo, se você tivesse uma lista de números inteiros e você queria para filtrar os números que estão> 10 e, em seguida, imprimir esses números para o console, você poderia fazer algo como:

List<Integer> numbers = Arrays.asList(12, 74, 5, 8, 16);
numbers.stream().filter(n -> n > 10).forEach(System.out::println);

Vou jogar RxJava no ringue, que também está disponível em Android . RxJava pode não ser sempre a melhor opção, mas vai dar-lhe mais flexibilidade, se desejar adicionar mais transformações em seus erros recolha ou punho ao filtrar.

Observable.from(Arrays.asList(1, 2, 3, 4, 5))
    .filter(new Func1<Integer, Boolean>() {
        public Boolean call(Integer i) {
            return i % 2 != 0;
        }
    })
    .subscribe(new Action1<Integer>() {
        public void call(Integer i) {
            System.out.println(i);
        }
    });

Output:

1
3
5

Mais detalhes sobre filter de RxJava pode ser encontrada aqui .

A configuração:

public interface Predicate<T> {
  public boolean filter(T t);
}

void filterCollection(Collection<T> col, Predicate<T> predicate) {
  for (Iterator i = col.iterator(); i.hasNext();) {
    T obj = i.next();
    if (predicate.filter(obj)) {
      i.remove();
    }
  }
}

O uso:

List<MyObject> myList = ...;
filterCollection(myList, new Predicate<MyObject>() {
  public boolean filter(MyObject obj) {
    return obj.shouldFilter();
  }
});

Que tal um pouco de Java simples e de maneira direta

 List<Customer> list ...;
 List<Customer> newList = new ArrayList<>();
 for (Customer c : list){
    if (c.getName().equals("dd")) newList.add(c);
 }

simples, legível e fácil (e funciona em Android!) Mas se você estiver usando Java 8 você pode fazê-lo em uma linha um doce:

List<Customer> newList = list.stream().filter(c -> c.getName().equals("dd")).collect(toList());

Note que ToList () está estaticamente importado

Tem certeza de que deseja filtrar a própria coleção, em vez de um iterador?

veja org.apache.commons.collections.iterators.FilterIterator

ou usando a versão 4 do Apache Commons org.apache.commons.collections4.iterators.FilterIterator

Vamos olhar como filtrar um built-in Lista JDK e uma MutableList usando Eclipse coleções (anteriormente GS coleções ).

List<Integer> jdkList = Arrays.asList(1, 2, 3, 4, 5);
MutableList<Integer> ecList = Lists.mutable.with(1, 2, 3, 4, 5);

Se você quiser filtrar os números inferiores a 3, seria de esperar os seguintes resultados.

List<Integer> selected = Lists.mutable.with(1, 2);
List<Integer> rejected = Lists.mutable.with(3, 4, 5);

Veja como você pode filtrar usando uma classe interna anônima como o Predicate.

Predicate<Integer> lessThan3 = new Predicate<Integer>()
{
    public boolean accept(Integer each)
    {
        return each < 3;
    }
};

Assert.assertEquals(selected, Iterate.select(jdkList, lessThan3));

Assert.assertEquals(selected, ecList.select(lessThan3));

Aqui estão algumas opções para filtrar listas JDK e Eclipse Coleções MutableLists usando o Predicados fábrica.

Assert.assertEquals(selected, Iterate.select(jdkList, Predicates.lessThan(3)));

Assert.assertEquals(selected, ecList.select(Predicates.lessThan(3)));

Aqui está uma versão que não alocar um objeto para o predicado, usando o Predicates2 fábrica em vez com o método selectWith que leva um Predicate2.

Assert.assertEquals(
    selected, ecList.selectWith(Predicates2.<Integer>lessThan(), 3));

Às vezes você quer filtro em uma condição negativa. Existe um método especial em Eclipse coleções para que chamou reject.

Assert.assertEquals(rejected, Iterate.reject(jdkList, lessThan3));

Assert.assertEquals(rejected, ecList.reject(lessThan3));

Veja como você pode filtrar usando um lambda Java 8 como o Predicate.

Assert.assertEquals(selected, Iterate.select(jdkList, each -> each < 3));
Assert.assertEquals(rejected, Iterate.reject(jdkList, each -> each < 3));

Assert.assertEquals(selected, gscList.select(each -> each < 3));
Assert.assertEquals(rejected, gscList.reject(each -> each < 3));

O método partition voltará duas colecções, contendo os elementos seleccionados por e rejeitadas pela Predicate.

PartitionIterable<Integer> jdkPartitioned = Iterate.partition(jdkList, lessThan3);
Assert.assertEquals(selected, jdkPartitioned.getSelected());
Assert.assertEquals(rejected, jdkPartitioned.getRejected());

PartitionList<Integer> ecPartitioned = gscList.partition(lessThan3);
Assert.assertEquals(selected, ecPartitioned.getSelected());
Assert.assertEquals(rejected, ecPartitioned.getRejected());

Nota:. Eu sou um committer para Eclipse coleções

Com o ForEach DSL você pode escrever

import static ch.akuhn.util.query.Query.select;
import static ch.akuhn.util.query.Query.$result;
import ch.akuhn.util.query.Select;

Collection<String> collection = ...

for (Select<String> each : select(collection)) {
    each.yield = each.value.length() > 3;
}

Collection<String> result = $result();

Dada uma coleção de [A, rápido, marrom, raposa, saltos, sobre, o, preguiçoso, cão] isso resulta em [Quick, marrom, saltos, sobre, preguiça], ou seja, todas as cordas mais de três caracteres.

Todos os estilos de iteração suportados pelo ForEach DSL são

  • AllSatisfy
  • AnySatisfy
  • Collect
  • Counnt
  • CutPieces
  • Detect
  • GroupedBy
  • IndexOf
  • InjectInto
  • Reject
  • Select

Para mais detalhes, consulte a https: //www.iam.unibe .ch / SCG / svn_repos / Fontes / ForEach

O Collections2.filter (coleção, predicado) método em biblioteca do Google Guava faz apenas o que você está procurando.

Desde java 9 Collectors.filtering está ativado:

public static <T, A, R>
    Collector<T, ?, R> filtering(Predicate<? super T> predicate,
                                 Collector<? super T, A, R> downstream)

Assim filtragem deve ser:

collection.stream().collect(Collectors.filtering(predicate, collector))

Exemplo:

List<Integer> oddNumbers = List.of(1, 19, 15, 10, -10).stream()
            .collect(Collectors.filtering(i -> i % 2 == 1, Collectors.toList()));

Isto, combinado com a falta de fechamento de reais, é o maior cólica para Java. Honestamente, a maioria dos métodos mencionados acima são bastante fáceis de ler e realmente eficiente; no entanto, depois de passar tempo com .Net, Erlang, etc ... compreensão de lista integrado no nível da linguagem torna tudo muito mais limpo. Sem acréscimos no nível da linguagem, Java simplesmente não pode ser tão limpo como muitas outras línguas neste domínio.

Se o desempenho é uma preocupação enorme, coleções do Google é o caminho a percorrer (ou escreve sua própria utilidade predicado simples). sintaxe Lambdaj é mais legível para algumas pessoas, mas não é tão eficiente.

E, em seguida, há uma biblioteca que eu escrevi. Vou ignorar quaisquer dúvidas em relação a sua eficiência (sim, o que ruim) ...... Sim, eu sei que é claramente reflexão a partir, e não, eu realmente não usá-lo, mas ele não funciona:

LinkedList<Person> list = ......
LinkedList<Person> filtered = 
           Query.from(list).where(Condition.ensure("age", Op.GTE, 21));

ou

LinkedList<Person> list = ....
LinkedList<Person> filtered = Query.from(list).where("x => x.age >= 21");

http://code.google.com/p/jfilter/ é melhor adequado para sua exigência.

JFilter é um simples e de alto desempenho biblioteca de código aberto para consulta coleção de feijão Java.

As características-chave

  • Suporte de coleta (java.util.Collection, java.util.Map e Array) propriedades.
  • Suporte de coleta dentro coleção de qualquer profundidade.
  • Suporte de consultas internas.
  • Suporte de consultas parametrizadas.
  • É possível filtrar 1 milhão de registros em alguns 100 ms.
  • Filter (query) é dado no formato JSON simples, é como consultas Mangodb. Seguem-se alguns exemplos.
  • { "id": { "$ le": "10"}
    • onde a propriedade object id é menor que igual a 10.
  • { "id": { "$ in": [ "0", "100"]}}
    • onde a propriedade object id é 0 ou 100.
  • { "Itens de Linha": { "lineAmount": "1"}}
    • onde a propriedade coleção Itens de Linha de tipo parametrizado tem lineAmount igual a 1.
  • { "$ e": [{ "id": "0"}, { "BillingAddress": { "cidade": "DEL"}}]}
    • onde a propriedade id é 0 e billingAddress.city propriedade é DEL.
  • { "Itens de Linha": { "impostos": { "chave": { "código": "GST"}, "valor": { "$ gt": "1,01"}}}}
    • onde a propriedade coleção Itens de Linha de tipo parametrizado que tem impostos mapear propriedade de tipo de tipo parameteriszed tem código igual a GST maior valor do que 1,01.
  • { '$ ou': [{ 'código': '10'}, { 'skus': { '$ e': [{ 'preço': { '$ em': [ '20', '40 ']}}, {' código ':' RedApple '}]}}]}
    • Selecione todos os produtos em que o código do produto é de 10 ou preço sku em 20 e 40 e do código sku é "RedApple".

Eu escrevi uma classe Iterable estendida que o apoio aplicação de algoritmos funcionais sem copiar o conteúdo de coleção.

Uso:

List<Integer> myList = new ArrayList<Integer>(){ 1, 2, 3, 4, 5 }

Iterable<Integer> filtered = Iterable.wrap(myList).select(new Predicate1<Integer>()
{
    public Boolean call(Integer n) throws FunctionalException
    {
        return n % 2 == 0;
    }
})

for( int n : filtered )
{
    System.out.println(n);
}

O código acima irá realmente executar

for( int n : myList )
{
    if( n % 2 == 0 ) 
    {
        System.out.println(n);
    }
}

Alguns realmente grandes grandes respostas aqui. Mim, eu gostaria de manter as coisas o mais simples e legível possível:

public abstract class AbstractFilter<T> {

    /**
     * Method that returns whether an item is to be included or not.
     * @param item an item from the given collection.
     * @return true if this item is to be included in the collection, false in case it has to be removed.
     */
    protected abstract boolean excludeItem(T item);

    public void filter(Collection<T> collection) {
        if (CollectionUtils.isNotEmpty(collection)) {
            Iterator<T> iterator = collection.iterator();
            while (iterator.hasNext()) {
                if (excludeItem(iterator.next())) {
                    iterator.remove();
                }
            }
        }
    }
}

A solução simples pré-Java8:

ArrayList<Item> filtered = new ArrayList<Item>(); 
for (Item item : items) if (condition(item)) filtered.add(item);

Infelizmente esta solução não é totalmente genérico, a saída de uma lista em vez do tipo da coleção dada. Além disso, levando em bibliotecas ou escrever funções que envolvem este código parece um exagero para mim a menos que a condição é complexo, mas, em seguida, você pode escrever uma função para a condição.

https://code.google.com/p/joquery/

suporta diferentes possibilidades,

coleção Dada,

Collection<Dto> testList = new ArrayList<>();

do tipo,

class Dto
{
    private int id;
    private String text;

    public int getId()
    {
        return id;
    }

    public int getText()
    {
        return text;
    }
}

Filtro

Java 7

Filter<Dto> query = CQ.<Dto>filter(testList)
    .where()
    .property("id").eq().value(1);
Collection<Dto> filtered = query.list();

Java 8

Filter<Dto> query = CQ.<Dto>filter(testList)
    .where()
    .property(Dto::getId)
    .eq().value(1);
Collection<Dto> filtered = query.list();

Além disso,

Filter<Dto> query = CQ.<Dto>filter()
        .from(testList)
        .where()
        .property(Dto::getId).between().value(1).value(2)
        .and()
        .property(Dto::grtText).in().value(new string[]{"a","b"});

Sorting (também disponível para o Java 7)

Filter<Dto> query = CQ.<Dto>filter(testList)
        .orderBy()
        .property(Dto::getId)
        .property(Dto::getName)
    Collection<Dto> sorted = query.list();

Agrupamento (também disponível para o Java 7)

GroupQuery<Integer,Dto> query = CQ.<Dto,Dto>query(testList)
        .group()
        .groupBy(Dto::getId)
    Collection<Grouping<Integer,Dto>> grouped = query.list();

junta (também disponível para o Java 7)

Dada,

class LeftDto
{
    private int id;
    private String text;

    public int getId()
    {
        return id;
    }

    public int getText()
    {
        return text;
    }
}

class RightDto
{
    private int id;
    private int leftId;
    private String text;

    public int getId()
    {
        return id;
    }

    public int getLeftId()
        {
            return leftId;
        }

    public int getText()
    {
        return text;
    }
}

class JoinedDto
{
    private int leftId;
    private int rightId;
    private String text;

    public JoinedDto(int leftId,int rightId,String text)
    {
        this.leftId = leftId;
        this.rightId = rightId;
        this.text = text;
    }

    public int getLeftId()
    {
        return leftId;
    }

    public int getRightId()
        {
            return rightId;
        }

    public int getText()
    {
        return text;
    }
}

Collection<LeftDto> leftList = new ArrayList<>();

Collection<RightDto> rightList = new ArrayList<>();

podem ser unidos como,

Collection<JoinedDto> results = CQ.<LeftDto, LeftDto>query().from(leftList)
                .<RightDto, JoinedDto>innerJoin(CQ.<RightDto, RightDto>query().from(rightList))
                .on(LeftFyo::getId, RightDto::getLeftId)
                .transformDirect(selection ->  new JoinedDto(selection.getLeft().getText()
                                                     , selection.getLeft().getId()
                                                     , selection.getRight().getId())
                                 )
                .list();

Expressões

Filter<Dto> query = CQ.<Dto>filter()
    .from(testList)
    .where()
    .exec(s -> s.getId() + 1).eq().value(2);

A minha resposta baseia-se que a partir de Kevin Wong, aqui como uma one-liner usando CollectionUtils de Primavera e um Java 8 lambda expressão.

CollectionUtils.filter(list, p -> ((Person) p).getAge() > 16);

Este é o mais conciso e legível como qualquer alternativa eu ??vi (sem o uso de bibliotecas baseadas em aspecto)

Primavera CollectionUtils é acessível a partir da versão primavera 4.0.2.RELEASE, e lembre-se que você precisa JDK 1.8 e nível de linguagem 8 +.

Usando java 8, especificamente lambda expression, você pode fazê-lo simplesmente como o exemplo abaixo:

myProducts.stream().filter(prod -> prod.price>10).collect(Collectors.toList())

onde para cada coleção product myProducts dentro, se prod.price>10, em seguida, adicionar este produto para a nova lista filtrada.

eu precisava para filtrar uma lista, dependendo dos valores já presentes na lista. Por exemplo, remova todos os valores a seguir, que é menos do que o valor atual. {2 5 3 4 7 5} -> {2 5 7}. Ou, por exemplo, para remover todas as duplicatas {3 5 4 2 3 5 6} -.> {3 5 4 2 6}

public class Filter {
    public static <T> void List(List<T> list, Chooser<T> chooser) {
        List<Integer> toBeRemoved = new ArrayList<>();
        leftloop:
        for (int right = 1; right < list.size(); ++right) {
            for (int left = 0; left < right; ++left) {
                if (toBeRemoved.contains(left)) {
                    continue;
                }
                Keep keep = chooser.choose(list.get(left), list.get(right));
                switch (keep) {
                    case LEFT:
                        toBeRemoved.add(right);
                        continue leftloop;
                    case RIGHT:
                        toBeRemoved.add(left);
                        break;
                    case NONE:
                        toBeRemoved.add(left);
                        toBeRemoved.add(right);
                        continue leftloop;
                }
            }
        }

        Collections.sort(toBeRemoved, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });

        for (int i : toBeRemoved) {
            if (i >= 0 && i < list.size()) {
                list.remove(i);
            }
        }
    }

    public static <T> void List(List<T> list, Keeper<T> keeper) {
        Iterator<T> iterator = list.iterator();
        while (iterator.hasNext()) {
            if (!keeper.keep(iterator.next())) {
                iterator.remove();
            }
        }
    }

    public interface Keeper<E> {
        boolean keep(E obj);
    }

    public interface Chooser<E> {
        Keep choose(E left, E right);
    }

    public enum Keep {
        LEFT, RIGHT, BOTH, NONE;
    }
}

Esta abelha vontade usado como este.

List<String> names = new ArrayList<>();
names.add("Anders");
names.add("Stefan");
names.add("Anders");
Filter.List(names, new Filter.Chooser<String>() {
    @Override
    public Filter.Keep choose(String left, String right) {
        return left.equals(right) ? Filter.Keep.LEFT : Filter.Keep.BOTH;
    }
});

Com Goiaba:

Collection<Integer> collection = Lists.newArrayList(1, 2, 3, 4, 5);

Iterators.removeIf(collection.iterator(), new Predicate<Integer>() {
    @Override
    public boolean apply(Integer i) {
        return i % 2 == 0;
    }
});

System.out.println(collection); // Prints 1, 3, 5

Em Java 8, você pode usar diretamente este método filtro e depois fazer isso.

 List<String> lines = Arrays.asList("java", "pramod", "example");

 List<String> result = lines.stream()              
         .filter(line -> !"pramod".equals(line))     
         .collect(Collectors.toList());              

 result.forEach(System.out::println); 
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top