Atribuição a Variáveis de Referência


Uma variável que se refere a um objeto é uma variável de referência. Um recipiente que guarda essa variável contém apenas a forma de chegar ao objeto, ou seja, contém um endereço de memória que representa um objeto específico no heap, ou ainda o valor null, pois se uma variável de referência não receber nenhum valor ou receber explicitamente o valor null, o recipiente irá guardar a representação de bits de null.
É possível atribuir um objeto recém-criado a uma variável de referência. Nesse caso, ocorrem três coisas: cria-se uma variável de referência do tipo da variável; cria-se um novo objeto desse tipo no heap; e atribui-se esse objeto à variável de referência.
A variável de referência também pode receber o valor null, o que significa que essa variável não se refere a um objeto, ou seja, apenas foi criado um container que irá receber o endereço de memória de um objeto.

class Pessoa { }

class Aluno extends Pessoa { }

public class Atribuicao_Objetos {
     
      public static void main(String[] args) {
            /*Aluno é subclasse de Pessoa, ou
             *seja, tudo que Aluno tem, Pessoa
             *também tem */
            Pessoa pessoa = new Aluno();
            /*Erro - Pessoa é superclasse de
             *Aluno, ou seja, existem coisas
             *que Aluno tem e Pessoa não tem */
            Aluno aluno = new Pessoa();
      }

}


Atribuindo uma variável de referência (objeto) a outra

A atribuição de um objeto a outro funciona da mesma maneira que a atribuição de variáveis primitivas, ou seja, é feita uma cópia do conteúdo de uma variável para a outra. No caso de variáveis de referência, o conteúdo é o endereço de memória para acessar o objeto a que se refere.
Isso quer dizer que as duas variáveis estarão acessando o mesmo objeto na memória (heap), já que as duas estarão guardando o mesmo conteúdo (endereço). Por causa disso, se algo for modificado em um objeto, automaticamente o outro também será, já que apontam para a mesma instância. Trata-se de duas variáveis apontando para o mesmo objeto.

class Retangulo {
      int ladoA;
      int ladoB
}

public class TestAtribuicao {
      //variável de instância
      Retangulo retangulo3 = new Retangulo();

      public static void main(String[] args) {
            //variáveis locais
            Retangulo retangulo = new Retangulo();
            Retangulo retangulo2 = new Retangulo();
            TestAtribuicao test = new TestAtribuicao();
           
            //um dos objetos recebem os valores
            retangulo.ladoA = 5;
            retangulo.ladoB = 10;
           
            /* retangulo2 e retangulo3 passam a 
             * apontar para o mesmo objeto que 
             * retangulo */
            retangulo = retangulo2;
            test.retangulo3 = retangulo;
           
            /* um valor é alterado em retangulo e,
             * como todas as tres variáveis apontam 
             * para o objeto, a alteração é vista
             * por todas as tres variáveis */
            retangulo.ladoA = 6;
            /* a regra independe de variável local ou
             * global */
            System.out.println(retangulo2.ladoA); //6
            System.out.println(test.retangulo3.ladoA);//6
           
      }    
}


A única exceção para essa regra é a classe String. Nesse caso, é possível mudar o valor de uma instância que não afetará a outra, pois esta classe é tratada de uma forma diferente.
A explicação para isso é que quando uma variável de referência do tipo String é modificada, uma nova String é criada (ou uma String correspondente é criada no pool da String, deixando a original intacta), e, a referência utilizada para modificá-la, é atribuída a esse novo objeto.
Em outras palavras, no caso da String, enquanto um objeto for atribuído a outro sem haver modificações eles estarão apontando para o mesmo lugar, mas quando houver alguma modificação em qualquer um dos dois será criado um novo objeto, não afetando o outro.


public class TestAtribuicaoString {
      public static void main(String[] args) {
            String valor = "teste";
            String valor2 = valor;
            //as duas continuam com o mesmo valor
            //imprime: teste teste
            System.out.println(valor + " " +  valor2);
            /* quando o valor de uma delas é alterado,
             * uma nova referência é criada, não
             * afetando a outra variável */
            valor = "teste2";
            //imprime: teste2 teste
            System.out.println(valor + " " +  valor2);
      }
}

Fonte: SCJP Sun Certified Programmer for Java 6 Study Guide

Atribuição a Variáveis Primitivas


Variáveis são recipientes de bits com um designado tipo que representam um valor.
Para variáveis primitivas, os bits representam valores numéricos utilizando um padrão (00000110).
O sinal = é usado para atribuir um valor a uma variável. Atribui-se um valor a uma variável primitiva utilizando uma literal ou o resultado de uma expressão.
Uma literal inteira é sempre int, por isso, para atribuir qualquer literal inteira a um tipo que não seja int (short, byte) o compilador faz o cast automaticamente. Por esse motivo, também todas as operações realizadas com literais numéricas resultam em int, ou seja, se uma operação entre literais numéricos for atribuída a um tipo diferente de int, será necessário fazer cast explicitamente.

public class Atribuicao_Primitivos {    
      //atribuição de uma literal
      int a = 7;
      //atribuição de literal c/ expressão
      int b = a + 2;
      int c = a * b;
     
      //compilador faz cast de int p/ byte
      byte d = 27;
      //pode usar cast explícito
      byte e = (byte) 27;
      /*erro, o resultado de qualquer expressão
       * com literais inteiras sempre resulta
       * em um int, por isso é necessário cast */
      byte f = d + e;
      byte g = (byte) (d + e);
}

Casting Primitivo

Casting permite converter valores primitivos de um tipo para outro tipo. Casts podem ser implícitos ou explícitos. Um cast implícito significa que não é necessário escrever o código para o cast; normalmente ocorre quando a conversão é para ampliação, ou seja, quando se quer colocar uma coisa pequena (byte) em um container maior (int).
Quando ocorre o contrário, ou seja, quando se quer colocar algo grande em um container menor, ocorre o cast explícito. Isso quer dizer que uma variável double, por exemplo, não pode ser atribuída a uma variável int sem que haja um cast explícito.
Quando uma variável maior é atribuída a uma variável menor, os bits que sobram na conversão, ou seja, que não cabem no container, simplesmente são descartados, alterando o resultado final.


public class Cast_Primitivos {    
      /* cast implícito - quando um tipo
       * menor é atribuído a um maior */
      int a = 100;
      long b = a;      
      /* cast explicito - quando um tipo maior é
       * atribuído a um menor; pode haver perda
       * de dados na conversão */
      float c = 100.001f;
      int d = (int)c;
}


Obs.: valores inteiros podem ser atribuídos a um double sem cast explícito, pois qualquer valor inteiro cabe em um container de 64 bits (double). O contrário só ocorre com cast explícito com perda de bits, alterando o resultado final.

Atribuindo Números de Ponto Flutuante

Toda literal de ponto flutuante é implicitamente um double (64 bits), não um float. Por isso, para uma variável float receber qualquer valor de ponto flutuante, é necessário cast explícito de double para float.


public class AtribuicaoPontoFlutuante { 
      /* erro - toda literal de ponto flutuante
       * é implicitamente double, por isso é
       * necessário cast explícito, já que
       * double é maior que float */
      float f = 32.3;
      float g = (float) 32.3;
      float h = 32.3f;
      float i = 32.3F;
}


Atribuindo uma Literal que é maior que a variável

Quando uma variável maior é atribuída a uma menor, gera um erro de compilação caso não seja feito um cast explícito.  Porém, conseqüentemente, o resultado será diferente após o cast, pois Java simplesmente desconsidera os bits de ordem superior que não couberem no container; em outras palavras, haverá perda dos bits da esquerda para a direita.
Existem ainda os operadores de atribuição compostos, que são +=, -=, *= e /=. Todos esses operadores são utilizados com cast implícito.


public class Literais_Grandes {   
      public static void main(String[] args) {
            //erro - um byte só pode guardar 127
            byte a = 128;
            /*é necessário cast eplícito, porém
             * haverá perda de bits, alterando
             * o resultado */      
            byte b = (byte) 128;
            System.out.println(b); //imp. -128     
            /*o resultado de uma expressão de
             * inteiros sempre resulta em um int,
             * por isso é necessário cast explícito*/
            byte var2 = 3;
            var2 = var2 + 7;
            var2 = (byte) (var2 + 7);
            /* porém, quando se utiliza operadores
             * compostos é possível usar o cast
             * implícito */
            byte var = 3;
            var += 7;
      }
}


No exemplo acima, a literal 128 é representada pela seqüência 10000000, porém, como literal é por default um inteiro, então é representado pela seqüência 00000000000000000000000010000000. Quando ocorre o cast, o compilador descarta 24 bits da esquerda para a direita, resultando na seqüência 10000000, que equivale ao byte -128 (o primeiro bit identifica o número como negativo ou positivo).

Atribuindo uma Variável Primitiva a outra Variável Primitiva

Quando uma variável primitiva é atribuída à outra, o valor de uma é copiado pra outra, sem que uma não possua nenhuma relação com a outra. Isso quer dizer que, se depois da atribuição o valor de qualquer uma das duas mudar, o da outra não será afetado, pois somente o conteúdo da variável (os bits que representam o valor) é copiado, e não a sua referência na memória.


public class Copia_Primitivos {   
      public static void main(String[] args) {
            int a = 10;
            System.out.println("A = " + a);
            int b = a;
            System.out.println("B = " + a);
            b = 30;
            System.out.println("Depois: A = " + a +
                                      " B = " + b);
      }
}



Fonte: SCJP Sun Certified Programmer for Java 6 Study Guide

Literais para Tipos Primitivos


Um literal primitivo é apenas uma representação de código-fonte dos tipos de dados primitivos, em outras palavras, um número inteiro, número de ponto flutuante, booleano que é digitado ao escrever código.

Literais Inteiros

Existem três formas de representar números inteiros em Java: decimal (base 10), octal (base 8) e hexadecimal (base 16).

Literais Decimais

Inteiros decimais são representados como realmente são.

Literais Octais

Inteiros octais usam somente os dígitos de 0 a 7. Em Java, um número na forma octal é representado pela colocação de um zero na frente do número. Quando chega ao número 7, deve-se voltar para o 0 e adicionar o número 1 na sua frente, e assim por diante.

Literais Hexadecimais

Números hexadecimais são construídos utilizando 16 símbolos diferentes. Como nunca foram inventados símbolos digitais únicos para números de 10 a 15, são utilizados caracteres alfabéticos para representar esses dígitos (a linguagem aceita letras maiúsculas ou minúsculas nesse caso, sem fazer diferença entre elas).
É possível ter até 16 dígitos em um número decimal, não incluindo o prefixo 0x.

Obs.: todas as três literais (octal, decimal e hexadecimal) são definidas como int por default, mas elas também podem ser atribuídas a uma variável long, colocando o sufixo L ou l depois do número.

Literais de Ponto Flutuante

Números de ponto flutuante são definidos por um número, um símbolo decimal, e mais números representando a fração.
Literais de ponto flutuante são definidos como double (65 bits) por default, então se for necessário assinar uma literal de ponto flutuante como float, deve-se colocar o sufixo F ou f no número.
É possível também colocar o sufixo D ou d para literais double, mas não é necessário porque este já é o default.

Literais Boolean

Um valor boolean somente pode ser definido como true ou false.

Literais Character

Um literal character é representado por um único caractere em aspas simples. Também pode receber o valor Unicode de um caractere.
Caracteres são apenas 16-bits, isso significa que qualquer literal numérica pode ser atribuída, desde que se encaixe nos 16 bits (65535 ou menos). Isso quer dizer que um char pode receber um literal decimal, octal ou hexadecimal, por exemplo, desde que caiba nos 16 bits; caso contrário, será necessário casting com perda de dados.

Valores Literais para Strings

Uma literal string é o valor de um objeto String. Uma string não é um tipo primitivo, mas pode ser representada como literal porque pode ser digitado diretamente no código.


public class Literais_Primitivos {
     
      //literal decimal
      byte num = 15;
     
      //literal octal
      int six = 06; //equivale ao decimal 6
     
      //literal hexadecimal
      int hex = 0x0001;
      long hex2 = 0xFFFFFFFL;
     
      //literais de ponto flutuante
      //erro - perda de precisão
      float f = 23.467890;
      //ok - sufixo F
      float g = 23.467890F;
      //sufixo D é opcional
      double d = 110599.995011D;
     
      //literais boolean
      boolean teste = false;
     
      //litetais character
      char ch = 'A';
      char ch2 = '\u004E'; //letra N
      char ch3 = 0x892; //hexadecimal literal
      char ch4 = 982; // int literal
      //necessário cast, pois o valor possui
      //mais que 16 bits - perda de precisão
      char ch5 = (char)70000;
     
      //literais string
      String nome = "Kamilla";
     
}



Fonte: SCJP Sun Certified Programmer for Java 6 Study Guide