Guia para a classe java.util.Arrays

Guia para a classe java.util.Arrays

1. Introdução

Neste tutorial, daremos uma olhada emjava.util.Arrays, uma classe de utilitário que faz parte do Java desde Java 1.2.

UsandoArrays, we pode criar, comparar, classificar, pesquisar, transmitir e transformar arrays.

2. Criando

Vamos dar uma olhada em algumas das maneiras como podemos criar matrizes:copyOf,copyOfRange efill.

2.1. copyOf ecopyOfRange

Para usarcopyOfRange, precisamos de nossa matriz original e o índice inicial (inclusivo) e o índice final (exclusivo) que queremos copiar:

String[] intro = new String[] { "once", "upon", "a", "time" };
String[] abridgement = Arrays.copyOfRange(storyIntro, 0, 3);

assertArrayEquals(new String[] { "once", "upon", "a" }, abridgement);
assertFalse(Arrays.equals(intro, abridgement));

E para usarcopyOf, pegaríamosintro lixar um tamanho de array de destino e receberíamos de volta um novo array desse comprimento:

String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);

assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised);
assertNull(expanded[4]);

Observe quecopyOf pads the array with nulls if our target size is bigger than the original size.

2.2. fill

Outra maneira, podemos criar uma matriz de comprimento fixo, éfill, , que é útil quando queremos uma matriz onde todos os elementos são iguais:

String[] stutter = new String[3];
Arrays.fill(stutter, "once");

assertTrue(Stream.of(stutter)
  .allMatch(el -> "once".equals(el));

ConfirasetAll para criar um array onde os elementos são diferentes.

Observe que precisamos instanciar o array nós mesmos de antemão - ao contrário de algo comoString[] filled = Arrays.fill(“once”, 3); - já que esse recurso foi introduzido antes que os genéricos estivessem disponíveis na linguagem.

3. Comparando

Agora vamos mudar para métodos de comparação de matrizes.

3.1. equals areiadeepEquals

Podemos usarequals para comparação de array simples por tamanho e conteúdo. Se adicionarmos um nulo como um dos elementos, a verificação do conteúdo falhará:

assertTrue(
  Arrays.equals(new String[] { "once", "upon", "a", "time" }, intro));
assertFalse(
  Arrays.equals(new String[] { "once", "upon", "a", null }, intro));

Quando temos matrizes aninhadas ou multidimensionais, podemos usardeepEquals não apenas para verificar os elementos de nível superior, mas também para realizar a verificação recursivamente:

Object[] story = new Object[]
  { intro, new String[] { "chapter one", "chapter two" }, end };
Object[] copy = new Object[]
  { intro, new String[] { "chapter one", "chapter two" }, end };

assertTrue(Arrays.deepEquals(story, copy));
assertFalse(Arrays.equals(story, copy));

Observe comodeepEquals passa, masequals falha _._

This is because deepEquals ultimately calls itself each time it encounters an array, enquantoequals will simplesmente compara referências de submatrizes.

Além disso, isso torna perigoso chamar um array com uma referência própria!

3.2. hashCode areiadeepHashCode

A implementação dehashCode nos dará a outra parte do contratoequals /hashCode que é recomendado para objetos Java. UsamoshashCode para calcular um número inteiro com base no conteúdo da matriz:

Object[] looping = new Object[]{ intro, intro };
int hashBefore = Arrays.hashCode(looping);
int deepHashBefore = Arrays.deepHashCode(looping);

Agora, configuramos um elemento da matriz original como nulo e recalculamos os valores de hash:

intro[3] = null;
int hashAfter = Arrays.hashCode(looping);

Como alternativa,deepHashCode verifica os arrays aninhados em busca de números correspondentes de elementos e conteúdos. Se recalcularmos comdeepHashCode:

int deepHashAfter = Arrays.deepHashCode(looping);

Agora, podemos ver a diferença nos dois métodos:

assertEquals(hashAfter, hashBefore);
assertNotEquals(deepHashAfter, deepHashBefore);

deepHashCode is the underlying calculation used when we are working with data structures like HashMap and HashSet on arrays.

4. Classificando e Pesquisando

A seguir, vamos dar uma olhada na classificação e pesquisa de matrizes.

4.1. sort

Se nossos elementos são primitivos ou implementamComparable, podemos usarsort para realizar uma classificação em linha:

String[] sorted = Arrays.copyOf(intro, 4);
Arrays.sort(sorted);

assertArrayEquals(
  new String[]{ "a", "once", "time", "upon" },
  sorted);

Take care that sort mutates the original reference, é por isso que fazemos uma cópia aqui.

sort will usa um algoritmo diferente para diferentes tipos de elementos de array. Primitive types use a dual-pivot quicksort eObject types use Timsort. Ambos têm o caso médio deO(n log(n))  para uma matriz classificada aleatoriamente.

No Java 8,parallelSort  está disponível para uma mesclagem de classificação paralela. Ele oferece um método de classificação simultânea usando vários stasksArrays.sort .

4.2. binarySearch

Pesquisar em um array não classificado é linear, mas se tivermos um array ordenado, podemos fazer emO(log n), que é o que podemos fazer combinarySearch:

int exact = Arrays.binarySearch(sorted, "time");
int caseInsensitive = Arrays.binarySearch(sorted, "TiMe", String::compareToIgnoreCase);

assertEquals("time", sorted[exact]);
assertEquals(2, exact);
assertEquals(exact, caseInsensitive);

Se não fornecermos umComparator como um terceiro parâmetro, entãobinarySearch conta com nosso tipo de elemento sendo do tipoComparable.

E novamente, observe queif our array isn’t first sorted, then binarySearch won’t work as we expect!

5. Transmissão

Como vimos antes,Arrays was atualizado em Java 8 para incluir métodos que usam a API Stream, comoparallelSort (mencionado acima),stream andsetAll.

5.1. stream

stream nos dá acesso total à API Stream para nosso array:

Assert.assertEquals(Arrays.stream(intro).count(), 4);

exception.expect(ArrayIndexOutOfBoundsException.class);
Arrays.stream(intro, 2, 1).count();

Podemos fornecer índices inclusivos e exclusivos para o fluxo, entretanto, devemos esperar umArrayIndexOutOfBoundsException se os índices estiverem fora de ordem, negativos ou fora do intervalo.

6. Transformando

Finalmente,toString,asList,esetAll  nos dá algumas maneiras diferentes de transformar arrays.

6.1. toString edeepToString

Uma ótima maneira de obter uma versão legível de nosso array original é comtoString:

assertEquals("[once, upon, a, time]", Arrays.toString(storyIntro));

Novamentewe must use the deep version to print the contents of nested arrays:

assertEquals(
  "[[once, upon, a, time], [chapter one, chapter two], [the, end]]",
  Arrays.deepToString(story));

6.2. asList

O mais conveniente de todos os métodosArrays para usarmos é oasList.. Temos uma maneira fácil de transformar um array em uma lista:

List rets = Arrays.asList(storyIntro);

assertTrue(rets.contains("upon"));
assertTrue(rets.contains("time"));
assertEquals(rets.size(), 4);

No entanto,the returned List will be a fixed length so we won’t be able to add or remove elements.

Observe também que, curiosamente,java.util.Arrays has its own ArrayList subclass, which asList returns. Isso pode ser muito enganoso ao depurar!

6.3. setAll

ComsetAll, podemos definir todos os elementos de um array com uma interface funcional. A implementação do gerador usa o índice posicional como parâmetro:

String[] longAgo = new String[4];
Arrays.setAll(longAgo, i -> this.getWord(i));
assertArrayEquals(longAgo, new String[]{"a","long","time","ago"});

E, é claro, o tratamento de exceções é uma das partes mais perigosas do uso de lambdas. Portanto, lembre-se de que aqui,if the lambda throws an exception, then Java doesn’t define the final state of the array.

7. Prefixo Paralelo

Outro novo método emArrays introduzido desde Java 8 éparallelPrefix. ComparallelPrefix, podemos operar em cada elemento da matriz de entrada de forma cumulativa.

7.1. parallelPrefix

Se o operador realizar a adição como na amostra a seguir,[1, 2, 3, 4] resultará em[1, 3, 6, 10]:

int[] arr = new int[] { 1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> left + right);
assertThat(arr, is(new int[] { 1, 3, 6, 10}));

Além disso, podemos especificar um subintervalo para a operação:

int[] arri = new int[] { 1, 2, 3, 4, 5 };
Arrays.parallelPrefix(arri, 1, 4, (left, right) -> left + right);
assertThat(arri, is(new int[] { 1, 2, 5, 9, 5 }));

Observe que o método é executado em paralelo, entãothe cumulative operation should be side-effect-free and associative.

Para uma função não associativa:

int nonassociativeFunc(int left, int right) {
    return left + right*left;
}

usarparallelPrefix produziria resultados inconsistentes:

@Test
public void whenPrefixNonAssociative_thenError() {
    boolean consistent = true;
    Random r = new Random();
    for (int k = 0; k < 100_000; k++) {
        int[] arrA = r.ints(100, 1, 5).toArray();
        int[] arrB = Arrays.copyOf(arrA, arrA.length);

        Arrays.parallelPrefix(arrA, this::nonassociativeFunc);

        for (int i = 1; i < arrB.length; i++) {
            arrB[i] = nonassociativeFunc(arrB[i - 1], arrB[i]);
        }

        consistent = Arrays.equals(arrA, arrB);
        if(!consistent) break;
    }
    assertFalse(consistent);
}

7.2. atuação

A computação de prefixo paralelo geralmente é mais eficiente que os loops seqüenciais, especialmente para matrizes grandes. Ao executar o micro-benchmark em uma máquina Intel Xeon (6 núcleos) comJMH, podemos ver uma grande melhoria de desempenho:

Benchmark                      Mode        Cnt       Score   Error        Units
largeArrayLoopSum             thrpt         5        9.428 ± 0.075        ops/s
largeArrayParallelPrefixSum   thrpt         5       15.235 ± 0.075        ops/s

Benchmark                     Mode         Cnt       Score   Error        Units
largeArrayLoopSum             avgt          5      105.825 ± 0.846        ops/s
largeArrayParallelPrefixSum   avgt          5       65.676 ± 0.828        ops/s

Aqui está o código de referência:

@Benchmark
public void largeArrayLoopSum(BigArray bigArray, Blackhole blackhole) {
  for (int i = 0; i < ARRAY_SIZE - 1; i++) {
    bigArray.data[i + 1] += bigArray.data[i];
  }
  blackhole.consume(bigArray.data);
}

@Benchmark
public void largeArrayParallelPrefixSum(BigArray bigArray, Blackhole blackhole) {
  Arrays.parallelPrefix(bigArray.data, (left, right) -> left + right);
  blackhole.consume(bigArray.data);
}

7. Conclusão

Neste artigo, aprendemos alguns métodos para criar, pesquisar, classificar e transformar arrays usando a classejava.util.Arrays.

Esta classe foi expandida em versões mais recentes do Java com a inclusão de métodos de produção e consumo de fluxo emJava 8e métodos de incompatibilidade emJava 9.

A fonte deste artigo é, como sempre,over on Github.