JavaRush /Blogue Java /Random-PT /Atribuição e inicialização em Java
Viacheslav
Nível 3

Atribuição e inicialização em Java

Publicado no grupo Random-PT

Introdução

O principal objetivo dos programas de computador é o processamento de dados. Para processar dados você precisa armazená-los de alguma forma. Proponho entender como os dados são armazenados.
Atribuição e inicialização em Java - 1

Variáveis

Variáveis ​​são contêineres que armazenam quaisquer dados. Vejamos o Tutorial oficial da Oracle: Declaring Member Variables . De acordo com este Tutorial, existem vários tipos de variáveis:
  • Campos : variáveis ​​declaradas na classe;
  • Variáveis ​​locais : variáveis ​​em um método ou bloco de código;
  • Parâmetros : variáveis ​​na declaração do método (na assinatura).
Todas as variáveis ​​devem ter um tipo de variável e um nome de variável.
  • O tipo de uma variável indica quais dados a variável representa (ou seja, quais dados ela pode armazenar). Como sabemos, o tipo de uma variável pode ser primitivo (primitivos ) ou objeto , não primitivo (não primitivo). Com variáveis ​​de objeto, seu tipo é descrito por uma classe específica.
  • O nome da variável deve estar em letras minúsculas, em camel case. Você pode ler mais sobre nomenclatura em " Variáveis:Nomeação ".
Além disso, se uma variável de nível de classe, ou seja, é um campo de classe, um modificador de acesso pode ser especificado para ele. Consulte Controlando o acesso aos membros de uma classe para obter mais detalhes .

Declaração de Variável

Então, lembramos o que é uma variável. Para começar a trabalhar com uma variável, você precisa declará-la. Primeiro, vamos examinar uma variável local. Em vez de um IDE, por conveniência, usaremos a solução online do tutorialspoint: Online IDE . Vamos executar este programa simples em seu IDE online:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
Então, como você pode ver, declaramos uma variável local com nome numbere tipo int. Pressionamos o botão “Executar” e obtemos o erro:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
O que aconteceu? Declaramos uma variável, mas não inicializamos seu valor. Vale ressaltar que este erro não ocorreu em tempo de execução (ou seja, não em Runtime), mas sim em tempo de compilação. O compilador inteligente verificou se a variável local seria inicializada antes de acessá-la ou não. Portanto, as seguintes afirmações decorrem disso:
  • Variáveis ​​locais só devem ser acessadas após terem sido inicializadas;
  • Variáveis ​​locais não possuem valores padrão;
  • Os valores das variáveis ​​locais são verificados em tempo de compilação.
Então, somos informados de que a variável deve ser inicializada. Inicializar uma variável é atribuir um valor a uma variável. Vamos então descobrir o que é e por quê.

Inicializando uma variável local

Inicializar variáveis ​​é um dos tópicos mais complicados em Java, porque... está intimamente relacionado ao trabalho com memória, à implementação da JVM, à especificação da JVM e outras coisas igualmente assustadoras e complicadas. Mas você pode tentar descobrir pelo menos até certo ponto. Vamos do simples ao complexo. Para inicializar a variável, usaremos o operador de atribuição e alteraremos a linha em nosso código anterior:
int number = 2;
Nesta opção não haverá erros e o valor será exibido na tela. O que acontece nesse caso? Vamos tentar raciocinar. Se quisermos atribuir um valor a uma variável, queremos que essa variável armazene um valor. Acontece que o valor deve ser armazenado em algum lugar, mas onde? No disco? Mas isso é muito lento e pode nos impor restrições. Acontece que o único lugar onde podemos armazenar dados “aqui e agora” de forma rápida e eficiente é na memória. Isso significa que precisamos alocar algum espaço na memória. Isto é verdade. Quando uma variável é inicializada, será alocado espaço para ela na memória alocada para o processo java dentro do qual nosso programa será executado. A memória alocada para um processo java é dividida em diversas áreas ou zonas. Qual deles alocará espaço depende do tipo de variável que foi declarada. A memória é dividida nas seguintes seções: Heap, Stack e Non-Heap . Vamos começar com a memória da pilha. Pilha é traduzida como pilha (por exemplo, uma pilha de livros). É uma estrutura de dados LIFO (Last In, First Out). Isto é, como uma pilha de livros. Quando adicionamos livros, colocamos-os em cima, e quando os retiramos, pegamos o de cima (ou seja, aquele que foi adicionado mais recentemente). Então, lançamos nosso programa. Como sabemos, um programa Java é executado por uma JVM, ou seja, uma máquina virtual Java. A JVM deve saber onde a execução do programa deve começar. Para fazer isso, declaramos um método principal, que é chamado de “ponto de entrada”. Para execução na JVM é criado um thread principal (Thread). Quando um thread é criado, ele recebe sua própria pilha na memória. Essa pilha consiste em quadros. Quando cada novo método é executado em uma thread, um novo quadro será alocado para ele e adicionado ao topo da pilha (como um novo livro em uma pilha de livros). Este quadro conterá referências a objetos e tipos primitivos. Sim, sim, nosso int será armazenado na pilha, porque... int é um tipo primitivo. Antes de alocar um quadro, a JVM deve entender o que salvar nele. É por esse motivo que receberemos o erro “a variável pode não ter sido inicializada”, pois se não for inicializada a JVM não conseguirá preparar a pilha para nós. Portanto, ao compilar um programa, um compilador inteligente nos ajudará a evitar cometer erros e quebrar tudo. (!) Para maior clareza, recomendo um artigo super-duper : “ Java Stack and Heap: Java Memory Allocation Tutorial ”. Tem um link para um vídeo igualmente legal:
Após a conclusão da execução de um método, os frames alocados para esses métodos serão deletados da pilha do thread, e junto com eles a memória alocada para este frame com todos os dados será limpa.

Inicializando Variáveis ​​de Objeto Local

Vamos mudar nosso código novamente para um pouco mais complicado:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
O que vai acontecer aqui? Vamos falar sobre isso novamente. A JVM sabe de onde deve executar o programa, ou seja, ela vê o método principal. Ele cria um thread e aloca memória para ele (afinal, um thread precisa armazenar os dados necessários para execução em algum lugar). Neste thread, um quadro é alocado para o método principal. Em seguida, criamos um objeto HelloWorld. Este objeto não é mais criado na pilha, mas sim na pilha. Porque object não é um tipo primitivo, mas um tipo de objeto. E a pilha armazenará apenas uma referência ao objeto no heap (devemos acessar esse objeto de alguma forma). A seguir, na pilha do método principal, serão alocados frames para execução do método println. Após executar o método principal, todos os frames serão destruídos. Se o quadro for destruído, todos os dados serão destruídos. O objeto objeto não será destruído imediatamente. Primeiro, a referência a ele será destruída e, portanto, ninguém mais fará referência ao objeto objeto e o acesso a esse objeto na memória não será mais possível. Uma JVM inteligente tem seu próprio mecanismo para isso - um coletor de lixo (coletor de lixo ou GC, para abreviar). Em seguida, ele remove da memória objetos que ninguém mais faz referência. Este processo foi novamente descrito no link fornecido acima. Tem até um vídeo com explicação.

Inicializando campos

A inicialização dos campos especificados em uma classe ocorre de maneira especial dependendo se o campo é estático ou não. Se um campo tiver a palavra-chave static, então este campo se refere à própria classe, e se a palavra static não for especificada, então este campo se refere a uma instância da classe. Vejamos isso com um exemplo:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
Neste exemplo, os campos são inicializados em momentos diferentes. O campo numérico será inicializado após a criação do objeto da classe HelloWorld. Mas o campo de contagem será inicializado quando a classe for carregada pela máquina virtual Java. O carregamento de classe é um tópico separado, por isso não vamos misturá-lo aqui. Só vale a pena saber que variáveis ​​estáticas são inicializadas quando a classe se torna conhecida em tempo de execução. Outra coisa é mais importante aqui, e você já percebeu isso. Não especificamos o valor em lugar nenhum, mas funciona. E realmente. Variáveis ​​que são campos, caso não tenham um valor especificado, são inicializadas com um valor padrão. Para valores numéricos, é 0 ou 0,0 para números de ponto flutuante. Para booleano isso é falso. E para todas as variáveis ​​de tipo de objeto o valor será nulo (falaremos sobre isso mais tarde). Ao que parece, por que isso acontece? Mas porque os objetos são criados no Heap (no heap). O trabalho com esta área é realizado em Runtime. E podemos inicializar essas variáveis ​​em tempo de execução, ao contrário da pilha, cuja memória deve ser preparada antes da execução. É assim que a memória funciona em Java. Mas há mais um recurso aqui. Esta pequena peça toca diferentes cantos da memória. Como lembramos, um quadro é alocado na memória Stack para o método principal. Este quadro armazena uma referência a um objeto na memória Heap. Mas onde a contagem é armazenada então? Como lembramos, esta variável é inicializada imediatamente, antes do objeto ser criado no heap. Esta é uma pergunta realmente complicada. Antes do Java 8, havia uma área de memória chamada PERMGEN. A partir do Java 8, esta área mudou e é chamada de METASPACE. Essencialmente, variáveis ​​estáticas fazem parte da definição da classe, ou seja, seus metadados. Portanto, é lógico que seja armazenado no repositório de metadados, METASPACE. MetaSpace pertence à mesma área de memória não-heap e faz parte dela. Também é importante levar em consideração que a ordem em que as variáveis ​​são declaradas é levada em consideração. Por exemplo, há um erro neste código:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

O que é nulo

Conforme dito acima, variáveis ​​de tipos de objetos, se forem campos de uma classe, são inicializadas com valores padrão e esse valor padrão é nulo. Mas o que é nulo em Java? A primeira coisa a lembrar é que os tipos primitivos não podem ser nulos. E tudo porque null é uma referência especial que não se refere a lugar nenhum, a nenhum objeto. Portanto, apenas uma variável de objeto pode ser nula. A segunda coisa que é importante entender é que null é uma referência. Faço referência também ao peso deles. Neste tópico, você pode ler a pergunta no stackoverflow: " A variável nula requer espaço na memória ".

Blocos de inicialização

Ao considerar a inicialização de variáveis, seria um pecado não considerar os blocos de inicialização. Se parece com isso:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
A ordem de saída será: bloco estático, bloco, Construtor. Como podemos ver, os blocos de inicialização são executados antes do construtor. E às vezes isso pode ser um meio conveniente de inicialização.

Conclusão

Espero que esta breve visão geral tenha sido capaz de fornecer algumas dicas sobre como funciona e por quê. #Viacheslav
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION