Nessa aula, trataremos do processo de construção e destruição dos objetos.
Ao final da aula, teremos como garantir a inicialização apropriada do estado das instâncias de uma classe e também a liberação ou transferência de recursos usados pelas instâncias quando estas não são mais necessárias.
Aprendemos até aqui que quando queremos criar um novo objeto, usamos a palavra chave new (um operador) e o chamado método construtor da classe, que tem o mesmo nome da classe.
Isso faz com que a memória para o novo objeto seja alocada, resultando em uma referência que é passada para a variável que está sofrendo a atribuição.
Vamos olhar esse processo em mais delahes agora.
Pessoa usuarioBiblioteca = new Pessoa();
Quando um objeto é criado, duas coisas são feitas em sequência:
No primeiro passo, o espaço alocado para a estrutura do objeto é definido em função dos campos da classe, sendo tipicamente: 16 bytes (metadados) + 4 bytes * Nº de campos complexos + soma dos tamanhos dos tipos primitivos.
No segundo passo, os campos do novo objeto são inicializados primeiramente com valores padrão:
Em seguida, o método construtor que foi chamado é executado, onde os campos podem ser preenchidos com valores escolhidos pelo programador, tipicamente passados como argumentos do construtor.
Por fim, a referência é atribuída à variável.
O método construtor funciona de forma similar a um método normal de instância, mas não possui retorno definido, pois é usado apenas com o operador new.
Também pode ser sobrecarregado, permitindo diversas maneiras de inicializar um objeto.
package br.ufrj.dcc.comp2.ple.aula_danilo.exemplos.biblioteca;
import java.time.LocalDateTime;
public class Pessoa {
String cpf;
String nome;
LocalDateTime dataNascimento;
String endereço;
String telefone;
int livrosLidos;
int paginasLidas;
public Pessoa(String cpf, String nome) {
this.cpf = cpf;
this.nome = nome;
}
public void ler(Livro livro) {
this.livrosLidos += 1;
this.paginasLidas += livro.numPaginas;
}
public void ler(Revista revista) {
this.paginasLidas += revista.numPaginas;
}
}
package br.ufrj.dcc.comp2.ple.aula_danilo.exemplos.biblioteca;
import java.time.LocalDateTime;
public class Pessoa {
String cpf;
String nome;
LocalDateTime dataNascimento;
String endereço;
String telefone;
int livrosLidos;
int paginasLidas;
public Pessoa() {
}
public Pessoa(String cpf, String nome) {
this.cpf = cpf;
this.nome = nome;
}
public Pessoa(String cpf, String nome, LocalDateTime dataNascimento) {
this.cpf = cpf;
this.nome = nome;
this.dataNascimento = dataNascimento;
livrosLidos = 0; // Uso do this não é obrigatório se não há conflito de nomes.
paginasLidas = 0;
}
public void ler(Livro livro) {
this.livrosLidos += 1;
this.paginasLidas += livro.numPaginas;
}
public void ler(Revista revista) {
this.paginasLidas += revista.numPaginas;
}
}
Em Java, quando um objeto sai do escopo de uso, ou não possui mais referências apontando para ele, é colocado em uma lista de "coleta de lixo".
Assim que possível, a JVM executa um processo chamado coletor de lixo (garbage collector, ou GC), que desaloca os objetos descartados da memória.
Dessa forma, não é necessário o programador desalocar manualmente os objetos.
Até a versão 9 do Java, o coletor de lixo executava o método finalize() do objeto antes de desalocá-lo.
A intenção desse método era liberar recursos, como arquivos abertos e conexões com bancos de dados, de forma segura.
package br.ufrj.dcc.comp2.ple.aula_danilo.exemplos.biblioteca;
import java.time.LocalDateTime;
public class Pessoa {
String cpf;
String nome;
LocalDateTime dataNascimento;
String endereço;
String telefone;
int livrosLidos;
int paginasLidas;
public Pessoa() {
}
public Pessoa(String cpf, String nome) {
this.cpf = cpf;
this.nome = nome;
}
public Pessoa(String cpf, String nome, LocalDateTime dataNascimento) {
this.cpf = cpf;
this.nome = nome;
this.dataNascimento = dataNascimento;
livrosLidos = 0; // Uso do this não é obrigatório se não há conflito de nomes.
paginasLidas = 0;
}
public void finalize() {
// Fazer a liberação de recursos aqui.
}
public void ler(Livro livro) {
this.livrosLidos += 1;
this.paginasLidas += livro.numPaginas;
}
public void ler(Revista revista) {
this.paginasLidas += revista.numPaginas;
}
}
Nas versões mais recentes (>= 9) isso não acontece mais e a liberação dos recursos deve ser feita usando um bloco try-catch-finally (no finally), ou usando um bloco try-with-resources, como mostrado abaixo:
// try-catch-finally
try {
Scanner scanner = new Scanner(System.in);
String linha = scanner.nextLine();
}
catch (NoSuchElementException) {
System.out.println("Acabaram as linhas...");
}
finally {
scanner.close();
}
O bloco try-with-resources libera automaticamente os recursos alocados em sua cláusula.
// try-with-resources
try (Scanner scanner = new Scanner(System.in)) {
String linha = scanner.nextLine();
}
catch (NoSuchElementException) {
System.out.println("Acabaram as linhas...");
}
Agora sabemos como inicializar apropriadamente nossos objetos, e como liberar recursos de forma correta.
Que tal definir os construtores da classe Livro e da classe Revista?
Perguntas:
Exercício:
Até a próxima aula!