Cinco coisas que você não sabia sobre... as ferramentas do dia a dia do Java

Ferramentas do Java para coisas do dia a dia, como análise, sincronização e som

Algumas ferramentas Java™ desafiam a categorização e são frequentemente classificadas como "coisas que funcionam". Este artigo sobre cinco coisas oferece uma coleção de ferramentas que você ficará feliz em ter, mesmo se terminar armazenando-as na gaveta da cozinha.

Ted Neward, Principal, Neward & Associates

Ted Neward photoTed Neward é o diretor da Neward & Associates, onde ele dá consultoria, orientação, ensina e faz apresentações sobre Java, .NET, Serviços XML e outras plataformas. Ele reside perto de Seattle, Washington.



28/Out/2010

Sobre esta série

Então, você acha que possui conhecimento sobre programação Java? A verdade é que a maioria dos desenvolvedores tem apenas algum conhecimento sobre a plataforma Java, aprendendo o suficiente para concluir a tarefa. Nesta série,Ted Neward examina a fundo a funcionalidade da plataforma Java para revelar fatos pouco conhecidos que podem ajudar você a superar até mesmo os desafios de programação mais difíceis.

Há muitos anos, quando estava no ensino médio e pensando em seguir uma carreira de escritor de ficção, eu assinava uma revista chamada Writer's Digest. Um artigo, eu lembro, era uma coluna sobre "pedaços de cadeia de caractere muito pequenos para salvar", a forma do colunista de descrever uma gaveta de bugigangas da cozinha cheia de coisas que simplesmente não pertenciam a nenhum outro lugar. Aquela frase permaneceu em minha mente e parece uma descrição adequada de meu tópico sobre este assunto, minha coluna final nesta série (pelo menos por enquanto).

A plataforma Java é cheia de "pedaços de cadeia de caractere" — ferramentas de linha de comandos e bibliotecas úteis que a maioria dos desenvolvedores Java nunca nem saberá que existem, que dirá usá-las. Muitas delas não se encaixam em nenhuma das categorias de programação que abordei até o momento na série cinco coisas, mas experimente-as mesmo assim: algumas poderão conquistar um espaço em sua gaveta de cozinha virtual.

1. StAX

Quando o XML primeiro apareceu no radar da maioria dos desenvolvedores Java, perto da virada do milênio, havia duas abordagens básicas para analisar arquivos XML. O analisador SAX é essencialmente uma máquina de estado gigante de eventos disparados para o desenvolvedor por uma série de métodos de retorno de chamada. O analisador DOM coloca todo o documento XML em memória e o divide em uma série de objetos discretos, que são vinculados para formar uma árvore. A árvore descreve toda a representação XML Infoset do documento. Ambos analisadores têm suas desvantagens: o SAX era de muito baixo nível para ser usado, mas o DOM era muito caro, particularmente para arquivos XML grandes — a árvore toda se transformava em um grande amontoado de miudezas.

Felizmente, os desenvolvedores Java acharam uma terceira forma de analisar arquivos XML, modelando o documento como uma série de "nós" que poderiam ser retirados um de cada vez do fluxo do documento, examinados e manipulados ou descartados. Este "fluxo" de "nós" ofereceu um meio termo entre o SAX e o DOM, e foi chamado de "Streaming API for XML", ou StAX. (A sigla foi usada para diferenciar a nova API do analisador SAX original, que tinha o mesmo nome.) O analisador StAX foi posteriormente incluído no JDK e reside no pacote javax.xml.stream .

Usar o StAX é bem direto: instancie um XMLEventReader, aponte-o para um arquivo XML bem formado e, a seguir, "retire" os nós (normalmente em um ciclo while ) um de cada vez para exame. Por exemplo, você poderia listar todos os alvos em um script de construção Ant conforme a Listagem 1:

Listagem 1. Simplesmente aponte o StAX para o alvo
import java.io.*;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.util.*;

public class Targets
{
    public static void main(String[] args)
        throws Exception
    {
        for (String arg : args)
        {
            XMLEventReader xsr = 
                XMLInputFactory.newInstance()
                    .createXMLEventReader(new FileReader(arg));
            while (xsr.hasNext())
            {
                XMLEvent evt = xsr.nextEvent();
                switch (evt.getEventType())
                {
                    case XMLEvent.START_ELEMENT:
                    {
                        StartElement se = evt.asStartElement();
                        if (se.getName().getLocalPart().equals("target"))
                        {
                            Attribute targetName = 
                                se.getAttributeByName(new QName("name"));
                            // Found a target!
                            System.out.println(targetName.getValue());
                        }
                        break;
                    }
                    // Ignore everything else
                }
            }
        }
    }
}

O analisador StAX não substitui todo o código SAX e DOM existente, mas definitivamente pode tornar algumas tarefas mais fáceis. Ele é particularmente útil em tarefas onde você não precisa toda a estrutura em árvore do documento XML.

Note que o StAX também tem uma API de nível mais baixo no XMLStreamReadercaso os objetos de evento ainda sejam de nível muito alto para serem trabalhados. E, apesar de talvez não ser tão útil quanto os leitores, o StAX também tem um XMLEventWriter e, de forma similar, uma classe XMLStreamWriter para saída XML.


2. ServiceLoader

Os desenvolvedores Java frequentemente desejam desacoplar o conhecimento necessário para usar um componente do conhecimento necessário para criar um. Isto é tipicamente alcançado criando uma interface descrevendo as ações que o componente pode realizar e usando algum tipo de intermediário para criar as instâncias do componente. Muitos desenvolvedores usam a estrutura Spring para este fim, mas há outra abordagem que é ainda mais leve do que um contêiner Spring.

A classe ServiceLoader do java.util pode ler um arquivo de configuração guardado em um arquivo JAR e encontrar implementações de uma interface e, a seguir, tornar essas implementações disponíveis como uma lista de objetos a partir da qual escolher. Se precisar de um componente servo pessoal para realizar suas tarefas, por exemplo, poderia adquirir um com o código na Listagem 2:

Listagem 2. IPersonalServant
public interface IPersonalServant
{
    // Process a file of commands to the servant
    public void process(java.io.File f)
        throws java.io.IOException;
    public boolean can(String command);
}

O método can() é o que permite decidir se a implementação de servo pessoal sendo oferecida atende a seus requisitos. O ServiceLoader na Listagem 3 é, essencialmente, uma lista de IPersonalServants que atendem a esses requisitos:

Listagem 3. Este IPersonalServant servirá?
import java.io.*;
import java.util.*;

public class Servant
{
    public static void main(String[] args)
        throws IOException
    {
        ServiceLoader<IPersonalServant> servantLoader = 
            ServiceLoader.load(IPersonalServant.class);

        IPersonalServant i = null;
        for (IPersonalServant ii : servantLoader)
            if (ii.can("fetch tea"))
                i = ii;

        if (i == null)
            throw new IllegalArgumentException("No suitable servant found");
        
        for (String arg : args)
        {
            i.process(new File(arg));
        }
    }
}

Presumindo que há uma implementação daquela interface, você obterá a Listagem 4:

Listagem 4. Jeeves implementa IPersonalServant
import java.io.*;

public class Jeeves
    implements IPersonalServant
{
    public void process(File f)
    {
        System.out.println("Very good, sir.");
    }
    public boolean can(String cmd)
    {
        if (cmd.equals("fetch tea"))
            return true;
        else
            return false;
    }
}

A única coisa que resta é configurar o arquivo JAR contendo essas implementações para ser reconhecido pelo ServiceLoader— o que pode ser traiçoeiro. O JDK quer que o arquivo JAR tenha um diretório META-INF/services que contém um arquivo de texto cujo nome corresponda ao nome de classe da interface completamente qualificado — , o que, neste caso, seria META-INF/services/IPersonalServant. O conteúdo daquele nome de classe da interface é nomes de implementação, um por linha, como mostra a Listagem 5:

Listagem 5. META-INF/services/IPersonalServant
Jeeves   # comments are OK

Felizmente, o sistema de compilação Ant (desde a versão 1.7.0) inclui uma abreviatura de código de serviço para a tarefa jar que torna isto relativamente fácil, como mostra a Listagem 6:

Listagem 6. Compilação Ant do IPersonalServant
    <target name="serviceloader" depends="build">
        <jar destfile="misc.jar" basedir="./classes">
            <service type="IPersonalServant">
                <provider classname="Jeeves" />
            </service>
        </jar>
    </target>

A partir daqui, é trivial invocar o IPersonalServant para que execute os comandos. No entanto, analisar e executar esses comandos pode ser complicado. O que me leva ao meu próximo "pequeno pedaço de cadeia de caractere".


3. Scanner

Muitos utilitários Java ajudarão você a construir um analisador e as linguagens funcionais existentes tiveram algum sucesso na construção de bibliotecas de funções de análise (combinadores de analisador). Mas, se só o que tiver para analisar é um arquivo de valores separados por vírgulas, ou uma série de arquivos de texto delimitados por espaços? A maioria dos utilitários é exagerada para este propósito e, ainda assim, o String.split() é muito pouco. (Em se tratando de expressões regulares, lembre-se do ditado que diz: "Você tinha um problema e tentou usar expressões regulares para solucioná-lo. Agora, você tem dois problemas.")

A classe Scanner da plataforma Java poderia ser sua melhor chance nesses casos. Destinada a ser usada como analisadora de texto leve, Scanner fornece uma API relativamente direta para separar texto estruturado em partes de tipo forte. Imagine, por assim dizer, diversos comandos do tipo DLS (de um romance da série Discworld de Terry Pratchett) dispostos em um arquivo de texto como o da Listagem 7:

Listagem 7. Tarefas para Igor
fetch 1 head
fetch 3 eye
fetch 1 foot
attach foot to head
attach eye to head
admire

Você ou, neste caso, um servidor pessoal chamado Igor, poderia facilmente analisar esta série de comandos ruins usando o Scanner, como mostra a Listagem 8:

Listagem 8. Tarefas para Igor, analisadas pelo Scanner
import java.io.*;
import java.util.*;

public class Igor
    implements IPersonalServant
{
    public boolean can(String cmd)
    {
        if (cmd.equals("fetch body parts"))
            return true;
        if (cmd.equals("attach body parts"))
            return true;
        else
            return false;
    }
    public void process(File commandFile)
        throws FileNotFoundException
    {
        Scanner scanner = new Scanner(commandFile);
        // Commands come in a verb/number/noun or verb form
        while (scanner.hasNext())
        {
            String verb = scanner.next();
            if (verb.equals("fetch"))
            {
                int num = scanner.nextInt();
                String type = scanner.next();
                fetch (num, type);
            }
            else if (verb.equals("attach"))
            {
                String item = scanner.next();
                String to = scanner.next();
                String target = scanner.next();
                attach(item, target);
            }
            else if (verb.equals("admire"))
            {
                admire();
            }
            else
            {
                System.out.println("I don't know how to " 
                    + verb + ", marthter.");
            }
        }
    }
    
    public void fetch(int number, String type)
    {
        if (parts.get(type) == null)
        {
            System.out.println("Fetching " + number + " " 
                + type + (number > 1 ? "s" : "") + ", marthter!");
            parts.put(type, number);
        }
        else
        {
            System.out.println("Fetching " + number + " more " 
                + type + (number > 1 ? "s" : "") + ", marthter!");
            Integer currentTotal = parts.get(type);
            parts.put(type, currentTotal + number);
        }
        System.out.println("We now have " + parts.toString());
    }
    
    public void attach(String item, String target)
    {
        System.out.println("Attaching the " + item + " to the " +
            target + ", marthter!");
    }
    
    public void admire()
    {
        System.out.println("It'th quite the creathion, marthter");
    }
    
    private Map<String, Integer> parts = new HashMap<String, Integer>();
}

Presumindo que Igor esteja registrado no ServantLoader, é fácil trocar a chamada can() para algo mais apropriado e reutilizar o código Servant anterior, mostrado na Listagem 9:

Listagem 9. O que Igor faz
import java.io.*;
import java.util.*;

public class Servant
{
    public static void main(String[] args)
        throws IOException
    {
        ServiceLoader<IPersonalServant> servantLoader = 
            ServiceLoader.load(IPersonalServant.class);

        IPersonalServant i = null;
        for (IPersonalServant ii : servantLoader)
            if (ii.can("fetch body parts"))
                i = ii;

        if (i == null)
            throw new IllegalArgumentException("No suitable servant found");
        
        for (String arg : args)
        {
            i.process(new File(arg));
        }
    }
}

Uma implementação DSL real obviamente faria mais do que somente imprimir para o fluxo de saída padrão. Deixarei para você os detalhes de rastrear quais partes são anexadas às quais (e ao fiel Igor, é claro).


4. Timer

As classes java.util.Timer e TimerTask fornecem formas convenientes e relativamente simples de executar tarefas de forma periódica ou uma vez só, atrasadas:

Listagem 10. Executar mais tarde
import java.util.*;

public class Later
{
    public static void main(String[] args)
    {
        Timer t = new Timer("TimerThread");
        t.schedule(new TimerTask() {
            public void run() {
                System.out.println("This is later");
                System.exit(0);
            }
        }, 1 * 1000);
        System.out.println("Exiting main()");
    }
}

O Timer tem uma série de sobrecargas schedule() , indicando se uma dada tarefa será executada uma vez ou repetidamente e precisa de uma instância de TimerTask para ser disparado. O TimerTask é essencialmente um Runnable (ele o implementa, na verdade), mas também vem com dois métodos adicionais: cancel(), para interromper a tarefa, e scheduledExecutionTime(), para retornar uma aproximação de quando a tarefa será disparada.

Note que Timer , no entanto, cria um encadeamento não daemon para disparar as tarefas em segundo plano, portanto na Listagem 10 preciso interromper a VM por meio de uma chamada para System.exit(). Em um programa de execução longa, o Timer provavelmente seria melhor se fosse criado como um encadeamento de daemon (usando os construtores que obtêm um parâmetro de operador boleano para indicar o status do daemon), de forma que não manteria a VM em execução.

Não há nada de mágico nesta classe, mas ela permite que sejamos mais claros sobre a intenção de disparar tarefas em segundo plano. Ela também pode economizar algumas linhas de código Thread e serve como um ScheduledExecutorService leve (para aqueles que não estão prontos para entrar no pacote inteiro java.util.concurrent ).


5. JavaSound

Apesar de não aparecer com frequência em um aplicativo do lado do servidor, sons podem servir como um sentido "passivo" para os administradores — e é um bom material para um trote. Apesar de ter demorado a aparecer na plataforma Java, a API JavaSound eventualmente entrou para o núcleo da biblioteca de tempo de execução, colocada nos pacotes javax.sound *— um pacote para arquivos MIDI e um para arquivos de áudio de amostra (como o formato de arquivo muito comum .WAV).

O "hello world" do JavaSound é reproduzir um clipe, mostrado na Listagem 11:

Listagem 11. Toque novamente, Sam
public static void playClip(String audioFile)
{
    try
    {
        AudioInputStream inputStream =
            AudioSystem.getAudioInputStream(
                this.getClass().getResourceAsStream(audioFile));
        DataLine.Info info = 
            new DataLine.Info( Clip.class, audioInputStream.getFormat() );
        Clip clip = (Clip) AudioSystem.getLine(info);
        clip.addLineListener(new LineListener() {
                public void update(LineEvent e) {
                    if (e.getType() == LineEvent.Type.STOP) {
                        synchronized(clip) {
                            clip.notify();
                        }
                    }
                }
            });
        clip.open(audioInputStream);        
        
        clip.setFramePosition(0);

        clip.start();
        synchronized (clip) {
            clip.wait();
        }
        clip.drain();
        clip.close();
    }
    catch (Exception ex)
    {
        ex.printStackTrace();
    }
}

A maioria disto é bastante direta (ou, pelo menos, o quanto o JavaSound consegue ser direto). A primeira etapa é criar um AudioInputStream à volta do arquivo a reproduzir. Para manter este método o mais livre de contexto possível, retiramos o arquivo como um InputStream do ClassLoader que carregou a classe. (O AudioSystem também obtém um Arquivo ou uma cadeia de caractere, se o caminho exato para o arquivo de som já for previamente conhecido.) Uma vez que isto tenha sido feito, o objeto DataLine.Info é alimentado para o AudioSystem para obter um Clipe, que é a forma mais fácil de reproduzir um clipe de áudio. (Outras abordagens oferecem maior controle sobre o clipe — como obter um SourceDataLine— , mas elas são exageradas para uma simples "reprodução".)

A partir daqui, deverá ser tão simples quanto chamar open() à volta do AudioInputStream. ("Deveria" quer dizer se você não encontrar o erro discutido na próxima seção.) Chame start() para iniciar a reprodução e drain() para aguardar até que a reprodução esteja concluída, e close() para liberar a linha de áudio. A reprodução ocorre em um encadeamento separado, portanto uma chamada para stop() suspenderá a reprodução e uma chamada subsequente para start() recomeçará de onde a reprodução foi pausada; use setFramePosition(0) para voltar ao início.

Sem som?

Um pequeno erro foi arquivado com o release do JDK 5: Em algumas plataformas com clipes de áudio curtos, o código aparentemente é executado se m problemas, mas... não há som. Aparentemente, o reprodutor de mídia dispara um evento STOP antes do que deveria nestes clipes curtos. (Consulte a seção Recursos para obter um link para a página do erro.)

Este erro foi marcado como "não corrigiremos", mas a alternativa é bastante direta: registre um LineListener para escutar por eventos STOP e, quando um ocorrer, chame notifyAll() no objeto do clipe. A seguir, no código "responsável pela chamada", aguarde até que o clipe termine (e notifyAll() seja chamado) chamando wait(). Em plataformas onde o erro não estiver presente, essas etapas serão redundantes, mas no Windows® e em algumas distribuições do Linux® , elas serão a diferença entre um "rosto feliz do desenvolvedor" e um "rosto bravo do desenvolvedor".


Conclusão

Pronto, agora você possui várias ferramentas. Sei que muitos de vocês estão cientes das ferramentas que abordei aqui, mas minhas viagens profissionais me dizem que existem muitos que se beneficiarão desta introdução, ou de uma lembrança de ferramentas há muito esquecidas na gaveta entulhada.

Vou fazer uma breve pausa nesta série para dar a outros contribuidores uma oportunidade de contribuir com suas áreas de conhecimento. Mas não se preocupe, eu voltarei, seja nesta série ou em uma nova aventura de cinco coisas que explore outras áreas de interesse. Até lá, eu o incentivo a continuar investigando a plataforma Java para descobrir segredos ocultos que o tornarão um programador mais produtivo.

Recursos

Aprender

Obter produtos e tecnologias

  • StAX, a Streaming API for XML, é uma API de processamento XML padrão que permite encadear dados XML de e para seu aplicativo.

Discutir

  • Participe da comunidade My developerWorks. Conecte-se com outros usuários do developerWorks enquanto explora blogs direcionados a desenvolvedores, fóruns, grupos e wikis.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=549252
ArticleTitle=Cinco coisas que você não sabia sobre... as ferramentas do dia a dia do Java
publish-date=10282010