Comentários
A maioria dos desenvolvedores já se deparou com problemas que exigiram um grande volume de variáveis do mesmo tipo, tornando necessário o uso de estruturas de dados como vetores e matrizes para serem resolvidos.
Fonte: Shutterstock.
Deseja ouvir este material?
Áudio disponível no material digital.
Prezado estudante, bem-vindo à quarta e última unidade do livro Linguagem Orientada a Objetos. Nesta unidade, vamos estudar alguns assuntos importantes dentro do desenvolvimento orientado a objetos (OO), estabelecer as ideias por meio do estudo dos arrays (vetores) unidimensionais e multidimensionais, aprender mais sobre a classe String, trabalhar com leituras de arquivos, aprender como interagir com um banco de dados e ver brevemente como funcionam as threads em Java.
Após a leitura desta unidade, espera-se que você, aluno, seja capaz de criar e manipular vetores unidimensionais e multidimensionais, manipular a classe String, criar aplicações utilizando banco de dados relacional e criar e manipular as threads para realizar processamento em paralelo.
Ao longo da unidade, é recomendado que o aluno desenvolva e teste todos os códigos apresentados, pois isso o levará a ter uma maior fixação do conteúdo, bem como utilize alguma plataforma de compartilhamento de código, como o GitHub. O livro apresentará diversos exemplos práticos que o auxiliarão de forma direta na compreensão da teoria, e as capacidades de reflexão, modelagem e abstração, por sua vez, serão treinadas ao longo deste livro.
O conteúdo desta unidade facilitará a compreensão da Linguagem Orientada a Objetos focando aplicações sobre a linguagem Java; para isso, a unidade foi dividida nas seguintes seções: a Seção 4.1 apresentará os conceitos por trás dos arrays e strings em Java; a Seção 4.2 trará aplicações relacionadas ao uso de banco de dados; e a Seção 4.3 mostrará os passos necessários para a criação de aplicações envolvendo threads.
Pelo fato desta unidade ser prática, convido você a implementar as atividades aqui propostas. Lembre-se de que a prática leva à perfeição.
Bons estudos!
Caro aluno, bem-vindo à primeira seção da quarta e última unidade de estudos sobre Linguagem Orientada a Objetos. Qual desenvolvedor nunca se deparou com problemas que exigiram um grande volume de variáveis do mesmo tipo? Acredito que a maioria dos desenvolvedores já se deparou com problemas que exigiram estruturas de dados como vetores e matrizes para serem resolvidos. Pois bem, nesta seção, estudaremos os arrays unidimensional e multidimensional e como manipular literais (strings) em Java. Para isso, três classes serão estudadas: String, StringBuilder e StringBuffer.
De forma a contextualizar sua aprendizagem, lembre-se de que você está trabalhando em um simulador de robô. O seu patrão, por sua vez, reviu o seu código e percebeu que, na sala em que o robô operará, existem poucas caixas, logo, pediu a você que criasse um array unidimensional para colocar as caixas de livros e impressoras. Como ele sabe que a sala possuirá mais caixas de HDs, solicitou a criação de um array multidimensional de caixas de HDs. Além disso, ele percebeu que o robô estava se movendo livremente pelo cenário (sala), mesmo sobre as caixas, e lhe pediu para restringir a sua região de navegação, argumentando que isso tornará a simulação mais realista.
Diante desse cenário que lhe foi apresentado, como você criará esse array unidimensional? O que é um array unidimensional? Como você criará um array multidimensional? E o que é um array multidimensional? (Esta seção o auxiliará na resposta de tais perguntas.)
Muito bem, agora que você foi apresentado à sua nova situação-problema, estude esta seção e compreenda como a linguagem Java trata os arrays de dados; esses conceitos são fundamentais em quaisquer linguagens de programação.
E aí, vamos juntos compreender esses conceitos e resolver esse desafio?
Bom estudo!
A linguagem Java permite a criação de arrays unidimensionais também chamados de vetores. Um vetor agrupa um conjunto de dados de um mesmo tipo organizados em linha ou coluna, e por trabalhar com dados do mesmo tipo, falamos que o vetor é uma estrutura de dados homogênea. A criação e a manipulação de vetores em Java têm algumas similaridades com a linguagem C, mas parte da alocação de vetores em Java é mais simples que na linguagem C, pois não é necessária a utilização de comandos como o malloc (alocação de memória). Analise a Figura 4.1 que nos mostra a representação de um array unidimensional.
Na Figura 4.1, podemos ver um vetor em forma de linha, e abaixo dos valores, temos os índices do vetor. Os índices são sempre valores inteiros que vão de zero até o tamanho do vetor menos um. Nesse exemplo, o nosso vetor tem tamanho 10, assim, os índices vão de 0 a 9. Os índices são utilizados para acessar as posições e os valores armazenados nos vetores. A tentativa de acessar um valor de índice negativo, maior ou igual ao tamanho do vetor lança uma exceção do tipo ArrayIndexOutOfBoundsException. O Quadro 4.1 agrupa um conjunto de comandos que fazem a criação e manipulação de um vetor unidimensional.
Operação sobre Vetores |
Exemplo de Código |
---|---|
Declaração de um vetor. |
|
Alocação de espaço de um vetor. | nomeVetor = |
Declaração e alocação de um vetor. |
|
Acesso de uma posição do vetor. | nomeVetor[indice] |
Atribuição de um valor a uma posição. | nomeVetor[indice] = valor; |
Acesso ao tamanho de um vetor. | nomeVetor. |
Imagine que queremos elaborar uma simples aplicação que cria um vetor de inteiros com tamanho 5, preencher esses valores e, por fim, imprimir os valores preenchidos. O Código 4.1 nos mostra essa primeira aplicação utilizando vetores.
int vet[] = new int[5];
vet[0] = 6;
vet[1] = 3;
vet[2] = 7;
vet[3] = 4;
vet[4] = 2;
for (int i = 0; i < vet.length; i++) {
System.out.println("Array[" + i + "]: " + vet[i]);
}
Na linha 1 do Código 4.1, temos a declaração do nosso array, nomeado de vet, que contém cinco posições; já nas linhas 2 a 6, fizemos a inicialização dos valores do vetor; por fim, nas linhas 7 a 9, temos um laço que percorre todas as posições do vetor e o imprime.
Por meio de uma análise do exemplo acima, podemos perceber que a inicialização do vetor ocupou muitas linhas de código, logo, outra forma de fazermos isso é inicializar os valores na criação do vetor. Observe o Quadro 4.2 em que são mostrados alguns exemplos de como isso pode ser feito para diferentes tipos de dados. Neste quadro, podemos perceber que existem duas formas de se declarar o vetor: na primeira delas, o colchete é colocado depois do nome da variável; já na segunda, o colchete é colocado antes do nome da variável; ambas as formas funcionam em Java.
Tipo do Vetor |
Exemplos usando a forma 1 |
Exemplos usando a forma 2 |
---|---|---|
Inteiro |
|
|
Real |
|
|
Caractere |
|
|
Os arrays multidimensionais, também chamados de matrizes, também são estruturas de dados muito importantes. Uma matriz agrupa um conjunto de dados de um mesmo tipo organizados em forma de linhas e colunas. Da mesma forma que os vetores, a criação e manipulação de matrizes em Java têm algumas similaridades com a linguagem C. Analise a Figura 4.2 que nos mostra a representação de um array bidimensional.
Na Figura 4.2, podemos ver uma matriz bidimensional em forma de linhas e colunas cujas células da matriz são acessadas pelos seus índices, que são sempre valores inteiros. O Quadro 4.3 agrupa um conjunto de comandos que fazem a criação e manipulação de uma matriz. Analise.
Operação sobre Matrizes |
Exemplo de Código |
---|---|
Declaração de uma matriz. |
|
Alocação de espaço de uma matriz. | nomeMat = |
Declaração e alocação de uma matriz. |
|
Acesso de uma posição da matriz. | nomeMat[indiceX][indiceY] |
Atribuição de um valor a uma posição. | nomeMat[indiceX][indiceY] = valor; |
Acesso ao número de linhas. | nomeMat. |
Acesso ao número de colunas da i-ésima linha. | nomeMat[i]. |
Imagine, agora, que queremos elaborar uma simples aplicação que cria uma matriz de doubles com três linhas e duas colunas já preenchidas na declaração, bem como imprimir os valores preenchidos. O Código 4.2 nos mostra essa aplicação utilizando matrizes.
double mat[][] = {{1.5, 5.2}, {3.6, 4.9}, {2.4, 8.1}};
for (int i = 0; i < mat.length; i++) {
for (int j = 0; j < mat[i].length; j++) {
System.out.println("M[" +i+ "][" +j+ "]: " + mat[i][j]);
}
}
No Código 4.2, na linha 1, temos a declaração e o preenchimento do nosso array bidimensional, chamado mat, que contém três linhas e duas colunas, totalizando seis elementos. Na linha 2, por sua vez, iniciamos o primeiro laço que percorrerá as linhas da matriz; já na linha 3, iniciamos o segundo laço que percorrerá as colunas da matriz; por fim, na linha 4, fizemos a impressão dos elementos da matriz. Por meio do exemplo acima, percebemos que podemos fazer a inicialização dos elementos da matriz também durante a sua declaração.
Em algumas aplicações, desejamos construir matrizes bidimensionais cujo comprimento das linhas varie. Um exemplo de aplicação em que essa ideia é utilizada é a construção de uma matriz bidimensional em que as linhas refletem os meses do ano (de janeiro a dezembro) e as colunas refletem os dias do mês. Como sabemos, o número de dias de cada mês varia, uma vez que janeiro, março, maio, julho, agosto, outubro e dezembro têm 31 dias, já os meses de abril, junho, setembro e novembro têm 30 dias e fevereiro tem 28 (nesse exemplo, desconsideramos os anos bissextos para simplificar). Dessa maneira, nessa aplicação, a matriz teria 12 linhas (indicando os meses) e um número variável de colunas, que vai de 28 a 31 (indicando os dias).
De forma a ilustrar essa ideia, vamos imaginar que queremos construir uma matriz como a indicada na Figura 4.3.
O Código 4.3 a seguir nos mostra como construir uma matriz com comprimento de linhas variado.
Implemente esse código e faça testes; mude os valores da matriz e analise o seu comportamento.
int mat[][] = {{2, 4, 7}, {6, 3}, {5, 1, 9, 0}};
for (int i = 0; i < mat.length; i++) {
for (int j = 0; j < mat[i].length; j++) {
System.out.println("M["+i+"]["+j+"]="+mat[i][j]);
}
}
Caro leitor, caso tenha interesse, no GitHub do autor há um exemplo de código que mostra a construção da matriz com dias do mês variável. Nessa aplicação, a matriz é construída de uma forma mais dinâmica e simples, mas com o mesmo efeito do exemplo explicado acima.
Existem diversas aplicações envolvendo vetores que necessitam que os dados estejam ordenados. A linguagem Java já possui métodos capazes de ordenar vetores de tipos básicos ou derivados, desde que implementem a classe Comparable. A seguir, no Código 4.4, mostraremos como utilizar o método estático sort (ordenar) da classe Arrays, presente na biblioteca java.util.Arrays, que deve ser importada.
Analise o código mostrado abaixo.
int vetor[] = {6, 3, 7, 4, 2, 5, 1, 9, 0, 8};
System.out.println("Vetor antes de ordenar");
for (int i = 0; i < vetor.length; i++) {
System.out.print(vetor[i]);
}
Arrays.sort(vetor);
System.out.println("Vetor depois de ordenar");
for (int i = 0; i < vetor.length; i++) {
System.out.println(vetor[i]);
}
Como curiosidade, no Código 4.4, o método de ordenação sort utiliza, internamente, uma variante do método quicksort que utiliza dois pivôs, por isso, esse método é chamado de dual-pivot quicksort.
Ao longo do livro, utilizamos a classe String para declarar variáveis (objetos) do tipo literal; agora, vamos entender com mais detalhes como ela funciona.
A classe String possui uma importante característica, que é ser imutável. Isso quer dizer que uma vez atribuído um valor literal para a variável, existirá uma cópia dessa variável na memória do computador. O Java, por sua vez, permite que você coloque outro conteúdo nessa variável, no entanto, ela continuará na memória, só que sem nenhum objeto apontando para si. Assim, se você construir uma aplicação que modifique constantemente o valor da variável, você terá um desempenho ruim em termos de gastos de memória e de processamento.
A linguagem Java possui duas outras importantes classes que manipulam strings, que são: a StringBuilder e a StringBuffer. Os valores literais armazenados nessas duas classes são mutáveis, ou seja, podem ser alterados a qualquer momento sem que seja feita a cópia do literal na memória.
A classe StringBuilder é uma boa alternativa à classe String se desejar que o conteúdo da string seja mutável. Já a classe StringBuffer é uma boa alternativa às outras duas classes se desejar que o conteúdo seja mutável e necessitar de uma aplicação thread safe. Dizemos que uma classe é thread safe quando duas ou mais threads não podem invocar métodos dessa classe simultaneamente. Para isso, existe um mecanismo de sincronização dos métodos que garante esse aspecto de segurança em aplicações multi-threads (mais detalhes sobre threads serão estudados na Seção 4.3).
A fim de que possa entender melhor como criar valores literais utilizando essas três classes apresentadas, analise o Código 4.5 abaixo.
String str1 = new String("Orientação a objetos");
String str2 = "Orientação a objetos";
StringBuilder str3 = new StringBuilder("Orientação a objetos");
StringBuilder str4 = "Orientação a objetos"; //não é aceito (dá erro)
StringBuffer str5 = new StringBuffer("Orientação a objetos");
StringBuffer str6 = "Orientação a objetos"; //não é aceito (dá erro)
No Código 4.5, na linha 1, vemos a primeira forma de criação de uma string, em que o objeto str1 aponta para um objeto do tipo String com a mensagem “Orientação a objetos”, que é armazenada em uma região de memória chamada Heap (pilha). Na linha 2, vemos a segunda forma de criação de strings feita diretamente, sem a utilização do construtor, e essa segunda forma tem um funcionamento diferente em relação à primeira, pois o objeto str2 aponta para o literal string com a mensagem “Orientação a objetos”, que é armazenada em uma região especial da memória, chamada String Pool. Já na linha 3, temos a construção de um objeto do tipo StringBuilder, que coloca esse objeto na memória heap; na linha 4, destacamos que não é possível a criação de objetos StringBuilder com atribuição direta de valores literais; na linha 5, temos a construção de um objeto do tipo StringBuffer, que aloca esse objeto na memória heap; por fim, na linha 6, destacamos que também não é possível a criação de objetos StringBuffer com atribuição direta de valores literais.
Como forma de entender melhor a ideia por trás das memórias Heap e String Pool, analise a Figura 4.4 a seguir. Repare que toda vez que criamos um objeto com o operador new, o objeto é colocado na heap, e toda vez que criamos um objeto literal diretamente (sem o new), este é colocado na string pool. A vantagem da string pool sobre a heap é que ela economiza gastos com memória.
Ainda nessa figura, analise também as saídas das comparações usando o operador de igualdade (==), que compara o endereço de memória dos objetos; isso explica o porquê da comparação str1 == str2 ser falsa e o porquê da comparação str5 == str6 ser verdadeira. Para o caso de querer comparar o conteúdo de duas strings, utilize o método equals e não o operador de igualdade (==).
Uma dica muito relevante é observar as documentações das classes String, StringBuilder e StringBuffer. É muito importante ver os métodos disponíveis nessas classes; repare que a maioria deles possui a mesma assinatura.
O Quadro 4.4 nos apresenta uma síntese das principais características das classes String, StringBuilder e StringBuffer.
Característica |
String |
StringBuilder | StringBuffer |
---|---|---|---|
Memória Usada | String Pool + Heap | Heap | Heap |
Modificável | Imutável | Mutável | Mutável |
Sincronizado |
Não | Não | Sim |
Thread Safe |
Não | Não | Sim |
Versão do Java |
Desde Java 1.0 | Desde Java 1.5 | Desde Java 1.0 |
Desempenho Geral |
Rápido | Rápido | Lento |
Desempenho Concatenação |
Muito Lento | Super Rápido | Rápido |
Quando Usar? |
Usar a classe String quando o conteúdo da string for fixo. | Usar StringBuilder quando o conteúdo da string não for fixo e não for necessário ser thread safe. | Usar StringBuffer quando o conteúdo da string não for fixo e for necessário ser thread safe. |
Após analisar o Quadro 4.4, perceba que a classe StringBuffer é sincronizada, ou seja, é thread safe, isso significa que duas threads não podem chamar métodos da classe StringBuffer simultaneamente. Já as classes String e StringBuilder são não sincronizadas, ou seja, não são thread safe, isso indica que duas threads podem chamar métodos dessas classes simultaneamente. Por hora, pense que uma thread é um fluxo de execução da sua aplicação em um determinado núcleo do seu processador (na terceira seção desta unidade estudaremos mais sobre as threads).
A Figura 4.5 nos mostra a organização hierárquica das classes que manipulam strings em Java.
Uma recomendação é que você dê uma olhada nas documentações das classes e interfaces mostradas na Figura 4.5, principalmente nas interfaces CharSequence e Appendable. A interface CharSequence representa uma sequência de caracteres; repare que muitos métodos das classes String, StringBuilder e StringBuffer aceitam como parâmetros objetos do tipo CharSequence, e isso garante uma maior interoperabilidade com outras classes que manipulam literais que implementam a interface CharSequence. Já a interface Appendable garante às classes que a implementam a capacidade de acrescentarem novos valores literais aos seus conteúdos.
Conforme vimos na seção, a classe String é imutável e as classes StringBuilder e StringBuffer são mutáveis. Analise a Figura 4.5 e reflita sobre o porquê de a classe String não implementar a interface Appendable (Anexável), bem como o porquê de as classes StringBuilder e StringBuffer implementarem a interface Appendable. Dica: pense em termos que você aprendeu sobre mutabilidade.
Caro estudante, nesta seção você estudou os conteúdos relacionados a vetores, matrizes, classes String, StringBuilder e StringBuffer, bem como analisou diversos exemplos sobre como esses conceitos são implementados na linguagem Java. Lembre-se de que todos os códigos aqui mostrados podem ser acessados no GitHub do autor.
Na seção seguinte, estudaremos como criar uma aplicação que interage com um banco de dados, avançando ainda mais no entendimento da linguagem Java.
ARANTES, J. da S. Livro-POO-Java. 2020. Disponível em: https://bit.ly/3eiUMcF. Acesso em: 8 set. 2020.
CAELUM. Java e orientação a objetos: apostila do curso FJ-11. [s.d.]. Disponível em: https://bit.ly/2ZpTqrZ. Acesso em: 8 set. 2020.
DEITEL, P. J.; DEITEL, H. M. Java: como programar. 10. ed. São Paulo: Pearson Education, 2016.
JAVATPOINT. Difference between StringBuffer and StringBuilder. [s.d.]. Disponível em: https://bit.ly/2EeUFTM. Acesso em: 8 set. 2020.
LOIANE GRONER. Curso de Java básico gratuito com certificado e fórum. 2016. Disponível em: https://bit.ly/2VUtDGT. Acesso em: 8 set. 2020.
LOIANE GRONER. Curso de Java Módulo 2: intermediário. Disponível em: https://bit.ly/2Fv0uNz. Acesso em: 8 set. 2020.
ORACLE. String. [s.d.]. Disponível em: https://bit.ly/2FNIHka. Acesso em: 8 set. 2020.
ORACLE. StringBuffer. [s.d.]. Disponível em: https://bit.ly/35KYBXD. Acesso em: 8 set. 2020.
ORACLE. StringBuilder. [s.d.]. Disponível em: https://bit.ly/2ROZAOx. Acesso em: 8 set. 2020.