Java com café

ProgressBar dentro de uma ActionBar - Android

Olá! Hoje vou mostrar como adicionar uma progressbar dentro de uma actionbar. Um bom exemplo de implementação deste recurso é o aplicativo do Gmail, onde você tem um item de menu (usado para atualizar seus e-mails) que quando clicado, é substituído pela progressbar até o procedimento ser concluído e então volta novamente a ser o item.
Para utilizar este recurso, a versão mínima requerida do sdk é a 11. Você também vai precisar dos ícones, que podem variar de acordo com o tema (baixe o pacote neste link). Para o exemplo estarei utilizando estes que são adequados para o tema Holo Light:
res/drawable-mdpi
res/drawable-hdpi
res/drawable-xhdpi

Para começar salve os ícones nas respectivas pastas de acordo com as legendas nas imagens. Agora vamos precisar de dois arquivos xml, um para o item de menu e outro para a progressbar. Para o item, dentro da pasta res/menu, crie um arquivo chamado atualizar.xml com o seguinte conteúdo:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:id="@+id/action_atualizar"
        android:icon="@drawable/ic_menu_refresh"
        android:showAsAction="always"
        android:title="Atualizar"/>
</menu>
E na pasta res/layout o arquivo actionbar_atualizar.xml:
<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="30dp"
    android:layout_gravity="right"
    android:paddingRight="5dp" />
Para controlar quando vai aparecer o item ou a progressbar, a estrutura da classe deverá ser parecida com a que segue (odeio colar trechos muito grandes de código no site, mas paciência que vou explicar tudo lá embaixo):
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity {

 private boolean atualizando = false;
 private Menu mOptionsMenu;

 @Override
 protected void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getActionBar().setDisplayHomeAsUpEnabled(true);

 }

 @Override
 public void onBackPressed() {

  if (atualizando == true) {
   Toast.makeText(this, "Aguarde...", Toast.LENGTH_LONG).show();
  } else {
   super.onBackPressed();
  }

 }

 private void atualizar() {

  atualizando = true;
  setAtualizando(atualizando);

  new Operacao().execute();

 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {

  switch (item.getItemId()) {

  case android.R.id.home:
   onBackPressed();
   break;

  case R.id.action_atualizar:
   atualizar();
   break;

  }

  return true;

 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {

  super.onCreateOptionsMenu(menu);
  mOptionsMenu = menu;
  getMenuInflater().inflate(R.menu.atualizar, menu);
  // atualizar();
  return true;

 }

 public void setAtualizando(boolean atualizando) {

  if (mOptionsMenu == null) {
   return;
  }

  MenuItem atualizarItem = mOptionsMenu.findItem(R.id.action_atualizar);

  if (atualizarItem != null) {

   if (atualizando) {
    atualizarItem.setActionView(R.layout.actionbar_atualizar);
   } else {
    atualizarItem.setActionView(null);
   }

  }

 }

 class Operacao extends AsyncTask<Void, Void, Void> {

  Exception exception = null;

  @Override
  protected Void doInBackground(Void... params) {

   try {

    // Neste bloco vocẽ executa suas operações
    Thread.sleep(2000);

   } catch (Exception e) {
    exception = e;
   }

   return null;

  }

  @Override
  protected void onPostExecute(Void result) {
   super.onPostExecute(result);

   if (exception == null) {
    atualizando = false;
    setAtualizando(atualizando);
   } else {
    Toast.makeText(MainActivity.this,
      "Ocorreu um erro: " + exception.getMessage(),
      Toast.LENGTH_LONG).show();
   }

  }

 }

}
Bom, no começo temos duas variáveis: atualizando e mOptionsMenu. A primeira é simplesmente para identificar se a atualização está ocorrendo ou não em determinado momento. A segunda receberá o menu assim que ele for criado e permitirá a troca entre o item e a progressbar quando necessário.
Em seguida temos os seguintes métodos:
  • onCreate: Não tem nada de especial além do comando getActionBar().setDisplayHomeAsUpEnabled(true), que adiciona o item "voltar" na actionbar. 
  • onBackPressed: É tratado se a atualização está ocorrendo e não permitirá voltar se for verdade.
  • atualizar: Configura a barra com o setAtualizando e executa a tarefa que executará as operações de atualização. Falo sobre a classe que executa a tarefa logo abaixo.
  • onOptionsItemSelected: Verifica qual item do menu foi clicado e executa a ação correspondente.
  • onCreateOptionsMenu: A variável recebe o menu e caso queira que a Activity já inicie atualizando, basta descomentar o comando atualizar().
  • setAtualizando: É o responsável por mostrar o item ou a progressbar na actionbar. 
Por fim temos a classe interna Operacao, uma subclasse de AsyncTask que nos permite executar tarefas em background e atualizar a interface sem precisar de threads. Não vou entrar em detalhes sobre este tipo de classe, apenas saiba que o método doInBackground é usado para executar as operações (no exemplo chamei Thread.sleep(2000) apenas pra simular um procedimento rápido) e onPostExecute é executado quando doInBackground finalizar. E temos a variável exception que é usada pelos métodos para descobrir se a operação finalizou com sucesso.

Qualquer dúvida deixe nos comentários!

Android + ksoap2: Recebendo um Array de Objetos

Neste tutorial pretendo mostrar de forma simples como criar um Web Service em Java utilizando o NetBeans e uma aplicação cliente para consumir o serviço no Android. O Web Service vai implementar uma operação que deverá retornar um array de objetos. Na aplicação Android esse array será recebido e utilizado para preencher uma lista de itens (ListView). 

Ferramentas utilizadas: 
  • NetBeans 7.1 + Tomcat 7 (servidor) 
  • Eclipse + ADT - Android Developer Tools v21.0.1-543035 (cliente) 

Criando o Web Service

Não vou entrar em detalhes sobre a criação da aplicação no NetBeans porque já existe um bom tutorial disponível aqui. Após criar o projeto, crie também uma classe para o objeto que será retornado. Para fins de exemplo vou utilizar a seguinte classe:

public class Fornecedor {

    int codigo;
    String razaoSocial;
    String cpfCnpj;

    public Fornecedor() {
    }

    public Fornecedor(int codigo, String razaoSocial, String cpfCnpj) {
        this.codigo = codigo;
        this.razaoSocial = razaoSocial;
        this.cpfCnpj = cpfCnpj;
    }

    public int getCodigo() {
        return codigo;
    }

    public void setCodigo(int codigo) {
        this.codigo = codigo;
    }

    public String getCpfCnpj() {
        return cpfCnpj;
    }

    public void setCpfCnpj(String cpfCnpj) {
        this.cpfCnpj = cpfCnpj;
    }

    public String getRazaoSocial() {
        return razaoSocial;
    }

    public void setRazaoSocial(String razaoSocial) {
        this.razaoSocial = razaoSocial;
    }

    @Override
    public String toString() {
        return getCodigo() + ". " + getRazaoSocial();
    }
}

É importante que sua classe tenha um construtor padrão, sem argumentos. Pode parecer inútil mas o Tomcat não vai implantar o projeto sem ele. O método toString deverá ser sobrescrito na classe, mas isto será útil apenas na aplicação cliente ao preencher a lista, pois será utilizada a mesma classe. 

Seguindo o tutorial que foi sugerido, você deve criar uma operação que não receberá parâmetros e que a saída será do tipo List<Fornecedor>. Para o exemplo vou chamar esta operação de getFornecedores. O código deverá ser parecido com este: 

@WebService(serviceName = "GetFornecedores")
public class GetFornecedores {

    @WebMethod(operationName = "getFornecedores")
    public List<Fornecedor> getFornecedores() {

        List<Fornecedor> listFornecedores = new ArrayList<Fornecedor>();

        listFornecedores.add(new Fornecedor(1, "Fornecedor 1", "0123456789"));
        listFornecedores.add(new Fornecedor(2, "Fornecedor 2", "0123456789"));
        listFornecedores.add(new Fornecedor(3, "Fornecedor 3", "0123456789"));

        return listFornecedores;

    }
    
}

Após implantar e executar o projeto, o arquivo wsdl pode ser acessado através do browser, de acordo com suas configurações:
http://localhost:8080/TestWSApp/GetFornecedores?wsdl
Com o projeto rodando vamos para a segunda parte.


Criando o cliente Android 

Crie um projeto no Eclipse com uma Activity principal e adicione a lib ksoap2 no Build Path. Copie a classe Fornecedor citada anteriormente e cole no pacote do projeto. Então crie uma classe chamada ConexaoWS com o seguinte código:

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

public class ConexaoWS {

 private static final String NAMESPACE = "http://testws.me.org/";
 private static final String METHOD_NAME = "getFornecedores";
 private static final String SOAP_ACTION = "http://testws.me.org/getFornecedores";
 private static final String URL = "http://10.1.0.116:8080/TestWSApp/GetFornecedores?wsdl";

 public Fornecedor[] getFornecedores() throws Exception {

  SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);

  SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
  envelope.dotNet = true;
  envelope.setOutputSoapObject(request);

  HttpTransportSE ht = new HttpTransportSE(URL);
  ht.call(SOAP_ACTION, envelope);

  SoapObject response = (SoapObject) envelope.bodyIn;

  Fornecedor[] fornecedores = new Fornecedor[response.getPropertyCount()];

  for (int c = 0; c < fornecedores.length; c++) {

   SoapObject soapObject = (SoapObject) response.getProperty(c);

   Fornecedor f = new Fornecedor();
   f.codigo = Integer.parseInt(soapObject.getProperty(0).toString());
   f.cpfCnpj = soapObject.getProperty(1).toString();   
   f.razaoSocial = soapObject.getProperty(2).toString();
   fornecedores[c] = f;

  }

  return fornecedores;

 }

}

As principais alterações a serem feitas são nas constantes: 

  • NAMESPACE: Pode ser encontrado no arquivo wsdl, mas normalmente é "http://" + pacote + "/" 
  • METHOD_NAME: Mesmo nome dado a operação 
  • SOAP_ACTION: NAMESPACE + METHOD_NAME 
  • URL: Endereço do arquivo wsdl (nos meus testes a conexão foi recusada quando utilizei localhost ou 127.0.0.1, portanto use o IP real da sua máquina) 

Com isto fica faltando o layout da aplicação e a chamada do método. Para o layout utilize a seguinte definição:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" >

    </ListView>

</RelativeLayout>

E para a classe principal utilize o seguinte código:
public class MainActivity extends ListActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // Operações de rede não podem ser realizadas na thread principal
  new MyAsyncTask().execute();

 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }

 private class MyAsyncTask extends AsyncTask<Void, Void, Fornecedor[]> {

  boolean erro = false;

  private final ProgressDialog dialog = new ProgressDialog(
    MainActivity.this);

  @Override
  protected void onProgressUpdate(Void... values) {

   this.dialog.setMessage("Aguarde...");
   this.dialog.show();

  }

  @Override
  protected Fornecedor[] doInBackground(Void... params) {

   Fornecedor[] retorno = null;

   try {

    ConexaoWS conexao = new ConexaoWS();
    retorno = conexao.getFornecedores();

   } catch (Exception e) {
    erro = true;
    e.printStackTrace();
   }

   return retorno;

  }

  @Override
  protected void onPostExecute(Fornecedor[] result) {

   if (this.dialog.isShowing()) {
    this.dialog.dismiss();
   }

   if (erro == false) {

    List<Fornecedor> listaFornecedores = Arrays.asList(result);
    ArrayAdapter<Fornecedor> adapter = new ArrayAdapter<Fornecedor>(
      MainActivity.this, android.R.layout.simple_list_item_1,
      listaFornecedores);
    setListAdapter(adapter);

   } else {
    
    Toast.makeText(MainActivity.this,
      "Ocorreu um erro ao acessar o Web Service.",
      Toast.LENGTH_LONG).show();
    
   }

  }

 }

}
Se você é leigo em programação para Android, recomendo que procure entender como funcionam as classes ListActivity e AsyncTask para melhor compreensão do exemplo. O resultado da aplicação será este:  


Espero que tenham gostado. Qualquer dúvida deixe nos comentários.

Os sistemas legados

É muito comum hoje em dia o profissional da área de sistemas passar anos na faculdade estudando linguagens modernas e quando entra no mercado de trabalho se vê diante de tecnologias totalmente obsoletas. Muitas empresas ainda mantém sistemas antigos rodando e pode ser difícil encontrar um lugar para trabalhar com tecnologias novas.



É notável a velocidade em que as coisas caminham no mundo da informática. Uma das "leis" na programação de computadores diz que Qualquer dado programa, quando rodando, é obsoleto. Mas mesmo com todas atualizações, novas versões com mais recursos e facilidades, as empresas ainda mantém seus antigos sistemas rodando por vários anos.

Por que as empresas mantém sistemas legados?

Muitas empresas acreditam que não devem trocar o sistema simplesmente porque ele está funcionando, mas na verdade é importante avaliar as desvantagens de mantê-lo como por exemplo a dificuldade na integração. Desvantagem esta que foi observada após a obrigatoriedade de emitir nota fiscal eletrônica, visto que muitos sistemas não possuem recursos para comunicação via web. Ainda nesse aspecto, com o avanço no desenvolvimento de dispositivos móveis e a alta disponibilidade de serviços de internet, esses sistemas não estão preparados para aproveitar tais recursos. Mesmo que apresentem soluções para contornar estes problemas, o resultado final não é dos mais elegantes.
Outro motivo pelo qual as empresas optam por não trocam o sistema é o alto custo despendido com o desenvolvimento de um novo sistema. Sim, existem soluções prontas a todos custos mas é preciso que a empresa veja isso como um investimento e entenda a importância de ter um sistema que realmente atenda suas necessidades.

Compensa trabalhar com uma tecnologia obsoleta?


O importante é ter conhecimento e não ficar fora do mercado, independente da tecnologia. As empresas tem dificuldade de encontrar profissionais para trabalhar com determinados sistemas e geralmente são vagas com uma boa remuneração. 
É necessário se manter atualizado pois muitos sistemas estão sendo construídos e outros sendo migrados, mas acredito que é preciso saber aproveitar as oportunidades e fazer o que tem que ser feito.

Window Function - PostgreSQL

Window function é um recurso muito útil porque nos permite realizar cálculos que antes só poderiam ser feitos com functions ou subquerys, assim podemos escrever menos código e evitar joins. Uma window function consegue particionar as linhas de uma consulta e fazer cálculos dentro destas partições. É como fazemos com funções agregadas, porém sem agrupar e mantendo as linhas separadas. Pra começar, disponibilizo as sentenças para criar e popular uma tabela com dados que vão servir de exemplo:

CREATE TABLE formula1
(
  posicao integer NOT NULL,
  piloto character varying(100),
  equipe character varying(100),
  pontos integer,
  CONSTRAINT pk_formula1 PRIMARY KEY (posicao)
);

INSERT INTO formula1 VALUES (1, 'FERNANDO ALONSO', 'FERRARI', 164);
INSERT INTO formula1 VALUES (2, 'MARK WEBBER', 'RED BULL', 124);
INSERT INTO formula1 VALUES (3, 'SEBASTIAN VETTEL', 'RED BULL', 122);
INSERT INTO formula1 VALUES (4, 'LEWIS HAMILTON', 'MCLAREN', 117);
INSERT INTO formula1 VALUES (5, 'KIMI RAIKKONEN', 'LOTUS', 116);
INSERT INTO formula1 VALUES (6, 'NICO ROSBERG', 'MERCEDES BENZ', 77);
INSERT INTO formula1 VALUES (7, 'JENSON BUTTON', 'MCLAREN', 76);
INSERT INTO formula1 VALUES (8, 'ROMAIN GROSJEAN', 'LOTUS', 76);
INSERT INTO formula1 VALUES (9, 'SERGIO PEREZ', 'SAUBER', 47);
INSERT INTO formula1 VALUES (10, 'KAMUI KOBAYASHI', 'SAUBER', 33);

Fazendo um simples select temos o seguinte resultado:

 posicao |      piloto      |    equipe     | pontos 
---------+------------------+---------------+--------
       1 | FERNANDO ALONSO  | FERRARI       |    164
       2 | MARK WEBBER      | RED BULL      |    124
       3 | SEBASTIAN VETTEL | RED BULL      |    122
       4 | LEWIS HAMILTON   | MCLAREN       |    117
       5 | KIMI RAIKKONEN   | LOTUS         |    116
       6 | NICO ROSBERG     | MERCEDES BENZ |     77
       7 | JENSON BUTTON    | MCLAREN       |     76
       8 | ROMAIN GROSJEAN  | LOTUS         |     76
       9 | SERGIO PEREZ     | SAUBER        |     47
      10 | KAMUI KOBAYASHI  | SAUBER        |     33

Analisando os dados, imagine uma forma de inserir mais uma coluna que mostrará a soma de pontos por equipes. Poderia usar uma subquery:

SELECT *, 
 (SELECT SUM(pontos) FROM formula1 aux
 WHERE formula1.equipe = aux.equipe
 GROUP BY equipe) AS pontos_equipe
FROM formula1
ORDER BY posicao

Poderia usar uma function também, mas fica melhor com uma window function:

SELECT *, SUM(pontos) OVER (PARTITION BY equipe) AS pontos_equipe 
FROM formula1
ORDER BY posicao

 posicao |      piloto      |    equipe     | pontos | pontos_equipe 
---------+------------------+---------------+--------+---------------
       1 | FERNANDO ALONSO  | FERRARI       |    164 |           164
       2 | MARK WEBBER      | RED BULL      |    124 |           246
       3 | SEBASTIAN VETTEL | RED BULL      |    122 |           246
       4 | LEWIS HAMILTON   | MCLAREN       |    117 |           193
       5 | KIMI RAIKKONEN   | LOTUS         |    116 |           192
       6 | NICO ROSBERG     | MERCEDES BENZ |     77 |            77
       7 | JENSON BUTTON    | MCLAREN       |     76 |           193
       8 | ROMAIN GROSJEAN  | LOTUS         |     76 |           192
       9 | SERGIO PEREZ     | SAUBER        |     47 |            80
      10 | KAMUI KOBAYASHI  | SAUBER        |     33 |            80

A chamada da função deve vir acompanhada da cláusula OVER. É isto que determina quais linhas estarão disponíveis para o processamento da função. A outra instrução, PARTITION BY, divide as linhas em grupos para que a função seja aplicada sobre elas. Também podemos definir a ordem que as linhas serão processadas pela função usando ORDER BY. A window function processa as linhas independente da ordem em que estão sendo exibidas, portanto deve-se ter cuidado para que os dados sejam processados na ordem desejada. Suponhamos que queremos saber quem é o piloto número 1 e 2 de cada equipe, sendo o número 1 com a maior quantidade de pontos: 

SELECT *, row_number() OVER (PARTITION BY equipe ORDER BY posicao) AS numero_piloto
FROM formula1
ORDER BY posicao

 posicao |      piloto      |    equipe     | pontos | numero_piloto 
---------+------------------+---------------+--------+---------------
       1 | FERNANDO ALONSO  | FERRARI       |    164 |             1
       2 | MARK WEBBER      | RED BULL      |    124 |             1
       3 | SEBASTIAN VETTEL | RED BULL      |    122 |             2
       4 | LEWIS HAMILTON   | MCLAREN       |    117 |             1
       5 | KIMI RAIKKONEN   | LOTUS         |    116 |             1
       6 | NICO ROSBERG     | MERCEDES BENZ |     77 |             1
       7 | JENSON BUTTON    | MCLAREN       |     76 |             2
       8 | ROMAIN GROSJEAN  | LOTUS         |     76 |             2
       9 | SERGIO PEREZ     | SAUBER        |     47 |             1
      10 | KAMUI KOBAYASHI  | SAUBER        |     33 |             2

A ordenação foi necessária neste caso porque apesar da consulta ser ordenada pela posição, a window function processa os dados de acordo com sua própria ordenação. 
Nesta última query temos o uso da função row_number, que é uma window function embutida. Funções deste tipo devem ser chamadas somente usando a sintaxe da window function (onde a cláusula OVER é obrigatória). 
Tanto as funções embutidas quanto as de agregação podem ser usadas como window functions, mas as de agregação somente se comportam como window functions quando chamadas com a cláusula OVER, caso contrário serão apenas funções de agregação regulares. Outras funções embutidas podem ser encontradas aqui, mas vou apresentar algumas: 
  • lag(coluna, deslocamento) - Usada para retornar valores de linhas anteriores onde o deslocamento determina quantas linhas antes. 
  • lead(coluna, deslocamento) - Parecida com a função anterior, mas para linhas posteriores. 
  • first_value(coluna) - Retorna o valor da primeira linha. 
  • last_value(coluna) - Retorna o valor da última linha. 
E pra finalizar, caso uma query tenha mais de uma window function com o mesmo comportamento, é possível utilizar uma cláusula WINDOW que será referenciada em OVER: 

SELECT *, row_number() OVER w AS numero_piloto
FROM formula1
WINDOW w AS (PARTITION BY equipe ORDER BY posicao)
ORDER BY posicao

Referência: http://www.postgresql.org/docs/9.1/static/tutorial-window.html

Exemplo de função no PostgreSQL

Esta semana precisei criar uma função no banco de dados PostgreSQL que me retornasse o menor valor dentre outros cotados. Nesta função eu passo dois parâmetros: codigo do material e codigo da cotação (ambos do tipo inteiro).
A função me retorna um tipo NUMERIC(20, 2), que é o tipo no qual venho trabalhando com valores monetários pois segundo a própria documentação do PostgreSQL:
It is especially recommended for storing monetary amounts and other quantities where exactness is required.
Outro detalhe importante que podemos observar é que na query, os valores contidos nos parâmetros são lidos utilizando $1 e $2 respectivamente. Vejamos:

CREATE FUNCTION func_menor_valor(codigo_material INT, codigo_cotacao INT)
RETURNS NUMERIC(20, 2) AS $$ 

 SELECT MIN(valor_total) AS menor_valor FROM view_cotacoes
 WHERE codigo_material = $1 AND codigo_cotacao = $2
 GROUP BY codigo_material;

$$ LANGUAGE SQL;

Enfim, isso é apenas um exemplo básico de função. Mais informações podem ser encontradas na documentação. Também é importante lembrar que a versão do PostgreSQL utilizada foi a 9.1.

JTable - Selecionar todo texto quando editar uma célula

Olá, hoje vou dar início a uma série de postagens rápidas. Normalmente quando abordo um assunto, sempre procuro introduzir os conceitos e explicar detalhadamente cada passo para realizar o procedimento. Mas como não estou tendo tempo pra fazer estas postagens mais elaboradas, resolvi mudar o estilo e tornar esse blog em algo parecido com um depósito de códigos. Assim mantenho o blog ativo e mantenho uma biblioteca com meus códigos pra quem quiser copiar (as vezes eu mesmo preciso de um código que fiz e não acho). 

Então vamos ao que interessa. 
Quando temos uma célula editável numa JTable e clicamos na mesma para fazer a edição, normalmente temos que selecionar e apagar o valor presente na célula para entrar com outro. Para melhorar isto e já selecionar todo conteúdo da célula quando ela entrar em edição, sobrescrevemos o método changeSelection da JTable conforme abaixo:

tbExemplo = new javax.swing.JTable()
{
    // Selecionar o conteúdo da célula quando editar
    public void changeSelection(int row, int column, boolean toggle, boolean extend)
    {
        super.changeSelection(row, column, toggle, extend);

        if (editCellAt(row, column))
        {
            Component editor = getEditorComponent();
            editor.requestFocusInWindow();

            if (editor instanceof JTextComponent){
                ((JTextComponent) editor).selectAll();          
            }
        }
    }
};

P.S.: Todos códigos foram testados por mim e são funcionais. Qualquer dúvida pergunte nos comentários.

Como limitar um JTextField

É muito comum encontrar nos fóruns especializados em Java a seguinte questão: "Como eu faço para limitar o tamanho de um JtextField?". Ocorre um certo desapontamento dos iniciantes na linguagem por não haver um método chamado setMaxLenght(x). Ou ainda com os que trabalham visualmente, procurar a propriedade no componente que limitaria o tamanho máximo de caracteres e não encontrar. Aí programadores que vieram do Delphi por exemplo dizem "O Delphi é muito melhor, blablabla...". Só digo uma coisa: Revejam seus conceitos sobre o que é Java.
A verdade é que o componente JTextField é definido sobre um modelo de documento que por padrão é chamado PlainDocument. Portanto, para para ter o controle sobre o JTextField devemos criar uma sub classe de PlainDocument, sobrescrever alguns métodos dela e trocar o modelo do JTextField para este novo.
Minha intenção não é entrar a fundo neste assunto, mas sim dar uma solução principalmente pra quem está trabalhando visualmente e não quer ficar editanto código a todo momento.
Então vamos lá, primeiramente crie a seguinte classe no seu projeto:

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

public class JTextFieldLimit extends PlainDocument {

    private int limit;
    // optional uppercase conversion
    private boolean toUppercase = false;
    
    private int maximo = 500;

    public JTextFieldLimit(int limit) {
        super();
        if (limit == 0)
            this.limit = maximo;
        else
            this.limit = limit;
    }

    public JTextFieldLimit(int limit, boolean upper) {
        super();
        if (limit == 0)
            this.limit = maximo;
        else
            this.limit = limit;
        toUppercase = upper;
    }

    @Override
    public void insertString(int offset, String str, AttributeSet attr)
            throws BadLocationException {
        if (str == null) {
            return;
        }

        if ((getLength() + str.length()) <= limit) {
            if (toUppercase) {
                str = str.toUpperCase();
            }
            super.insertString(offset, str, attr);
        }
    }
}

Esta classe não é nenhuma novidade e pode ser encontrada em vários sites pela internet (na verdade fiz algumas mudanças nela). O que ela faz é permitir a limitação de caracteres e opcionalmente fazer a conversão para maiúsculas. Mas o problema é que pra cada JTextField criado, temos que chamar o método setDocument passando essa classe e os parâmetros. Então para resolver este problema, crie o seguinte método na classe onde estão seus campos de texto:

    public void preparaForm() {

        // JTextFields limitados e com maiúsculas
        for (Component c : this.getComponents()) {
            if (c instanceof JTextField) {
                ((JTextField) c).setDocument(new JTextFieldLimit(((JTextField) c).getColumns(), true));
            }
        }
        
    }

Este método deve ser chamado sempre antes de exibir o seu formulário. Ele vai verificar cada componente do formulário e caso este seja do tipo JTextField, será configurado para utilizar o modelo de documento apresentado. Mas onde vou definir o limite de caracteres para cada campo? Observe que o primeiro parâmetro passado para o documento é: ((JTextField) c).getColumns(). Visualmente você poderá definir este número na propriedade column do componente. Mas porque essa propriedade? Isto está parecendo uma bela gambiarra... Fique tranquilo, a propriedade column é apenas um número usado para calcular o tamanho preferido para a largura do campo. Se ele for zero, o tamanho preferido será qualquer um dependendo da implementação do objeto. Isto é, você define a largura visualmente utilizando seu gerenciador de layout preferido e pouco importará o valor definido para a propriedade column.
Enfim, trabalhando desta forma você não precisará ficar escrevendo código para configurar o documento e limite de caracteres para cada campo de texto. Isto não é problema se o projeto é simples, mas se estiver desenvolvendo vários formulários, não é muito produtivo.

Observação: O método só funciona caso esteja trabalhando com JInternalFrames e caso não esteja, pode haver alguma diferença no método que retorna os componentes (this.getComponents). Se for um JDialog por exemplo, ficaria assim:  
this.getRootPane().getContentPane().getComponents().

Por hoje é só! Qualquer dúvida deixe nos comentários.

Editado em 17/12/2012:

Devido a alguns JTextFields estarem dentro de JPanels e JTabbedPanes, desenvolvi um método recursivo que poderá ser chamado dentro do método preparaForm citado anteriormente:
 
    private void preparaJTextFields(Object obj) {

        if (obj instanceof JTextField) {
            ((JTextField) obj).setDocument(new JTextFieldLimit(((JTextField) obj).getColumns(), true));
        }
       
        if (obj instanceof JPanel) {
            for (Component c : ((JPanel) obj).getComponents()) {
                preparaJTextFields(c);
            }
        }

        if (obj instanceof JTabbedPane) {
            for (Component c : ((JTabbedPane) obj).getComponents()) {
                preparaJTextFields(c);
            }
        }

    }   

E a chamada pode ser feita assim:
 
    public void preparaForm() {

        //Centraliza o JInternalFrame
        Dimension d = this.getDesktopPane().getSize();
        this.setLocation((d.width - this.getSize().width) / 2, (d.height - this.getSize().height) / 2);

        //JTextFields limitados
        preparaJTextFields(this.getContentPane());
        
    }

E aproveito ainda para mostrar como não permitir acentuação nos JTextFields, basta trocar a linha super.insertString(offset, str, attr); da classe JTextFieldLimit por:
 
//super.insertString(offset, str, attr);
            
// Sem acentos
String unaccented = Normalizer.normalize(str, Normalizer.Form.NFD);
unaccented = unaccented.replaceAll("[^\\p{ASCII}]", "");
super.insertString(offset, unaccented, attr);

Como listar todos itens de um JMenuBar

Hoje vou ensinar como retornar todos os objetos JMenuItems contidos num JMenuBar. Eventualmente você pode querer retornar também os objetos JMenu, então fica livre para fazer sua implementação do método.
O método que vou apresentar pode ser muito útil para fazer o controle de acesso da aplicação, permitindo que o item de menu seja exibido ou não de acordo com as permissões de cada usuário.
A estrutura de menus em Java é basicamente a seguinte:

JMenubar
   |
   |__ JMenu
   |       |
   |       |__JMenuItem
   |  
   |__JMenu
           |
           |__JMenu
                  |
                  |__JMenuItem

Como podemos observar, temos uma barra de menu e dentro dela temos 2 menus. No primeiro menu temos um item e no segundo temos um sub menu e dentro deste outro item. Então para recuperar todos itens, considerando que podem existir itens dentro de sub menus, devemos utilizar a recursividade.
Resumidamente, um método recursivo é um método que faz chamadas a ele mesmo. Certos problemas, como o apresentado, exigem esta abordagem, pois os elementos estão dispostos numa estrutura do tipo árvore que pode ter várias ramificações. Sendo assim, segue abaixo uma possível implementação do método que resolve este problema.

    /**
     * Retorna todos JMenuItems de um JMenuBar.
     *
     * @param obj Uma variável do tipo JMenuBar.
     * @return void
     */
    public void getMenuItems(Object obj) {

        if (obj instanceof JMenuBar) {

            for (Component c : ((JMenuBar) obj).getComponents()) {
                getMenuItems(c);
            }
        }

        if (obj instanceof JMenu) {

            for (Component c : ((JMenu) obj).getMenuComponents()) {
                getMenuItems(c);
            }
        }

        if (obj.getClass() == JMenuItem.class) {
            System.out.println("JMenuItem: " + ((JMenuItem) obj).getName());
        }

    }

Fiquem a vontade para modificar e utilizar. Até a próxima!

Como vincular dados a um JComboBox

Atualmente estou trabalhando num projeto no qual utilizo o framework JPA. Então a cada dia que passa eu acabo aprendendo algo novo sobre esta API e espero com essa experiência poder ajudar os que estão começando agora.
Hoje vou falar um pouco sobre como vincular dados que se encontram em uma tabela do banco a um componente JComboBox. A vinculação é simples de se fazer, mas a exibição da propriedade desejada é um pouco mais complicada como veremos adiante. Vou apresentar um passo a passo que traduzi do site netbeans.org, mas com algumas correções (o exemplo de código do site na parte "To render the combo box propertly" não funciona).
Vamos ao que interessa:

1. Clique com o botão direito no combo box e escolha Bind > elements (Vincular > elements). 

2. Clique em Import Data to Form (Importar dados para o Formulário...). Selecione a conexão e tabela que voce deseja vincular o combo box. Clique em OK. 

3. No combo From the Binding Source (Origem da vinculação), selecione o item que representa o result list da entidade. Por exemplo, se a entidade se chama Cliente.java, o objeto da lista deve ser gerado como clienteList. 

4. Deixe o valor em Binding Expression (Expressão de vinculação) como null e clique em OK. 

5. Clique com o botão direito no combo box novamente e escolha Bind > selectedItem (Vincular > selectedItem). Vincule a propriedade que será afetada pela selecão do usuário e clique em OK (normalmente o result list escolhido no passo 3). 

Até aqui já temos os dados vinculados ao combo box, porém se executar o programa verá que o combo mostrará dados da entidade selecionada convertida em String (objeto.toString()) e não uma propriedade específica:


Isto acontece porque, segundo o próprio site do NetBeans, a biblioteca Beans Binding não tem uma classe DetailBinding que permite especificar os valores exibidos no JComboBox.
Uma forma de resolver isto é escrever um código personalizado para mostrar a propriedade que queremos: 

1. Selecione o combo box. 

2. Na aba Propriedades da Janela de Propriedades, selecione a propriedade render. 

3. Clique no botão ... 

4. No combo na parte superior do editor de propriedades, selecione Custom Code (Código personalizado). 

5. Na área de texto, entre com um código parecido com o que segue abaixo onde Usuario é o nome da sua entidade e getUsuario é a propriedade que retorna o valor a ser vinculado. 

new DefaultListCellRenderer() {

    @Override
    public Component getListCellRendererComponent(
            JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        if (value instanceof Usuario) {
            Usuario usuario = (Usuario) value;
            setText(usuario.getUsuario());
        }
        return this;
    }
}
Feito isso, o valor selecionado deverá aparecer no combo:


Uma outra abordagem mais simples para resolver o problema é sobrescrever o método toString da entidade em questão:

    @Override
    public String toString() {
        return getUsuario();
    } 

Basta fazer com que o método retorne o valor que você deseja mostrar no combo box. É isso aí! Espero que tenham gostado.

Introdução aos Design Patterns

Padrões de projeto são sempre bem vindos quando o assunto é Engenharia de Software. Existe uma variedade deles, mas pra este post teremos apenas uma introdução para entender melhor o que são e pra que servem. Vamos começar com uma definição resumida da Wikipédia:
"Um Padrão de Projeto (Design Pattern) descreve uma solução geral reutilizável para um problema recorrente no desenvolvimento de sistemas orientados a objetos. Não é um código final, é um modelo de como resolver um problema que pode ser usado em muitas situações diferentes. Os Padrões de Projeto definem as relações e interações entre as classes ou objetos, sem especificar os detalhes das classes ou objetos envolvidos, ou seja, estão num nível de generalidade mais alto."
Os conceitos de padrões de projeto vem sendo discutidos ao longo dos anos e se tornou mais popular depois do lançamento do livro Design Patterns: Elements of Reusable Object-Oriented Software em 1995. Então os autores do livro (Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides) ficaram conhecidos como sendo a Gangue dos Quatro (Gang of Four) ou simplesmente GoF.
Outra definição interessante veio de Christopher Alexander cujo seus estudos contribuíram para a utilização de padrões geométricos e matemáticos no urbanismo e arquitetura:
“Cada padrão descreve um problema que ocorre freqüentemente em seu ambiente, e então descreve o núcleo de uma solução para o problema, de maneira que você possa usar esta solução um milhão de vezes, sem fazê-la do mesmo jeito duas vezes.”
Mas o que um cara urbanista e arquiteto está fazendo aqui? Você já deve ter percebido que padrões de projeto não estão ligados apenas a engenharia de software mas também a outras engenharias, como a civil por exemplo. Imagina se você entraria num prédio cujo projeto foi na base do "codifica-conserta"? Você se sentiria seguro? Neste caso é ainda mais crítico porque vidas estariam em risco. De qualquer forma precisamos de boas soluções para nossos projetos e ter padrões já experimentados e aprovados é uma ótima ajuda. Foi com base nos estudos de Alexander que cientistas da computação desenvolveram padrões de projetos usados na engenharia de software.
Dando continuidade ao que interessa, atualmente os padrões são divididos em 3 categorias:
  • Criacionais: Diz respeito a criação de objetos;
  • Comportamentais: Diz respeito a maneira que objetos e classes distribuem suas responsabilidades para realizar uma tarefa
  • Estruturais: Diz respeito a maneira como classes e objetos são organizados estruturalmente
Existem 23 padrões de projetos oficiais que se encaixam nestas categorias. Digo oficiais porque eles são os padrões documentados pela GoF. Existem diversos outros padrões e qualquer pessoa pode criar um, desde que tenha a solução para um problema comum e siga o modelo de documentação. Para documentar um padrão, deve-se definir alguns elementos:
  • Nome e classificação
  • Problema
  • Solução
  • Consequências
  • Casos de uso
Estes são os principais elementos a serem documentados. Pode não parecer importante mas, sem uma forma de documentar os padrões, eles não existiriam ou poderiam até existir mas seriam um monte de diagramas e textos soltos. Foi o próprio Alexander que definiu em seu livro Notes on the Synthesis of Form, The Timeless Way of Building e A Pattern Language, como os padrões devem ser descritos.

Bom pessoal, por hoje é só e espero falar mais sobre alguns padrões em posts futuros.

Introdução ao J2ME

Olá! No post de hoje pretendo mostrar como iniciar no desenvolvimento de aplicações para dispositivos móveis. Vou mostrar como preparar o ambiente de desenvolvimento e também como fazer um pequeno programa.


O Ambiente

A primeira coisa a se fazer é baixar o Sun Java Wireless Toolkit, também conhecido como J2ME Wireless Toolkit. Esse kit de ferramentas é utilizado para criar aplicações que serão executadas em dispositivos móveis com a tecnologia Java. A instação do toolkit não tem segredo, é só na base do next next finish...
O próximo passo é instalar a IDE. Para o exemplo que vou mostrar, vamos utilizar o Eclipse 3.5. Espero que já esteja familiarizado com ele.
E para integrar o toolkit e a IDE vamos utlizar o plugin EclipseME. A instalção do plugin é simples:

  1. Feche o Eclipse caso esteja aberto;
  2. Descompacte o arquivo do plugin;
  3. Recorte as pastas features e plugins que foram descompactadas;
  4. Cole as pastas na pasta onde encontra-se o eclipse.exe (vai perguntar se quer mesclar as pastas, responda sim);

Aliás, esses passos servem para instalação offline de qualquer plugin para Eclipse.
Pronto, agora só mais algumas configurações e o ambiente estará preparado. Como essas configurações dependem da criação de um novo projeto, vamos pra próxima parte!


Criando um projeto

Abra o Eclipse. Clique com o botão direito dentro do Package Explorer e New > Other...


Na janela que abrir navegue por J2ME e escolha J2ME Midlet Suite, então clique em Next:


Agora escolha o nome do projeto e clique em Next novamente:


Neste momento será solicitado que configure o dispositivo que será usado para executar a aplicação. Então clique em Manage Devices...


Outra janela será aberta, porém ainda sem nenhum dispositivo. Clique em Import...


Agora clique em Browse... e selecione a pasta WTK2.5.2_01 (ou outra versão disponível) que se encontra na raiz do disco (geralmente em C:, só não estará neste local se você tiver mudado durante a instalação). Clique em OK:


De volta na janela Import Devices clique em Refresh. Uma pesquisa será feita na pasta do toolkit e alguns dispositivos devem aparecer na lista:


Clique em Finish e voltamos na janela de gerenciamento com um dispositivo padrão selecionado:


Somente um dispositivo pode ser selecionado. Deixe o padrão e clique em OK. Então voltamos na janela de propriedades do projeto, mas agora com os dois combobox preenchidos:


Em device podemos selecionar DefaultGrayPhone ou DefaultColorPhone. Deixe o padrão que é DefaultGrayPhone e clique em Finish.

Pronto, agora temos um projeto criado e podemos iniciar o desenvolvimento.


Programando em J2ME

Vamos criar nosso primeiro programa. Abra a pasta do projeto no Package Explorer e clique com o botão direito no pacote src, escolha New > Other...


Dentro de J2ME escolha J2ME Midlet e clique em Next. Digite o nome HelloWorld e clique em Finish. Então será criada uma classe parecida com esta:
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class HelloWorld extends MIDlet {

public HelloWorld() {
}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}

protected void pauseApp() {
}

protected void startApp() throws MIDletStateChangeException {
}

}

O que é MIDlet? MIDlet é como são chamados os aplicativos em J2ME. Aplicativos Java para web são Applets, para servidores são Servlets e para dispositivos móveis são MIDlets. Na verdade as coisas são um pouco mais complicadas, mas a grosso modo podemos definir assim.
Observe que nossa classe estende MIDlet e por isso foram herdados 3 métodos:

  • startApp: É chamado quando se inicia a aplicação (equivalente ao main em J2SE). Também é chamado quando a aplicação é despausada.
  • pauseApp: É chamado quando o aplicativo fica em segundo plano (por exemplo, quando o usuário atende uma ligação)
  • destroyApp: É executado quando o aplicativo é finalizado e pode ser usado para liberar recursos, entre outras coisas.

Também temos o construtor que é uma espécie de método chamado quando a classe é instanciada (você já deve saber disto). Mas vamos ao que interessa. Declare a seguinte variável de instância:
Display d;
Deve aparecer um erro devido ao import, apenas peça que o Eclipse corrija pra você.
Cada MIDlet deve ter uma referência a uma variável do tipo Display. Este objeto pode recuperar informações sobre a tela (ex. quantidade de cores) e também é usado para exibir objetos.
No J2ME temos uma superclasse chamada Displayable da qual todos os outros objetos estendem. Objetos como Alert, Form ou TextBox estendem de Displayable, portanto eles SÃO Displayable. O objeto Display pode exibir qualquer tipo de Displayable. Veja como é a hierarquia destas classes:



Somemte um objeto Displayable pode ser exibido por vez. Então usamos o objeto Display para decidir o que será exibido na tela. Para fazer isto primeiro temos que pegar o Display atual do MIDlet. Faça a chamada dentro do construtor:
d = Display.getDisplay(this);

Veja que o construtor configura a variável d com o atual Display do MIDlet. Display não foi instanciado, em vez disto usamos o método estático para selecionar o Display atual. Agora dentro de startApp coloque o seguinte código:
Alert mensagem = new Alert("Primeiro MIDlet", "Hello World!", null, AlertType.INFO);
d.setCurrent(mensagem);

Nesse trecho de código criamos um objeto do tipo Alert e depois solicitamos que fosse exibido na tela. O código final ficou assim:
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class HelloWorld extends MIDlet {

Display d;

public HelloWorld() {
d = Display.getDisplay(this);
}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}

protected void pauseApp() {
}

protected void startApp() throws MIDletStateChangeException {
Alert mensagem = new Alert("Primeiro MIDlet", "Hello World!", null, AlertType.INFO);
d.setCurrent(mensagem);
}

}

Para executar clique com o botão direito na classe, Run As > Emulated J2ME MIDlet. O resultado deverá ser algo parecido com isto:


Bom, por hoje é só. Isto foi apenas uma pequena introdução ao J2ME. Espero que apartir daí você possa pesquisar mais sobre o assunto e desenvolver aplicações mais sofisticadas. Eu acredito que qualquer linguagem que aprendemos iniciando no famoso Hello World, temos chances de prosperar. Mas se você tentar aprender alguma linguagem e seu primeiro programa não for o Hello World, certamente irá fracassar. Enfim, isto é apenas uma superstição boba mas eu prefiro não contrariar :)

Bons estudos!!

Garbage Collector - Java

Olá! Hoje vou falar sobre um assunto muito interessante: Coleta de lixo. Uma pergunta muito comum quando falamos de coleta de lixo é "How many objects are eligible for the garbage collector?". Como saber quantos objetos são elegíveis a serem descartados? Vamos descobrir como responder esta questão e também aprender sobre um conceito importante conhecido por Island of Isolation ou Ilha de Isolamento. Antes de continuar vamos ver algo sobre atribuições.


Atribuições

Quando atribuímos um objeto a uma variável, o objeto não é armazenado na variável em si, mas sim no heap. O heap é um espaço na memória onde todos os objetos ficam alocados durante a execução de um programa. As variáveis de instância também residem no heap.
Quando atribuímos um primitivo a uma variável, ela realmente armazena o padrão de bits que representa o valor sendo atribuído a ela, mas se tratando de objetos, a variável armazena o endereço do objeto no heap. Por isto esse tipo de variável que faz referência a objetos é chamada de variável de referência. Abaixo temos um exemplo de código e em seguida uma representação visual:
Object objeto1 = new Object();
Object objeto2 = new Object();
Object objeto3 = new Object();

O resultado disto é:

Garbage Collector

O propósito da coleta de lixo é descartar os objetos que não podem mais ser acessados. Isto é uma forma de gerenciar a memória e evitar que os programas fiquem sem espaço para continuar sua execução.
Quando algum objeto no heap não pode mais ser mais alcançado por ninguém (por nenhum thread ativo), ele é elegível a coleta de lixo. Isto pode acontecer de duas formas:

  1. A variável passa a fazer referência a null:
objeto1 = null;

O resultado disto é:


A outra forma é fazer a variável referenciar outro objeto:
objeto1 = objeto2;

O resultado é:

Qualquer uma das formas tornaria o objeto1 qualificado para coleta.
Suponhamos que o objeto1 esteja "solto" no heap. Posso resolver descartá-lo quando quiser? Não. Somente a JVM pode decidir quando isso deve ser feito, mas como bom programador você pode recomendar que a coleta seja feita. E para fazer isto basta usar a instrução:
System.gc();

Não há garantia de que a coleta seja feita, mas geralmente a JVM atende o pedido.
É importante lembrar que um objeto só existirá no heap caso ele tenha sido inicializado (pela keyword new). Portanto a seguinte declaração não cria nenhum objeto no heap:
Object o = null;

Se o objeto nunca foi criado então não existe objeto a ser coletado.


O Método finalize()

A classe Object possui um método chamado finalize(). Como todas classes são filhas de Object, implícita ou explicitamente, todas herdam tal método. Este método é chamado pelo coletor de lixo quando não há mais referências ao objeto. Mas lembre-se que: se não há garantia que a coleta seja executada quando você solicitar, finalize() pode não ser executado.
O método finalize() de Object não executa nenhuma ação, mas pode ser sobrescrito para que faça algo antes que o objeto seja coletado. Pode ser feito uma liberação de recursos, como fechar um arquivo, finalizar uma conexão, etc. Também podemos usá-lo para salvar o objeto da coleta, daqui a pouco explico melhor.
O método finalize() dá ao programador o poder sobre a vida e a morte dos objetos. Para entender melhor essa afirmação, veja dois conceitos importantes sobre o método:

  • finalize() só é chamado uma única e exclusiva vez para qualquer objeto durante a coleta.
  • Chamar finalize() pode salvar o objeto da coleta.

A primeira afirmação significa que se você chamar finalize() por conta própria, ele não será chamado novamente quando a coleta for executada. E a segunda significa que podemos inserir algum código em finalize() para fazer que o objeto tenha uma nova referência e não seja coletado. Mas ressucitar um objeto não é tão simples assim. Abaixo temos um pequeno exemplo de como isto é feito:
public class Objeto{

// Sobrescreve finalize
public void finalize(){
// Da uma nova referência para objeto1
objeto1 = new Objeto();
}

// Objeto que vai ressuscitar
static Objeto objeto1 = new Objeto();

public static void main(String[] args) {

objeto1 = null; // Elegível a coleta

System.gc(); // Chama finalize como consequência

// Aqui faz o thread dormir um pouco
// enquanto a coleta é feita (espero que seja)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

objeto1.go(); // Não deveria causar um NullPointerException?

}

void go(){
System.out.println("Estou vivo!");
}
}

Veja que quando main começar a ser executado, objeto1 faz referência a null, se tornando elegível a coleta. Então é solicitado que a coleta seja feita (reze pra JVM aceitar o pedido). Logo após fazemos o thread main descançar um pouco... Há muitas incertezas neste momento. Desejamos que a coleta seja feita e que isso aconteça enquanto main tira um cochilo. Primeiro não sabemos se a JVM vai nos atender e muito menos se isso será feito num segundo. Mas com sorte tudo vai correr bem. Nos meus testes a JVM respondeu bem e a saída foi:
Estou vivo!

Uma parte importante do código é o cochilo de main. Como main e o coletor de lixo rodam em threads separados, não sabemos quem vai tomar o processador primeiro. (mais sobre threads aqui). Pelo que eu percebi, o coletor sempre fica pra depois e isso é compreensível, porque senão ele poderia prejudicar o desempenho do programa. Se o trecho de código onde main fica em espera for removido, antes que o coletor seja executado o método go() será chamado e causará um NullPonterException. Isso mostra que chamar o coletor não garante que ele seja executado naquele exato momento.
Continuando... Agora vamos praticar um pouco. Veja uma questão que tem grandes chances de aparecer no exame SCJP:
Given:
interface Animal {
void makeNoise();
}

class Horse implements Animal {
Long weight = 1200L;

public void makeNoise() {
System.out.println("whinny");
}
}

public class Icelandic extends Horse {
public void makeNoise() {
System.out.println("vinny");
}

public static void main(String[] args) {
Icelandic i1 = new Icelandic();
Icelandic i2 = new Icelandic();
Icelandic i3 = new Icelandic();
i3 = i1;
i1 = i2;
i2 = null;
i3 = i1;
} //Line 14
}
When line 14 is reached, how many objects are
eligible for the garbage collector?

A. 0
B. 1
C. 2
D. 3
E. 4
F. 6

Analisando o código vemos que existem algumas coisas querendo nos desviar do foco principal: Interface e sobrescrição de métodos. Horse implementa o médodo de Animal corretamente e declara uma variável do tipo Long (Long não é long, isso fará diferença no resultado). Depois Icelandic, que é subclasse de Horse, sobrescreve o método makeNoise corretamente. Tirando essas distrações, entramos na execução do código:
Icelandic i1 = new Icelandic();
Icelandic i2 = new Icelandic();
Icelandic i3 = new Icelandic();

A melhor coisa a se fazer é desenhar o esquema:

Note que cada objeto Icelandic possui uma variável weight, herdada de Horse.
Então continuando no programa temos i3 fazendo referência a i1:
i3 = i1;


Neste momento já temos 2 objetos qualificados pra coleta. Porque 2? Lembra da variável Long? Pois então, Long é uma classe wrapper e portanto é um objeto no heap (variáveis de instância residem do heap). Continuando no progama temos:
i1 = i2;


Aqui não temos muitas mudanças, nenhum objeto é qualificado. i1 faz referência ao objeto de i2, mas o objeto que i1 referenciava ainda é acessível através de i3. Continuando:
i2 = null;


Neste momento i2 passou a referenciar null, mas o objeto que era referenciado por ele anteriormente ainda é referenciado por i1. E finalmente temos:
i3 = i1;


Novamente i3 faz referencia a i1, só que agora i1 faz referência ao objeto que era referenciado por i2. Isso torna o objeto (que antes era referenciado por i1 e depois por i3), qualificado para coleta.
Ufa! Mas enfim quantos objetos se tornaram elegíveis a coleta? Quatro. Dois objetos do tipo Icelandic e dois do tipo Long. Geralmente esse tipo de pergunta inclui essas variáveis de instância que são tipos de classe wrapper só pra confudir.
Um modo de saber quantos objetos foram descartados é sobrescrever finalize() para que mostre alguma mensagem. Veja o código:
public class Icelandic {
Long x;

public void finalize() {
System.out.println("tchau");
}

public static void main(String[] args) {
Icelandic i1 = new Icelandic();
Icelandic i2 = new Icelandic();
Icelandic i3 = new Icelandic();

i3 = i1;
i1 = i2;
i2 = null;
i3 = i1;

System.gc();
}
}

A saída deste código é:
tchau
tchau

Quatro objetos foram coletados. Note que a classe tem uma variável de instância Long.


Ilhas de Isolamento

Ilhas de isolamento ocorrem quando variáveis de instância fazem referência umas as outras formando um circuito fechado. Então se as referência a seus referências forem anuladas, os objetos perderão seu vínculo com elas e terão referências apenas uns dos outros. Analise o código:
public class Icelandic {

// Variável de instância
Icelandic i;

public void finalize() {
System.out.println("tchau");
}

public static void main(String[] args) {

Icelandic i1 = new Icelandic();
Icelandic i2 = new Icelandic();

// Variáveis de instância apontam uma para outra
i1.i = i2;
i2.i = i1;

// As variáveis locais perdem a referência
i1 = null;
i2 = null;

System.gc();
}
}

O cenário inicial deste código é representado abaixo:


Depois das declarações a variavel de instância i de i1 (i1.i) faz referência a i2. E a variável de instância i de i2 (i2.i) faz referência a i1:


Logo depois i1 e i2 passam a referenciar null:


Consegue sentir como é dramático este cenário? Embora os objetos tenham variáveis que fazem referência entre eles, nenhum thread ativo pode alcançá-los. Não importa se eles podem acessar um ao outro, de fora ninguém pode acessá-los. Portanto estão qualificados a coleta de lixo.

Conclusão

Sei que esse assunto é um pouco confuso, mas basta fazer os desenhos pra exergar a solução. Bons estudos e até a próxima!