Heap e Stack

As definições de memórias heap e stack se encontram desde os programas mais básicos envolvendo OO.

Geralmente, as diversas partes das aplicações Java que são baseados em objetos, métodos, variáveis e residem em um dos 2 seguintes lugares da memória: stack ou heap. As variáveis locais residem no stack. Os objetos, juntamente com seus atributos, residem no heap.

Literais

Um literal primitivo é basicamente a representação do código-fonte dos tipos de dados primitivos, resumindo: um booleano, um inteiro, um número de ponto flutuante ou qualquer caractere que seja digitado enquanto é escrito um código.

Na linguagem Java, existem 3 opções para representar números inteiros: octal (base 8), decimal (base 10) e hexadecimal (base 16).

Os números hexadecimais são formados com o uso de 16 símbolos distintos. Uma vez que não foram criados símbolos numéricos unitários para algarismos que vão de 10 a 15, utilizamos caracteres alfabéticos para representar esses dígitos. Uma listagem de 0 a 15 na representação hexadecimal ficaria dessa maneira:

  • 0 1 2 3 4 5 6 7 8 9 a b c d e f

O Java permite minúsculas ou maiúsculas para o dígitos adicionais; esse é um dos raros momentos em que não é sensitive case.

Os outros tipos de números de ponto flutuante são definidos como um número, em símbolo decimal e outros números que representam a fração. Desse modo, para se atribuir um literal de ponto flutuante a um float, é necessário sufixá-lo com ‘F’ ou ‘f’.

A representação do literal char é feita por um caractere entre aspas simples ('). Também é possível digitar o valor Unicode do caractere usando a notação Unicode, que adiciona um prefixo \u ao valor, conforme é demonstrado no exemplo abaixo.


        char n = ‘\u004e’;\\letra N
        

Também é possível você utilizar um código de escape se quiser representar um caractere que não possa ser digitado como um literal, incluindo os caracteres de tabulação horizontal, de alimentação de linha, backspace, nova linha, aspas duplas e simples.


        char char1 = ‘\n’; //nova linha
        char char2 = ‘\”’; //aspas duplas
        

É essencial que você assimile que variáveis são basicamente depósitos de bits, com um tipo determinado.


        JButton botao = new JButton();
        

O que estará no depósito de JButton botao? Uma variável referenciando o objeto, em outras palavras, uma variável de referência. O depósito de bits de uma variável de referência contém bits que representam um modo de acessar o objeto. O sinal de igualdade (=) é utilizado para a atribuição de um valor a uma variável e foi denominado de forma adequada de operador de atribuição. O código seguinte é válido.


        byte bt = (byte) 20;
        

Mas apenas porque o compilador lhe deu uma oportunidade e proporcionou que você tomasse um atalho, com atribuições a variáveis inteiras, menores que um tipo int.

Um inteiro literal é sempre um tipo int; entretanto, o importante é que o resultado de uma expressão que envolva qualquer item do tamanho de um tipo int, ou menor, será um int.

Ou seja, some dois tipos byte e obterá um int, mesmo se esses dois tipos byte forem pequenos e ocuparem, cada um, 8 bits apenas. Divida um tipo int por um tipo short e terá um tipo int. Multiplique um short por um byte e terá um int. Analise o seguinte trecho de código.


        byte a = 2;
        byte b = 7;
        byte c = a+b; //Essa linha não compila
        

Tentou-se atribuir a uma variável do tipo byte a soma de dois bytes, cujo total é 9. Esse valor é possível dentro de um byte, mas o compilador não o aceitou. Ele sabe a regra “int ou de tipos menores sempre resultando em um tipo "int”. A compilação teria sido realizada com sucesso se tivéssemos feito uma conversão explícita.


        byte d = (byte) (b + c);
        

Entretanto, se quisermos atribuir um valor do tipo double a um tipo long, estaremos tentando uma conversão por compactação e o compilador terá consciência disso.

Uma conversão forçada, chamada de casting ou downcasting, que não tenha êxito não produz erro de compilação e nem de execução, mas será visto um resultado muito estranho caso o valor esteja fora do intervalo capaz de contê-lo.

A transformação “valor maior em container menor”, mais conhecida compactação, requer uma conversão explícita. Vamos analisar alguns exemplos.


        package devmedia;
        public class Main{
        public static void main (String args [ ])
        {
        System.out.println((byte)200);
        System.out.println((short)120000);
        System.out.println(1000000*10000*10);
        System.out.println(1000000*10000*10L);
        System.out.println(1000000L*10000*10);
        }
        }
        

Saída:
-56
-11072
1215752192
14100654080
100000000000

As variáveis locais são, algumas vezes, denominadas de variáveis de pilha, automáticas, temporárias ou de método, mas as regras impostas a elas serão as mesmas, independentemente do nome usado.

Embora seja possível deixar uma variável local não inicializada, o compilador irá reclamar caso tente utilizar uma variável local antes de inicializá-la com algum valor. Tipos booleanos são inicializados com falso, tipos numéricos são inicializados com zero, atributos char são inicializados com espaço em branco e tipos objetos com nulo. Arrays são objetos, mas se o array for inicializado, o que ocorrerá com os seus elementos? Os elementos do array receberão seus valores-default.

Devido ao problema do compilador não poder informar com certeza em algumas ocasiões, você terá que inicializar sua variável fora do bloco condicional apenas para satisfazer o compilador. Veja o exemplo:


        import java.awt.Dimension;
        public class Referencia{
        public static void main (String args[ ])
        {
        Dimension d = new Dimension (7,15);
        System.out.println(“Altura - “ + d.height);
        Dimension a = d;
        a.height = 45;
        System.out.println(“Altura após alteração - “ + d.height) ;
        }
        }
        

No exemplo acima temos 2 variáveis de referência que possuem o mesmo endereço, ou seja, as 2 variáveis de referência apontam para o mesmo objeto.

Passando variáveis para métodos

Os métodos podem ser declarados para utilizar primitivos ou referências. É necessário saber como a variável do chamador poderá ser afetada pelo método que foi chamado.

O mais primordial que você precisa estar ciente é que nem mesmo estará passando a variável de referencia real, mas sim uma cópia dessa variável. Resumindo, após a passagem, tanto o método chamado quanto o método chamador terão cópias idênticas da referência e, sendo assim, referenciarão exatamente o mesmo objeto, e não uma cópia, em memória.


        package devmedia;
        class Funcionario{
        String name = “Java”;
        public String toString(){
        return name;
        }
        }
        public class PassagemReferencia{
        static void alteraFuncionario(Funcionario f){
        f.name = “Davi”;
        }
        public static void main (String args [ ]){
        Funcionario a = new Funcionario();
        System.out.println(a);
        alteraFuncionario(a);
        System.out.prinln(a);
        }
        }
        

Saída:
Java
Davi

Sombreamento de variáveis

Existe a possibilidade de sombrear uma variável de vários modos; analisaremos somente a que provavelmente o embananará: ocultar um atributo sombreando-o com uma variável local.

O sombreamento está envolvido com redeclarar uma variável. O efeito do sombreamento é a ocultação da variável declarada anteriormente. São duas variáveis de diferentes escopos e que possuem o mesmo nome. Vamos ao próximo exemplo:


        package devmedia;
        public class TesteSombreamento {
        static int size = 9;
        static void changet(int size)
        {
        size = size + 100;
        System.out.println(“O valor de size é: “ + size);
        }
        public static void main (String [ ] args)
        {
        System.out.println(“Size: ” + size);
        changet(size);
        System.out.println(“Size após do método change: “ + size);
        }
        }
        

Saída:
Size: 9
O valor de size é: 109
Size após método change: 9

Vamos a um exemplo de sombreamento de variáveis, porém envolvendo referências.


        package devmedia;
        class Caderno {
        int liv = 11;
        }
        public class Pagina {
        Caderno c = new Caderno();
        void changelt(Caderno caderno)
        {
        caderno.liv = 88;
        System.out.println("Todas as referências, com o mesmo nome, apontam para o mesmo lugar: " + caderno.liv);
        caderno = new Caderno();
        caderno.liv = 55;
        System.out.println("Neste momento a referência caderno aponta para " + caderno.liv);
        }
        public static void main (String args[ ])
        {
        Pagina p = new Pagina();
        System.out.println("Primeiro valor de p.c.liv: " + p.c.liv);
        p.changelt(p.c);
        System.out.println("Esta referência não foi alterada e continua apontando para o mesmo lugar: " + p.c.liv);
        }
        }
        

Saída do console:
Primeiro valor de p.c.liv: 11
Todas as referências, com o mesmo nome, apontam para o mesmo lugar: 88
Neste momento a referência caderno aponta para 55
Esta referência não foi alterada e continua apontando para o mesmo lugar: 88

Continuaremos na próxima parte do artigo falando de Arrays e blocos de inicialização.


Leia todos artigos da série