Objetivo do novo pacote file
O Java 7 introduziu várias funcionalidades úteis para a linguagem, incluindo o novo pacote de I/O (entrada/saída) de arquivo que oferece um controle mais refinado na funcionalidade do sistema de arquivos, particularmente para os sistemas com base no POSIX, do que o que era possível utilizando o velho pacote java.io. Esse artigo apresenta primeiramente uma introdução inicial à nova API e depois explora-o em detalhes utilizando um exemplo de projeto web para gerenciamento de arquivos, chamado WebFolder. Esse projeto oferece um mecanismo de gerenciamento do sistema de arquivos para computadores remotos. Permite operações tais como navegar no sistema de arquivos, inspecionar, renomear, copiar e apagar os arquivos. A nova API de I/O permite estender as capacidades do projeto para manipular conteúdos de arquivos ZIP e também verificar as modificações. O projeto de demonstração pode ser baixado gratuitamente em http://webfolder.sf.net.
Apesar da API de manipulação básica ter tido algumas atualizações entre as versões, para o Java 7 a equipe decidiu fornecer um pacote alternativo com um novo design para cobrir as operações do sistema de arquivos de uma nova maneira.
A base da API de manipulação de arquivo reside no pacote java.nio.file com os dois sub pacotes java.nio.attribute e java.nio.file.spi. A nova API separa operações relacionadas a arquivos do pacote java.io e também fornece métodos adicionais que visam tornar o gerenciamento do sistema de arquivos mais simples. Conceitualmente a nova API foi feita como um conjunto de interfaces de entidades cobrindo os objetos básicos do sistema de arquivos e classes operacionais que cobrem operações sobre o próprio sistema de arquivos. O conceito foi herdado do pacote java.util, no qual as Collections e Arrays fornecem muitas operações básicas sobre estrutura de dados de agregação como uma coleção e um array respectivamente. O novo pacote contém diferentes nomes de classes base e interfaces para evitar confusão, especialmente quando os pacotes java.io e java.nio.file são utilizados juntos.
O novo pacote não tem apenas uma organização diferente de classes de suporte de arquivos e operações sobre o sistema de arquivos, também amplia as capacidades da API, fornecendo uma maneira simples para copiar e mover arquivos.
Comparação entre as operações das classes normais e das novas classes
A tabela a seguir apresenta uma breve visão das classes e interfaces base de ambos pacotes:
Java < 7 java.io, javax.swing.filechooser | Java >= 7 java.nio.file | Comentário |
File | Path e Files | Enquanto a classe File fornece ambas as operações de localização de arquivos e sistema de arquivos, a nova API dividiu isso em dois. A interface Path fornece apenas a localização de arquivo e permite operações adicionais relacionadas com caminho, enquanto a classe Files permite a manipulação de arquivos com várias novas funcionalidades não disponíveis na classe File, como copiar e ler o conteúdo completo do arquivo ou configurar seu proprietário. |
FileSystemView | FileSystem | A classe abstrata FileSystemView fornece uma visão do sistema de arquivos subjacentes, utilizada apenas no contexto de escolha de arquivo no Swing. A classe FileSystem pode representar diferentes sistemas de arquivos definidos localmente ou remotamente, assim como sobre mecanismos alternativos de armazenamento, como imagens ISO ou arquivos ZIP. Essa classe contém fábricas que oferecem implementações concretas de diferentes interfaces como a Path. |
No analog | FileStore | Representa alguns atributos do armazenamento do arquivo como, por exemplo, o tamanho total do arquivo. Pode ser obtido por meio de um Path em particular ou do FileSystem. |
Assim como uma diferente organização de objetos e operações, a nova API do sistema de arquivos é capaz de explorar as funcionalidades relativamente novas do Java, tal como o autoboxing, na maioria dos métodos e construtores, e a nova API está mais simples e fácil para utilizar como resultado dessa nova organização.
Nas próximas seções serão detalhadas algumas melhorias específicas.
Percorrer o sistema de arquivos e operações em grupo
O novo pacote de arquivo introduz uma nova maneira de percorrer um sistema de arquivos, que pretende melhorar a eficiência da memória em comparação com a velha versão com base em array e filtros. Concluindo, a nova abordagem possibilita percorrer o sistema de arquivos em profundidade. A nova implementação utiliza o padrão de projeto Visitor. Mesmo sendo possível imitar o padrão Visitor usando um filtro com um File tradicional nas operações transversais, é muito mais difícil fornecer um algoritmo simples e eficiente em termos de memória para uma travessia em vários níveis.
O padrão Visitor foi introduzido com a interface FileVisitor. Como a interface é genérica, é possível utilizá-la para percorrer o sistema de arquivos por meio de uma implementação básica de File, no entanto, a nova implementação de I/O precisa que o FileVisitor seja utilizado apenas sobre objetos que herdam de Path. A interface declara quatro métodos, com o SimpleFileVisitor fornecendo uma implementação da interface que se pode herdar e assim implementar qualquer método conforme a necessidade de uso. A tabela a seguir apresenta uma visão geral dos métodos do FileVisitor e seus comportamentos no SimpleFileVisitor:
Nome | Próposito | Padrão |
visitFile | Invocado para cada arquivo comum percorrido, incluindo links simbólicos, a menos que seja definida uma filtragem. Qualquer operação significativa relacionada a arquivos pode ser processada aqui, por exemplo: para fazer um backup ou uma busca por algo no arquivo. Uma decisão sobre parar ou continuar percorrendo pode ser tomada. O método não é invocado para diretórios. | Retorna CONTINUE |
preVisitDirectory | Se o item visitado é um diretório em vez de um arquivo, então esse é o método invocado ao invés do visitFile. Ele permite pular um diretório em particular, ou criar um diretório correspondente em uma determinada localização para operações de cópia. | Retorna CONTINUE |
postVisitDirectory | O método é invocado quando o diretório inteiro foi completamente percorrido. É uma maneira conveniente para finalizar operações com o diretório. Por exemplo, se o propósito de percorrer for excluir todos os seus arquivos, então o próprio diretório pode ser excluído nesse método. | Retorna CONTINUE |
visitFileFailed | Se alguma exceção não tratada ocorrer enquanto estiver percorrendo o sistema de arquivos, então esse método é invocado. Se uma exceção for relançada, então toda a travessia ser interrompida e a exceção será propagada para o código que iniciou a caminhada no sistema de arquivos, usando o Files.walkFileTree. Uma exceção pode ser analisada aqui e a decisão por continuar a percorrer pode ser tomada. | Lança IOException |
Essa interface é bem poderosa, permitindo os casos de uso mais convencionais no sistema de arquivo, incluindo arquivamento, pesquisa, copia ou exclusão de arquivos. O tratamento de exceções também é bem flexível. Porém, se for necessário apenas obter o conteúdo de algum diretório sem percorrê-lo em profundidade, é possível utilizar a antiga operação do File.list(), então funcionalidades similares estão disponíveis na nova API de I/O também, embora retorne uma coleção ao invés de um array comum.
Novas funcionalidades que não existem no java.io
Apesar das possibilidades de percorrer o sistema de arquivos e operações de agrupamento oferecidas pela nova API de I/O serem bastante úteis, elas já são suportadas pelo java.io padrão. Em complemento, no entanto, a nova API de I/O fornece capacidades específicas de sistemas operacionais que não são suportadas pelo pacote antigo. Um importante exemplo de funcionalidade é trabalhar com os links e links simbólicos, que agora podem ser criados ou processados em qualquer operação de travessia do sistema de arquivos. Essa funcionalidade de trabalhar com links e links simbólicos funciona somente para sistemas de arquivos que o suportam - caso contrário, uma UnsupportedOperationException será lançada. Outra extensão está relacionada com o gerenciamento dos atributos de arquivos como proprietário e permissões. A tabela a seguir fornece uma breve visão geral do link e das operações estendidas de atributos de arquivos. Todas essas operações podem ser realizadas através da classe Files.
Operação | Propósito | Comentário |
createLink | Criar um link físico mapeado para um certo arquivo. | |
createSymbolicLink | Criar um link simbólico mapeado para um arquivo ou diretório. | |
getFileAttributeView | Acessar os atributos do arquivo na forma de uma implementação específica do sistema de arquivos do FileAttributeView. | Embora este método traga flexibilidade para fornecer um conjunto de atributos não foram pré definidos, ainda é necessário utilizar a implementação das classes específicas e como resultado, limitar a portabilidade do código. |
getOwner | Obter o proprietário do arquivo. | Funciona somente com arquivos de sistemas que suportam o atributo de proprietário. |
getPosixFilePermissions | Obter as permissões do arquivo. | Específico para o sistema POSIX. |
isSymbolicLink | Indicar se o caminho informado é de um link simbólico. | Específico para o sistema de arquivos. |
readSymbolicLink | Ler o caminho destino de um link simbólico. | Específico para o sistema de arquivos. |
readAttributes | Ler os atributos do arquivo. | Há duas variações deste método, para retornar os atributos de formas diferentes. |
setAttribute | Definir um atributo do arquivo. | O nome do atributo pode ser incluído como qualificador no FileAttributeView. |
Verifique a documentação da nova API de I/O quando for necessário utilizar as operações apresentadas nessa tabela.
Observadores
A API também fornece um mecanismo para monitorar o estado de um arquivo ou diretório em particular, de modo que eventos como criação, modificação ou remoção possam ser observados. Infelizmente, o modelo "push" não é garantido para os eventos observados, e em muitos casos um mecanismo de pool pode ser utilizado, fazendo essa implementação menos atrativa no meu ponto de vista. O serviço de observação é também dependente de sistema, então não é possível construir uma aplicação verdadeiramente portável utilizando este serviço. Há 5 interfaces que cobrem as funcionalidades de observação. A tabela a seguir apresenta uma visão rápida das interfaces e seus usos.
Interface | Proposito | Uso |
Watchable | Um objeto desse tipo pode ser registrado no serviço de observação. Uma chave de observação é obtida para ser usada no monitoramento dos eventos de modificação. | Uma implementação concreta da interface foi obtida para registrar o interesse em eventos observados relacionados com o objeto. Note que a interface Path é um Watchable. |
WatchService | Um serviço disponível no sistema de arquivos para registrar os objetos Watchable e então usa o WatchKey para monitorar as modificações. | Um WatchService pode ser obtido do objeto FileSystem. |
WatchKey | Comprovante de registro que é usado para modificar os eventos de pool. | O objeto pode ser armazenado e então usado para colocar os eventos no pool de modificação. Ele pode ser usado também diretamente para obter o WatchService quando os eventos de modificação estão disponíveis. |
WatchEvent | Carrega o evento observado. | O objeto WatchEvent é passado na chamada da notificação de evento, tipo de evento e o caminho do objeto afetado pode ser obtido através dele. |
WatchEvent.Kind | Carrega informações do tipo de evento observado. | Usado para especificar os tipos de eventos interessados no registro do Watchable. Ele também fornece um WatcheEvent da chamada da notificação. |
Gostaria de enfatizar dois cenários que podem ser utilizados o serviço de observação:
- O primeiro é quando se faz necessário monitorar as alterações de um objeto em particular. Neste caso, um objeto Watchable pode ser registrado no serviço de observação para obter uma chave de observação, e então com a chave de observação pode ser verificado os eventos de modificação. O mecanismo de pool que recebe a chave de observação não é bloqueante, então se um novo evento ocorrer, uma lista vazia de eventos pode ser retornado desse pool. É possível introduzir um tempo de espera para reduzir a sobrecarga do pool, em troca da perda de alguma precisão na notificação dos eventos que podem ocorrer;
- O segundo cenário que utiliza o mecanismo de serviço de observação é adequado para eventos de modificação do pool relacionados a muitos objetos observados. Como no primeiro cenário, é necessário registrar todos os objetos observados, entretanto a chave do observador retornada pode ser ignorada. Ao invés de usar a chave do observador do mecanismo de pool, um serviço do mecanismo de pool é usado para obter as chaves assim que algum evento de modificação ocorrer, e então processa os eventos usando um operação de pool ao invés da chave do observador. Neste caso uma chave do observador é garantida para determinado evento. Uma simples thread pode ser usada para gerenciar todas chaves do observador. O serviço de observação do mecanismo de pool é mais flexível, já que pode trabalhar de forma bloqueante, não bloqueante ou bloqueante com timeout. Como resultado ele também pode ser mais preciso.
A seguir serão apresentados alguns exemplos do segundo cenário utilizado no projeto WebFolder.
Operações úteis
A próxima funcionalidade central que a nova API de I/O traz é um conjunto de métodos utilitários que tornam o pacote autossuficiente, fazendo com que, para a maioria dos casos de uso, nenhuma funcionalidade adicional do pacote java.io seja necessária. Streams de entrada, streams de saída e canais de bytes são obtidos diretamente usando os métodos do Files. Operações completas, como copiar e mover arquivos, são suportadas através da API. Para complementar, o conteúdo completo do arquivo pode ser lido agora como uma lista de Strings ou um array de bytes. No entanto note que não há parâmetro de controle de tamanho, então deve-se fazer uma consulta ao tamanho do arquivo para evitar possíveis problemas de memória.
Mais detalhes sobre a nova organização da API de I/O de arquivos
Finalmente, o sistema e armazenamento de arquivos são partes essenciais do novo pacote de I/O. Como visto, o elemento chave do pacote é a localização do arquivo representado pela interface Path. Uma implementação concreta da interface Path pode ser obtida por meio da classe factory FileSystem, que por sua vez é obtida através da classe factory FileSystems. O diagrama a seguir apresenta o relacionamento entre os elementos chaves da nova API de I/O de arquivos.
As informações de armazenamento podem ser obtidas tanto de um arquivo em particular (Path) como do sistema de arquivos (FileSystem).
Trabalhando com o sistema de arquivos
Todas as implementações dos sistemas de arquivos são guardados pelos seus fornecedores correspondentes, cujas classes base são definidas no pacote java.nio.file.spi. O conceito de fornecedor de serviços permite ao desenvolvedor estender facilmente a cobertura dos sistemas de arquivos. Alguns sistemas de arquivos interessantes são fornecidos no pacote, como por exemplo, um transformador de conteúdo de arquivo ZIP, que possibilita a maioria das operações normais, como: manipulação de conteúdo, criação, remoção e modificação de arquivos. Veremos um exemplo mais adiante.
Operações concorrentes e atômicas
A visão geral da nova API de I/O ficaria incompleta se não fosse mencionado que a implementação teve grande preocupação com a concorrência, e por consequência, a maioria das operações são seguras em ambientes concorrentes. Mover um arquivo também é atômico. Trabalhar com o conteúdo de diretórios também pode ser seguro ao se obter uma implementação concreta da interface SecureDirectoryStream. Neste caso, todas as operações relacionadas com os diretórios permanecem consistentes se o diretório for movido ou modificado por um manipulador externo. Somente caminhos relativos são utilizados ao se fazer isso.
Exemplo do mundo real
A melhor maneira para se aprender algo novo é codificando de verdade. Como mencionado anteriormente, o gerenciador de arquivo WebFolder baseado na web foi inicialmente desenvolvido usando java.io e, para auxiliar no aprendizado, o WebFolder foi migrado para o NIO. A seguir serão apresentados os códigos atualizados. Os exemplos são pequenos para facilitar a leitura, mas o código completo pode ser baixado na página web do projeto.
1. Para obter o conteúdo de um diretório:
try (RequestTransalated rt = translateReq(getConfigValue("TOPFOLDER", File.separator), req.getPathInfo()); DirectoryStream<Path> stream = Files.newDirectoryStream(rt.transPath);) { for (Path entry : stream) { // acrescentando as informações do diretório no modelo. result.add(new Webfile(entry, rt.reqPath)); } } catch (Exception ioe) { log("", ioe); } /* Não precisa de bloco finally, uma vez que a API suporta o AutoCloseable e a nova sintaxe do bloco try. */
Neste exemplo, um modelo de diretório (directory) é populado para ser apresentado em uma tela de página web. O Files.newDirectoryStream é usado para obter o iterador com o conteúdo do diretório.
2. Percorrer em profundidade:
Path ffrom = …. Files.walkFileTree(ffrom, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path targetdir = fto.resolve(fto.getFileSystem().getPath(ffrom.relativize(dir).toString())); try { Files.copy(dir, targetdir, StandardCopyOption.COPY_ATTRIBUTES); } catch (FileAlreadyExistsException e) { if (!Files.isDirectory(targetdir)) throw e; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path targetfile = fto.resolve(fto.getFileSystem().getPath(ffrom.relativize(file).toString())); Files.copy(file, targetfile, StandardCopyOption.COPY_ATTRIBUTES); return FileVisitResult.CONTINUE; } });
Este código copia o conteúdo de um diretório para outro local do sistema de arquivos. O preVisitDirectory se encarrega de copiar o próprio diretório. Desde que o diretório alvo possa ser outro sistema de arquivos, o exemplo é uma maneira prática de se extrair todo o conteúdo de um arquivo ZIP enquanto preserva a estrutura de diretório, ou para colocar uma estrutura de diretório dentro de um arquivo ZIP. A opção COPY_ATTRIBUTES preserva todos os atributos do arquivo original, inclusive a timestamp, no arquivo copiado.
Uma implementação similar pode ser usada para apagar todo o conteúdo de um diretório. Neste caso o método postVisitDirectory deve ser implementado, em vez do preVisitDirectory, para apagar o próprio diretório após o seu conteúdo ter sido removido.
@Override public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { if (e == null) { if (dir.getParent() != null) { Files.delete(dir); return FileVisitResult.CONTINUE; } else return FileVisitResult.TERMINATE; } else { // falha ao percorrer o diretório. throw e; } }
Este exemplo verifica se o diretório de destino não está no nível da raiz antes de apagá-lo. Todas as possíveis exceções são propagadas para serem tratadas por quem chama o método.
3. Sistema de arquivos do ZIP:
FileSystem fs = FileSystems.newFileSystem(zipPath, null); Path zipRootPath = fs.getPath(fs.getSeparator()); …. fs.close();
O zipRootPath pode ser usado para percorrer o conteúdo de um arquivo ZIP para vários propósitos. O sistema de arquivos obtido é completamente funcional e muitas operações, incluindo: copiar, mover e remover; funcionarão. No entanto o serviço de observação não está disponível para o sistema de arquivos do ZIP. Note também que o sistema de arquivos é fechado após o seu uso. Se outro sistema de arquivos for aberto no mesmo ZIP, então é possível observar uma falha na operação, então lembre-se desta possibilidade ao escrever o seu código. O fechamento padrão do sistema de arquivos não é necessário. Parece que o novo pacote de I/O mantém somente uma instância e cuida da concorrência.
4. Observação:
Há diversas formas de se usar um serviço de observação, o código a seguir ilustra as duas maneiras mais comuns que foram mencionadas anteriormente:
WatchService ws = dir.getFileSystem().newWatchService(); WatchKey wk = dir.register(ws, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
Após obter uma chave de observação, a chave pode ser passada para uma thread de observação para monitorar os eventos relevantes:
@Override public void run() { for (;;) { if (watchKey != null) { for (WatchEvent<?> event : watchKey.pollEvents()) updateScreen(event.kind(), event.context()); } boolean valid = watchKey.reset(); if (!valid) { break; } } }
Se os eventos não puderem ser consumidos rápido o suficiente, então um evento do tipo OVERFLOW será recebido. A chave de observação pode ser cancelada se não houver mais eventos interessantes nela. Um serviço de observação também pode ser fechado após o seu uso. Outra abordagem é usar um método do serviço de observação para sondar eventos de modificação caso haja muitos objetos em observação registrados.
public void run() { for (;;) { try { WatchKey watchKey = watchService.take(); // poll(10, ); processWatchKey(watchKey); } catch (InterruptedException ie) { break; } } }
Um serviço de observação pode ser obtido através do sistema de arquivos padrão e então usado em uma thread simples de monitoração. O método take() foi usado e, uma vez que foi bloqueado, não há laços (for) sendo desperdiçados. O método processWatchKey tem uma implementação similar a uma anteriormente fornecida, associada às chaves de observação de eventos, para apoiar a sondagem. No entanto não há loop extra para isso, uma vez que a chave obtida de um serviço observado já está associada aos eventos.
Recapitulando
A nova API de I/O de arquivos fornece:
1. Um poderoso mecanismo para percorrer o sistema de arquivos ajudando a realizar grupo de operações complexas.
2. Manipulação de arquivo específico e objetos do sistemas de arquivos, e seus atributos como: links, proprietário e permissões.
3. Um método utilitário prático para manipular todo o conteúdo do arquivo, como: ler, copiar e mover.
4. Um serviço de observação para monitoração das modificações no sistema de arquivos.
5. Operações atômicas no sistema de arquivos que fornecem sincronização de processos realizados no sistema de arquivos.
6. Sistemas de arquivos personalizados, definidos em determinada organização de arquivos, como arquivo morto.
Migração
Há quatro razões que se deve considerar para a migração de um sistema baseado no antigo pacote de I/O para a nova API:
- Apresentação de problemas de memória, com a implementação de arquivos complexos;
- Necessidade de suportar operações em arquivos no formato ZIP;
- Necessidade de refinar o controle sobre os atributos do sistema POSIX;
- Necessidade de observar os serviços.
Como uma regra geral, se dois ou mais desses itens forem necessários no projeto, então a migração pode valer a pena, caso contrário é recomendado manter a implementação atual. Uma razão para não migrar é que a implementação para a nova API de I/O de arquivo não irá deixar o código mais compacto ou mais legível. Por outro lado, as novas operações para percorrer os arquivos podem ser um pouco mais lentas no primeiro acesso em certas implementações do ambiente de execução. Parece que a implementação da Oracle para Windows faz muito mais cache, que leva uma quantidade de tempo considerável no primeiro acesso. No entanto a implementação da OpenJDK (IcedTea) não apresenta este problema no Linux, por isso parece ser um problema especifico da plataforma/implementação.
Caso decida realizar a migração, a tabela a seguir apresenta algumas dicas:
Atual | Migrado | Comentário |
fileObj = new File(new File(pe1, pe2), pe3) | pathObj = fsObj. getPath(pe1, pe2, pe3) | fsObj pode ser obtido através do FileSystems.getDefault(), já que o sistema de arquivos está preservado no próprio Path, ele pode ser recuperado de qualquer caminho obtido do mesmo sistema de arquivos. |
fileObj.someOperation() | Files.someOperation(pathObj) | Na maioria dos casos as operações tem o mesmo nome, no entanto alguns parâmetros adicionais relacionados com links e atributos podem ser acrescentados. |
fileObj.listFiles() | Files.newDirectoryStream(pathObj) | O Files.walkFileTree permite percorrer em profundidade. |
new FileInputStrean(file) | Files.newInputStream(pathObj) | Opções adicionais podem especificar como os arquivos são abertos. |
new FileOutputStream(file) | Files.newOutputStream(pathObj) | Opções adicionais podem especificar como os arquivos são abertos. |
new FileWriter(file) | Files.newBufferedWriter(pathObj) | Opções adicionais podem especificar como os arquivos são abertos. |
new FileReader(file) | Files.newBufferedReader(pathObj) | Opções adicionais podem especificar como os arquivos são abertos. |
new RandomAccessFile(file) | Files.newByteChannel(pathObj) | Opções de abertura e criação de atributos de arquivo podem ser especificadas. |
A classe File e a interface Path têm duas formas de conversão entre elas - o pathObj.toFile() e fileObj.toPath(). Podendo ajudar a reduzir os esforços da migração e focar somente nas novas funcionalidades oferecidas pela nova API de I/O de arquivo. Como parte do processo de migração, uma substituição da implementação personalizada de cópia de arquivos por Files.copy pode ser considerada. A própria interface Path fornece diversos métodos práticos que podem reduzir o código necessário, previamente baseado nos objetos Files. Uma vez que o novo código estiver sendo executado no Java 7 ou superior, vale a pena melhorar o tratamento de exceções e a liberação de recursos. O código a seguir demonstra o velho e novo mecanismo:
ClosableResource resource = null; try { Resource = new Resource(…); // processando o recurso. } catch(Exception e) { } finally { if (resource != null) try { resource.close(); } catch(Exception e) { } }
Pode ser substituído por um código mais compacto, como:
try (Resource = new Resource(…);) { // processando o recurso. } catch(Exception e) { }
O Resource implementa a interface AutoCloseable, e todos os padrões provenientes do JRT são AutoCloseable.
Sobre o Autor
Dmitriy Rogatkin é CTO da WikiOrgCharts, sendo responsável pela direção tecnologica da empresa. Trabalhou anteriormente com tecnologias inicialmente voltadas para softwares corporativos: foi arquiteto chefe por mais de 10 anos na MetricStream, uma empresa líder no mercado de software GRC. Adora testar diferentes ideias através da criação de softwares open source, variando desde aplicações multimídia para desktop até frameworks e servidores de aplicações. Entre seus projetos figuram o TJWS, um servidor de aplicações leve, servindo de alternativa para quando o uso do profile completo do Java EE no servidor de aplicações for muito carregado, e a TravelPals, que ajuda a conectar as pessoas enquanto viajam e a planejarem o tempo fora.