[OCPJP6] 022 – Atribuições

Passar variáveis para métodos

Em Java quando você passa uma variável para um método, você estará passando uma cópia dos bits que representam esta variável (pass-by-value). Veja os diferentes comportamentos em variáveis primitivas e de referência (objeto).

Variáveis primitivas

Neste caso o que acontece dentro do escopo (método) “morre” lá, ou seja as alterações feitas sobre a variável primitiva serão perdidas. Veja o exemplo abaixo:

package certificacao;

public class Primitivo {
    private static int order = 0;
    public static void main(String[] args) {
        Primitivo p = new Primitivo();
        int w = 10;
        p.somar(w);
        System.out.println(
            (++order) + " Depois - somar(w): " + w);
    }
    public void somar(int w) {
        w = w + 10;
        System.out.println(
            (++order) + " Dentro - somar(w): " + w);
    }
}

Qual sera o resultado?

1 Dentro - somar(w): 20
2 Depois - somar(w): 10

Repare que o parâmetro “w” foi alterado apenas dentro do escopo (método). Exatamente o comportamento esperado – uma copia dos bits sem alterar a variável “w” criada no main.

Variáveis de referência (objeto)

Já no caso das variáveis de referencia (objeto), os valores do objeto podem ser alterados de acordo com o nível de acesso. Veja os exemplos:

public class ReferenciaMain {
    public static void main(String[] args) {
        ReferenciaObjeto obj = new ReferenciaObjeto();
        somar(obj, 100);
        System.out.println(
            "Depois - somar(obj, 100): " + obj.a);
        alterar(obj);
        System.out.println(
            "Depois - alterar(obj): " + obj.a);
    }
    static void somar(ReferenciaObjeto referencia, int param) {
        referencia.a = referencia.a + param;
    }
    static void alterar(ReferenciaObjeto referencia) {
        referencia.a = 555;
        referencia = new ReferenciaObjeto();
        referencia.a = 2222;
    }
}
class ReferenciaObjeto { int a = 1000; }

E qual seria o primeiro resultado?

1100 é a resposta correta já que um dos valores do objeto foi alterado.

E o segundo?

Essa é uma questão que confunde. A resposta correta é 555, porque o valor da variável foi alterada e em seguida a variável que era referenciada foi apontada para um novo objeto (new). Lembre que na passagem de parâmetro o objeto referenciado cria um link para o objeto e o perdera em uma nova instancia.

Um adicional (sem passagem de parâmetros)

public class Primitivo {
    public static void main(String[] args) {
        int a = 10;
        int b = a;
        a = 20;
        System.out.println("b depois de 'referenciar' a: " + b);
    }
}

E agora? b = 20 ?

No caso acima também será feito uma copia e o valor de “b” resultara em 10. Variáveis primitivas sempre serão copiadas e não referenciadas.

Comentem, estendam o assunto… Compartilhem =)

Anúncios

[OCPJP6] 021 – Atribuições

Cast de tipos primitivos

No post anterior vimos um exemplo de cast com tipo primitivo, vamos complementar o assunto. Veja algumas regras abaixo:

  • Toda soma de número inteiros resulta em int (como vimos no post anterior)
  • Todo número inteiro é implicitamente int. Tome cuidado com o tamanho, talvez seja necessário converter para um tipo maior – por exemplo long
  • Todo número flutuante é implicitamente um double
  • Não precisa de cast quando ocorre ampliação
  • É necessário cast quando houver perda de precisão

Exemplos

Cast para um tipo menor que float (float > int)

float f1 = 128.987f;
int i1 = (int) f1; // PERDA DE PRECISAO
// s1 = 128

Cast para um tipo menor que float (float > short)

float f1 = 128.987f;
short s1 = (short) f1; // PERDA DE PRECISAO
// s1 = 128

Cast para um tipo menor que float (float > byte)

float f1 = 128.987f;
byte b1 = (byte) f1;
// E agora? 128?

Um ponto de atenção no exemplo acima, byte vai de -128 até 127! Qual seria o resultado?

-128 será a resposta, porque a JVM irá remover os primeiros (esquerda) 24 bits, dando o resultado -128.

// Compare os resultados
Integer.toBinaryString((byte)128);
Integer.toBinaryString(-128);

Cast para um tipo maior que float (float > double)

float f1 = 128.545f;
// 32 bits cabe dentro de 64 (cast implicito)
double d1 = f1;
// d1 = 128.98699...
}

Adicional (pegadinhas de cast)

byte b1 = 128; // NÃO COMPILA, certo?

byte b2 = 3;
b2 += 7; // COMPILA :O

byte b3 = 3;
b3 = b3 + 7; // NÃO COMPILA (ahhhh)
b3 = (byte) (b3 + 7); // AGORA COMPILA

No exemplo acima b2 += inclui o cast implicito =D

Comentem, estendam o assunto… Compartilhem =)

[OCPJP6] 020 – Atribuições

Tipos primitivos

Veja a tabela abaixo:

Tipo Tamanho
byte 8 bit
short 16 bit
int 32 bit
long 64 bit
float 32 bit
double 64 bit
boolean
char 16 bit

 Todas expressões com tipos primitivos menor que int (char, byte, short) resultam em int.

Vamos entender melhor em exemplos:

byte

byte a = 1;
byte b = 2;
byte c = a + b; // NAO COMPILA!

short

short a = 1;
short b = 1;
short c = a + b + 1; // NAO COMPILA!
Por que o código acima não compila?

Como destacado anteriormente “toda expressão” (soma, subtração…) com tipos menores que int resultam em int, ou seja, no exemplo de com byte (a + b)  o resultado será um int e consequentemente no exemplo de short a mesma coisa.

E como fazer funcionar?

Transformando c em um int 🙂

byte a = 1;
byte b = 2;
int c = a + b; // =D
Mas e se mesmo assim eu quiser que c seja um byte ou short?

Cast resolve 🙂

byte a = 1;
byte b = 2;
byte c = (byte) (a + b); // =D

Comentem, estendam o assunto… Compartilhem =)

[OCPJP6] 019 – Atribuições

literais

Literal é uma sequência de caracteres (dígitos, letras, e outros caracteres) que representa um valor constante que referência uma variável. Neste capítulo vamos ver como os literais se comportam com os tipos primitivos.

literais do tipo inteiro

Os literais de tipo inteiro (dígitos) podem ser divididos em 3 categorias: decimal (base 10), octal (base 8) e hexadecimal (base 16).

Decimal

Um inteiro do tipo decimal não tem muitos segredos, é a representação “tradicional”.

int length = 123;

Octal

Um inteiro do tipo octal usa somente dígitos de 0 a 7 e com até 21 dígitos não incluindo o zero inicial. Veja no exemplo abaixo que quando acaba o dígito 7 iniciamos novamente com 01.

package certificacao;
public class ExemploOctal {
    private static int um = 01;
    private static int dois = 02;
    private static int tres = 03;
    private static int quatro = 04;
    private static int cinco = 05;
    private static int seis = 06;
    private static int sete = 07;
    private static int oito = 010;
    private static int nove = 011;
    private static int dez = 012;

    public static void main(String[] args) {
        System.out.println(um); System.out.println(dois);
        System.out.println(tres); System.out.println(quatro);
        System.out.println(cinco); System.out.println(seis);
        System.out.println(sete); System.out.println(oito);
        System.out.println(nove); System.out.println(dez);
    }
}

Hexadecimal

Os números hexadecimais são construídos com o uso de 16 símbolos distintos:

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

Veja alguns exemplos válidos:

int x = 0X0001;
int y = 0x7fffffff;
int z = 0xCafe; // =D

literais do tipo flutuante

São definidos com números e símbolos. É importante notar que double tem 64 bits e float tem 32 bits, mais precisamente, quando quiser dizer que um literal flutuante é um float é necessário indicar com f ou F. Vejam alguns exemplos abaixo:

// por padrao um numero com casas decimais e' double
// o double tambem pode indicar d ou D
double d1 = 123456789.987654321;

float f1 = 123.4567890; // nao compila (necessario declarar f/F)
float f2 = 123.4567890f; // agora sim

literais do tipo boolean

Só podem ser true ou false. Simples assim.

literais do tipo caractere

São representados por um único caractere entre aspas simples, ou pelo valor unicode do caractere. Importante notar que os caracteres são inteiros de 16 bits sem sinal. (0 a 65535). Veja os exemplos abaixo:

char a = 0x892;
char b = 982;
char c = (char) 70000; // com cast funciona na compilacao
char d = (char) -98; // so' com cast mesmo

Comentem, estendam o assunto… Compartilhem =)

[OCPJP6] 018 – Orientação a objetos

static

Dando continuidade ao post [011], métodos e variáveis static podem ser acessados a partir de uma classes não instanciada (new). Mas isso não impede que métodos ou variáveis static seja acessado por uma instância. Resumindo, métodos e variáveis static são compartilhados sem a necessidade de instância da sua classe de declaração.

Pontos importantes

  • Não existe sobrescrita de método static.
  • Dentro de um método static não é possível acessar métodos e variáveis não static.
  • Tanto métodos como variáveis static não podem ser acessados pela internamente como “this.”, se uma classe Automovel tiver uma variável tipo static, internamente ela poderia ser acessada Automovel.tipo e não this.tipo. Não compilaria porque this represeta a instância da classe.
  • Variáveis não static não podem receber (=) variáveis static.

Exemplos

package certificacao;
public class Automovel {
    public static void ligar() {
        System.out.println("Automovel..."); } }

public class Carro extends Automovel {
    public static void ligar() {
        System.out.println("Carro..."); } }

public class Main {
    public static void main(String[] args) {
        Automovel a = new Carro();
        a.ligar();
    }
}

Analisando o código acima, qual seria o resultado esperado?

  • Automovel…
  • Carro…

A resposta correta é “Automovel…” porque o método ligar() não referência uma instância. Se o método não fosse static, teriamos um exemplo de polimorfismo e obteríamos o resultado “Carro…”. A melhor forma de acessar o método ligar() sem ter dúvidas é: Automovel.ligar() ou Carro.ligar().

package certificacao;
public class Main {
    public static int count = 0;
    public Main() { count += 1; }

    public static void main(String[] args) {
        Main m = new Main(); // 1a instancia
        m = new Main(); // 2a instancia
        m = new Main(); // 3a instancia
        System.out.println(Main.count);
    }
}

Analisando o código acima, qual seria o resultado esperado?

O resultado será 3, porque a variável static count está compartilhada para qualquer instância.

Comentem, estendam o assunto… Compartilhem =)

[OCPJP6] 017 – Orientação a objetos

Cast

Significa conversão. Mais especificamente em Java, conversão de variável. O cast pode ocorrer em variáveis de objetos e primitivos, para este capítulo vamos ver apenas o cast em objetos. Dividido em downcast e upcast.

downcast

Em downcast a conversão da variável de referência é para baixo na herança, ou seja o cast da variável de referência pode ser declarada apenas para classes específicas, caso contrário não irá compilar. Uma das exceções mais conhecidas ClassCastException (indica que a conversão em tempo de execução está errada) pode acontecer no downcast. Vamos ver alguns exemplos:

package certificacao;
public class Homem { public void correr(){} }
public class Menino extends Homem { public void brincar(){} }

No exemplo abaixo irá ocorrer um ClassCastException em tempo de execução, porque a instância da variável de referência “homem” é Homem e não Menino. O compilador aceita o cast (downcast) porque Menino é um Homem, mas em execução “homem” não é um Menino e então ocorre o erro.

// ...
Homem homem = new Homem();
Menino menino = (Menino) homem;

No próximo exemplo ocorrerá um erro de compilação porque não existe uma herança (generalização -> especialização).

// ...
Animal animal = new Animal();
Menino menino = (Menino) animal;

upcast

Bem mais simples, o upcast é o contrário do downcast e acontece implicitamente. Vamos ver alguns exemplos:

// ...
Menino menino = new Menino();
Homem homem = menino;

Acessando métodos

Os métodos visíveis das variáveis são referentes ao tipo. Masssss nada impede de acessar um método de uma especialização. Vamos ver alguns exemplos:

// ...
Menino menino = new Menino();
Homem homem = menino;
homem.correr(); // visível
homem.brincar(); // NAO COMPILA
((Menino)homem).brincar(); // AGORA SIM =D

Comentem, estendam o assunto… Compartilhem =)

[OCPJP6] 016 – Orientação a objetos

Sobrecarga de construtores

Como vimos anteriormente o grande propósito de um construtor é instanciar uma classe, e indo além podemos definir contratos para instanciar uma determinada classe, ou seja, definir atributos obrigatórios e/ou padrões.

Por exemplo, uma classe Quadrado(int lado) define que toda instância de Quadrado obrigatoriamente deve atribuir um valor “lado”.

Legal né… E se eu quisesse oferecer a possibilidade de instanciar um Quadrado com lados fracionários? Quadrado(double lado)

package certificacao;
public class Quadrado {
    double lado;
    public Quadrado(int l) { this.lado = l; }
    public Quadrado(double l) { this.lado = l; }
}

E se houvesse uma herança (generalização -> especialização)? Até onde poderiamos ir? 😛

  • O construtor mais específico irá ser chamado
  • Se houver ambiguidade não irá compilar
package certificacao;
public class Pessoa {
    public Pessoa() { System.out.println("()"); }
    public Pessoa(Object param1) { System.out.println("(Object)"); }
    // Lembre-se: String é uma especialização de Object
    public Pessoa(String param1) { System.out.println("(String)"); }
}

– E se eu fizesse: new Pessoa(null); o que aconteceria?

Se você respondeu: (String) você acertou, porque String é a classe mais específica entre Object e String 😉

 – E seu ao invés de Pessoa(Object param1) eu tivesse Pessoa(Integer param1) o que aconteceria?

new Pessoa(null); não compilaria, porque o compilador não saberia o que fazer, já que Integer e String são ambíguos a null. Para funcionar seria necessário um cast new Pessoa((String)null);. Veremos mais sobre cast em breve.

Comentem, estendam o assunto… Compartilhem =)