Introdução à programação de Java, Parte 2
Publicado em 22/Fev/2016
Esse conteúdo é a parte # de # na série: Introdução à programação de Java, Parte 2
https://www.ibm.com/developerworks/br/views/global/libraryview.jsp?series_title_by=introdução+à+programação+de+java
Esse conteúdo é parte da série:Introdução à programação de Java, Parte 2
O conteúdo deste tutorial é direcionado para programadores que estão conhecendo a linguagem Java e não estão familiarizados com seus recursos mais sofisticados. O tutorial pressupõe que você concluiu " Introdução à programação de Java, Parte 1: Princípios básicos da linguagem Java" para:
Quando dois métodos são criados com o mesmo nome, mas com listas de argumentos diferentes (ou seja, números ou tipos de parâmetros diferentes), você tem um sobrecarregado . No tempo de execução, o Java Runtime Environment (JRE; também conhecido como Java Runtime) decide qual variação do seu método sobrecarregado deve chamar com base nos argumentos que foram passados para ele.
Suponha que Person precisa de alguns métodos para imprimir uma auditoria do seu estado atual. Chamo esses métodos de printAudit(). Cole o método sobrecarregado na Listagem 1 na visualização do editor do Eclipse em Person :
Lista 1. printAudit(): Um método sobrecarregado
Quando uma subclasse de outra classe fornece sua própria implementação de um método definido em uma classe-pai, isso é chamado de substituição de método. Para ver como a substituição de método é útil, você precisa trabalhar um pouco no seu Employee . Depois de configurar, mostrarei como a substituição de método pode ser útil.
Para declarar tal classe em um arquivo chamado Employee.java, clique com o botão direito no pacote com.makotojava.intro no Eclipse. Clique em New > Class... para abrir a caixa de diálogo New Java Class, mostrada na Figura 1.
Figura 1. Caixa de diálogo New Java Class
Insira Employee como nome da classe e Person como sua superclasse; depois clique em Concluir. É possível ver a classe Employee em uma janela de edição. Não é preciso declarar explicitamente um construtor, mas vá em frente e implemente os dois construtores de qualquer forma. Em primeiro lugar, certifique-se de que a janela de edição de classe Employee tenha o foco e acesse Source > Generate Constructors from Superclass.... Você verá uma caixa de diálogo em que é possível selecionar os construtores para implementar, tal como mostrado na Figura 2.
Figura 2. Caixa de diálogo Generate Constructors from Superclass
Lista 2. A nova classe Employee aprimorada
Lista 3. A classe Employee com os atributos de Person.
Agora, como prometido, você está pronto para um exercício de métodos de substituição. Você substituirá o método printAudit() (consulte a Listagem 1) usada para formatar o estado atual de uma instância Person . Employee herda esse comportamento de Person. Se você instanciar Employee, configurar seus atributos e chamar uma das sobrecargas de printAudit(), a chamada será bem-sucedida. Entretanto, a auditoria que é produzida não representará totalmente um Employee. O problema é que printAudit() não pode formatar os atributos específicos de um Employee, porque Person não sabe sobre eles.
Para implementar essa solução no seu IDE do Eclipse, verifique se Employee está aberto na janela do editor ou se foi selecionado na visualização Project Explorer. Em seguida, acesse Source > Override/Implement Methods..., e você verá uma caixa de diálogo, mostrada na Figura 3, na qual pode selecionar os métodos para substituir ou implementar.
Figura 3. Caixa de diálogo Override/Implement Methods
Observe a chamada para super.printAudit(). Aqui, você está pedindo para a superclasse (Person) exibir seu comportamento para printAudit(); depois, você aumentará com o comportamento Employee-type printAudit() .
A chamada para super.printAudit() não precisa acontecer primeiro; apenas pareceu uma boa ideia imprimir esses atributos primeiro. Na verdade, você nem precisa chamar o super.printAudit() . Se não chamá-lo, deve-se formatar os atributos de Person por conta própria no Employee.printAudit() .
As variáveis e métodos que você tem em Person e Employee são variáveis e métodos de instância . Para usá-los, você deve instanciar a classe de que precisa ou ter uma referência para a instância. Cada instância do objeto possui variáveis e métodos e, para cada uma delas, o comportamento exato será diferente, porque se baseia no estado da instância do objeto.
Para controlar "contadores" de instâncias da classe
Lista 4. Usando variáveis de classes
Em Listagem 4, para usar a constante para MALE no Person como chamada do construtor, bastaria fazer uma referência ao seu nome. Para usar uma constante fora da classe, você criaria um prefácio com o nome da classe em que ela foi declarada:
Se estiver acompanhando desde a Parte 1, você já chamou o método estático Logger.getLogger() várias vezes — sempre que recuperou uma instância Logger para escrever a saída no console. Observe, porém, que não precisou de uma instância de Logger para tal; em vez disso, fez uma referência à classe Logger propriamente dita. Esta é a sintaxe para fazer um método de classes . Tal como acontece com as variáveis de classes, a palavra-chave static identifica Logger (neste exemplo) como um método de classes. Às vezes, os métodos de classes também são chamados de métodos estáticos por esse motivo.
Agora, você combina o que aprendeu sobre variáveis e métodos estáticos para criar um método estático em Employee. Declara que uma variável private static final contém um Logger, que todas as instâncias compartilham e que pode ser acessado ao chamar getLogger() em Employee . A Listagem 5 mostra como.
Lista 5. Criando um método de classes (ou estático)
A instância Logger é declarada com acesso do tipo private ; portanto, nenhuma classe fora de Employee pode acessar a referência diretamente.
Para recuperar a classe Employee com o objeto Logger , faça esta chamada:
Lista 6. Comparando objetos com ==
Se executar o código da Listagem 6 dentro do Eclipse (selecione Employee na visualização Project Explorer e, depois, selecioneRun As > JUnit Test), a saída deve ser:
No primeiro caso na Listagem 6, os valores das primitivas são os mesmos; logo, o operador == gera true. No segundo caso, os objetos Integer se referem à mesma instância e, novamente, == gera true. No terceiro caso, embora os objetos Integer agrupem o mesmo valor, == gera false porque integer1 e integer2 se referem a objetos diferentes. Pense em == como um teste para "mesmo objeto".
Essa declaração chama o método equals() do objeto a, passando para ele uma referência ao objeto b. Por padrão, um programa Java simplesmente verificaria se os dois objetos são iguais usando a sintaxe == . Mas, como equals() é um método, ele pode ser substituído. Compare o caso de teste JUnit na Listagem 6 com aquele da Listagem 7 (que chamei de anotherTest()), que utiliza equals() para comparar os dois objetos:
Lista 7. Comparando objetos com equals()
Na Listagem 7, é de se esperar que o método equals() de Integer gere true se == gerar true. Observe, porém, o que acontece no segundo caso, em que é possível criar objetos separados que agrupam o valor 1: == gera false porque integer1 e integer2 se referem a objetos diferentes; mas equals() gera true.
Figura 4. Caixa de diálogo Override/Implement Methods
Essa caixa de diálogo foi utilizada antes, mas, neste caso, você deseja implementar o método de superclasse Object.equals() . Portanto, localize Object na lista de métodos para substituir ou implementar, selecione o método equals(Object) e clique em OK. O Eclipse gera o código correto e o coloca no seu arquivo de origem
O Eclipse pode gerar um método equals() para você com base nas variáveis de instância (atributos) definidas para uma classe. Como Employee é uma subclasse de Person, você primeiramente gera equals() para Person. Na visualização Project Explorer do Eclipse, clique com o botão direito em Person e escolha Generate hashCode() e equals() para abrir a caixa de diálogo mostrada na Figura 5, onde você seleciona os atributos que serão incluídos nos métodos hashCode() e equals() .
Figura 5. Caixa de diálogo Generate hashCode() e equals()
Lista 8. Um método equals() gerado pelo Eclipse
Em seguida, o método verifica se os objetos Class são iguais (isto é, o objeto passado precisa ser um objeto Person ). Se forem iguais, cada valor de atributo do objeto passado é verificado para ver se corresponde ao valor por valor com o estado da instância Person fornecida. Caso os valores de atributo sejam nulos, equals() verifica todos os que conseguir; havendo correspondência, os objetos são considerados iguais. Talvez você não queira esse comportamento para todos os programas, mas funciona para a maioria dos propósitos.
Tente seguir as etapas de "Geração automática de equals()" para gerar um equals() para Employee. Depois de ter o equals() gerado, inclua o caso de teste JUnit a seguir (que chamei de yetAnotherTest()) nele:
Lembra-se do método printAudit() do início desta seção? Se achou que ele estava trabalhando demais, você acertou. Formatar o estado de um objeto em String é um padrão tão comum que os designers da linguagem Java o inseriram no próprio Object , em um método chamado (evidentemente) toString(). A implementação padrão de toString() não é muito útil, mas todo objeto tem um. Neste exercício, você torna o toString() padrão um pouco mais útil.
Se acha que o Eclipse pode gerar um método toString() para você, tem razão. Volte para seu Project Explorer e clique com o botão direito na classe Person ; depois, escolha Source > Generate toString().... Você verá uma caixa de diálogo semelhante a da Figura 5. Selecione todos os atributos e clique em OK. Faça o mesmo para Employee. O código gerado pelo Eclipse para Employee é mostrado na Listagem 9.
Lista 9. Um método toString() gerado pelo Eclipse
O código gerado pelo Eclipse para toString não inclui a superclasse com seu toString() ( Employee como superclasse sendo Person). É possível corrigir isso rapidamente, usando o Eclipse, com esta substituição:
Uma exceção é um evento que ocorre durante a execução do programa e interrompe o fluxo normal das suas instruções. A manipulação de exceção é uma técnica essencial da programação de Java. Em essência, você agrupa seu código em um bloco try (que significa "experimente isto e diga-me se causa uma exceção") e o utiliza para capturar vários tipos de exceções.
Lista 10. Está vendo o erro?
O Eclipse avisa você sobre muitos erros em potencial no desenvolvimento — mais uma vantagem de utilizar um IDE para fazer desenvolvimento de Java.
Essa saída diz que você está tentando fazer referência a um objeto por meio de uma referência (ponteiro) null , o que é um erro de desenvolvimento muito grave.
A Listagem 11 mostra o código de erro da Listagem 10 limpa com os blocos de códigos padrão para a manipulação de exceção: try, catche finally.
Lista 11. Capturando uma exceção
Juntos, os blocos try, catche finally formam uma rede para capturar exceções. Em primeiro lugar, a instrução try agrupa um código que pode lançar uma exceção. Neste caso, a execução cai imediatamente no bloco catch ou no manipulador de exceções. Quando todas as tentativas e capturas são concluídas, a execução continua para o bloco finally , independentemente de uma exceção ter sido lançada. Quando capturar uma exceção, é possível tentar se recuperar graciosamente dela ou sair do programa (ou método).
Quando ocorre uma exceção, o Java Runtime procura um manipulador de exceções em algum lugar da pilha. Se não encontrar um até o momento de chegar ao topo da pilha, interrompe o programa abruptamente, como foi visto na Listagem 10.
É possível ter diversos blocos catch , mas eles precisam ser estruturados de maneira específica. Se quaisquer exceções forem subclasses de outras exceções, as classes-filhas são colocadas na frente das classes-pais na ordem dos blocos catch . A Listagem 12 mostra um exemplo de tipos de exceções diferentes estruturadas em sua sequência hierárquica correta.
Lista 12. Exemplo de hierarquia de exceções
Neste exemplo, FileNotFoundException é uma classe-filha de IOException; portanto, precisa ficar na frente do bloco IOException catch . Além disso, IOException é uma classe-filha de Exception e precisa ficar na frente do bloco Exception catch .
A partir do JDK 7, o gerenciamento de recurso ficou muito mais simples. À medida que trabalha mais com arquivos e o pacote java.io , entenderá a nova sintaxe. O código na Listagem 12 precisou declarar uma variável para conter a referência bufferedReader e, em finally , precisou fechar BufferedReader.
Lista 13. Sintaxe Resource-management
Essencialmente, variáveis de recurso são designadas depois de try entre parênteses; quando o bloco try sai do escopo, esses recursos são fechados automaticamente. Os recursos precisam implementar a interface java.lang.AutoCloseable ; se você tentar usar essa sintaxe em uma classe de recurso que não faz isso, será avisado pelo Eclipse.
Todos os aplicativos Java precisam de um ponto de entrada em que o Java Runtime saiba que deve começar a executar o código. Esse ponto de entrada é main() . Os objetos do domínio normalmente não têm métodos main() , mas pelo menos uma classe de cada aplicativo deve ter.
Você está trabalhando desde a Parte 1 com o exemplo de um aplicativo de recursos humanos que inclui Person e suas subclasses Employee . Agora, é possível ver o que acontece quando uma nova classe é incluída no aplicativo.
O propósito de uma classe do driver (como o nome sugere) é "conduzir" um aplicativo. Observe que esse driver simples para o aplicativo de recursos humanos contém um main() :
Você já encontrou exemplos de herança algumas vezes neste tutorial. Esta seção revisa alguns materiais da Parte 1sobre herança e explica em mais detalhes como ela funciona — incluindo a hierarquia de herança, construtores e abstração de herança.
Lista 14. Classe pública Person
Agora, suponha que você possui uma classe Employee que herda de Person. Employeecomo definição de classe (ou gráfico de herança) seria semelhante ao seguinte:
Linguagens como C++ fornecem suporte ao conceito de diversas heranças: Em qualquer ponto da hierarquia, uma classe pode herdar de uma ou mais classes. A linguagem Java fornece suporte somente para herança única— o que significa que é possível usar a palavra-chave extends somente com uma única classe. Assim, a hierarquia de classes para qualquer classe Java sempre consiste de uma linha reta até chegar a java.lang.Object. No entanto, como você aprenderá mais tarde no tutorial, a linguagem Java fornece suporte para a implementação de diversas interfaces em uma única classe, oferecendo um tipo de solução alternativa para a herança única.
Lista 15. Inicializando um novo Employee
Lista 16. Chamando uma superclasse
Um construtor de dentro de uma classe pode ser chamado por outro construtor por meio da palavra-chave this , juntamente com uma lista de argumentos. Como em super(), a chamada this() precisa ser a primeira linha no construtor. Por exemplo:
Tablela 1. Regras de acesso do construtor
Nenhum modificador ( pacote privado) O construtor pode ser chamado por qualquer classe no mesmo pacote.
Suponha que você precisa de um método para examinar o estado de um objeto Employee e garantir que seja válido. Essa necessidade pareceria ser comum para todos os objetos Employee , mas se comportaria de forma suficientemente diferente entre todas as possíveis subclasses nas quais tem potencial zero de reutilização. Neste caso, você declara o método validate() como abstract (forçando todas as subclasses a implementá-lo):
Cada subclasse direta de Employee (como Manager) passou a ser obrigada a implementar validate() . No entanto, após uma subclasse ter implementado o método validate() , nenhuma de suas subclasses precisa implementá-lo.
Uma declaração de interface se parece com uma declaração de classe, exceto pelo fato de que você usa a palavra-chave interface . É possível nomear a interface como você quiser (sujeito às regras do idioma), mas, por convenção, os nomes das interfaces se parecem com nomes de classe.
Para usar uma interface, você a implementa , o que significa que fornece um corpo de método que, por sua vez, fornece o comportamento para cumprir o contrato da interface. Você usa a criação implements como palavra-chave para implementar uma interface:
Suponha que você implementou a interface StockOptionRecipient na classe Manager , como mostrado na Listagem 17:
Lista 17. Implementando uma interface
Quando implementa a interface, você fornece um comportamento para o método ou métodos contidos na interface. É preciso implementar os métodos com assinaturas que correspondem às assinaturas da interface, com a adição do modificador de acesso public .
O Eclipse pode gerar facilmente a assinatura de método correta para você caso você decida que uma das suas classes deve implementar uma interface. Basta mudar a assinatura de classe para implementar a interface. O Eclipse coloca uma linha ondulada vermelha sob a classe, sinalizando-a como estando em erro porque a classe não fornece os métodos na interface. Clique no nome de classe, pressione Ctrl + 1 e o Eclipse sugere "correções rápidas" para você. Dentre elas, escolha Add Unimplemented Methods e o Eclipse gera os métodos para você, colocando-os na parte inferior do arquivo de origem.
Uma interface define um novo tipo de dados de referência , que pode ser utilizado para se referir a uma interface em qualquer lugar em que você faria referência a uma classe. Essa capacidade inclui o momento de declarar uma variável de referência, ou lançar de um tipo para outro, tal como mostrado na Listagem 18.
Lista 18. Designando uma nova instância Manager para uma referência StockOptionEligible
Como é possível ver, é válido designar uma nova instância Manager para uma referência StockOptionEligible e passar uma nova instância Manager para um método que espera uma referência StockOptionEligible .
É possível designar uma referência de uma classe que implementa uma interface com uma variável de um tipo de interface, mas algumas regras se aplicam. Na Listagem 18, é possível ver que designar uma instância Manager para uma referência variável StockOptionEligible é válido. O motivo é que a classe Manager implementa tal interface. Entretanto, tal designação não seria válida:
Designações como essas seguem as regras de designação vistas na seção "Herança". Tal como acontece com as classes, é possível designar uma referência de interface a uma variável do mesmo tipo ou um tipo de superinterface.
Como variáveis e métodos de membro, as classes Java também podem ser definidas em qualquer escopo, incluindo public, privateou protected. As classes aninhadas podem ser úteis quando você deseja lidar com o processamento interno dentro da sua classe e maneira orientada a objetos, mas tal funcionalidade está limitada à classe em que é necessária.
Assim como cada objeto Manager representa um único ser humano, o objeto DirectReports representa uma coleção de pessoas reais (funcionários) subordinados a um gerente. DirectReports variam conforme o Manager . Neste caso, faz sentido que eu me refira somente à classe aninhada DirectReports no contexto da instância delimitadora de Manager; portanto, tornei-a do tipo private.
Por ser do tipo private, apenas Manager pode criar uma instância de DirectReports. Suponha, porém, que você queria dar a uma entidade externa a capacidade de criar instâncias de DirectReports. Neste caso, parece que você poderia dar à classe DirectReports um escopo do tipo public ; assim, qualquer código externo poderia criar instâncias de DirectReports , tal como mostrado na Listagem 19.
Lista 19. Criando instâncias de DirectReports instances: First attempt
O código na Listagem 19 não funciona e você provavelmente está se perguntando o porquê. O problema (bem como sua solução) está na forma como DirectReports é definido dentro de Manager e com as regras do escopo.
Se tivesse uma variável de membro de Manager, você esperaria que o compilador exigisse que fosse feita uma referência a um objeto Manager antes que fosse possível referenciá-lo, certo? Bem, o mesmo se aplica a DirectReports, pelo menos do modo como você o definiu na Listagem 19.
Para criar uma instância de uma classe pública aninhada, você usa uma versão especial do operador new . Combinado com uma referência a uma instância delimitadora de uma classe externa, new permite que você crie uma instância da classe aninhada:
Observe que a sintaxe pede uma referência à instância delimitadora, além de um ponto e da palavra-chave new , seguida pela classe que você deseja criar.
A Listagem 20 utiliza o exemplo da Listagem 17, incluindo um método padrão para manipular tipos de Employee que não são StockOptionEligible. A listagem começa com um método em HumanResourcesApplication para processar as opções de ação, seguido por um teste JUnit para orientar o método:
Lista 20. Manipulando tipos de Employee que não são StockOptionEligible
Neste exemplo, forneço implementações de duas interfaces que usam classes anônimas internas. Primeiramente, existem duas implementações separadas de StockOptionEligible— uma para Employees e uma para Persons (a fim de obedecer à interface). Depois, vem uma implementação de StockOptionProcessingCallback que é utilizada para manipular as opções de ação de processamento para as instâncias Manager .
Tablela 2. Construções de expressão regular comum
^ Negação do que vem a seguir (ou seja, "não qualquer coisa")
Dispondo da sintaxe padrão da Tabela 2, é possível lidar com o exemplo simples da Listagem 21, usando as classes na API Java Regular Expressions.
Lista 21. Reconhecimento de padrões com expressão regular
Em primeiro lugar, a Listagem 21 cria uma classe Pattern ao chamar compile(), que é um método estático em Pattern, com uma sequência literal representando o padrão que você deseja corresponder. Essa literal utiliza a sintaxe de padrão de expressão regular. Neste exemplo, a tradução do padrão é:
Localize uma cadeia de caracteres do formulário a seguida por zero ou mais caracteres, seguida por string.
Em seguida, a Listagem 21 chama matcher() em Pattern. Essa chamada cria uma instância Matcher . O Matcher procura a cadeia de caracteres que você passou em busca de correspondências com relação à sequência padrão utilizada ao criar Pattern.
A Listagem 21 localiza uma única correspondência começando em 0 e terminando em 7. Consequentemente, a chamada para matches() gera true, a chamada para start() gera 0 e a chamada para end() gera 8.
Em cada padrão, você normalmente cria grupos ao delimitar suas partes entre parênteses. Os grupos são numerados da esquerda para a direita, começando com 1 (o grupo 0 representa a correspondência inteira). O código na Listagem 22 substitui cada palavra de wiki por uma cadeia de caracteres que "agrupa" a palavra:
Lista 22. Correspondendo grupos
Execute o código da Listagem 22 e obtenha a saída de console a seguir:
Listagem 22 faz referência à correspondência inteira ao incluir $0 na cadeia de caracteres de substituição. Qualquer parte de uma cadeia de caracteres de substituição do formulário $ int se refere ao grupo identificado pelo número inteiro (portanto, $1 se refere ao grupo 1, etc.). Em outras palavras, $0 é equivalente a matcher.group(0);.
Lista 23. Instanciando ArrayList
Como é possível ver, ArrayList é heterogêneo: Contém dois tipos de String e um tipo de Integer . Antes do JDK 5, a linguagem Java não tinha nada para restringir esse comportamento, o que causou muitos erros de codificação. Na Listagem 23, por exemplo, tudo parece estar bem até o momento. Mas, em relação a acessar os elementos de ArrayList, o que a Listagem 24 tenta fazer?
Lista 24. Uma tentativa de acessar elementos em ArrayList
Lista 25. Uma segunda tentativa, utilizando genéricos
Os genéricos aprimoram a linguagem Java com uma sintaxe especial para lidar com entidades, tais como Lists, que você normalmente deseja percorrer elemento por elemento. Se você quiser iterar por meio de ArrayList, por exemplo, poderia reescrever o código da Listagem 25 como:
Essa sintaxe funciona para qualquer tipo de objeto que é do tipo Iterable (ou seja, implementa a interface Iterable ).
Uma classe principal fica no centro de algum tipo de wrapper. Ou seja, a "coisa" no centro da classe poderia ser aplicada amplamente e os recursos (atributos, por exemplo) que a cercam são idênticos.
Comportamento comum: Você faz praticamente as mesmas operações, independentemente da "coisa" que está no centro da classe.
A "coisa" é a classe da qual a coleção é composta.
As operações (tais como add, remove, sizee clear) são praticamente iguais, independentemente do objeto do qual a coleção é composta.
O E, que significa Elemento, é a "coisa" que mencionei antes. O concreteListClass é a classe do JDK que você está instanciando. O JDK inclui várias implementações de List<E> , mas você utiliza ArrayList<E>. Outra forma de ver uma classe genérica discutida é Class<T>, em que T significa Tipo. Quando você vê E em código Java, geralmente está se referindo a algum tipo de coleção. Quando vê T, está denotando uma classe parametrizada.
Lista 26. Parametrizando SimpleList
SimpleList pode ser parametrizada com qualquer subclasse Object . Para criar e usar uma SimpleList de, por exemplo, objetos java.math.BigDecimal , você poderia fazer isto:
Esse exemplo é apenas uma pequena amostra do que é possível fazer com enums. Na verdade, enums são muito parecidos com classes; logo, podem ter construtores, atributos e métodos:
Uma diferença entre uma classe e um enum é que o construtor de um enum precisa ser declarado como private e não pode ampliar (ou herdar de) outros enums. Entretanto, um enumpode implementar uma interface.
Esta seção é uma visão geral do pacote java.io . Você aprende a usar algumas das suas ferramentas para coletar e manipular dados de várias origens.
Com muita frequência, os dados que você usa nos seus programas Java vêm de uma origem de dados externa, como um banco de dados, transferência direta de bytes por soquete ou armazenamento de arquivos. A linguagem Java lhe oferece muitas ferramentas para obter informações dessas origens e a maioria delas está localizada no pacote java.io .
É possível acessar arquivos no sistema de arquivos utilizando fluxos. No nível mais baixo, os fluxos permitem que um programa receba bytes de uma origem ou envie a saída para um destino. Alguns fluxos manipulam todos os tipos de caracteres de 16 bits (tipos Reader e Writer ). Outros manipulam somente bytes de 8 bits (tipos InputStream e OutputStream ). Existem vários tipos de fluxos dentro dessas hierarquias, todos localizados no pacote java.io . No nível mais alto de abstração há fluxos de caracteres e fluxos de bytes.
StringReader / StringWriter: Leia e escreva caracteres de e para Strings na memória.
InputStreamReader / InputStreamWriter (e subclasses FileReader / FileWriter): Forme uma ponte entre fluxos de bytes e fluxos de caracteres. Os tipos de Reader leem bytes de um fluxo de bytes e os convertem em caracteres. Os tipos de Writer convertem caracteres em bytes para inseri-los em fluxos de byte.
Lista 27. Lendo de um File
Lista 28. Escrevendo em um File
A leitura e composição de fluxos de caracteres um caractere de cada vez não é exatamente eficiente; portanto, na maioria dos casos, provavelmente é melhor usar E/S armazenada em buffer. Para ler de um arquivo usando E/S armazenada em buffer, o código é semelhante à Listagem 27, exceto pelo fato de que você agrupa InputStreamReader em um BufferedReader, como mostrado na Listagem 29.
Lista 29. Lendo de um File com E/S armazenada em buffer
A composição em um arquivo usando E/S armazenada em buffer é igual: Você agrupa o OutputStreamWriter em um BufferedWriter , como mostrado na Listagem 30.
Lista 30. Escrevendo em um File com E/S armazenada em buffer
A serialização Java é mais uma das bibliotecas essenciais da plataforma Java. A serialização é usada principalmente para persistência de objeto e objeto remoto - dois casos de uso em que você precisa ser capaz de fazer uma captura instantânea do estado de um objeto e reconstituir mais tarde. Esta seção oferece uma amostra da API Java Serialization e demonstra como utilizá-la nos seus programas.
Serialização é um processo no qual o estado de um objeto e seus metadados (como o nome da classe de objeto e os nomes dos seus atributos) são armazenados em um formato binário especial. Colocar o objeto nesse formato —serializando-o— preserva todas as informações necessárias para reconstituir (ou desserializar) o objeto sempre que precisar.
Persistência do objeto— armazenar o estado do objeto em um mecanismo de persistência permanente, como um banco de dados
Objeto remoto— enviar o objeto para outro computador ou sistema
A interface Serializable marca o objeto da classe Person— e todas as subclasses de Person— no tempo de execução como serializável.
Lista 31. Serializando um objeto
Para orientar o código na Listagem 31, você poderia usar um teste JUnit, como mostrado aqui (em HumanResourcesApplicationTest.java):
A finalidade de serializar um objeto é ser capaz de reconstituí-lo ou desserializá-lo. A Listagem 32 lê o arquivo que você acabou de serializar e desserializa seu conteúdo, restaurando, assim, o estado da List de objetos Employee .
Lista 32. Desserializando objetos
Novamente, para orientar o código na Listagem 32, você poderia usar um teste JUnit como este (de HumanResourcesApplicationTest.java):
Para a maioria dos fins de aplicativos, marcar seus objetos como serializable é tudo o que você precisa fazer no que diz respeito à serialização. Quando realmente precisar serializar e desserializar seus objetos explicitamente, é possível usar a técnica mostrada na Listagem 31 e na Listagem 32. Porém, à medida que seus objetos de aplicativo evoluem e você inclui atributos neles e remove atributos deles, a serialização assume uma nova camada de complexidade.
No princípio da comunicação com middleware e objeto remoto, os desenvolvedores eram os principais responsáveis por controlar o "formato de ligação" dos seus objetos, o que causava inúmeros problemas conforme a tecnologia evoluía.
Página inicial de tecnologia Java: O site oficial de Java possui links para tudo o que está relacionado à plataforma Java, incluindo a especificação da linguagem Java e a documentação da API Java.
Java 8: Saiba mais sobre o JDK 8 e as ferramentas que vêm com ele.
Refactoring: Improving the Design of Existing Code (Martin Fowler et al., Addison-Wesley, 1999): Esse livro é um recurso excelente para aprender a escrever um código mais limpo e mais passível de manutenção.
Cinco coisas que você não sabia sobre ...: Esta série do developerWorks contém introduções breves para dicas e histórias menos conhecidas (mas frequentemente introdutórias) de programação de Java.