Depois de muito tempo sem postar nada irei hoje dar continuidade ao post anterior (Certificação SCJP – Orientação a Objetos), irei abordar aqui a terceira parte do estudo sobre a certificação – Atribuições – Parte 1.
Objetivos abordados:
1.3 (Já Abordado) Desenvolver código que declare, inicialize, e use primitivos, arrays, enuns, e objetos como static, instância e variáveis locais. Também, usar identificadores válidos para nomes de variáveis;
7.6 Escrever código que aplique corretamente os operadores apropriados incluindo operadores de atribuições (limitado a: =, +=, -=, *= e /=)
Visão Global de Stack e Heap
Entender o básico sobre stack e heap tornará muito mais fácil a compreensão de assuntos como passagem de argumentos, polimorfismo, threads, exceptions, e garbage collection.
A maior parte dos pedaços dos programas como métodos, variáveis e objetos vivem em um dos dois lugares na memória: stack ou heap.
Um heap é uma estrutura de dados organizada como árvore binária, seguindo algumas regras.
Um stack ou pilha é uma estrutura de dados baseado no princípio Last in First Out – LIFO (último a entrar, primeiro a sair)
- Variáveis de Instância e objetos se localizam na heap
- Variáveis locais se localizam na stack
package scjpatribuições.exemplo1;
class Raca{
}
public class Animal {
Raca r; //Variável de Instância
String nome; //Variável de Instância
public static void main(String args[]){
Animal a; //Variável local 'a'
a = new Animal();
a.raca(a);
}
public void raca(Animal animal){ //Variável local 'animal'
r = new Raca();
animal.setNome("Girafa");
}
public void setNome(String animalNome){ //Variável local 'animalNome'
this.nome = animalNome;
}
}
Observe o que acontece na stack e heap quando o programa acima é executado:
- Linha 9 – main() é colocado na stack (pilha);
- Linha 10 – a variável de referência ‘a’ é criada na stack porém não há nenhum objeto ainda;
- Linha 12 – é criado o objeto Animal e associado para a variável ‘a’;
- Linha 13 – Uma copia da variável de referência ‘a’ é passado para o método ‘raca’;
- Linha 15 – raca() é colocado na stack com o parâmetro ‘animal’ como variável local (stack);
- Linha 16 – é criado o objeto Raca na heap, e associado à variável de instância da classe Animal;
- Linha 17 – setNome() é adicionado na stack com o parâmetro ‘animalNome’ como variável local (stack);
- Linha 21 – a variável de instância ‘nome’ referência para o objeto String;
- Podemos notar que duas variáveis locais diferentes referenciam o mesmo objeto Animal (na heap): ‘a’ e ‘animal’;
- Podemos notar também que uma variável local (‘animalNome’) e uma variável de instância (‘nome’) referenciam a mesma string “Girafa” na heap;
- Após a linha 21 completa, setNome é removido da stack com isso a variável local ‘animalNome’ desaparece também apesar de o objeto ainda está sendo referenciado na heap. E o mesmo acontece para os outros métodos e variáveis.
Literais, atribuições e variáveis (Objetivos 1.3 e 7.6)
Um literal primitivo é uma representação de código dos valores de um tipo primitivo, sendo eles: inteiros (10), pontos flutuantes (25.4), booleanos (true), caracteres (‘a’).
- Inteiro: Os números inteiros podem ser representados de 3 maneiras: decimal ( base 10 ), octal ( base 8 ) e hexadecimal ( base 16 )
- Inteiro Decimal: São os inteiros na forma como o conhecemos e em Java são representados sem utilizar qualquer prefixo ou tipo, como: int size = 20;
- Inteiro Octal: Os inteiros octais usam apenas os dígitos de 0 a 7. Para representar em Java um valor Octal basta por um ’0′ em frente ao numero, como abaixo:
package scjpatribuições.exemplo2; public class octal { public static void main(String args[]){ int seis = 06; int sete = 07; int oito = 010; int nove = 011; System.err.println("Octal 010 = "+oito); System.err.println("Octal 08 not exists = "+08); //Causará um erro dizendo que o valor é muito grande } } - Inteiro Hexadecimal: Os inteiros hexadecimais usam 16 símbolos distintos de 0 a 15. A partir do valor 10 ao 15 usamos caracteres alfabéticos desconsiderando a regra do Case-sensitive: 0 1 2 3 4 5 6 7 8 9 a b c d e f. Vale ressaltar que após o prefixo 0x pode-se apenas incluir 16 dígitos
package scjpatribuições.exemplo3; public class hexadecimal { public static void main(String args[]){ int um = 0X0001; int um_tambem = 0x0001; long nove = 0x9L; // Podemos utilizar Long adicionando no valor o sufixo 'L' int dez = 0xa; int onze = 0xb; System.err.println("hexadecimal = "+dez); } } - Pontos flutuantes: Composto por um numero decimal e frações. Para declarar podemos utilizar double (64 bits) ou float (32 bits):
double a = 32.221D; double a1 = 32.221; float b = 11.475F; float b1 = 11.475;
- Booleanos: podem apenas ser definidos com true ou false;
- Caracteres: são representados utilizando o tipo char e entre aspas simples. Podemos utilizar valores hexadecimais colocando no inicio \u:
char a = 'a'; char var = '@'; char n = '\u004E'; char var1 = 0x892; char var2 = 982; char var3 = -98; char var4 = '\n'; //nova linha char var5 = '\"' //aspas duplas
Operadores de atribuições
No que se diz respeito a atribuições, é importante que você saiba que para os tipos primitivos os bits representam um valor numérico. Então para atribuir um valor basta você utilizar o operador = e o valor à direita será associado à variável a direita. Se você atribuir o valor 6 a uma variável o padrão de bits que estará armazenado no deposito é igual a 00000110.
Só que ai surge uma pergunta, e o que acontece quando não utilizamos variáveis primitivas como uma String, JButton, JTextField?
String s = new String("string");
JButton b = new JButton();
JTextField tf = new JTextField();
Em ‘s’, ‘b’ e ‘tf’ apenas estará armazenado uma variável referenciando o objeto correspondente, ou seja, uma variável de referência. Ai você se pergunta, no caso da String num seria “string”? Não pois não sabemos como o construtor desse objeto está trabalhando e não precisamos saber, pois a maneira como essas referencias a objetos são armazenadas é específicas de máquinas virtuais. Apenas saiba que o valor dessas variáveis não é o objeto. Então agora trabalharemos com as variáveis primitivas.
Atribuições primitivas
Existe atualmente 12 operadores de atribuição mas apenas 5 caem no exame. Legal não é? Você pode atribuir valor a uma variável primitiva utilizando um valor literal ou uma expressão.
int x = 8; int y = 1 + x; double z = x / y;
É importante saber que o literal inteiro como o 8 é implicitamente inteiro. Duvida? e se eu fizer isso:
byte b = 30;
é possivel? é sim. Quando fazemos isso o compilador automaticamente converte o valor literal para byte. Ou seja o compilador aplica um cast
byte b = (byte) 30;
isso se aplica para char e short pois atribuímos um valor menor do que um tipo int. Ai vai uma pergunta, podemos fazer a soma de bytes? sim e não! Quando tentarmos atribuir a um byte a soma de bytes o seu compilador irá acusar de que há a possibilidade de perder a precisão. Porém existe uma forma de ser fazer isso, veja:
byte a = 8; byte b = 3; byte d = b+a; // Erro! possible loss of precision. byte e = (byte) b+a; // Ainda ocorre a possibilidade de se perder a precisão porém nós forçamos.
Conversão (Casting) de primitivos
Nós fizemos uma conversão explicita no código anterior. A conversão implícita ocorre quando fazemos uma transformação que envolva ampliação, ou seja, quando inserimos um item menor do que o container.
int a = 10; long b = a;
Atribuindo Números de Pontos flutuantes
Pontos flutuantes tem um comportamento parecido com os tipos Inteiros, porém com algumas modificações. É importante saber que todo literal de pontos flutuantes é implicitamente um double (64 bits) e não um float. Isso quer dizer que se você tentar atribuir um literal double a um float (64 em 32 bits) ele acusará erro. Para fazer isso é necessário fazer o casting forçado, veja como:
float f = 32.3; //Erro: cannot convert from double to float float f = 32.3F; //pode ser feito dessa forma, float f = 32.3f; // ou dessa, float f = (float)32.3; // ou dessa.
Atribuindo valores inválidos para uma variável
Um exemplo de atribuição inválida ocorre quando tentarmos fazer isto:
byte a = 128; // byte pode apenas armazenar 127
Podemos corrigir isso adicionando o cast
byte a = (byte) 128; int b = (int) 32.3;
o que acontece então? quando fazemos o cast forçado algumas informações podem ser perdidas e é exatamente isso que acontece. Ficando o valor 32 na variável ‘b’ e -128 na variável ‘a’. Para entender porque isso acontece com byte é recomendado que estude um pouco mais sobre bytes =D. O que vale dizer aqui é que podemos forçar o cast de alguns literais, porém podemos perder algumas informações. Outra coisa importante podemos vê agora, você se lembra quando tentamos fazer a soma de bytes sem o cast?
byte a = 7; a = a+3; // Erro a = (byte) (a+3); // Válido
Podemos fazer isso dessa forma também:
byte a = 7; a += 3; // Válido
Quando utilizamos o operador +=, -=, *= ou /= automaticamente é adicionado um cast explicito. Não esqueça, no exame isso pode ser uma pegadinha! =D
Atribuindo uma variável primitiva para outra
Nada muito complicado acontece quando atribuímos a uma variável um valor de outra. Observe:
int a = 10; int b = a;
Quando fazemos isso dizemos que o valor de ‘b’ [seqüência de bits] é o mesmo valor de ‘a’, ou seja, eles não referenciam o mesmo valor na heap e sim possuem o mesmo valor (copia) em lugares diferente na heap. Se alterarmos o valor de ‘b’ isso não afetará em nada no valor de ‘a’. Just this.
Atribuindo variáveis de referência
Podemos atribuir a uma variável de referencia um objeto criado recentemente da seguinte forma:
String a = new String();
Como foi explicado antes (stack e heap) 1º é criado a variável de referência ‘a’ de tipo String , 2º é criado um novo objeto String na heap e 3º atribui a variável a ao objeto String criado recentemente na heap. Podemos também atribuir null indicando que a variável não referencia nenhum objeto.
Escopos de Variáveis
Observe o código:
class exemplo {
static int e = 20; // Variável statica
int x; // Variável de Referência
{ x = 7; int x2 = 5;} // Bloco de inicialização
exemplo() { x+= 8; int x3 = 6;} // Construtor
void execute(){
int y = 0;
for (int z = 0; z < 4; z++){
y+=z+x;
}
}
}
Não me pergunte qual significado desse código que nem eu sei, o que precisamos entender é que podemos destacar algumas variáveis (e, x, x2, x3, y e z) e todas elas possuem escopos:
- ‘s’ é static, ou seja, possui um longo escopo; elas são criadas quando a classe é carregada, e elas sobrevivem enquanto a classe permanecer carregada na JVM;
- ‘x’ é uma variável de instância, ou seja, elas são criadas quando uma nova instancia é criada e elas permanecem ainda que a instância seja removida;
- ‘y’ é uma variável local, ou seja, elas vivem ao longo da execução de um método na stack, contudo podem continuar vivas ainda fora de scopo;
- ‘z’ é uma variável de bloco, ou seja, permanece somente enquanto o bloco é executado;
- x2 é uma variável de bloco de inicialização e x3 de construtor, parecidas com variáveis locais.
se você tentar executar o código abaixo provavelmente ocorrerá um erro pois o método main é static, ou seja, foi criado quando a classe foi carregada. Isso significa que a variável de instância x ainda não foi criada, então não pode ser acessada.
public class exemplo{
int x = 10;
public static void main(String args[]){
x++; //Não compila
}
}
se você tentar executar o código abaixo provavelmente ocorrerá um erro pois a variável x foi criada localmente e não pode ser acessada por outr método ainda que esteja trabalhando. Look:
public class exemplo{
public static void main(String args[]){
exemplo e = new exemplo();
e.execute();
}
void execute(){
int x = 1;
incrementa();
System.out.println("Value: "+x);
}
void incrementa(){
x++; //Ainda está 'viva', porém fora de escopo
}
}
Usando Variável ou Array não inicializadas ou atribuidas
Em java nós temos a possibilidade de inicializar ou não uma variável, porém quando tentarmos acessar uma variável não inicializada podemos ter um comportamento diferente do esperado. Esse comportamente depende também do tipo (primitivas ou objetos) ou do escopo dessa variável.
- Primitivos e Objetos como Variáveis de Instância: Variáveis de instância (variáveis membro – class level) são inicializadas com o seu valor default toda vez que uma instância é criada da classe. Observe:
Tipo Valor padrão Objetos de referência null byte, short, int e long 0 float e double 0.0 boolean false char ‘\u0000′ - Arrays como Variáveis de Instância: Um array é um objeto, logo o seu valor default é null. Mas e se for inicializado? o que acontece com o que contem neste array? Se este array for do tipo de Objeto de referência ele recebe o valor padrão dos objetos de referências (null). Se for um tipo primitivo acontece o mesmo processo.
public class exemplo{ static int [] var = new int[100]; public static void main(String args[]){ for (int i = 0; i < 100; i++){ System.out.println("Var["+i+"]: "+var[i]); } } }… quando rodar este programa ele irá imprimir os 100 inteiros criados com seus valores default (0).
- Primitivos e Objetos como Variáveis Locais: Variáveis locais (primitivas ou objetos) devem sempre ser inicializadas (fora de qualquer condicional ou loop) antes de serem usadas, incluindo arrays, caso contrário ocorrerá um erro de compilação. Observe:
public static void main(String args[]){ String s; System.out.println(s); //Erro de compilação if (s == null){ // Mesmo se fizermos isso ocorre erro de compilação nessa linha System.out.println("String s is null"); } }… o certo seria:
public static void main(String args[]){ String s = new String(); // ou String s = null; // ou String s = ""; System.out.println(s); //Funciona Corretamente if (s == null){ // Funciona Corretamente. System.out.println("String s is null"); } } - Atribuições entre variáveis de referência: Ocorre da mesma forma como nas variáveis locais, é copiado o padrão de bits a ela associada na outra. Porém quando falamos de ‘copiar’ dizemos que duas variáveis de referências apontam para o mesmo padrão de bits, esse padrão de bits referencia a um objeto específico na heap. Observe:
public static void main(String[] args) { Dimension a = new Dimension(10,30); System.err.println("Valor de a.height = "+a.height); Dimension b = a; b.height = 20; System.err.println("Valor de a.height após alterar b = "+a.height); }… nesse caso as variáveis ‘a’ e ‘b’ contém valores idênticos pois ambos referência o mesmo objeto Dimession na heap. Agora tente fazer isso com o objeto String. Funciona? não… pois a String possui um tratamento diferente (excessão). Isso acontece porque toda vez que você atribui um valor a uma String um novo Objeto String é criado na heap.
Por enquanto ficarei por aqui e até a segunda parte de Atribuições.
Vlw.







