Java com café: julho 2010

Threads - Java

Olá, tudo bem?

Hoje vou tratar de um assunto muito interessante: Threads. Mas antes quero comentar outra coisa: Tenho desenvolvido uma forma bem legal de estudar para a certificação e creio que possa se aplicar a outros campos também. Funciona da seguinte maneira: Você vai lendo até chegar numa parte que o assunto ficou cansativo, então pule para outro capítulo que ache interessante. Isso proporciona uma visão global do assunto quando voltar aos primeiros capítulos e te mantém interessado nos estudos.

Mas vamos ao que interessa. Pra começar, o que são threads???

Threads são linhas de execução de um processo. É a maneira de um processo se dividir em duas ou mais partes para que possam ser executadas "ao mesmo tempo". Nem sempre é ao mesmo tempo porque na maioria das vezes temos somente uma CPU disponível. Então alguém fica a cargo de dividir as fatias de tempo entre os concorrentes. Embora quase sempre, mesmo com apenas uma CPU, temos a impressão de que tudo está sendo feito ao mesmo tempo. Mas não está! A CPU está processando um pouco de cada coisa pra te dar essa impressão. Este assunto é bem vasto, mas vamos nos ater aos threads.
Um processo no contexto que estou falando seria o programa. Poderíamos desejar que o programa faça várias coisas ao mesmo tempo. Por exemplo, um browser: Quando o browser carrega uma página com muitas imagens, ele se conecta a vários links para baixar as imagens ao mesmo tempo. Ele não abre uma por uma, existe um thread para cada tarefa de forma que todas são executadas ao mesmo tempo. Lembre-se que "ao mesmo tempo" nem sempre é verdade, mas vamos acreditar que seja.
Em Java, podemos implementar threads instanciando a classe java.lang.Thread. Um objeto do tipo Thread é um objeto como qualquer outro, possui métodos e variáveis. A grande diferença é que ele pussui sua própria pilha de chamada (falei da pilha nesse post sobre construtores). O método main é responsável por iniciar todo o processamento, portanto ele é considerado o thread principal.
As perguntas sobre thread no exame são consideradas a mais difíceis! Portanto é bom praticar muito.
Vamos ver um pouco de ação:
class MyRunnable implements Runnable /*ou extends Thread*/{

public void run() {
  System.out.println("Rodando meu Thread");
}

}

Como você pode ver, temos uma classe que implementa Runnable. Existem duas formas de se trabalhar com threads, implementando Runnable ou estendendo Thread. Você deve conhecer estas duas formas para o exame, mas geralmente a melhor prática é implementar, pois assim fica livre para estender outra classe.
Quando implementamos Runnable devemos sobrescrever o método run. Dentro do método run colocamos todo o trabalho que desejamos executar concorrentemente. Se por acaso tivéssemos escolhido estender Thread, o método run deveria ser sobrescrito da mesma forma.
Mas esta classe não faz nada ainda. Apenas estamos dizendo que o código dentro de run deverá ser executado por um thread. Agora realmente vamos ter um pouco de ação:
public class MyThread {

public static void main(String[] args) {

  MyRunnable mr = new MyRunnable();
  Thread t = new Thread(mr);
  t.start();

}
}

Em uma outra classe instanciamos MyRunnable. Criamos um objeto tipo Thread e passamos como parâmetro a variável que faz referência a MyRunnable. Então iniciamos o thread. O resultado é:
Rodando meu Thread.

Sempre devemos deixar o código do trabalho a ser feito pelo thread separado do código que vai chamá-lo, da maneira como foi mostrado. Em uma classe escrevemos o que era pra ser feito, em outra pedimos pra fazer.
Também é importante lembrar que o thread sempre espera que sua classe tenha um método run sem parâmetros. Se este método for sobrecarregado, o thread não vai chamá-lo, e mesmo que você chame-o, a execução não será a esperada de um thread. A execução acontecerá na mesma pilha de main.


Instanciando um thread

Podemos instanciar um thread de duas formas. A primeira acontece quando estendemos Thread:
class MyRunnable extends Thread{

public void run() {
  System.out.println("Rodando meu Thread");
}
}

public class MyThread{

public static void main(String[] args) {

  MyRunnable mr = new MyRunnable();
  //Thread t = new Thread(mr);
  mr.start();

}
}

Veja que esta forma é mais simples, pois MyRunnable já estendeu Thread. Então precisamos somente instanciar para executar. Mas temos implicações, pois não podemos estender outras classes. A segunda forma é a que foi mostrada primeiramente, onde implementamos Runnable. Primeiro precisamos instanciar MyRunnable e depois Thread:
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);

Também poderíamos passar MyRunnable para diferentes threads, de modo que todos eles executem a mesma tarefa:
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);

t1.start();
t2.start();
t3.start();

Outra observação válida para o exame é que podemos passar um thread ao construtor de outro thread. Suponhamos que neste caso MyRunnable estende Thread, portanto ela É-UM thread. Então podemos passá-la a um construtor de outro thread:
Thread t = new Thread(new MyRunnable());

Isso é possível porque Thread possui vários construtores sobrecarregados e um deles aceita objetos Runnable. Então como Thread implementa Runnable, podemos passar tanto um Runnable quanto um Thread. Mas passar um Thread é totalmente desnecessário porque cria um novo thread sendo que o ideal é passar somente um Runnable.
Tenha em mente também que chamar o método run diretamente de uma classe que implementa Runnable é válido, porém não significa que vai se comportar como um thread:
MyRunnable mr = new MyRunnable();
mr.run();

A chamada entrará na pilha local, pois sempre que quisermos executar com thread devemos instanciar Thread. Lembre-se que o método que inicia o thread é start() e não run.
Até agora estamos indo bem, mas não tiramos proveito do recurso em momento algum. Como saberemos se os threads estão sendo executados ao mesmo tempo? Como poderemos ter certeza que eles não estão sendo executados um após o outro em sequência? Vamos modificar a classe MyRunnable:
class MyRunnable implements Runnable{
public void run() {
  System.out.println("Meu nome é: " + Thread.currentThread().getName());
}
}

Agora cada thread que executar terá que mostrar seu nome. Não se preocupe em não ter uma instância de Thread na classe pois o método chamado é estático. Ele retorna uma referência ao thread em execução e depois pega o nome dele. Agora vamos executar:
public class MyThread{

public static void main(String[] args) {

  MyRunnable mr = new MyRunnable();
  Thread t1 = new Thread(mr);
  Thread t2 = new Thread(mr);
  Thread t3 = new Thread(mr);

  t1.start();
  t2.start();
  t3.start();
}
}

A saída será parecida com a seguinte:

Meu nome é: Thread-0
Meu nome é: Thread-2
Meu nome é: Thread-1


E se mandar executar outra vez?

Meu nome é: Thread-2
Meu nome é: Thread-0
Meu nome é: Thread-1


E em cada execução o resultado poderá ser diferente. Não podemos prever a ordem que eles serão executados. Não há garantia nenhuma. O mecanismo responsável por alocar os threads (agendador) é quem decide quem vai entrar em execução (ou sair, ou esperar). A única coisa que podemos ter certeza é que cada thread será iniciado e executado até a sua conclusão.
Vamos ver mais um exemplo pra fechar o post. Faça a seguinte mudança no método run:
public void run() {
    for(int x = 0; x /* sinal de menor aqui */ 100; x++){
System.out.println("Meu nome é: " + Thread.currentThread().getName() + " e x = " + x);
}

A saída que eu obtive em uma das execuções foi:
Meu nome é: Thread-0 e x = 0
Meu nome é: Thread-0 e x = 1
Meu nome é: Thread-0 e x = 2
Meu nome é: Thread-1 e x = 0
Meu nome é: Thread-1 e x = 1
Meu nome é: Thread-1 e x = 2
Meu nome é: Thread-1 e x = 3
Meu nome é: Thread-1 e x = 4
Meu nome é: Thread-1 e x = 5
Meu nome é: Thread-0 e x = 3
Meu nome é: Thread-0 e x = 4
Meu nome é: Thread-0 e x = 5
...
Veja que Thread-0 toma a CPU e conta até 2, então sai de cena. Entra Thread-1. Por algum motivo que não sabemos explicar, a CPU permitiu que Thread-1 contasse até 5. Então a "bola" volta pra Thread-0 que continua. E Thread-2? Até o momento ainda não recebeu sua parcela da CPU, coitado!
Então finalizo este post e aproveito para dizer que o conteúdo passado aqui é somente o básico, tem muito mais sobre threads do que você imagina. Se possível outro dia volto a falar mais sobre o assunto.

Bons estudos!

Tipos Primitivos - Java

Hoje vou falar sobre tipos primitivos em Java. Veremos uma breve introdução sobre variáveis e em seguida detalhes sobre cada tipo.

Variáveis

Variáveis são espaços na memória onde podemos guardar informações. Provavelmente todos programas que você escrever vão usar variáveis, seja para guardar um nome, o caminho de uma pasta, etc. Também, como o próprio nome diz, elas podem variar, ter seu valor modificado durante a execução do programa.
Variáveis são nada mais que depósitos de bits. Cada valor é convertido para binário e então pode ser colocado na memória. Usamos o nome da variável para acesar o valor. O nome da variável é apenas o endereço para acessar o valor atribuído a ela.
As variáveis possuem tipos diferentes, dependendo da informação a ser armazenada. Por exemplo: Podemos ter um depósito tipo short que nos dará um espaço para guardar qualquer valor que convertido em binário possua 16 bits (65535 valores possíveis).
Em Java temos os seguintes tipos primitivos:
Tipo      Bits      Bytes
___________________________
byte     8         1
short    16        2
int      32        4
long     64        8
float    32        4
double   64        8
char     16        2
boolean  -         -

Eles são chamados de tipos primitivos porque são tipos já pre definidos pela linguagem e seus nomes são palavras reservadas. Eles não são como os objetos. Objetos são um tipo de dado criado pelo usuário que possuem um estado e tem métodos. Primitivos não possuem estados nem métodos. Objetos podem mudar de tipo, mas os primitivos não. Uma vez declarado o tipo, assim ele será e somente o valor poderá mudar.
Os primitivos apenas determinam o tipo e o tamanho da informação a ser guardado. Vou fazer um breve comentário sobre os tipos primitivos disponíveis em Java:
  • byte: O byte aceita até 256 valores. Eles podem estar compreendidos entre -128 <= x <= 127. Porque alguém usaria um tipo desses? Poderia usar para salvar bytes num grande vetor, como uma memória virtual.
  • short: O tipo short suporta valores entre -32,768 <= x <= 32,767. Ele possui o dobro de tamanho do byte e sua aplicação poderia ser a mesma. Não se preocupe em decorar esses intervalos. Apenas o intervalo do tipo byte é bom saber mas os outros saiba apenas o número de bits de acordo com a tabela.
  • int: O tipo int está compreendido entre -2,147,483,648 <= x <= 2,147,483,647. Geralmente esse tipo é usado como padrão para valores inteiros. E muitas vezes, para os valores que seu programa usará, este tipo dá e sobra!
  • long: Um valor do tipo long pode estar entre 9,223,372,036,854,775,808 <= x <= 9,223,372,036,854,775,807. Como você pode ver, este tipo é usado quando precisar guardar valores realmente grandes que o int não poderia fazê-lo.
  • float: O intervalo de números deste tipo é complicado de determinar, apenas saiba que ele possui 32 bits de precisão. Ele é usado para representar os números reais, não só inteiros mas também os fracionários. O formato desse tipo de número é normalmente chamado de floating point ou ponto flutuante. Eles podem representar números com uma boa precisão fornecendo uma aproximação para valores imensos. A forma como são representados pela linguaguem é um assunto muito interessante, recomendo que você procure algo sobre se ainda não conhece. Obs.: Este tipo nunca deve ser usado para valores realmente precisos, como moeda por exemplo. Para isto use a classe java.math.BigDecimal.
  • double: Assim como o float, o double representa números floating point. A diferença é que ele possui 64 bits de precisão. Para valores decimais (não inteiro) esse tipo é a melhor escolha. Como mencionado anteriormente, ele também não pode ser usado para representar valores de moeda.
  • char: O tipo char representa um único caractere Unicode. Unicode é um padrão para representar caracteres utilizando números inteiros de 16 bits (sem sinal). Assim ele pode armazenar o mesmo tamanho de informação que um short poderia, exceto que ele sua faixa é apenas números positivos. Pode-se digitar valores Unicode utilizano o prefixo "\u".
  • boolean: Esse tipo de dado só tem duas possibilidades: false ou true. Ele representa um bit de informação, embora seu "tamanho" não é algo precisamente definido. Desta forma valores booleanos podem ser somente uma das possibilidades citadas, nunca tente usar 0 ou 1! Outras linguagens como c++ aceitam isto, mas Java não.
É importante saber a sequência: byte, short, int, long, float, double. Lembre que double é maior que float, o próprio nome diz que é o dobro de precisão.
Outro detalhe é que todos tipos numéricos possuem sinal, isto é, podem ser tanto positivos quanto negativos e que isto afeta seu intervalo.
Como disse no começo do tópico, os valores são guardados em formato binário (001011...), sendo assim precisamos de um desses bits para definir o sinal do número. Este bit será o que se encontra mais a esquerda do número. Por exemplo: Temos um byte 00010011. O primeiro zero a esquerda representa o sinal. Sobram sete bits para representar o número, ou seja, 2^7 = 128 possibilidades.
Portanto, o byte pode representar valores entre -128 <= x <= 127, ou melhor: de -128 a -1 e de 0 a 127. Também podemos dizer que há 256 valores aí, metade negativa e metade -1 positiva. Menos um porque os positivos começam pelo zero, e ele é considerado um binário positivo.
A fórmula para calcular o intervalo mínimo (negativo) é -2^(bits - 1) e o máximo (positivo) é 2^(bits - 1) - 1.
Lembrando também que em Java não existe a palavra unsigned como em c++. Isto que dizer que todos valores devem respeitar as faixas permitidas como foi mostrado anteriormente. Não tente fazer algo como byte b = 128; sem fazer nenhuma conversão.

Bom, por hoje é só e bons estudos!

Sobrescrita / Sobercarga (Overriding / Overloading) - Java

O assunto de hoje é sobre o objetivo 1.5 e 5.4 do exame SCJP. Os objetivos são basicamente determinar se um método esta corretamente sendo sobrescrito ou sobrecarregado e sobre os tipos de retorno válidos para o método.
Também fala sobre desenvolver código que declare e chame métodos sobrescritos ou sobrecarregados, e códigos que chamem construtores da superclasse sendo eles sobrescritos ou sobrecarregados.

Métodos sobrescritos

Sempre que herdamos um método da superclasse, podemos sobrescrevê-lo. Fazendo isto temos a oportunidade de determinar um comportamento específico do método para a subclasse. Então estamos dizendo "Sei que existe um método genérico herdado da superclasse para fazer isto, mas este método não me serve e quero que esta subclasse tenha o seu próprio". Tudo bem, mas não esqueça que métodos marcados como final, private ou static não são herdados e por este motivo não podem ser sobrescritos.
Para demonstrar como os métodos são sobrescritos vou usar o mesmo código que usei no tópico sobre casting:
//Superclasse
public class Animal {

void comer(){
 System.out.println("Animal comendo...");
}

}
//Subclasse
class Dog extends Animal{

void rolar(){
 System.out.println("Dog rolando...");
}
//Dog sobrescreveu o método comer
void comer(){
 System.out.println("Dog comendo...");
}
}

Dog herdou o método comer de animal mas por algum motivo teve que escrever seu próprio método comer. Então se num determinado momento quisermos chamar:
new Dog().comer();

Teremos o resultado Dog comendo.... Sem problemas.
Vamos analisar alguns casos agora. Se você herdou métodos abstratos de uma classe abstrata, então é obrigatório sobrescrevê-los (na prática você estaria implementando porque não tem nada pra sobrescrever). Mas se sua classe também for abstrata não precisa, você pode simplente "passar a vez" para a última classe concreta da árvore. Só não esqueça que em algum momento você será obrigado a sobrescrever os métodos abstract.
Uma questão interessante que também foi abordada no post sobre casting é a seguinte: Quando sobrescrevemos um método e depois fazemos o upcast, qual método será chamado? Seguindo o mesmo exemplo do código acima, se fizermos a seguinte chamada:
Animal a = new Dog();
a.comer();

Qual versão do método será executada? Sabemos que quando convertemos pra cima da árvore de herança, os métodos do subtipo são perdidos mas neste caso a versão executada será a do subtipo. Porque? O compilador só verifica o tipo da variável e não da da instância. A referência é do tipo Animal? Então só métodos de Animal poderão ser executados. Mas em tempo de execução, o objeto real que está sendo executado é Dog, então o método de Dog será chamado.
Parece confuso mas pense: Estamos criando uma referência Animal para Dog. O que o compilador vai fazer? Ele vai pensar "Vou criar um objeto tipo Dog na memória e uma variável x tipo Animal que aponte pra este objeto." A variável sendo do tipo Animal não tem conhecimento dos métodos em Dog. Acontece que quando o programa for executado a JVM vai pensar "A variável x está chamando o método em Animal, mas o objeto sobrescreveu? Então execute o método do objeto!".

Outro ponto importante é que os métodos sobrescritos não podem declarar um modificador de acesso mais restritivo do que o que foi sobrescrito. Por exemplo:
//Superclasse
public class Animal {

protected void comer(){
 System.out.println("Animal comendo...");
}

}
//Subclasse
class Dog extends Animal{

void comer(){ //Problema!!!
 System.out.println("Dog comendo...");
}
}

Declaramos o método como protected mas depois tentamos deixá-lo default. Isto não compila porque default é mais restritivo que protected. Como expliquei antes, em tempo de execução, a JVM chama a versão do método sobrescrito, e o modificador de acesso ficou mais restrito. Se ele estivesse em um pacote diferente não poderia ser chamado.
Vamos ver agora umas regras importantes:
A lista de argumentos deve se manter a mesma!. Se ela mudar o método não será mais sobrescrito e sim sobrecarregado. Lembre-se que sobrescrever não é nada mais que usar o mesmo nome do método herdado.
O modificador de acesso do novo método não pode ser mais restritivo (menos pode). Já falei sobre isto!
Métodos só podem ser sobrescritos se forem herdados. Ou seja, métodos final, private ou static não podem. E classes fora do pacote só podem sobrescrever métodos public ou protected (métodos protected são herdados).
O tipo de retorno deve ser o mesmo ou um subtipo do método original. Isto chama-se retornos covariantes ou covariant returns. Este é um assunto a parte mas apenas lembre-se que o tipo de retorno pode ser o mesmo ou um subtipo. Não tem problema em retornar um subtipo porque com certeza ele "cabe" no supertipo.
O método que sobrescreveu pode lançar qualquer exceção de tempo de execução (não verificada / unchecked), mesmo que o método original não tenha lançado.
O método que sobrescreveu não pode lançar exceções verificadas (checked) novas ou mais abrangentes que as declaras pelo método original. Este é um assunto para outro post.
Pra finalizar a parte de sobrescrita vou fazer só mais duas observações importantes: Podemos chamar a versão da superclasse do método usando a palavra chave super:
//Superclasse
public class Animal {

void comer() {
 System.out.println("Animal comendo...");
}

public static void main(String[] args) {
 Dog g = new Dog();
 g.comer();
}
}

// Subclasse
class Dog extends Animal {

void rolar() {
 System.out.println("Dog rolando...");
}

// Dog sobrescreveu o método comer
void comer() {
 super.comer(); //Chamou a versão da superclasse
 System.out.println("Dog comendo...");
}

}

E outra, mesmo não tento falado sobre exceções vou insistir em colocar uma questão aqui que poderá cobrada no exame:
//Superclasse
public class Animal {

void comer() throws Exception {
 //Lança uma exceção
}
public static void main(String[] args) {
 Animal a = new Dog(); //Referência polimórfica
 a.comer();
}
}

// Subclasse
class Dog extends Animal {

void rolar() {
 System.out.println("Dog rolando...");
}

// Dog sobrescreveu o método comer
void comer() {
 System.out.println("Dog comendo...");
}
}

Um método foi sobrescrito, mas está sendo chamado através de uma referência polimórfica (do superclasse) para apontar para o objeto do subtipo. Como vimos o compilador vai pensar que você está chamando a versão da superclasse. Mas a JVM em tempo de execução vai chamar a versão da subclasse. A versão da superclasse declarou uma exceção, mas a do subtipo não. O código não compila devido a exceção declarada. Mesmo que a versão usada em tempo de execução seja a do subtipo.

Métodos Sobrecarregados

Metodos sobrecarregados tem uma diferença básica dos métodos sobrescritos: Sua lista de argumentos DEVE ser alterada. Por este motivo, os métodos sobrecarregados permitem que se utilize o mesmo nome do método numa mesma classe ou subclasse e isto pode ser usado para dar mais opções a quem for chamar o método. Por exemplo, podemos ter um método que receba uma variável int e outro que receba double, e a pessoa que for chamar não precisará se preocupar com fazer nenhuma conversão.
Diferente dos métodos sobrescritos, os sobrecarregados podem mudar o tipo de retorno e o modificador de acesso sem restrições. Eles também podem lançar exceções verificadas novas ou mais abrangentes.
Os métodos sobrecarregados podem ser declarados na mesma classe ou na subclasse. Saber diferenciar métodos sobrecarregados dos sobrescritos é importante pra não cair em pegadinhas como:
public class Animal {

public void comer(int x, String s) {
 System.out.println("Animal comendo...");
}
}

class Dog extends Animal {

protected void comer(int x, float s) {
 System.out.println("Dog comendo...");
}
}

O código acima pode parecer violar uma das regras de sobrescrita (modificador mais restritivo) porém ele está é sobrecarregando pois houve mudança na lista de argumentos.
Uma questão importante em métodos sorbrecarregados é: Qual método será chamado? Isso vai depender exclusivamente dos argumentos passados. Por exemplo, no caso anterior se você chamar:
new Dog().comer(2, "a");

Vai retornar Animal comendo.... Mas e se usarmos argumentos de objetos ao invés de tipos primitivos?
public class Animal {

void teste(Animal a) {
 System.out.println("Animal");
}


public static void main(String[] args) {
 new Dog().teste(new Dog()); //Chamou passando um Dog
 new Dog().teste(new Animal()); //Chamou passando um Animal
}
}

class Dog extends Animal {

void teste(Dog d) {
 System.out.println("Dog");
}
}

A saída será normal, executou o método de acordo com o tipo passado:
Dog
Animal


Mas e se a referência usada fosse polimórfica:
Animal a = new Dog();
a.teste(a);

Qual versão será executada? Você poderia pensar que seria a versão de Dog, porque estamos passando uma referência ao objeto Dog. Mas não é, a saída seria Animal pois mesmo sabendo que em tempo de execução o objeto utilizado será um Dog e não um Animal, quando o método é sobrecarregado isto não é decidido em tempo de execução. Então lembre-se: Quando o método é sobrecarregado, apenas o tipo da referência importa para saber qual método será chamado.
Resumindo, o método sobrescrito a ser chamado é definido em tempo de execução e o sobrecarregado no tempo de compilação.
Então pra fechar o tópico vou dar mais uma dica: Fique atento aos métodos que parecem ser sobrescritos mas estão sendo sobrecarregados:
public class Animal {

void teste() { }

}

class Dog extends Animal {

void teste(String s) {}

}

A classe Dog tem dois métodos: A versão teste sem argumentos que herdou de Animal e a versão sobrecarregada. Um código que faz referência a Animal pode chamar somente teste sem argumentos mas uma referência a Dog pode chamar qualquer um.

Dever de casa
Dado:
class Plant{

String getName() { return "plant"; }
Plant getType() { return this; }

}

class Flower extends Plant{

 //Insira o código aqui

}

class Tulip extends Flower { }

Quais instruções, inseridas na linha comentada, irão compilar? (Marque todas corretas)
A. Flower getType() { return this; }
B. String getType() { return "this"; }
C. Plant getType() { return this; }
D. Tulip getType() { return new Tulip(); }

Conversão de Tipos ou Casting - Java

Não sei se alguem já notou mas as anotações sobre Java que estou postando aqui são apropriadas para quem vai prestar o exame SCJP (Sun Certified Java Programmer). Como também pretendo ser um programador certificado pela Sun, estou estudando e quero aproveitar para compartilhar com você meus resumos.
Hoje vou falar sobre o objetivo 5.2 do exame que é sobre Conversão de Variáveis de Referência (também conhecido por Cast ou Casting de tipos):
Dado um cenário, desenvolver código que demonstre o uso do polimorfismo. Além disso, determinar quando a conversão será necessária e saber diferenciar erros de compilação de erros de tempo de execução relacionados a conversão de referências a objetos.
Temos 2 pontos principais neste objetivo: Uso do polimorfismo e saber diferenciar erros de compilação e de tempo de execução. Veja também que no final está "referências a objetos". Isto quer dizer que esse tipo de conversão não é como conversão entre tipos primitivos (que não será abordado neste post).
Vamos começar analisando o código:
//Superclasse
public class Animal {
 
 void comer(){
  System.out.println("Animal comendo...");
 }
 
}
//Subclasse
class Dog extends Animal{
 
 void rolar(){
  System.out.println("Dog rolando...");
 }
}

Upcasting ou Conversão ampliadora

Considerando o código acima poderíamos naturalmente fazer as seguintes declarações:
Animal animal; //Declarou um tipo genérico
animal = new Dog(); //Apontou para um mais específico
//Ou simplesmente:
Animal animal = new Dog();
//Ou ainda:
Animal animal = (Animal)new Dog(); //Conversão explícita

Porque? Tipos genéricos podem apontar para tipos mais específicos. Isto chama-se upcasting ou conversão ampliadora. Esta conversão é sempre segura, pois estamos indo de um tipo mais específico para um mais genérico (de baixo pra cima na árvore de herança) e sabemos que a superclasse poderá fazer tudo que a subclasse fazia pois herdou dela. Mas fazendo isto estamos perdendo os métodos de Dog.
Nesse caso a variável de referência animal que é do tipo Animal faz referência a Dog e isto é possível porque Dog É-UM Animal (Este relacionamento É-UM significa que Dog extends Animal).
Por estes motivos o compilador permite fazer o cast sem nenhuma notação especial nem nada explícito.


Até aqui tudo bem, mas e se tentar realizar um comportamento mais específico? Por exemplo, tentar chamar um método que só existe em Dog? Não pode porque só comportamentos de Animal serão aceitos:
animal.rolar(); //Problema! Método não definido para o tipo Animal

O código não compila porque animal não tem o método rolar(). Entenda que estamos transformando um Dog num tipo mais genérico e que ele agora só poderá executar ações deste novo tipo. Ele perdeu os comportamentos específicos que tinha.
Mas um coisa interessante é que se Dog sobrescrever algum método de Animal, qual dos métodos será executado? Vamos modificar o código:
//Superclasse
public class Animal {
 
 void comer(){
  System.out.println("Animal comendo...");
 }
 
}
//Subclasse
class Dog extends Animal{
 
 void rolar(){
  System.out.println("Dog rolando...");
 }
 //Dog sobrescreveu o método comer
 void comer(){
  System.out.println("Dog comendo...");
 }
}

Suponhamos que faça a seguinte chamada:
Animal animal = new Dog();
animal.comer();

Quem vai comer? Animal ou Dog? Neste caso o método em Dog seria executado porque ele sobrescreveu. Então quando fazemos o upcasting perdemos os métodos do subtipo mas se algum for sobrescrito a versão a ser executada é a do subtipo. Muito louco isso não é?

Downcasting

Seguindo a raciocínio do upcasting, se quisermos "voltar" um tipo genérico para o mais específico (conversão redutora), devemos fazer a conversão explicitamente:
Dog dog = (Dog) animal;
dog.comer();

Veja que mudamos apenas o tipo da variável, não o tipo da referência. Então esta conversão se torna possível pois animal faz referência a Dog.
Então você se pergunta: Porque o upcasting é automático e o downcasting não? Agora chegamos num ponto interessante: No upcasting a conversão nunca falha. No downcasting, nem sempre teremos esta certeza. No exemplo citado acima, a conversão deu certo porque animal fazia referência a Dog mas e se não fizesse? Se animal fizesse referência a Animal mesmo?
Animal animal = new Animal();
Dog dog = (Dog) animal;

Como isso poderá ser feito se Animal não é um Dog? Animal nem sabe da existência de Dog. A conversão não pode ser feita porque os tipos são incompatíveis.
Só que o mais intrigante vem agora: O código compila!
Se você usa o Eclipse vá em Project > Clean..., selecione seu projeto e veja se apareceu algum erro em Problems? Não vai aparecer nada, porque pro compilador está tudo bem, ele não sabe e nem quer saber se animal faz referência a Animal ou Dog. Mas quando tentar executar:
Exception in thread "main" java.lang.ClassCastException: Animal cannot be cast to Dog
at Animal.main(Animal.java:46)
Então não tente converter um objeto para um tipo que ele não é. Mas lembre-se que o compilador não sabe se você vai tentar isto.
Para saber se dada conversão é aceita devemos usar o operador instanceof. Este operador retorna true ou false para uma comparação entre tipos:
 if (dog instanceof Animal)
 {
  System.out.println("dog é um Animal.");
 }

Na IDE Eclipse, quando começar a digitar "ins" e pressionar Ctrl + Barra de espaço ele já sugere o operador, então pressionando Enter ele já vai preencher um bloco de código igual a este:
 if (name instanceof type) {
  type new_name = (type) name;
  
 }

Aí é só você ir dando TAB e preenchendo os campos. Ele faz isso porque sabe que quase sempre que usamos o instanceof é pra saber se um objeto é de determinado tipo, e sendo positivo, já queremos que o objeto seja convertido neste tipo.
Aproveitando que já dei essa dica, tem outras duas que eu também uso muito para criar o método main e pro comando System.out.println: Para o main apenas digite main e Ctrl + Barra de espaço, para o println digite sysout Ctrl + Barra de espaço.

Só por curiosidade: Podemos converter qualquer objeto no tipo Object, como já falei no outro post, o Object é como uma classe "mãe" no Java:
Object obj = (Object)new Animal();

Mas essa conversão não tem muita utilidade pois faz com que animal perca todos seus métodos como já foi explicado. Por hoje é só!

Bons estudos e boas conversões!

Construtores - Java

Hoje vou falar sobre construtores:


Construtores são esses caras que constroem as coisas, ou melhor, objetos. Não estranhe a foto que usei para ilustrar esse post, é apenas uma homenagem ao homem trabalhador. Aquele que trabalha sob o sol, pegando no pesado, para construir nossos lares e etc. Bom, mas não vamos nos desviar do assunto.
Uma coisa é verdade: toda classe em java tem pelo menos um construtor(até abstratas, enums), exceto interfaces. Decore isto.
Mas é claro que interfaces não tem construtores, elas não fazem nada! Mas não subestime-as, elas são importantes para fornecer um acoplamento fraco entre as classes. Outro dia vou abordar este assunto.

Então mais uma vez: Todas as classes (até os enums) tem pelo menos um construtor, exceto as interfaces.
Mas o que são esses construtores? Um construtor é um "método" que é executado sempre que uma classe é instanciada. Toda vez que você digitar a palavra chave new o construtor será executado e inicializará o objeto. As vezes passamos algum parâmetro tipo new (2, 2, 2) .

Quando fazemos isto estamos inicializando a classe com os dados parâmetros. O construtor serve para inicializar as variáveis de instância da classe, como também executar códigos legais toda vez que inicializar uma classe. Entenda códigos legais como coisas que você gostaria que fossem executadas toda vez que criar um objeto, ficou claro?
Vamos ver o que é um construtor na prática:
class A {
 A(){
  super(); 
  System.out.println("Executou construtor A");
  //Códigos legais aqui
 }
}

Veja que o construtor é como um método mas ele não tem um tipo de retorno, nem void. Também tem o mesmo nome da classe e sempre terá. Ele pode ter modificador de acesso, até private (mas saiba das implicações disto). O compilador criará automaticamente um construtor igual o mostrado acima caso você não codifique nenhum. Porque ele faz isto? 

Todo construtor executa primeiramente uma chamada super() ou this() dependendo do caso, quando executado. Somente uma dessas chamadas é válida antes do resto. Mesmo que você não coloque explicitamente, o compilador irá colocar um super() pra você porque em algum momento ele vai ter que inicializar sua superclasse antes de inicializar a classe em si. 

Então você deve se perguntar se a classe A tem uma superclasse porque não está herdando de ninguém. Errado! Todas as classes herdam de Object implicitamente. Object é tipo uma classe "mãe" no Java (ela fornece métodos como equals por exemplo).
Se temos duas classes A e B, com B herdando A, quando criamos uma instância de B os construtores são executados em forma de pilha: B chama A e A chama Object. Object é executado, A é executado e por último B:

OBJECT
A
B

Mais adiante vou mostrar o resultado disto. Antes de continuar vamos rever alguns conceitos. Quando você digitar:

class A {
}

Seu compilador irá adicionar automaticamente o construtor:
class A {
 A(){
  super(); 
 }
}

O contrutor criado por ele sempre será sem argumentos. E se digitar só:
 
class A {
 A(){
 }
}

Não tem problema, o compilador adicionará o super();
O compilador só não vai fazer nada se for adicionado um construtor sobrecarregado:

class A {
 A(String s){
  super(); 
  System.out.println("Executou construtor A");
  //Códigos legais aqui
 }
}

Isso mesmo! Construtores podem ser sobrecarregados (mas não sobrescritos porque não são herdados), mas lembre-se:

Se houver um construtor sobrecarregado qualquer, o compilador não vai criar outro automaticamente
Sabendo disto você não cairá em pegadinhas do tipo:
class A {
 A(String s){
  super(); 
  System.out.println("Executou construtor A");
  //Códigos legais aqui
 }
}

public class B extends A{
 
 public static void main(String[] args) {
  new B();
 }
 
}

Acontece que a classe B possui aquele construtor automático que falei anteriomente. E na classe A, por ter definido um construtor sobrecarregado, não temos o construtor padrão sem argumentos, então código não compila:

Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Implicit super constructor A() is undefined for default constructor. Must define an explicit constructor

at B.(B.java:10)
at B.main(B.java:13)

Sempre tente detectar isto quando uma questão abordar construtores e como funciona a pilha. Logicamente o código terá mais alguma lógica obscura tentando lhe desviar do problema e tentando esconder o obvio. Neste caso, para resolver o problema, você mesmo deve criar construtor em A sem argumentos ou em B passando o parametro como pede:
 B(String s) {
  super(s);
 }

E a chamada a B deverá ser:

  new B(null);

Veja que o construtor de B recebe o parâmetro e o passa para a superclasse, isto é perfeitamente possível.
Um ponto importante é que nada pode ser feito sem que o construtor seja executado. Nenhum acesso a variável de instância ou método antes que o super() final seja chamado e construa Object. Isto que dizer que um objeto não pode ser utilizado até ser inicializado, o que é obvio, mas também significa que você não pode tentar sabotar o construtor do objeto antes de chamar super():
class A {
 A(String s){
  System.out.println("Antes de executar o super de Object");
  super(); // Problema!!!
  System.out.println("Executou construtor A");
  //Códigos legais aqui
 }
}

Outra coisa é que um construtor pode chamar outro. Um construtor só pode ser chamado dentro de outro. Isto que dizer que ou você chama outro construtor da mesma classe que tenha sido sobrecarregado utilizando o this() ou chama o da superclasse utilizando super(). Como já foi dito, somente uma dessas chamadas poderá ser feita dentro do contrutor e deverá ser a primeira coisa a ser feita.
public class B{
 B() {
  super();
  System.out.println("Executou o construtor padrão.");
 }

 B(String s){
  this();
  System.out.println("Chamou o construtor padrão.");
 }
 
 public static void main(String[] args) {
  new B(null);
 }
}

Veja que o código produz a seguinte saída:

Executou o construtor padrão.
Chamou o construtor padrão.



A saída é "invertida" por causa da pilha. O main chamou o construtor B sobrecarregado, antes de qualquer coisa ele chamou B padrão que chamou super(). Então Object foi criado e volta pra B padrão, então é executado. Volta para o B sobrecarregado e mostra que "Chamou.." no final, ele ja tinha chamado mas estava esperando os outros.
Agora veja o seguinte exemplo:
class A {
 void A(){
  super();
  System.out.println("Executou construtor A");
  //Códigos legais aqui
 }
}

O que tem de errado com a classe? Não... aquilo não é um construtor, é apenas um método qualquer porque ele tem um tipo de retorno e construtores não tem. Tome cuidado.
Só pra finalizar o post porque já está ficando cansativo, analise este código:
class A {
 A(){
  System.out.println("A");
 }
}

class B extends A{
 B() {
  System.out.println("B");
 }
}

public class C extends B{
 C() {
  System.out.println("C");
 }
 
 public static void main(String[] args) {
  new C();
 }
}

Qual será a saída?? Não vou responder...
Bons estudos e até a próxima!

Modificadores de acesso - Java

Hoje vou mudar de assunto e falar um pouco sobre Java. Assim como outras linguagens de programação, o Java tem níveis de acesso que podemos definir para classes, métodos e variáveis. Existem 3 modificares de acesso: Public, Protected e Private. Mas os níveis de acesso são 4, porque implicitamente se nenhum desdes modificadores forem usados o acesso é definido como Default.
Os níveis de acesso podem dizer se um método por exemplo pode ser acessado somente pela classe, por outras classes dentro do seu pacote ou fora dele:
Classe > Pacote > Global

Public e Private

Primeiramente, se você está começando a aprender agora, entenda estes dois modificadores pois são os mais simples. O Public é o menos restritivo de todos. Ele permite que sua classe, método ou variável possa ser acessada globalmente no projeto.
O Private é o mais restritivo. Ele permite que sua variável ou método só seja visto dentro da classe em que se encontra. Classes e interfaces não podem ser Private (nem Protected).
Private é muito usado para encapsular atributos da classe. Neste caso, o acesso aos atributos é feito por um método Public que poderá validar os dados, por exemplo:
private int x = 0;

A variável não poderia ser acessada diretamente. Somente através do método:
 public void setX(int x){
if (x > 0){
this.x = x;
}
}

Default

Default é o nível de acesso usado quando não colocamos nenhum modificador:
 class Animal{}
Quando não usamos modificadores, estamos dizendo que a classe, método ou variável só é visível dentro do seu pacote. O Default então cria uma restrição de pacote. Por este motivo ele é o meio termo entre o Private e Public.

Protected

O Protected é o mais incompreendido de todos. Ele é parecido com Default (restrição de pacote) mas permite que o membro seja acessado através de herança. Isto gera uma certa confusão mas preste atenção: O método ou variável protected só pode ser acessado de fora do pacote através da herança e nunca diretamente! Vejamos o que estou querendo dizer:

package Superclasse;

public class Animal {

private int x = 1;

protected int getX() {
return x;
}
}
Temos um pacote Superclasse contendo a classe Animal. Animal tem uma variável x private e um método protected para retornar o valor desta variável. Até aí tudo bem, vamos criar uma classe em outro pacote que herde de Animal:

package Subclasse;
import Superclasse.Animal;

public class Homem extends Animal{

public static void main(String[] args) {

Homem homem = new Homem();
System.out.println(homem.getX()); //Ok, homem herda de animal
}
}

Vimos que homem herda o método protected getX de Animal. Mas agora tente fazer o seguinte:
package Subclasse;
import Superclasse.Animal;

public class Homem extends Animal{

public static void main(String[] args) {

Homem homem = new Homem();
System.out.println(homem.getX());


Animal animal = new Animal();
System.out.println(animal.getX()); //Erro, tentou acessar diretamente

}
}
Ao tentar acessar o método getX através da instancia de Animal, fazendo isto diretamente o código não compila e mostra o seguinte erro:

Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method getX() from the type Animal is not visible

at Subclasse.Homem.main(Homem.java:13)

E se outra classe de outro pacote instanciar Homem, terá acesso ao método? Não, novamente somente através da herança poderá ter acesso.
Para não esquecer como isso funciona eu imaginei a seguinte situação: Temos o pai, o filho e o amigo do filho. O pai tem dinheiro e pode gastá-lo. Ele dá a mesada ao filho. O filho pode gastar a mesada, mas nunca o dinheiro do pai. O filho pode emprestar dinheiro ao amigo. O amigo pode gastar o dinheiro emprestado, mas nunca o do filho e nem o do pai. Resumindo, você pode ter acesso ao que lhe foi dado, mas não pode acessar de quem lhe deu.
Espero ter deixado claro o funcionamento do protected.

Bons estudos!