Operações de matriz em Java
1. Visão geral
Qualquer desenvolvedor Java sabe que produzir uma solução limpa e eficiente ao trabalhar com operações de array nem sempre é fácil de alcançar. Ainda assim, eles são uma peça central no ecossistema Java - e teremos que lidar com eles em várias ocasiões.
Por esse motivo, é bom ter uma "folha de dicas" - um resumo dos procedimentos mais comuns para nos ajudar a resolver o quebra-cabeça rapidamente. Este tutorial será útil nessas situações.
2. Matrizes e classes auxiliares
Antes de continuar, é útil entender o que é um array em Java e como usá-lo. Se é a primeira vez que você está trabalhando com ele em Java, sugerimos dar uma olhada emthis previous post, onde cobrimos todos os conceitos básicos.
Observe que as operações básicas suportadas por uma matriz são, de certa forma, limitadas. Não é incomum ver algoritmos complexos para executar tarefas relativamente simples quando se trata de matrizes.
Por esse motivo, para a maioria de nossas operações, usaremos classes e métodos auxiliares para nos ajudar: a classeArrays fornecida por Java e a classeArrayUtils do Apache.
Para incluir o último em nosso projeto, teremos que adicionar a dependênciaApache Commons:
org.apache.commons
commons-lang3
3.8.1
Podemos verificar a versão mais recente deste artefatoon Maven Central.
3. Obter o primeiro e o último elemento de uma matriz
Essa é uma das tarefas mais comuns e simples, graças à natureza de acesso por índice das matrizes.
Vamos começar declarando e inicializando uma matrizint que será usada em todos os nossos exemplos (a menos que especifiquemos de outra forma):
int[] array = new int[] { 3, 5, 2, 5, 14, 4 };
Sabendo que o primeiro item de uma matriz está associado ao valor de índice 0 e que tem um atributolength que podemos usar, é simples descobrir como podemos obter esses dois elementos:
int firstItem = array[0];
int lastItem = array[array.length - 1];
4. Obter um valor aleatório de uma matriz
Usando o objetojava.util.Random, podemos facilmente obter qualquer valor de nosso array:
int anyValue = array[new Random().nextInt(array.length)];
5. Anexar um novo item a uma matriz
Como sabemos, as matrizes mantêm um tamanho fixo de valores. Portanto, não podemos simplesmente adicionar um item e ultrapassar esse limite.
Precisamos começar declarando um novo array maior e copiar os elementos do array base para o segundo.
Felizmente, a classeArrays fornece um método útil para replicar os valores de uma matriz para uma nova estrutura de tamanhos diferentes:
int[] newArray = Arrays.copyOf(array, array.length + 1);
newArray[newArray.length - 1] = newItem;
Opcionalmente, se a classeArrayUtils estiver acessível em nosso projeto, podemos fazer uso de suaadd method (ou sua alternativaaddAll) para cumprir nosso objetivo em uma instrução de uma linha:
int[] newArray = ArrayUtils.add(array, newItem);
Como podemos imaginar, este método não modifica o objetoarray original; temos que atribuir sua saída a uma nova variável.
6. Inserir um valor entre dois valores
Devido ao seu caractere de valores indexados, inserir um item em uma matriz entre dois outros não é um trabalho trivial.
O Apache considerou este um cenário típico e implementou um método em sua classeArrayUtils para simplificar a solução:
int[] largerArray = ArrayUtils.insert(2, array, 77);
Temos que especificar o índice no qual queremos inserir o valor, e a saída será um novo array contendo um número maior de elementos.
O último argumento é um argumento variável (também conhecido como vararg) portanto, podemos inserir qualquer número de itens no array.
7. Compare duas matrizes
Mesmo que os arrays sejamObjects e, portanto, forneçam um métodoequals, eles usam a implementação padrão dele, contando apenas com a igualdade de referência.
De qualquer forma, podemos invocar o métodojava.util.Arrays ‘equals para verificar se dois objetos de matriz contêm os mesmos valores:
boolean areEqual = Arrays.equals(array1, array2);
Nota: este método não é eficaz parajagged arrays. O método apropriado para verificar a igualdade de estruturas multidimensionais é oArrays.deepEquals.
8. Verifique se uma matriz está vazia
Esta é uma atribuição simples, tendo em mente que podemos usar o atributolength dos arrays:
boolean isEmpty = array == null || array.length == 0;
Além disso, também temos um método seguro para nulos na classe auxiliarArrayUtils que podemos usar:
boolean isEmpty = ArrayUtils.isEmpty(array);
Esta função ainda depende do comprimento da estrutura de dados, que considera nulos e submatrizes vazias como valores válidos também, então teremos que ficar de olho nestes casos extremos:
// These are empty arrays
Integer[] array1 = {};
Integer[] array2 = null;
Integer[] array3 = new Integer[0];
// All these will NOT be considered empty
Integer[] array3 = { null, null, null };
Integer[][] array4 = { {}, {}, {} };
Integer[] array5 = new Integer[3];
9. Como embaralhar os elementos de uma matriz
Para embaralhar os itens em uma matriz, podemos usar o recursoArrayUtil:
ArrayUtils.shuffle(array);
Este é um métodovoide opera com os valores reais da matriz.
10. Matrizes de caixa e unbox
Freqüentemente, encontramos métodos que suportam apenas matrizes baseadas emObject.
Novamente, a classe auxiliarArrayUtils é útil para obter uma versão em caixa de nosso array primitivo:
Integer[] list = ArrayUtils.toObject(array);
A operação inversa também é possível:
Integer[] objectArray = { 3, 5, 2, 5, 14, 4 };
int[] array = ArrayUtils.toPrimitive(objectArray);
11. Remover duplicatas de uma matriz
A maneira mais fácil de remover duplicatas é convertendo o array em uma implementaçãoSet.
Como podemos saber,Collections usa genéricos e, portanto, não suporta tipos primitivos.
Por esse motivo, se não estivermos lidando com matrizes baseadas em objetos como em nosso exemplo, primeiro precisaremos encaixotar nossos valores:
// Box
Integer[] list = ArrayUtils.toObject(array);
// Remove duplicates
Set set = new HashSet(Arrays.asList(list));
// Create array and unbox
return ArrayUtils.toPrimitive(set.toArray(new Integer[set.size()]));
Nota: podemos usarother techniques to convert between an array and a Set object também.
Além disso, se precisarmos preservar a ordem de nossos elementos, devemos usar uma implementaçãoSet diferente, comoLinkedHashSet.
12. Como imprimir uma matriz
Da mesma forma que com o métodoequals, a funçãotoString do array usa a implementação padrão fornecida pela classeObject, que não é muito útil.
Os sclassesArrays eArrayUtils ão enviados com suas implementações para converter as estruturas de dados emString legível.
Além do formato um pouco diferente que eles usam, a distinção mais importante é como eles tratam objetos multidimensionais.
A classe do Java Util fornece dois métodos estáticos que podemos usar:
-
toString: não funciona bem com matrizes denteadas
-
deepToString: suporta quaisquer matrizes baseadas emObject, mas não compila com argumentos de matriz primitivos
Por outro lado,Apache’s implementation offers a single toString method that works correctly in any case:
String arrayAsString = ArrayUtils.toString(array);
13. Mapear uma matriz para outro tipo
Muitas vezes, é útil aplicar operações em todos os itens da matriz, possivelmente convertendo-os em outro tipo de objeto.
Com este objetivo em mente,we’ll try to create a flexible helper method using Generics:
public static U[] mapObjectArray(
T[] array, Function function,
Class targetClazz) {
U[] newArray = (U[]) Array.newInstance(targetClazz, array.length);
for (int i = 0; i < array.length; i++) {
newArray[i] = function.apply(array[i]);
}
return newArray;
}
Se não usarmos Java 8 em nosso projeto, podemos descartar o argumentoFunction e criar um método para cada mapeamento que precisamos realizar.
Agora podemos reutilizar nosso método genérico para diferentes operações. Vamos criar dois casos de teste para ilustrar isso:
@Test
public void whenMapArrayMultiplyingValues_thenReturnMultipliedArray() {
Integer[] multipliedExpectedArray = new Integer[] { 6, 10, 4, 10, 28, 8 };
Integer[] output =
MyHelperClass.mapObjectArray(array, value -> value * 2, Integer.class);
assertThat(output).containsExactly(multipliedExpectedArray);
}
@Test
public void whenMapDividingObjectArray_thenReturnMultipliedArray() {
Double[] multipliedExpectedArray = new Double[] { 1.5, 2.5, 1.0, 2.5, 7.0, 2.0 };
Double[] output =
MyHelperClass.mapObjectArray(array, value -> value / 2.0, Double.class);
assertThat(output).containsExactly(multipliedExpectedArray);
}
Para tipos primitivos, teremos que encaixotar nossos valores primeiro.
Como alternativa, podemos recorrer aJava 8’s Streams para realizar o mapeamento para nós.
Precisamos transformar a matriz emStream deObjects primeiro. Podemos fazer isso com o métodoArrays.stream.
Por exemplo, se quisermos mapear nossos valoresint para uma representaçãoString personalizada, vamos implementar isso:
String[] stringArray = Arrays.stream(array)
.mapToObj(value -> String.format("Value: %s", value))
.toArray(String[]::new);
14. Filtrar valores em uma matriz
Filtrar valores de uma coleção é uma tarefa comum que talvez tenhamos que executar em mais de uma ocasião.
Isso porque no momento em que criamos o array que receberá os valores, não podemos ter certeza de seu tamanho final. Portanto,we’ll rely on the Streams approach again.
Imagine que queremos remover todos os números ímpares de uma matriz:
int[] evenArray = Arrays.stream(array)
.filter(value -> value % 2 == 0)
.toArray();
15. Outras operações comuns de matriz
Obviamente, existem muitas outras operações de matriz que talvez precisemos executar.
Além das mostradas neste tutorial, cobrimos extensivamente outras operações nas postagens dedicadas:
16. Conclusão
Arrays são uma das principais funcionalidades do Java e, portanto, é realmente importante entender como eles funcionam e saber o que podemos e o que não podemos fazer com eles.
Neste tutorial, aprendemos como podemos lidar com operações de matriz adequadamente em cenários comuns.
Como sempre, o código-fonte completo dos exemplos de trabalho está disponível emour Github repo.