1. Propriedades: getters e setters

Quando um grande projeto está sendo desenvolvido por dezenas de programadores ao mesmo tempo, geralmente surgem problemas se eles manusearem os dados armazenados nos campos de classe de maneira diferente.

Talvez as pessoas não estudem a documentação da classe em detalhes ou talvez ela não descreva todos os casos. Como resultado, há situações frequentes em que os dados internos de um objeto podem ser "corrompidos", tornando o objeto inválido.

Para evitar essas situações, costuma-se tornar todos os campos de classe privados em Java . Somente os métodos da classe podem modificar as variáveis ​​da classe. Nenhum método de outras classes pode acessar diretamente as variáveis.

Se você deseja que outras classes possam obter ou alterar os dados dentro dos objetos de sua classe, você precisa adicionar dois métodos à sua classe — um método get e um método set. Exemplo:

Código Observação
class Person
{
   private String name;

   public Person(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }

   public void setName(String name)
   {
      this.name = name;
   }
}


privatename field



Inicialização do campo através do construtor


getName()— Este método retorna o valor do campo name




setName()— Este método altera o valor do campo name

Nenhuma outra classe pode alterar diretamente o valor do campo de nome. Se alguém precisar obter o valor do campo name, terá que chamar o getName() método em um Personobjeto. Se algum código quiser alterar o valor do campo name, ele precisará chamar o setName() método em um Personobjeto.

O getName()método também é chamado de " getter para o campo de nome" e o setName()método é chamado de " setter para o campo de nome".

Esta é uma abordagem muito comum. Em 80-90% de todo o código Java, você nunca verá variáveis ​​públicas em uma classe. Em vez disso, eles serão declarados private(ou protected) e cada variável terá getters e setters públicos.

Essa abordagem torna o código mais longo, mas mais confiável.

Acessar uma variável de classe diretamente é como virar seu carro através de linhas amarelas duplas : é mais fácil e rápido, mas se todos fizerem isso, as coisas ficarão piores para todos.

Digamos que você queira criar uma classe que descreva um ponto ( x, y). Veja como um programador novato faria:

class Point
{
   public int x;
   public int y;
}

Veja como um programador Java experiente faria isso:

Código
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y;
   }
}

O código é mais longo? Sem dúvida.

Mas você pode adicionar validação de parâmetro para getters e setters. Por exemplo, você pode garantir que xe ysejam sempre maiores que zero (ou não menores que zero). Exemplo:

Código Observação
class Point {
   private int x;
   private int y;

   public Point(int x, int y) {
      this.x = x < 0 ? 0 : x;
      this.y = y < 0 ? 0 : y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x < 0 ?  0 : x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y < 0 ? 0 : y;
   }
}


2. Tempo de vida do objeto

Você já sabe que os objetos são criados usando o newoperador, mas como os objetos são excluídos? Eles não existem para sempre. Não há memória suficiente para isso.

Em muitas linguagens de programação, como C++, existe um deleteoperador especial para deletar objetos. Mas como isso funciona em Java?

Em Java, tudo é organizado de maneira um pouco diferente. Java não tem operador delete. Isso significa que os objetos não são excluídos em Java? Não, eles são excluídos, é claro. Caso contrário, os aplicativos Java rapidamente ficariam sem memória e não se falaria em programas rodando ininterruptamente por meses.

Em Java, a exclusão de objetos é totalmente automatizada. A própria máquina Java lida com a exclusão de objetos. Esse processo é chamado de coleta de lixo, e o mecanismo que coleta o lixo é chamado de coletor de lixo ( GC ).

Então, como a máquina Java sabe quando excluir um objeto?

O coletor de lixo divide todos os objetos em "acessíveis" e "inacessíveis". Se houver pelo menos uma referência a um objeto, ele será considerado alcançável. Se não houver nenhuma variável que se refira a um objeto, o objeto é considerado inacessível e é declarado como lixo, o que significa que pode ser deletado.

Em Java, você não pode criar uma referência a um objeto existente — você só pode atribuir referências que já possui. Se apagarmos todas as referências a um objeto, ele será perdido para sempre.

referências circulares

Essa lógica parece ótima até encontrarmos um contra-exemplo simples: suponha que temos dois objetos que fazem referência um ao outro (armazenam referências um ao outro). Nenhum outro objeto armazena referências a esses objetos.

Esses objetos não podem ser acessados ​​a partir do código, mas ainda são referenciados.

É por isso que o coletor de lixo divide os objetos em alcançáveis ​​e inacessíveis, em vez de "referenciados" e "não referenciados".

Objetos alcançáveis

Primeiro, os objetos que estão 100% ativos são adicionados à lista alcançável. Por exemplo, o thread atual ( Thread.current()) ou o console InputStream ( System.in).

Em seguida, a lista de objetos alcançáveis ​​se expande para incluir objetos referenciados pelo conjunto inicial de objetos alcançáveis. Em seguida, ele é expandido novamente para incluir objetos referenciados por esse conjunto ampliado e assim por diante.

Isso significa que, se houver alguns objetos que apenas se referem uns aos outros, mas não há como acessá-los a partir de objetos alcançáveis, esses objetos serão considerados lixo e serão excluídos.


3. Coleta de lixo

fragmentação de memória

Outro ponto importante relacionado à exclusão de objetos é a fragmentação da memória. Se você criar e excluir objetos constantemente, em breve a memória ficará fortemente fragmentada: áreas de memória ocupada serão intercaladas com áreas de memória desocupada.

Como resultado, podemos facilmente entrar em uma situação em que não podemos criar um objeto grande (por exemplo, um array com um milhão de elementos), porque não há uma grande quantidade de memória livre. Em outras palavras, pode haver memória livre, mesmo muito, mas pode não haver um grande bloco contíguo de memória livre

Otimização de memória (desfragmentação)

A máquina Java resolve esse problema de uma maneira específica. Parece algo assim:

A memória é dividida em duas partes. Todos os objetos são criados (e excluídos) em apenas metade da memória. Quando chega a hora de limpar os buracos na memória, todos os objetos da primeira metade são copiados para a segunda metade. Mas eles são copiados um ao lado do outro para que não haja buracos.

O processo é mais ou menos assim:

Passo 1: Depois de criar objetos

Coleta de lixo em Java

Passo 2: Aparecimento de "buracos"

Coleta de lixo em Java 2

Passo 3: Eliminação de "buracos"

Coleta de lixo em Java 3

E é por isso que você não precisa excluir objetos. A máquina Java simplesmente copia todos os objetos acessíveis para um novo local e libera toda a área de memória onde os objetos costumavam ser armazenados.