Categoria: Java (Página 1 de 4)

Automatizando o acesso a uma página web a.k.a. como eu “burlei” um sistema de agendamentos online

Neste artigo mostrarei, de maneira resumida, como automatizar a navegação em uma página web para fins diversos.

Mais especificamente: como eu fiz para conseguir um horário melhor num sistema de agendamento online através de um pequeno código que verifica novos horários vagos automaticamente. 😀

Leia mais

Aprendendo a programar, para iniciantes

Tendo um blog com razoável acesso por estudantes, tendo treinado e ajudado diversos estagiários e desenvolvedores juniores, participando ativamente de comunidades como o Stack Overflow em Português e tendo lecionado sobre desenvolvimento de sistemas web em duas faculdades, conheço muito bem as dificuldades enfrentadas por muitos daqueles que estão lutando para dar seus primeiros passos com programação.

Neste artigo, quero lhe dar algumas dicas para seu desenvolvimento profissional, baseando-me em minha própria experiência.

Leia mais

Problemas com “merge” no JPA?

java_ee

Muitos desenvolvedores enfrentam grandes dificuldades em trabalhar com JPA (Java Persistence API). Vejo que a maioria dessas dificuldades tem raiz na falta de compreensão de certos detalhes do funcionamento do JPA.

Analisarei neste artigo um caso que gera muita confusão, que é o uso do método merge().

Leia mais

Jericho Selector, ou jQuery para Java

CSS3

É com grande satisfação que venho anunciar a publicação da primeira versão estável (stable release) da biblioteca Jericho Selector.

O Jericho Selector é uma extensão para a conhecida biblioteca de leitura e manipulação de HTML Jericho HTML Parser que permite selecionar elementos de um documento HTML ao estilo do jQuery, ou seja, utilizando seletores CSS.

Por quê Jericho? Diferente do jsoup, por exemplo, ele permite alterar documents HTML mantendo a formatação original. Outras bibliotecas reescrevem o documento inteiro. De qualquer forma, eu gosto de ter opções! 😉

Jericho Selector é totalmente livre, sob a licença MIT.

Como usar

O Jericho Selector está disponível no Repositório Central do Maven, então basta configurar o seu projeto com a seguinte dependência:

<dependency>
    <groupId>br.com.starcode.jerichoselector</groupId>
    <artifactId>jericho-selector</artifactId>
    <version>1.0.1-RELEASE</version>
</dependency>

Depois, faça a importação do método estático $ que pode ser usado como ponto de entrada para uso do Jericho Selector:

import static br.com.starcode.jerichoselector.jerQuery.$;

Então você poderá realizar consultas da seguinte forma:

$(html, "p.my-text")

O que foi feito

Antes de iniciar a implementação do Jericho Selector, tive que implementar um interpretador (parser) completo de seletores CSS. Para isso, implementei outra biblioteca que chamei de parCCSer. Ela foi baseada na especificação oficial do W3C para CSS3 e cobre praticamente todos os aspectos da especificação, exceto alguns detalhes que se aplicariam apenas no contexto de um navegador e também algo relacionado a suporte de caracteres UTF-8. Ela também está sob a licença MIT.

O Jericho Selector então utiliza a árvore de objetos gerada pelo parCCser, assim como a API do Jericho HTML Parser para consultar os elementos de um documento HTML com base em um dado seletor.

Todas as implementações possuem cobertura em testes unitários de mais de 90% do código, sem contar os casos excepcionais que o plugin de cobertura não consegue analisar.

O que precisa ser feito

Nas próximas semanas pretendo adicionar ao Jericho Selector funcionalidades de uma API fluente, análogas ao jQuery, para que seja possível realizar operações com os elementos recuperados usando lambdas e outros recursos. Métodos como closest, parentsUntil, find, each são meus primeiros alvos.

Outro ponto a melhorar é o desempenho da biblioteca. Alguns seletores específicos podem ter sua execução otimizada utilizando cache ou métodos específicos do Jericho HTML Parser como getAllElementsByClass.

O que você pode fazer

Reporte qualquer problema e sugira novas funcionalidades!

Código-fonte

Confira o código e maiores detalhes na minha página do GitHub:

https://github.com/utluiz/jericho-selector/

Jericho Selector, a.k.a jQuery for Java

CSS3

I’m pleased to announce the first stable release of Jericho Selector.

Jericho Selector is an extension to the known library Jericho HTML Parser that allows you to select elements from an HTML document just like you do with jQuery, using CSS selectors.

Why Jericho? Different from jsoup, it allows you to modify the document keeping the original formatting. Other libraries rewrite the entire document. Anyway, I like to have a choice! 😉

Jericho Selector is completely free. It uses MIT license.

How to use

Jericho Selector is available at Maven Central Repository, so you just need to add the following dependency to your project:

<dependency>
    <groupId>br.com.starcode.jerichoselector</groupId>
    <artifactId>jericho-selector</artifactId>
    <version>1.0.1-RELEASE</version>
</dependency>

Import the static method $ that is the entry point for Jericho Selector:

import static br.com.starcode.jerichoselector.jerQuery.$;

Then you can query HTML elements just like jQuery:

$(html, "p.my-text")

What has been done

Before implementing Jericho Selector, I had to implement a full CSS parser. In order to do that, I created another library called parCCSer. It was based on the official W3C CSS3 specification and covers almot all of the specification, except some details that are valid only in the context of a browser and also something related to UTF-8 support that I considered not entirely necessary. It’s also under MIT license.

Jericho Selector then uses the object tree generated by parCCser, as the Jericho HTML Parser API, to query the HTML document elements given a CSS selector.

All implementitions are covered by unit testes above 90%, without taking in account excepcional cases that the plugin are not able to analyse.

What is coming

In the next weeks I aim to add some fluent API features to Jericho Selector, similar to jQuery, so you can make some operations using lambdas, for example. Methods like closest, parentsUntil, find, each are my priorities.

Another point to improve is the performance. Specific selectors can be optimized using cache or specific Jericho HTML Parser methods like getAllElementsByClass.

What you can do

Report any problem and suggest new features!

Souce code

You can get check out the source code from GitHub account:

https://github.com/utluiz/jericho-selector/

Entendendo as Configurações de Memória da Máquina Virtual Java (JVM)

java

Talvez você já tenha ouvido falar que em Java não é preciso preocupar-se com gerenciamento de memória, ou ainda que em Java a memória nunca acaba por causa do Garbage Collector (GC).

Bem, estou aqui para lhe dizer que tudo isso é besteira. Depois de erros banais como NullPointerException e SqlException, os problemas mais sérios e comuns que tenho visto ao longo dos anos são justamente relacionados à memória, isto é: OutOfMemoryError.

Seja por falta de configuração adequada ou implementação equivocada. Código ruim pode alocar objetos indeterminadamente sem remover as referências a eles, de forma que nem o GC dá um jeito. Porém, neste artigo vamos focar na parte de configuração.

O erro OutOfMemoryError manifesta-se em duas formas principais: Java Heap Space e PermGen Space. Ambos são, respectivamente, relacionados a trechos de memória dinâmico e permanente do Java.

A maioria das aplicações sérias passa, em algum momento, por ajustes na quantidade de memória disponível tanto para a seção dinâmica quanto para a seção permanente.

Vamos ver o que é tudo isso e como efetuar a configuração.

Heap Space (Memória dinâmica)

O Heap Space é o local onde o Java armazena suas variáveis e instâncias de objetos. Este espaço de memória pode aumentar ou diminuir ao longo do tempo, dinamicamente, de acordo com a quantidade de objetos usados no programa.

Para definir quanta memória pode ser usada pelo Java, é preciso informar parâmetros ao executar a JVM. Caso você não saiba, executar o java cria uma nova instância da JVM, portanto qualquer parâmetro passado a este programa é uma configuração para essa JVM.

Já dei uma breve introdução sobre isso no artigo Instalando, Configurando e Usando o Eclipse Kepler, porém cada programa pode ter seu modo de configurar. Se você está usando um programa qualquer ou um servidor de aplicação (JBoss, Websphere, Weblogic, Glassfish, Tomcat, etc.), procure na documentação do mesmo onde fica a configuração dos parâmetros de memória.

Neste artigo, vou exemplificar uma chamada diretamente ao comando java via linha de comando.

Quantidade máxima de memória dinâmica

O parâmetro Xmx define a quantidade máxima de memória dinâmica que a Máquina Virtual Java pode alocar para armazenar esses objetos e variáveis.

É importante definir uma quantidade máxima de memória razoavelmente maior do que a média usada na aplicação para evitar não só OutOfMemoryError como também escassez de memória.

Trabalhar no limite da memória disponível faz o Garbage Collector executar muitas vezes para coletar objetos não usados e isso resulta em pausas indesejadas no programa durante a varredura dos objetos.

Quantidade inicial de memória dinâmica

O Xms define a quantidade inicial de memória dinâmica alocada no início da JVM.

É importante verificar quanto sua aplicação usa em média e definir um valor próximo disso. Dessa forma, não ocorrerão muitas pausas para alocação de memória, resultando em um desempenho maior de inicialização até o ponto em que a aplicação está executando num patamar estável.

PermGen Space (Memória permanente)

O Java também possui a PermGen Space, outra parte da memória chamada de “estática” ou “permanente”, utilizada para carregar suas classes (arquivos .class), internalizar Strings (string pool), entre outras coisas.

Como regra geral, a memória permanente não pode ser desalocada. Isso implica que, por exemplo, se sua aplicação tem muitos Jars e carrega muita classes, em algum momento poderá ocorrer um erro de PermGen Space. Isso é comum com quem abusa de frameworks com modelos de classes “pesados”, sendo um cenário comum a turminha JSF, PrimeFaces, Hibernate e JasperReports.

O erro ocorre porque não é possível ao Java carregar novas classes quando não há espaço e não é mais possível aumentar a memória permanente, pois não dá para descartar classes já carregadas para dar lugar a novas.

Da mesma forma que na memória Heap, é possível informar à JVM a quantidade de memória permanente máxima que pode ser alocada e a quantidade de memória permanente inicialmente alocada.

O parâmetro XX:MaxPermSize define a quantidade máxima de memória permanente que a JVM pode utilizar e o parâmetro XX:PermSize define o tamanho inicial alocado.

Os mesmos princípios mencionados anteriormente para definição de quanta memória Heap deve ser reservada podem ser aplicados aqui.

Gerações de Objetos

A seção dinâmica da memória, Heap Space, é ainda dividida entre “nova” e “velha” geração.

A seção da “nova geração” (Young Generation) é reservada para a criação de objetos. É o berçário do Java. Estatisticamente, os objetos recém-criados são mais suscetíveis a serem coletados.

Após ter algum tempo de vida, o GC pode promover os objetos que não forem coletados para a seção da “velha geração” (Old Generation), onde ficam os objetos com mais tempo de vida e com menos chances de serem coletados.

Os parâmetros XX:MaxNewSize e XX:NewSize definem, respectivamente, a quantidade máxima de memória reservada para os objetos da “nova geração” e a quantidade inicial de memória para tais objetos.

Note que a memória da “nova geração” fica dentro do espaço do Heap Space, portanto cuidado com os valores definidos.

Nunca tive a necessidade de modificar esses parâmetros e creio que na grande maioria dos casos você também não vai precisar. Use-os somente em caso de última necessidade.

Ilustração da divisão de memória da JVM

A imagem a seguir ilustra os conceitos apresentados no artigo:

java-memory

A memória total alocada pelo Java é a soma do Heap Space e PermGen Space. O espaço para a Young Generation fica dentro do Heap.

Exemplo

Cada programa Java tem sua forma de configuração, porém ao executar diretamente o comando java, você pode simplesmente passar os parâmetros da seguinte forma:

java -Xmx2g -Xms1024m -XX:MaxPermSize=1g -XX:PermSize=512m

No exemplo acima, definimos:

  1. -Xmx2g: Quantidade máxima de memória dinâmica de 2 Gigabytes
  2. -Xms1024m: Quantidade inicial de memória dinâmica de 1024 Megabytes ou 1 Gigabyte
  3. -XX:MaxPermSize=1g: Quantidade máxima de memória permanente de 1 Gigabyte
  4. -XX:PermSize=512m: Quantidade inicial de memória permanente de 512 Megabytes

Considerações

As configuração de memória apresentadas neste artigo resolvem 90% ou mais dos problemas cotidianos relacionados à memória com a JVM. Entretanto, este é apenas o tipo de ajuste mais básico que existe.

Você pode encontrar informações bem mais detalhadas, incluindo parâmetros de configurações específicas, em artigos voltados para otimização da JVM. Por exemplo:

Por fim, é importante lembrar que não existe uma fórmula mágica para determinar valores de memória para um sistema. Antes, deve ser levado em conta o tipo de uso, recursos utilizados, quantidade de usuários e capacidade do hardware.

Não creio que isso possa ser determinado objetivamente por alguma fórmula, mas através de um processo dedutivo conduzido por um profissional experiente.


Este artigo foi baseado em minha resposta no Stack Overflow em Português.

Pensando TDD

Ontem, dia 8 de outubro de 2014, ministrei uma palestra sobre TDD na Faculdade de Tecnologia de Sorocaba, durante a 21ª edição da Semana de Tecnologia, evento anual promovido pela faculdade para aproximar os alunos das práticas mais modernas do mercado.

TDD or not TDD?

Test-Driven Development (Desenvolvimento Orientado a Testes), ou simplesmente TDD, é uma disciplina de desenvolvimento ágil que, resumidamente, prioriza testes automatizados ainda na fase de projeto com o objetivo de obter software de qualidade, isto é, com código limpo e que funcione.

Indo direto ao ponto, TDD não é sobre TDD, mas sobre a disciplina e prática de como torna-se um bom Engenheiro ou Artesão de Software, dependendo da metáfora que você adota.

Embora esse assunto não seja novidade, é de muita importância para a maior parte dos profissionais de TI brasileiros, sem contar as empresas, pois infelizmente estamos de forma geral muito defasados com as melhores práticas reconhecidas no restante do mundo.

Veja, a redescoberta do TDD já tem mais de uma década e a maioria dos profissionais que encontro nunca viu um projeto que realmente aplicasse a metodologia. E o problema não é somente com relação ao TDD. Parece que somente agora empresas estão começando a aceitar certas práticas geralmente adotadas por processos ágeis, tais como testes automatizados, integração contínua, reuniões diárias, programação em par, etc.

A palestra, intitulada “Pensando TDD”, também não é necessariamente sobre TDD, mas sobre a necessidade de praticarmos o desenvolvimento de software como uma disciplina diária e não com a “lei do mínimo esforço” que, com o tempo, gera um déficit técnico tão grande que não resta alternativa a não ser literalmente jogar o software no lixo.

Material

Os slides da apresentação estão disponíveis no SlideShare e no Google Drive. Muitos deles são conceituais. Para ver comentários e informações adicionais sobre cada um, use a função de notas do SlideShare ou baixe o PPT.

Lembrando que a licença dos documentos que tenho produzido é Creative Commons. Você é livre para usar e modificar o material livremente. Só peço que cite a fonte original (meu blog, por exemplo).

Se tiver dúvidas sobre algum ponto, escreve para mim.

Agradecimentos

Por fim, gostaria de agradecer à organização do evento e à diretoria da Fatec de Sorocaba, assim como à GFT, empresa onde trabalho, por incentivar a participação em eventos e comunidades de tecnologia.

Obrigado também a todos os alunos que se mostraram muito receptivos a novos conhecimentos e suportaram a hora e meia comigo falando. Sucesso para todos vocês!

Trabalhando com Genéricos em Java

cup_generics Trabalhar com genéricos é uma abordagem muito produtiva para reutilização de código.

Exemplo: DAOs genéricos

Implementações genéricas de DAOs (Data Access Object) é algo que vejo repetidas vezes em diferentes projetos e equipes. Implementando alguns métodos genéricos, é possível evitar a criação de dezenas de classes semelhantes num projeto. Se bem planejado e usado, técnicas como essa economizam tempo e facilitam a manutenção.

Em Java, podemos criar uma classe base com um tipo genérico para representar nosso DAO. Considere o seguinte trecho de código:

public class BaseDao<T, ID> {
    public T find(ID entityId) { /*...*/ return null; }
    public void insert(T entity) { /*...*/ }
    public int update(T entity) { /*...*/ return 0; }
    public void delete(ID entityId) { /*...*/ }
}

Há duas principais abordagens para usar uma classe genérica como essa.

Herança

Podemos estender a classe BaseDao, gerando subclasses específicas que estendem as funcionalidades padrão.

Exemplo:

public class ClienteDao<Cliente, Integer> { ... }

public class LoginDao<Login, String> { ... }

Uso direto

Quando não há necessidade de especialização, é possível usar diretamente a classe base.

Exemplo:

BaseDao<Cliente, Integer> baseDao = new BaseDao<>();

BaseDao<Login, Integer> loginDao = new BaseDao<>();

Identificando o tipo genérico

Em determinadas situações é desejável descobrir em tempo de execução qual é o tipo genérico de uma classe.

Continuando em nosso exemplo do DAO, poderíamos ler alguma anotação ou configuração associada à classe Cliente quando o DAO fosse deste tipo.

Como fazer isso nos dois tipos de DAOs mencionadosacima?

Herança

Considere o código:

public class BaseDao<T, ID> {

    final private Class<T> entityType;

    public BaseDao() {
        entityType = (Class<T>) ((ParameterizedType)
                getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    //...

}

Supondo que existem subclasses, tal como ClienteDao extends BaseDao<Cliente, Integer>, então o atributo entityType receberá a referência para a classe Cliente.

Trechos de código semelhantes a esse estão disponíveis em várias postagens e respostas do Stack Overflow, mas é algo que pode ser considerado “inseguro”, pois há cenários em que isso simplesmente não funciona.

Uso direto

O código do construtor, como exibido acima, não funciona numa instanciação direta de BaseDao.

O seguinte exemplo vai gerar um erro:

 BaseDao<Login, Integer> loginDao = new BaseDao<>(); //não vai instanciar o objeto

Qual o motivo?

Type erasure

Primeiro, é preciso entender que a informação do tipo genérico de uma variável é removida em tempo de execução.

Por exemplo:

List<String> lista = new ArrayList<String>();

O compilador “garante” que você só consiga adicionar objetos do tipo String dessa lista, mas quando sua classe é executada na JVM, não há qualquer verificação quanto ao tipo.

Sim, você pode dar um cast inseguro de List<String> para List e então adicionar outro tipo de objeto. O compilador vai aceitar, afinal ele “confia” em você.

É impossível para a instância lista saber o seu próprio tipo genérico, já que no fim das contas só existe uma única classe ArrayList, o tipo genérico é apenas um mecanismo de segurança para o desenvolvedor.

Li uma vez que manter o type satefy implicaria em um overhead muito grande para a máquina virtual, pois o Java precisaria praticamente criar uma cópia da classe List para cada tipo genérico usado no programa, de modo a ter literalmente uma versão da classe para cada tipo.

Quando é possível recuperar o tipo genérico

Repare no método getGenericSuperclass(). Ele retorna os tipos que a classe atual define para os parâmetros genéricos da superclasse imediata. Algo análogo ocorre com o getGenericInterfaces().

Isso significa que se você tiver uma subclasse assim:

class ClienteDao extends BaseDao<Cliente, Integer> { ... }

O código funcionaria perfeitamente, pois você está definindo um tipo para o parâmetro genérico da superclasse. Isso funciona porque o tipo genérico não está apenas numa variável, mas declarando de forma estática (fixa) na própria definição da classe.

Mas como você instancia diretamente o GenericDao, não há uma generic superclass e, portanto, a exceção sendo lançada.

Basta usar herança?

Usar sempre herança como no exemplo com extends é uma saída, mas muitos não recomendam porque há chances do código “quebrar” com novos erros estranhos.

Essa é uma possibilidade, pois como a própria documentação do método getGenericSuperclass() afirma, ele só retorna o tipo genérico implementado na superclasse imediata do seu DAO. Então se houver uma hierarquia diferente de exatamente dois níveis, o código vai falhar.

Recomendação

A recomendação que vai funcionar de forma mais simples e direta é passar a classe desejada por parâmetro para o construtor do GenericDao.

Exemplo:

public class GenericDao<T extends Serializable> {

    private final Class<T> entityType;

    public GenericDao(Class<T> entityType) {
        this.entityType = entityType;
    }

    ...

}    

Muito mais simples, certo?

O maior inconveniente é a chamada verbosa de criação da classe:

BaseDao<Cliente> clienteDao = new BaseDao<Cliente>(Cliente.class);

Na verdade, a partir do Java 7 poderíamos omitir o tipo Cliente da classe instanciada:

BaseDao<Cliente> clienteDao = new BaseDao<>(Cliente.class);

Melhorou um pouco, mas podemos fazer melhor. Que tal criar um método factory?

Exemplo:

public class BaseDao<T> {

    final private Class<T> entityType;

    public static <X> BaseDao<X> getInstance(Class<X> entityType) {
        return new BaseDao<X>(entityType);
    }

    private BaseDao(Class<T> entityType) {
        this.entityType= entityType;
    }

    ...

}

E a criação da instância da classe fica assim:

GenericDao<Cliente> clienteDao = BaseDao.getInstance(Cliente.class);

Alguns podem achar demais, porém considero este um design mais claro.

Considerações

Certamente há formas ainda mais flexíveis de trabalhar com genéricos usando Injeção de Dependência, Spring Beans e outros, mas por agora, estes fogem ao escopo do artigo.

O importante é compreender que Generics é um recurso importante e flexível da plataforma Java, com aplicações diretamente em nosso dia-a-dia.

Nuances sobre serialização de objetos com herança em Java

serialization

A serialização de objetos em Java é um recurso muito importante e útil em aplicações específicas. Diversas APIs do Java beneficiam-se dela, por exemplo, para chamadas de métodos remotos (RMI) e migração de sessões em Servlets de aplicações web clusterizadas.

Serializar um objeto consiste em converter os valores de uma instância em uma sequência (stream) de bytes (dados binários) de forma que o estado do objeto possa ser posteriormente recuperado.

Tornar uma classe serializável em Java é muito simples: basta implementar a interface java.io.Serializable.

Porém, nem sempre os detalhes são tão óbvios. E quanto a atributos herdados? O que ocorre se a superclasse não for serializável?

Tratando-se de herança, existem algumas nuances quanto ao que será ou não incluído na serialização. O ObjectOutputStream irá serializar todas as classes da hierarquia que são marcados com java.io.Serializable e seus descendentes. Desses, os atributos não estáticos, não transientes e que também são marcados com a referida interface serão serializados.

Meio complicado, não? Vamos ver um…

Exemplo prático (com erro)

Primeiro, duas classes que serão referenciadas, uma serializável e outra não:

class OutraClasseSerializavel implements Serializable {
    int outroValorSerializavel;
}

class OutraClasse {
    int outroValor;
}

Segundo, uma classe “pai” e uma “filha”:

class Pai {
    OutraClasse outraClassePai;
    OutraClasseSerializavel outraClasseSerializavelPai;
    int valorPai;
}

class Filha extends Pai implements Serializable {
    OutraClasse outraClasseFilha;
    OutraClasseSerializavel outraClasseSerializavelFilha;        
    int valorFilha;
}

Note que as duas classes possuem valores e referências para classes serializáveis e não serializáveis.

O que acontece se tentarmos serializar a classe Filha? Ocorre um java.io.NotSerializableException por causa da referência à classe não serializável OutraClasse na classe Filha.

Exemplo prático

Se removermos a referência à classe não serializável da classe Filha, o erro não ocorre:

class Filha extends Pai implements Serializable {
    OutraClasseSerializavel outraClasseSerializavelFilha;        
    int valorFilha;
}

Testando e analisando o resultado

Vamos fazer um teste:

Filha filha = new Filha();

//valores da classe filha
filha.valorFilha = 11;
filha.outraClasseSerializavelFilha = new OutraClasseSerializavel();
filha.outraClasseSerializavelFilha.outroValorSerializavel = 33;

//valores da classe pai
filha.valorPai = 22;
filha.outraClasseSerializavelPai = new OutraClasseSerializavel();
filha.outraClasseSerializavelPai.outroValorSerializavel = 44;
filha.outraClassePai = new OutraClasse();
filha.outraClassePai.outroValor = 55;

//serializa
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("filha.out")));
oos.writeObject(filha);
oos.close();

//recupera classe serializada
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("filha.out")));
Filha filhaRecuperada = (Filha) ois.readObject();
ois.close();

Finalmente, vamos imprimir e analisar os valores retornados…

Atributo primitivo na classe serializável

System.out.println(filhaRecuperada.valorFilha);

Saída:

11

Obviamente, o atributo valorFilha é devidamente serializado e recuperado porque faz parte da classe serializável e é um tipo primitivo.

Referência à classe serializável em uma classe também serializável

System.out.println(filhaRecuperada.outraClasseSerializavelFilha.outroValorSerializavel);

Saída:

33

O atributo outraClasseSerializavelFilha também foi serializado corretamente, assim como seu valor, porque é uma referência a uma classe serializável a partir da classe Filha que é serializável.

Atributo primitivo na classe Pai, que não é serializável

System.out.println(filhaRecuperada.valorPai);

Saída:

0

Observamos agora que, embora não ocorram erros, atributos estáticos em uma superclasse não serializável não são serializados.

Referência à classes serializáveis e não serializáveis em uma superclasse não serializável

System.out.println(filhaRecuperada.outraClassePai);
System.out.println(filhaRecuperada.outraClasseSerializavelPai);

Saída:

null

null

E, finalmente, observamos que referências a classes de qualquer tipo (serializáveis ou não) em uma superclasse não serializável também serão excluídas da serialização.

Considerações

Estender uma classe para torná-la serializável não funciona, pois como foi visto o processo de serialização ignora as superclasses não serializáveis e um erro ocorre ao incluirmos um atributo não serializável.

Mas existe alguma solução? A resposta é sim!

Solução: readObject e writeObject

A documentação da classe java.io.Serializable aponta alguns métodos que devem ser implementados para que você possa alterar “manualmente” a forma como o Java serializa e desserializa um objeto.

As assinaturas são:

private void writeObject(java.io.ObjectOutputStream out)
    throws IOException
private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException;

Exemplo de implementação

Segue uma implementação básica dos métodos readObject() e writeObject() na classe Filha que resolvem o problema da serialização tanto do atributo inteiro da superclasse quanto das referências a outros objetos:

class Filha extends Pai implements Serializable {

    int valorFilha;
    transient OutraClasse outraClasseFilha;
    OutraClasseSerializavel outraClasseSerializavelFilha;

    private void readObject(java.io.ObjectInputStream stream)
            throws IOException, ClassNotFoundException {
        valorFilha =  stream.readInt();
        outraClasseFilha = new OutraClasse();
        outraClasseFilha.outroValor = stream.readInt();
        outraClasseSerializavelFilha = (OutraClasseSerializavel) stream.readObject();

        valorPai = stream.readInt();
        outraClassePai = new OutraClasse();
        outraClassePai.outroValor = stream.readInt();
        outraClasseSerializavelPai = (OutraClasseSerializavel) stream.readObject();
    }

    private void writeObject(java.io.ObjectOutputStream stream)
            throws IOException {
        stream.writeInt(valorFilha);
        stream.writeInt(outraClasseFilha.outroValor);
        stream.writeObject(outraClasseSerializavelFilha);

        stream.writeInt(valorPai);
        stream.writeInt(outraClassePai.outroValor);
        stream.writeObject(outraClasseSerializavelPai);
    }

}

Então fazemos um novo teste:

Filha filha = new Filha();

//valores da classe filha
filha.valorFilha = 11;
filha.outraClasseSerializavelFilha = new OutraClasseSerializavel();
filha.outraClasseSerializavelFilha.outroValorSerializavel = 22;
filha.outraClasseFilha = new OutraClasse();
filha.outraClasseFilha.outroValor = 33;

//valores da classe pai
filha.valorPai = 44;
filha.outraClasseSerializavelPai = new OutraClasseSerializavel();
filha.outraClasseSerializavelPai.outroValorSerializavel = 55;
filha.outraClassePai = new OutraClasse();
filha.outraClassePai.outroValor = 66;

//serializa
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("c.out")));
oos.writeObject(filha);
oos.close();

//recupera classe serializada
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("c.out")));
Filha filhaRecuperada = (Filha) ois.readObject();
ois.close();

//valores da classe filha
System.out.println(filhaRecuperada.valorFilha);
System.out.println(filhaRecuperada.outraClasseSerializavelFilha.outroValorSerializavel);
System.out.println(filhaRecuperada.outraClasseFilha.outroValor);

//valores da classe pai
System.out.println(filhaRecuperada.valorPai);
System.out.println(filhaRecuperada.outraClasseSerializavelPai.outroValorSerializavel);
System.out.println(filhaRecuperada.outraClassePai.outroValor);

E obtemos a saída:

11
22
33
44
55
66

Todos os atributos foram salvos!

Conclusão

Embora o Java não resolva toda a questão da serialização automagicamente, ele nos fornece um mecanismo prático e flexível para resolver isso, pois permite controlar completamente como o objeto é salvo e recuperado do arquivo. Por outro lado, isso exige a codificação manual de cada elemento, na ordem correta.

Este artigo é baseado na minha resposta no Stack Overflow em Português

Créditos da imagem do sapo: GridGain Blog

Entenda definitivamente como usar o Apache POI: user model, shiftRows e clonagem de célula

Trabalhar com arquivos Excel é uma tarefa comum em empresas que fornecem software que gerenciam dados e valores de negócio. Mas nem sempre é fácil.

Há algum tempo publiquei um artigo sobre como adicionar segurança em arquivos do Excel com o POI. Também estou trabalhando num pequeno framework para gerar arquivos Excel com base em templates baseado no MVEL e no POI.

Deu pra notar que estou mexendo bastante com isso, né? Vamos a algumas dicas para quem está começando com o Apache POI.

User models – o que são e como usar

A primeira dica é algo que você deve saber. A biblioteca POI possui três diferentes APIs para trabalhar com arquivos Excel:

  1. HSSF: classes para trabalhar com o formado binário mais antigo do Excel (XLS).
  2. XSSF: classes para trabalhar com o formato mais novo do Excel baseado em XML (XLSX).
  3. SS: supermodelo que engloba tanto HSSF quanto XSSF.

Cada uma dessas APIs representa um user model, isto é, um modelo de dados para que o código “cliente” (o seu código) possa manipular um tipo específico de arquivo.

Ao trabalhar com classes do pacote hssf, você somente poderá ler e criar arquivos XLS. O pacote xssf somente lê e cria arquivos XLSX.

Enfim, use sempre as classes do pacote ss (supermodel) pois elas servem para os dois tipos de arquivos do Office. Caso contrário, você terá que fazer implementações distintas para trabalhar com arquivos binários e XML.

Desmistificando o método shiftRows

Uma das funcionalidades que implementei no desenvolvimento do framework foi a replicação automática de linhas. Também implementei um operador condicional para incluir ou não linhas no arquivo de acordo com variáveis. Para tanto, foi necessário implementar código para incluir e remover intervalores de linhas do arquivo.

Estudando a API, encontrei a função shiftRows da classe Sheet. Este método é frequentemente mal usado pelos programadores que não entendem bem como ele funciona.

Ok, a ideia do termo shift seria para deslocar as linhas. Mas o método possui os três parâmetros:

void shiftRows(int startRow, int endRow, int n)

Em geral, o entendimento inicial que as pessoas tem é que esse comando seria similar à funcionalidade do Excel de replicar linhas. Por exemplo, você seleciona as linhas de 1 a 3 e replica n vezes. Considere o comando:

sheet.shiftRows(0, 2, 5);

Será que esse comando copia as três primeiras linhas da planilha cinco vezes? A resposta é não.

Na verdade, o método shiftRows apenas desloca as linhas, alterando o número das linhas selecionadas. O comando acima simplesmente incrementa em 5 o número da linha das três primeiras linhas da planilha.

Após a chamada ao método, a linha 0 (primeira linha) irá para a posição 5 (sexta linha), a linha 1 irá para a posição 6 (sétima linha) e a linha 2 irá para a posição 7 (oitava linha).

Confuso? Pense da seguinte forma:

Pegue as linhas de 0 a 2 e mova cinco posições para “baixo”.

O mesmo raciocínio poderia ser aplicado caso o terceiro parâmetro fosse negativo. No caso, isso significa mover as linhas para “cima”. Enfim, este parâmetro diz em quanto as linhas selecionadas devem ser deslocadas, para “baixo” ou para “cima”.

Problemas com o shiftRows

É preciso tomar cuidado ao usar o método para não acabar com uma planilha corrompida. Isso já ocorreu várias vezes com várias pessoas que conheço.

O problema é que, ao deslocar as linhas com shiftRows, elas podem sobrepor outras linhas que já existem naquela posição. E isso pode causar vários comportamentos estranhos, desde a perda de alguns dados até uma planilha totalmente corrompida.

Imagine, no exemplo anterior, se já existissem as linhas 5, 6 ou 7.

Aparentemente, o POI não tem um tratamento para isso. Sinceramente, não verifiquei se isso foi corrigido em versões recentes, mas não é raro lidarmos com versões anteriores em projetos já existentes.

Portanto, sempre que for incluir linhas, mova todas as linhas até o final da planilha.

Exemplo:

sheet.shiftRows(
    posicaoParaInserir, 
    sheet.getLastRowNum(), 
    quantidadeLinhasNovas);

Para remover linhas, primeiramente é preciso remover manualmente os objetos do tipo Row existentes no intervalo, depois deslocar todas as linhas de baixo para cima.

Veja como ficou uma implementação minha para remover um intervalo de linhas:

private void removeRows(Sheet sheet, int position, int count) {
    for (int i = 0; i < count; i++) {
        Row row = sheet.getRow(position + i);
        if (row != null) {
            sheet.removeRow(row);
        }

    }
    sheet.shiftRows(position + count, sheet.getLastRowNum(), -count);
}

Explicando novamente, o código acima faz o seguinte:

  1. Remove as linhas existentes no intervalo começando em position até a quantidade count. Note que é necessário verificar se o objeto Row existe para cada linha, porque a implementação é de uma lista esparsa, isto é, as linhas que não tem informação não existem na memória.

  2. Desloca as linhas que estão acima do intervalo para cima, aplicando um valor negativo ao tamanho do intervalo. Em outras palavras, se eu quiser excluir as linhas 6 a 10, todas as linhas a partir da linha 11 terão o seu número subtraído de 5.

Clonar células

No Excel é simples duplicar o conteúdo com formatação. No POI esta tarefa demanda um certo trabalho. Não há um comando pronto, sendo necessário copiar o conteúdo, o estilo e alguns outros atributos de cada célula.

Fiz uma implementação baseada neste artigo do Stack Overflow:

private void cloneRow(Row sourceRow, Row newRow) {

    // Loop through source columns to add to new row
    for (Cell oldCell : sourceRow) {

        // Grab a copy of the old/new cell
        Cell newCell = newRow.createCell(oldCell.getColumnIndex());

        // Copy style from old cell and apply to new cell
        CellStyle newCellStyle = workbook.createCellStyle();
        newCellStyle.cloneStyleFrom(oldCell.getCellStyle());
        newCell.setCellStyle(newCellStyle);

        // If there is a cell comment, copy
        if (oldCell.getCellComment() != null) {
            newCell.setCellComment(oldCell.getCellComment());
        }

        // If there is a cell hyperlink, copy
        if (oldCell.getHyperlink() != null) {
            newCell.setHyperlink(oldCell.getHyperlink());
        }

        // Set the cell data type
        newCell.setCellType(oldCell.getCellType());

        // Set the cell data value
        switch (oldCell.getCellType()) {
            case Cell.CELL_TYPE_BLANK:
                newCell.setCellValue(oldCell.getStringCellValue());
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                newCell.setCellValue(oldCell.getBooleanCellValue());
                break;
            case Cell.CELL_TYPE_ERROR:
                newCell.setCellErrorValue(oldCell.getErrorCellValue());
                break;
            case Cell.CELL_TYPE_FORMULA:
                newCell.setCellFormula(oldCell.getCellFormula());
                break;
            case Cell.CELL_TYPE_NUMERIC:
                newCell.setCellValue(oldCell.getNumericCellValue());
                break;
            case Cell.CELL_TYPE_STRING:
                newCell.setCellValue(oldCell.getRichStringCellValue());
                break;
        }
    }

    // If there are are any merged regions in the source row, copy to new row
    Sheet sheet = sourceRow.getSheet();
    for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
        CellRangeAddress cellRangeAddress = sheet.getMergedRegion(i);
        if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) {
            CellRangeAddress newCellRangeAddress = new CellRangeAddress(
                    newRow.getRowNum(),
                    (newRow.getRowNum() +
                            (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())),
                    cellRangeAddress.getFirstColumn(),
                    cellRangeAddress.getLastColumn());
            sheet.addMergedRegion(newCellRangeAddress);
        }
    }

}

Considerações finais

A biblioteca POI é bem flexível e permite manipular arquivos de planilha de forma relativamente simples e eficiente.

Porém, determinadas funcionalidades podem representar um desafio até para o entendimento dela. Espero que este artigo, dentro dos cenários propostos, lhe possa ser útil. 😀

Página 1 de 4

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.