JAVA com Orientação a Objetos © !"#$% #!&'&#!!(% ) * + % % , , -% .% / 0 % 1 + 23% % 4 14 , 5 , 4 5 ) 6 5 ) 5* , / , 7 23% % + / % 8 / 7 % 23 / + 239 , ) 2 0 * : !"#$ JAVA com Orientação a Objetos 6 ; : %'$#' #Informática, 2. Programação de Computador – Programas e Dados < =>;!?(@(A@B!!@$'36@1 $01.642 $$A % !& & ' () * + ,' , * - ./&01/231/ 4.35 ../32))).6 7 4.35 ../32)80) 2! !9!&"&- :::&!&"&- /363. Agradecimento Inicialmente agradeço a Deus por me dar forças e condições de realizar este trabalho. Agradeço ainda a meus amigos, todos esses anos trabalhando com vocês só me fizeram uma pessoa melhor. Em especial, a minha família, a minha querida esposa Fernanda, a meu pai Alexandre, a minha mãe Leonidia, a meus irmãos e a meus queridos sobrinhos. Obrigado a todos pelo apoio tão importante que me fornecem. Introdução Caro leitor, é um prazer apresentar para vocês a tecnologia Java e os conceitos vinculados à programação orientada a objetos. A ideia central deste livro é garantir aos estudantes da área de Ciência da Computação e afins uma forma alternativa de conhecer essa tecnologia e o paradigma que atualmente dominam parte do mercado. O livro é resultado de anos de experiência trabalhando com disciplinas ligadas à área de programação, no qual foram aplicados conhecimentos da tecnologia Java, assim como a prestação de serviços que envolviam tais elementos. Ao final da leitura deste livro, espera-se que o leitor tenha conhecimento suficiente para desenvolver e entender as aplicações que utilizam as diversas características que norteiam a tecnologia Java. Nos últimos anos, a programação Java tornou-se uma das mais populares do mercado e tem feito com que os profissionais, que dominam seus preceitos, passem a ser mais valorizados e até mesmo disputados por empresas, considerando a carência existente para tais nichos de mercado. Tal fatia de mercado, dominada pela tecnologia Java, deve-se em sua maioria às suas potencialidades, uma vez que são aplicações que podem estar presentes em desde eletrodomésticos, aparelhos celulares, carros até as mais simples, tais como as existentes na Web. Assim, a linguagem Java tem características que a tornam diferenciada, sendo de grande importância no que tange a criação de software. Diante disso, foi montado um roteiro neste livro, no qual se priorizou garantir ao leitor uma visão ampla de todos os conceitos importantes para sua iniciação na programação com a tecnologia Java. No Capítulo 1, são apresentados os fundamentos da programação com a linguagem Java, considerando elementos importantes, como, por exemplo, as palavras reservadas, estrutura básica de um programa Java e as vantagens e desvantagens de utilizar a tecnologia mantida atualmente pela Oracle. No segundo capítulo, começaremos a ver os fundamentos do paradigma de programação orientada a objetos, considerando passo a passo cada conceito, fazendo com que o leitor entenda os aspectos básicos e necessários para a continuidade no aprendizado, como, por exemplo, a ideia de classe e objetos na tecnologia Java. Continuando, no Capítulo 3, são trabalhadas VI t JAVA com Orientação a Objetos as estruturas mais avançadas da programação orientada a objetos, fazendo com que o leitor consiga dar mais um passo no aprendizado do paradigma e da tecnologia Java, sendo considerados conceitos, tais como construtores, destrutores e outros. Assim como no Capítulo 3, no Capítulo 4 continuaremos a trabalhar com os elementos mais avançados da programação orientada a objetos, tais como os importantíssimos conceitos de herança e polimorfismo, que devem ser vistos e tratados como elementos-chave da tecnologia Java e do paradigma de programação orientada a objetos. Claro que devemos lembrar sempre do conceito de portabilidade oriundo da estruturação muito bem pensada da tecnologia Java como um importante diferencial. Em nosso quinto capítulo, será apresentado a você, leitor, outros elementos da tecnologia Java, no qual iremos lidar com ferramentas úteis para o dia a dia de um programador. Trabalharemos nesse capítulo com bibliotecas matemáticas, números randômicos ou ainda veremos como lidar com datas, manipular strings e assim por diante. Logo, serão consideradas diversas possibilidades e vantagens que podem ser exploradas, o que com certeza irá auxiliar os programadores a dominarem tais conceitos. O Capítulo 6 tem por objetivo apresentar o trabalho de tratamento de exceções, sendo de extrema importância seu entendimento, já que se trata de um elemento central nas atividades que exigem a manipulação de dados, seja em arquivos texto, seja mesmo em um banco de dados. Este tema é de grande importância, pois a tecnologia Java possibilita, em sua programação, que sejam tratados eventuais problemas ou erros antes que estes venham a realmente ocorrer, permitindo que sejam tomadas as devidas providências para uma autorrecuperação do software durante a execução. Podemos enxergar isso como uma prevenção para os possíveis erros nos trechos mais suscetíveis a problemas. Finalizando, em nosso sétimo capítulo, começaremos a trabalhar com a manipulação de arquivos, lidando com o processo de criação, persistência de dados e leitura de dados utilizando classes e interfaces disponibilizadas pela tecnologia Java. Amigo leitor, em todo o livro tentarei manter uma linguagem mais simples, clara e cotidiana, na qual o principal objetivo e descomplicar o entendimento dos diversos conteúdos mencionados. Recomendo que sejam feitos os exemplos disponibilizados, que foram pensados considerando os aspectos que devem acompanhá-lo em sua jornada. Logo, vamos ao que interessa e tenha uma ótima leitura. Sumário Capítulo 1 Conceitos básicos da tecnologia Java..........................1 1.1 Fundamentos da tecnologia Java ....................................................1 1.2 Estrutura e conceitos básicos da programação Java .......................5 1.3 Entrada de dados ........................................................................... 13 1.4 Estruturas de controle................................................................... 15 1.5 Arrays e matrizes ........................................................................... 21 1.6 Funções. ......................................................................................... 25 Capítulo 2 Programação orientada a objetos.............................29 2.1 Conceitos da programação orientada a objetos ........................... 30 2.1.1 Classe .......................................................................................... 32 2.1.1.1 Qualificadores de acesso ......................................................... 33 2.1.1.2 Pacotes ..................................................................................... 35 2.1.1.3 Import...................................................................................... 36 2.1.1.4 Comentários ............................................................................ 37 2.1.2 Atributos ..................................................................................... 38 2.1.3 Métodos ...................................................................................... 39 2.1.3.1 Identificador this ..................................................................... 40 2.1.4 Mensagem e modularização de código ...................................... 41 2.1.5 Objeto ou instanciação............................................................... 44 Capítulo 3 Construtores, destrutores e encapsulamento............51 3.1 Construtores .................................................................................. 51 3.2 Destrutores e Garbage Collector (Coletor de Lixo) ...................... 54 3.2.1 Garbage Collector ....................................................................... 55 3.3 Encapsulamento ............................................................................ 56 VIII t JAVA com Orientação a Objetos Capítulo 4 Herança, polimorfismo e interface...........................63 4.1 Herança .......................................................................................... 63 4.2 Polimorfismo ................................................................................. 69 4.2.1 Sobrecarga .................................................................................. 70 4.2.2 Cast ............................................................................................. 71 4.2.3 instanceof ................................................................................... 73 4.2.4 Sobrecarga de construtores ....................................................... 74 4.2.5 Redefinição ................................................................................. 74 4.3 Interface ......................................................................................... 76 4.3.1 Herança múltipla ........................................................................ 83 Capítulo 5 Ferramentas úteis para o dia a dia............................85 5.1 Manipulação de strings ................................................................. 85 5.1.1 Construtores da classe String .................................................... 86 5.1.2 Outros métodos.......................................................................... 86 5.2 Data e hora ..................................................................................... 88 5.3 Operações matemáticas ................................................................ 93 5.4 Trabalho com vetores especiais .................................................... 95 5.4.1 Classe Vector............................................................................... 96 5.4.2 Classe HashMap ......................................................................... 98 5.4.3 Classe Arrays .............................................................................. 99 Capítulo 6 Tratamento de exceções e entrada de dados............101 6.1 Tratadores de exceções ................................................................ 102 6.1.1 Throws....................................................................................... 106 6.2 Entrada de dados ......................................................................... 107 SumáriotIX Capítulo 7 Manipulação de arquivos de texto ..........................111 7.1 Arquivos ....................................................................................... 111 7.2 Entradas ....................................................................................... 112 7.3 Saídas ........................................................................................... 114 7.4 Streams ........................................................................................ 116 7.5 Acessos aleatórios em arquivos .................................................. 118 Referências Bibliográficas................121 Apêndice I Instalação do Sdk e Configuração das Variáveis de Ambiente (Windows Xp) .................................................................................... 123 Apêndice II JAVADOC ........................................................................................... 129 Capítulo 1 Conceitos básicos da tecnologia Java Quando falamos em programação, não há como deixar de vislumbrar tal atividade como sendo um elemento central na vida de um profissional ligado à área de Ciência da Computação e afins. No cotidiano, o mínimo que se espera de um profissional de TI é que este domine os elementos e os conceitos básicos, aplicados a uma determinada linguagem de programação. O mercado atualmente oferta uma infinidade de possibilidades de linguagens de programação para a construção de softwares, indo das mais complexas e trabalhosas até as mais simples. Existem, entre essas linguagens de programação, várias que são bem aceitas no mercado, que consideram obviamente variáveis estratégicas, tais como preço, velocidade da curva de aprendizagem e desenvolvimento. Entre tais, a linguagem e a plataforma de desenvolvimento de aplicações Java vêm no decorrer dos anos tornado-se uma das mais utilizadas e aceitas no mercado. Isso, graças ao seu potencial tecnológico combinado ao paradigma de orientações a objetos, além de considerar os custos envolvidos. O Java é uma tecnologia que foi originalmente desenvolvida pela Sun Microsystem em meados de 1995, sendo regida pela GPL (General Public License). A Oracle Corporation adquiriu todos os direitos pela Sun e obviamente, também sobre a tecnologia Java. Para a maioria dos autores e desenvolvedores, a tecnologia Java pode ser definida como: “simples, distribuída, interpretada, robusta, segura, de arquitetura neutra, portátil, multifunções e dinâmica“. Ao considerar tais perspectivas, o objetivo deste capítulo é apresentar e preparar o leitor para compreender e aplicar os fundamentos básicos da tecnologia Java. Estes são úteis e de extrema importância para a compreensão dos demais conceitos vinculados à plataforma de desenvolvimento mantida pela Oracle e que serão considerados nos próximos capítulos de nosso livro. 1.1 Fundamentos da tecnologia Java A maioria dos autores restringe-se a citar que o Java consiste em uma 2 t JAVA com Orientação a Objetos linguagem de programação orientada a objetos baseada na sintaxe da linguagem de programação C. Porém, podemos ir além disso ao citar que o Java é uma plataforma tecnológica que possui diversas características que o mercado considera como importante e que devido a isto, tem garantido uma fatia considerável das aplicações desenvolvidas com essa tecnologia. Com certeza, pode-se afirmar que a característica que mais se destaca neste processo e a torna diferenciada diante das demais linguagens consiste na portabilidade dos sistemas desenvolvidos com o Java. Entende-se portabilidade como, nada mais nada menos, a independência de plataforma - no caso, o sistema operacional (Windows, Linux, Solaris, MAC OS) e o hardware, no qual os softwares serão executados. Tal característica nasce do fato da estrutura das aplicações desenvolvidas com a tecnologia Java ser inicialmente compilada e depois, interpretada. Indo um pouco mais além no entendimento, o ponto central desse processo esta na geração do bytecode, também conhecido como arquivo com extensão “.class”. Este é obtido ou gerado por meio da compilação do código-fonte dos arquivos “java puros” ou de extensão “.java”, que serão interpretados futuramente. É importante mencionar que com isso, graças ao fato da tecnologia Java possuir para cada plataforma (sistema operacional + hardware) uma Máquina Virtual Java, também conhecida como Java Virtual Machine (JVM), este processo torna todas as aplicações Java portáveis entre si. Assim, quem irá preocupar-se com as especificidades de cada plataforma não é mais o programador, mas sim a JVM fornecida. Essa máquina virtual é capaz de interpretar os arquivos compilados (“.class”), iniciando a execução do software, independentemente da plataforma na qual o programa foi construído. Por exemplo, uma vez compilado nosso programa utilizando o sistema operacional Windows em uma estrutura de hardware da família x86, caso queiramos que nosso programa rode em uma máquina com o sistema operacional Linux, não será necessário realizar uma nova compilação para este software, como acontece com as outras tecnologias e linguagens de programação. Neste contexto, será apenas necessária a presença da JVM para o Linux instalado, o que hoje é algo natural nas máquinas comercializadas. Como o leitor pode reparar, esta característica ímpar só é possível graças à existência da JVM, que é fornecida pela tecnologia Java e na qual cada sistema operacional possui uma específica, distribuída pela mantenedora da tecnologia. A JVM pode ser vista como sendo uma camada intermediária Capítulo 1 - Conceitos básicos da tecnologia Javat3 entre sua aplicação e a plataforma que executará seu programa. A maioria dos computadores atuais já é vendida com algum tipo de Máquina Virtual Java e isso tem garantido uma popularização ainda maior das aplicações Java, o que nos faz crer que, em sua maioria, qualquer aplicação Java funcionará corretamente nos computadores obtidos atualmente no mercado. Para uma melhor visualização do processo citado como portabilidade, a Figura 1 demonstra o modelo computacional definido pela tecnologia Java e suas aplicações. Vamos conferir? Figura 1: Estrutura de compilação e execução de programas no Java. Como definição, podemos mencionar que um programa Java consiste em um conjunto de instruções, que a JVM é responsável por interpretar e dessa forma, garantir a independência de plataforma na qual o programa foi construído e será executado. Isto é, basta que haja uma implementação de uma máquina virtual para a plataforma ser utilizada. O termo plataforma, como já vimos, normalmente no meio computacional é definido como a combinação de sistema operacional mais hardware. Porém, para a plataforma Java, ela é definida somente com o software, por isso o conceito de máquina virtual. A plataforma Java possui dois componentes que precisamos conhecer quando a adquirimos, sendo: 4 t JAVA com Orientação a Objetos Máquina Virtual Java (JVM): A máquina imaginária que é implementada por meio da emulação em um software executado em uma máquina real e que fornece as especificações da plataforma de hardware para a qual todo o código Java está compilado. A JVM faz parte do ambiente de execução Java, mais popularmente conhecido como JRE (Java Run-Time Environment); Interface para Desenvolvimento de Aplicações (API Java): Trata-se das interfaces de desenvolvimento para a utilização na criação de aplicações Java, no caso são as bibliotecas para a criação de programas que a própria linguagem Java fornece, disponibilizados por meio do JDK (Java Development Kit) ou como vem sendo disponibilizado nos últimos anos como SDK (Source Development Kit). Para o desenvolvimento de aplicações Java, é necessário que se tenha instalado no computador o SDK. Desta forma, obtenha uma cópia na página oficial da Oracle e instale em seu computador. O Apêndice deste livro contém mais informações sobre o processo de instalação do pacote SDK, assim como a configuração das variáveis de ambiente necessárias para a correta utilização do conjunto de ferramentas da tecnologia Java. Após a correta instalação e configuração das variáveis de ambiente do SDK, podemos usar um editor de texto qualquer para a construção do código de nosso programa Java. Porém, existem diversos ambientes de desenvolvimento, também conhecidos como IDE (Integrated Development Environment), para a programação Java, sendo que os mais utilizados no mercado são: Eclipse, NetBeans e JBuilder. Como nossa intenção consiste no aprendizado da linguagem Java, não nos apegaremos a nenhuma IDE, o que deixa você, leitor, livre para a utilização ou não de uma dessas ferramentas de desenvolvimento, sendo que apenas um editor de texto será suficiente para concluir o aprendizado deste livro. Um aspecto importante a ser citado aqui é que a linguagem Java possui diferentes tipos de programas, sendo que a classificação é feita por meio da modalidade X localização de execução dos mesmos. Assim, esses programas podem ser definidos como: applications, applets e servlets. Nesta unidade, somente trabalharemos com as applications no console, ou seja, prompt de comando, seja Windows, seja Linux. Capítulo 1 - Conceitos básicos da tecnologia Javat5 1.2 Estrutura e conceitos básicos da programação Java Agora, vamos ao que interessa. Nesta seção do livro, vamos aprender como são construídas as estruturas básicas para os programas escritos na linguagem Java, além de conhecer os conceitos mais básicos da programação com a tecnologia. O primeiro detalhe que deve ser considerado na linguagem consiste no fato de que todo programa Java é uma classe, independentemente das três possíveis modalidades dos programas Java citadas anteriormente (Applications, Applets e Servlets). A estrutura básica é sempre a mesma: “uma classe”. Um programa Java é organizado em arquivos de texto com a extensão “.java” e cada arquivo pode conter várias classes, mas usualmente teremos apenas uma. Aqui, temos uma consideração importante e que você não deve esquecer jamais: O nome do arquivo da classe java deve possuir exatamente o mesmo nome que foi dado internamente à classe, pois a máquina virtual procura a classe por meio do nome do arquivo. Uma metodologia que pode auxiliá-lo a entender a tecnologia Java neste momento consiste na utilização de analogias com outras linguagens de programação, das quais você já tenha conhecimento e domínio. Então, vamos a elas! Se fôssemos realizar uma comparação básica, poderíamos dizer que as classes no Java podem ser comparadas às famosas units do Pascal. Já para os adeptos do C e C++, a estrutura de nossos programas praticamente será a mesma, uma vez que a tecnologia Java está baseada em tais linguagens. Ainda, veremos mais à frente em nossos capítulos que os atributos no Java são como as variáveis existentes em Pascal e C, e os métodos das classes Java seriam os procedimentos e as funções da linguagem C e Pascal. Vários destes aspectos citados serão considerados quando estivermos tratando do conceito de orientação a objetos. Outro detalhe a ser considerado é a definição de blocos de código que, no Java, são definidos com a utilização de “{“ e “}”, assemelhando-se assim ao C ou C++, diferentemente dos elementos em Pascal, que utilizam no caso, “BEGIN” e “END”. Por enquanto, preocupe-se apenas em entender como os blocos são definidos, sendo de extrema importância para começarmos a 6 t JAVA com Orientação a Objetos entender os conceitos fundamentais para a construção de programas simples com a tecnologia Java. Então, diante disso, vamos ao que interessa. Chegou a hora de desenvolvermos nosso primeiro programa em Java. Transcreva o conteúdo abaixo para um editor de texto simples, tal como o ‘notepad’ do Windows ou mesmo o ‘Vi’ do Linux. Por enquanto, siga o exemplo conforme é apresentado, digitando como pode ser observado na primeira coluna, visto que a linguagem Java é case sensitive, ou seja, existem diferenças entre as letras maiúsculas e minúsculas em sua compilação e interpretação. A seguir, faremos uma análise do que foi feito em nosso primeiro programa. //Classe PrimeiroPrograma.java class PrimeiroPrograma{ public static void main (String arg []){ System.out.println(“Olá leitor”); } } Agora que você já construiu seu primeiro programa em Java e considerando o que já foi citado anteriormente neste capítulo, salve seu programa lembrando que o nome do arquivo Java deve ser necessariamente igual ao nome de declaração da classe - no caso específico, o arquivo deve ser salvo como “PrimeiroPrograma.java”. Salve o arquivo tomando o cuidado necessário para assegurar que a extensão “.java” seja garantida por seu editor de textos, uma vez que eles podem manter a extensão “.txt” e isso impedirá a compilação e a interpretação de seu programa. Feito isso, vamos à explicação de nosso programa, entendendo o código apresentado. Para que você tenha um programa executável construído com a linguagem Java, sua classe deve sempre possuir um método específico, que caracteriza a classe como uma aplicação que a Máquina Virtual Java deverá interpretar no momento da execução do programa. Todo programa/ classe Java que possuir a função executável ou, como veremos nos capítulos seguintes, o método executável com a assinatura “public static void main (String[] args){ }” apresentada na linha 3 de nosso código anterior, será considerado um programa executável pela JVM. Capítulo 1 - Conceitos básicos da tecnologia Javat7 Logo, este trecho será executado automaticamente quando for encontrado no programa submetido à maquina virtual. Um detalhe importante que deve ser comentado nesse trecho trata-se do vetor (array) “String[] args”. Esse trecho presente na assinatura de nossa função executável pode conter parâmetros, que foram passados pela linha de comando na execução de nosso programa. Demonstraremos isso a seguir. Outro detalhe a ser destacado e que você deve reparar no código apresentado, está no fato de que, por convenção, ou seja, algo aceito por todos os programadores Java, os nomes das classes no Java são sempre iniciados com letra maiúscula e, caso venham a ser compostos, cada nova palavra será iniciada novamente com uma letra maiúscula, definindo seu início. Note que isso é feito em nosso código anterior e, diante disso, passe a adotar em seus programas Java. Na quarta linha de nosso primeiro programa, é apresentada a linha System.out.println(“Olá leitor”);, que nada mais faz do que realizar a impressão em console. Esse comando informa ao sistema (System) que será realizada uma operação de saída (out), sendo que tal operação consiste em uma impressão com quebra de linha (println). Existem outras possibilidades de realizar a impressão em console Java e estaremos lidando com elas no momento certo. Então, memorize a linha demonstrada, pois sempre iremos utilizar essa linha de código quando quisermos realizar a impressão em console em nossos próximos programas. Certo pessoal, até aqui conseguimos vencer uma parte importante de nosso primeiro capítulo, considerando que os aspectos citados são comuns a qualquer programa Java que você venha a construir de agora em diante. Mas, está faltando visualizarmos o resultado de nosso primeiro programa. Para isso. teremos de compilar e interpretar nosso programa e, logo, necessitamos de outras ferramentas. Existem dois comandos básicos que a tecnologia Java fornece por meio do JDK para tais funções, sendo: javac e java, respectivamente. Tais comandos só funcionarão em seu prompt de comando, como, por exemplo, o DOS do Windows, depois de definidas as variáveis de ambiente, para que os comandos estejam disponíveis em qualquer caminho de seu sistema operacional, sendo: t JAVA_HOME: Deve conter o caminho da pasta, na qual foi instalado o SDK, ou seja, o local onde possui todas as ferramentas e APIs básicas fornecidas pela tecnologia Java em seu computador; 8 t JAVA com Orientação a Objetos t CLASSPATH: Deve conter o caminho no qual estão localizadas as bibliotecas dos programas que você está utilizando. Por padrão, o valor desta variável é apenas “.”, que significa que as bibliotecas utilizadas estão na mesma pasta da aplicação que você está desenvolvendo. Caso seja necessário, incremente a essa variável os caminhos nos quais existam outras APIs importantes para o desenvolvimento de sua aplicação, que podem ter sido adquiridas na Internet, por exemplo. Para que você obtenha maiores informações sobre a criação de variáveis de ambiente, disponibilizamos, ao final deste livro, no ANEXO I, um tutorial sobre o assunto. Além disso, podem ser encontrados na Internet diversos tutoriais sobre o assunto, sendo que sempre devemos levar em consideração o sistema operacional a ser utilizado no momento do desenvolvimento e da configuração. Uma vez que o JDK está instalado corretamente e foram definidas as variáveis de ambiente, estamos prontos para compilar e executar nosso primeiro programa. Assim, digite os comandos apresentados abaixo em um prompt de comando do seu sistema operacional, considerando que no prompt, devemos estar no diretório no qual são mantidas nossas classes. Em nosso exemplo, a classe PrimeiroPrograma.java está no diretório c:/>programas. javac PrimeiroPrograma.java java PrimeiroPrograma O primeiro comando apresentado consiste no javac que irá realizar a compilação de sua classe. Já o segundo comando apresentado, java, irá realizar a interpretação do bytecode gerado por meio da compilação, conforme a estrutura apresentada na Figura 1. Certo! Se tudo ocorreu conforme o esperado, será impresso o texto “Olá leitor”, como é apresentado na imagem a seguir. Capítulo 1 - Conceitos básicos da tecnologia Javat9 Caso o leitor tenha dúvidas sobre como realizar a navegação nos diretórios de seu computador, indicamos a leitura de qualquer tutorial que auxilie na manipulação de comandos na linha de comando de seu sistema operacional. Mas, outro ponto importante na construção de uma aplicação no Java, ou seja, na construção das classes e que deve ser considerado para darmos um passo adiante em nosso aprendizado consiste na definição das variáveis de uma classe Java. Em muitos momentos, conforme já mencionamos, as variáveis na linguagem Java aparecerão nos textos, livros e tutoriais, sendo tratadas como atributos. Logo, qual a diferença entre atributos e variáveis? A diferença entre eles é que um atributo está intrinsecamente ligado às características da classe, ou seja, descreve algo ligado ao contexto ou à ideia central à qual a classe está relacionada ou preocupa-se em descrever. Um exemplo seria um atributo para manter os dados de RG de uma pessoa. Já as variáveis não precisam ser aplicadas necessariamente a um contexto da classe, como, por exemplo, em uma variável auxiliar para o comando de repetição FOR ou mesmo uma constante. Por enquanto, não se preocupe com isso, pois no decorrer da disciplina, essa diferença se tornará mais clara e logo você saberá diferenciar tais elementos de programação com base nos paradigmas da orientação a objetos. Em continuidade ao nosso aprendizado sobre os conceitos básicos da tecnologia Java, vamos por a mão na massa e criar nosso segundo programa, utilizando o armazenamento em memória com variáveis. Transcreva o código a seguir para um editor de texto, assim como fizemos em nosso primeiro programa. Depois disso, compile e execute-o por meio dos comandos javac e java, respectivamente, em um prompt de comando de seu sistema 10 t JAVA com Orientação a Objetos operacional, conforme realizado no exemplo anterior. Não se esqueça: Para que você consiga executar tais comandos seguido do nome de seu programa, aqui SegundoPrograma.java, você deve estar localizado no diretório no qual a classe se encontra, no caso onde foi salva. Fique atento também ao fato mencionado de que o nome de nosso arquivo Java deve ser igual ao da classe - neste exemplo, nosso arquivo Java deve ser nomeado como SegundoPrograma.java. // SegundoPrograma.java class SegundoPrograma{ public static void main (String arg []){ int a = 5; a = 5+10; System.out.println(“O valor é:”+a); } } No código de nosso segundo programa, começamos a manipular o armazenamento na memória volátil, ou seja, a criação de variáveis. No programa, foi inicialmente criada uma variável do tipo inteiro ‘a’, que recebe uma atribuição de valor, no caso o valor ‘5’. Depois, na quarta linha de nosso segundo programa, é feita uma operação cujo resultado é armazenado na mesma variável ‘a’, que é impressa na linha seguinte. Note que, em regra, quando se quer definir variáveis, constantes e mesmo atributos no Java, como veremos nos conceitos de orientação a objetos, em primeiro lugar você deve informar o tipo de dado que será mantido, no caso String, int ou qualquer outro tipo de dados, sendo ele primitivo ou não. Depois, é dado o nome de acesso à variável e finalmente, atribuímos um valor inicial ou não, conforme é apresentado a seguir. Tipo variável = ValorInicial; final tipo CONSTANTE = Valor; Capítulo 1 - Conceitos básicos da tecnologia Javat11 Na segunda linha de nossa caixa anterior, a palavra reservada final determina que tal variável não sofrerá alterações, sendo mantido o valor atribuído no momento de sua declaração, consistindo assim em uma constante para todo o programa, enquanto este estiver em execução. Outro detalhe importante que você deve saber e nunca mais esquecer é que o Java é uma linguagem de programação fortemente orientada a objetos e com exceção dos tipos primitivos, “qualquer” coisa no Java consiste em uma classe/objeto. Devemos destacar que existem várias palavras que são reservadas pela linguagem Java e elas não podem ser utilizadas em vários momentos em seu programa, como, por exemplo, na definição de nome dos atributos, classes ou outros elementos. Tais palavras reservadas são apresentadas na Tabela 1 a seguir. Tabela 1. Palavras reservadas. abstract double int strictfp boolean else interface super break extends long switch byte native synchronized case new this catch package throw char for private throws class goto protected transient const if public try continue implements return void default import short volatile do instanceof static while Várias dessas palavras reservadas são elementos naturais em outras linguagens de programação, principalmente na linguagem C. Assim, o leitor que já possui algum domínio do C ou C++, não terá dificuldades com a manipulação das palavras reservadas no Java. Outro ponto importante no Java é que devem ser considerados, uma vez que são extremamente necessários, os operadores matemáticos. No caso, utilizamos os operadores +, -, * e / para realizarmos as operações matemáticas básicas, ou seja, soma, subtração, multiplicação e divisão, respectivamente. No Capítulo 5, iremos trabalhar com a API Math, que possibilitará a você realizar operações matemáticas mais complexas. Devemos ainda considerar 12 t JAVA com Orientação a Objetos outros operadores que são de suma importância para a linguagem Java, estando relacionados principalmente ao processo lógico dentro de nossos programas, como no caso dos testes condicionais, sendo que os mais utilizados são listados no quadro da Tabela 2. Tabela 2. Operadores no Java. OPERADORES DESCRIÇÃO == Igualdade != Diferente > Maior < Menor >= Maior igual <= Menor igual ! Negação && E lógico || Ou lógico Entre as várias considerações que podem ser feitas quanto ao uso dos operadores, devemos destacar ainda a outra funcionalidade atribuída ao operador “+”, que consiste na concatenação de Strings, como pode ser observado em nosso próximo exemplo. Conforme mencionado anteriormente e como pode ser vislumbrado na Tabela 1, uma String não é um tipo primitivo, mas sim uma classe. Logo, você deve estar perguntando-se: “Então, qual a diferença entre um tipo primitivo e uma classe”? Existem várias diferenças que poderiam ser citadas, mas como diversas delas estão relacionadas ao conceito de orientação a objetos, do qual ainda não tratamos, vamos prender-nos inicialmente à diferença de que uma classe geralmente agrega funcionalidades das quais os tipos primitivos não dispõem. Essas funcionalidades, no caso, são denominadas de métodos e que conheceremos mais profundamente no Capitulo 2. Um exemplo que demonstra essa diferença com a utilização de uma funcionalidade pode ser observado no exemplo a seguir, no qual é realizada a conversão de uma String em um inteiro, sendo de extrema importância para os vários programas que iremos construir. Logo, tente memorizar tal funcionalidade da classe Integer. Capítulo 1 - Conceitos básicos da tecnologia Javat13 // TerceiroPrograma.java public class TerceiroPrograma { public static void main(String args[]){ String entrada = “1”; entrada = entrada+”1”; Integer numero = Integer.parseInt(entrada); System.out.println(entrada); System.out.println(numero); } } Note que no exemplo anterior, é criada uma variável do tipo String denominada entrada, que recebe o caractere “1”. Note que não é um número, mas sim um caractere. Em nossa segunda linha do exemplo, o valor existente na variável de entrada é concatenado a um novo caractere, no caso “1”. Note que o resultado dessa operação matematicamente seria o valor 2, mas, no caso, por se tratar de manipulação, em específico uma concatenação, o valor mantido na variável será “11”, pois não estamos lidando com uma operação matemática. Na terceira linha é feita a transformação desse conjunto de caracteres em um tipo número, que possibilitará as operações matemáticas. Nas duas últimas linhas do exemplo são realizadas as impressões em console dos valores existentes nas duas variáveis do programa. 1.3 Entrada de dados Bom pessoal, um importante aspecto de qualquer programa computacional consiste na entrada de dados. Pois bem, a primeira coisa a ser feita é conhecer a classe Scanner do pacote java.util. A classe Scanner possibilita que seja realizada a leitura dos dados procedentes tanto do teclado quanto de outras fontes como arquivos de texto. A Scanner passou a ser disponibilizada a partir da distribuição Java 5, na qual se buscou simplificar todo o trabalho com a manipulação de dados oriundos de fontes de dados, no caso as streams vindas do sistema (System.in) ou mesmo de arquivos de texto. Pois bem, nada como um exemplo para que possamos memorizar todos os conceitos e detalhes vinculados à classe Scanner. Assim, transcreva o código a seguir e execute, observando a utilização de nossa nova funcionalidade. 14 t JAVA com Orientação a Objetos //QuartoPrograma.java import java.util.Scanner; class QuartoPrograma{ public static void main(String[] arg){ Scanner entrada = new Scanner(System.in); System.out.println(“Digite um numero “); //lendo o número digitado no teclado int valor = entrada.nextInt(); System.out.println(“o numero digitado “ + valor); } } Repare que em nosso exemplo anterior, surgiu um novo elemento que está relacionado à importação de uma classe para sua utilização. Este conceito ficará mais claro em nosso próximo capítulo quando estivermos falando sobre os conceitos relacionados à mensagem entre as classes por meio de métodos. Mas, voltando ao exemplo, repare que na quarta linha é criada uma variável denominada entrada do tipo Scanner, que é inicializada ‘escutando’ as entradas do sistema (System.in). Repare que na sexta linha de nosso programa, por meio da função ou do método nextInt( ) da variável de entrada, o valor digitado pelo usuário será atribuído à variável valor, que será impressa na linha seguinte. No momento da execução do programa, você irá reparar que logo após ser impressa a mensagem solicitando que o usuário digite um número, o programa aguardará a entrada do valor. Isso se deve às características do método nextInt( ) da classe Scanner. Um ponto importante a ser citado está no fato de que, assim como no caso das impressões no console, existem outras possibilidades para realizar a entrada de dados no Java, porém, com certeza, a utilização da classe Scanner é a mais simples. Portanto, em nossos exemplos iniciais, a utilização da classe Scanner será o suficiente até que conheçamos as outras possibilidades e você escolha qual a mais adequada para seus programas. Capítulo 1 - Conceitos básicos da tecnologia Javat15 1.4 Estruturas de controle Certo pessoal, chegamos a um ponto importante de sua leitura, no qual iremos aprender a trabalhar com uma das características mais importantes em um programa, que consiste nas estruturas de controle. Agora, comentaremos sobre os famosos if/else, switch, while, do-while e for. Podemos mais uma vez considerar a relação de analogia para entender estes comandos. Essencialmente, a única diferença desses comandos no Java para a programação em Pascal está nos indicadores de bloco “{” e “}”, sendo, porém, semelhantes em C ou C++. Então, vamos trabalhar com cada um deles. O primeiro consiste no comando de seleção if que possibilita que um programa analise se uma expressão lógica é verdadeira ou falsa e a partir disto, direcione a execução de seu programa. A estrutura do if trabalha com a perspectiva de acontecimentos, logo, realiza o que sua tradução do inglês, que é “Se”, se propõe. Sendo mais detalhista, trata-se de uma estrutura na qual, “Se” a expressão lógica do comando for verdadeira, a sequência natural do if será executada. Porém, “Se” a expressão lógica for falsa, surgirá um novo elemento na estrutura, que consiste na cláusula else (então). Essa cláusula será executada na sequência do if, obviamente se ela tiver sido implementada. if (expressão_lógica){ Sequência 1; }else{ Sequência 2; } Para que não existam dúvidas, vamos a um pequeno exemplo de fixação dos conceitos do comando de seleção if/else. 16 t JAVA com Orientação a Objetos //Classe ExemploIf.java class ExemploIf{ public static void main (String[] args){ if (args.length > 0){ System.out.println(“Dentro do if”); }else{ System.out.println(“Dentro do else”); } } } Assim como fizemos em nossos exemplos anteriores, salve sua classe com o nome “ExemploIf.java” e utilize os comandos javac e java, respectivamente, para a compilação e a execução de seu programa, conforme é apresentado na figura a seguir. Repare que em nossa figura anterior, junto ao comando java e ao nome da classe, consta um conjunto de valores “Teste do if/else”. Estes, por sua vez, são os argumentos passados para o programa durante a execução, que podem ser acessados por meio do vetor de String “args”, presente na assinatura do método principal void main. No exemplo apresentado no código java, o código dentro do bloco definido pela cláusula if só será realizado caso, pelo menos, um argumento seja passado no momento da execução do programa, do contrário, o vetor args estará vazio e o bloco da cláusula else será executado. Capítulo 1 - Conceitos básicos da tecnologia Javat17 Outra interessante estrutura de seleção que deve ser mencionada consiste na cláusula switch. A switch é equivalente de maneira lógica a um conjunto de cláusulas if organizadas de forma encadeada e isso é usualmente mais eficiente durante uma execução de um programa. A expressão utilizada pelo switch deve retornar necessariamente um resultado ordinal, ou seja, classificado em um dos possíveis blocos da cláusula, mais conhecidos como “case”. As diretivas encontradas a partir do caso escolhido são executadas até o final da cláusula switch ou até uma ordem de “break” que encerra a cláusula. Se o valor a ser testado não possuir um caso específico, então será executada a diretiva default, que é opcional, sendo colocada ao final da cláusula switch, após todos os casos de classificação possíveis. Assim, para um melhor entendimento do mencionado, vamos a mais um exemplo. /Classe ExemploSwitch.java class ExemploSwitch{ public static void main (String args[]){ switch(args[0].charAt(0)) { case ‘A’:System.out.println(“Vogal A”); break; case ‘E’:System.out.println(“Vogal E”); break; default:System.out.println(“Não é vogal”); } } } Repare que, em nosso exemplo, trabalhamos mais uma vez com a utilização do vetor args, ao qual os valores são passados durante a execução, conforme feito no exemplo para a cláusula if. Aqui, nosso programa faz uma verificação se a vogal presente na primeira posição do vetor é a vogal ‘A’ ou ‘E’, ou seja, realiza a classificação dos valores testados. É interessante mencionarmos a existência da diretiva default no exemplo, que será executada se nenhum dos casos for realizado. Então, salve seu programa e execute, conforme apresentado a seguir. 18 t JAVA com Orientação a Objetos Veja que neste exemplo, fizemos uso mais uma vez dos argumentos para passar valores para que sejam verificados em nosso programa, em específico a vogal ‘A’. Prosseguindo, outra estrutura a ser mencionada no Java é a estrutura de repetição condicional while. O while consiste em uma cláusula na qual seu bloco será executado durante ou ‘ENQUANTO’ a expressão lógica a ser testada for verdadeira. A execução do bloco só será realizada desde que a expressão lógica analisada seja verdadeira. A síntaxe para a estrutura de repetição while é descrita a seguir. while ( expressão lógica ){ Sequência; } Assim, mais uma vez iremos recorrer a um exemplo para a fixação desta estrutura de controle de fluxo. No programa abaixo, iremos imprimir os valores menores que o passado no argumento durante a execução para nosso programa. //Classe ExemploWhile.java class ExemploWhile{ public static void main (String args[]){ int j = 10; Capítulo 1 - Conceitos básicos da tecnologia Javat19 while (j > Integer.parseInt(args[0])){ System.out.println(«»+j); j--; } } } Outra possibilidade na linguagem de programação Java para a estrutura de repetição while consiste na estrutura do-while, que pode ser utilizada como uma condição ao final do loop. Essa estrutura está intrinsecamente ligada à tradução dos termos, que é: “faça/enquanto”. Isso faz com que tenhamos a garantia de que o bloco de código definido será executado ao menos uma vez. No caso, ambas as estruturas apresentadas, while e do-while são repetições baseadas em condições lógicas. Observe a seguir a síntaxe da estrutura do-while. do{ Sequência; } while (expressão_lógica); Vamos brincar com a estrutura do-while? Então, transcreva o código abaixo, compile e execute-o. //ExemploDoWhile.java public class ExemploDoWhile { public static void main (String args[]) { int min, max; Scanner entradas = new Scanner(System.in); System.out.print(“Digite o valor minimo:”); min = entradas.nextInt(); System.out.print(“Digite o valor maximo:”); max = entradas.nextInt(); do { System.out.println(“” + min + “ < “ + max); min++; 20 t JAVA com Orientação a Objetos max--; } while (min < max); System.out.println(“” + min + “ < “ + max + “ Condicao invalida.”); } } No exemplo, são capturados dois valores vindos do teclado. O mais importante de nosso exemplo está na cláusula do-while, que irá imprimir os valores mínimo e máximo passados, e que serão impressos em tela e decrementados, pois fazem parte do bloco “do”, ou seja, o bloco será executado ao menos uma vez. Diante disso, outras impressões só ocorrerão caso a condição existente na diretiva while seja satisfeita. Repare, na figura a seguir, a execução do código proposto. Porém, existem situações nas quais a repetição necessita ser apenas incremental e decremental, obviamente com uma condição de parada para ambos os casos. Para estes momentos, a estrutura ‘for’ executará a sequência do comando enquanto a expressão lógica for verdadeira. Um fato interessante que deve ser comentado sobre essa estrutura é a possibilidade da criação e da inicialização da variável de controle na entrada do comando. Ela será utilizada para o controle da condição, sendo incrementada ou decrementada a cada repetição. A seguir observe a estrutura de repetição ‘for’. Capítulo 1 - Conceitos básicos da tecnologia Javat21 for(inicialização; condição; incremento/decremento){ Sequência; } Assim como nos exemplos anteriores, vejamos uma aplicação dessa estrutura. //Classe ExemploFor.java class ExemploFor{ public static void main (String args[]){ for (int j=0; j<10; j++){ System.out.println(j); } } } Repare que na condição a ser satisfeita, foi inicializada uma variável ‘j’ que irá auxiliar no processo de controle com o seu incremento. O mesmo poderia ser feito com o decremento do valor atribuído a ‘j’. No caso, no Java, assim como em C ou C++, o incremento e o decremento podem ser obtidos apenas com a utilização dos comandos ‘j++’ ou ‘j- -’. 1.5 Arrays e matrizes Agora, em nosso primeiro capítulo, trabalharemos com os arrays ou também conhecidos como vetores e matrizes, que nada mais são que vetores multidimensionais. No caso, os arrays são vistos como estruturas homogêneas e estáticas, ou seja, mantêm uma estrutura regular que não muda de tamanho. Logo, o espaço destinado aos armazenamentos de valores, bem como o tipo de dado, é sempre do mesmo tipo do início ao fim da vida do programa. A primeira posição de um vetor no Java consiste na posição 0. A 22 t JAVA com Orientação a Objetos linguagem disponibiliza diversas propriedades que podem ser úteis na manipulação de vetores, uma vez que sua criação é muito simples, como veremos no exemplo a seguir. Uma das propriedades mais importantes de um array é length, que representa o tamanho de seu vetor. Ela se torna útil, pois além de verificar qual o tamanho do vetor, auxilia no acesso a determinadas posições, como, por exemplo, a última, que iremos acessar como (length – 1). Mas, vamos ao que realmente interessa, que é conhecer a sintaxe de criação de nossos vetores, bem como verificar sua manipulação. Para isso, o código abaixo demonstra a utilização dos arrays no Java. //Classe ExemploArray.java class ExemploArray{ public static void main(String[] args){ int[] vetor = new int[ 10 ]; System.out.println(“Tamanho vetor:”+vetor. length); for (int i = 0; i < vetor.length; i++) { vetor[i] = i; System.out.println(vetor[i]); } } } Uma vez executado como feito anteriormente, nosso exemplo define um vetor com 10 posições do tipo inteiro. Repare que a definição do espaço de memória para tal vetor depende da utilização da sintaxe ‘[ ]’. Várias podem ser as possibilidades de utilização dos vetores, que, como mencionado, consistem em um espaço de memória destinado ao armazenamento de valores durante a execução, auxiliando na organização de nossos programas, como exemplificado com a utilização do comando for. Eles serão muito úteis em exemplos nos quais não são utilizados conceitos relacionados ao banco de dados, pois auxiliam na manutenção dos valores durante a execução. No caso, teremos acesso sempre a uma determinada posição, sendo que podemos considerar um vetor como uma linha na qual iremos acessar um endereço de memória mantido em sequência, como demonstrado abaixo. Capítulo 1 - Conceitos básicos da tecnologia Javat23 0 10 1 20 2 50 3 70 4 5 6 7 8 9 Assim, se acessarmos o vetor em sua segunda posição, obteremos o valor 50. Outro detalhe a ser mencionado na definição de valores para um vetor é que estes podem ser inseridos no vetor no momento da definição de seu tamanho, no momento de sua criação. No caso, execute o exemplo a seguir e observe seu funcionamento. //Classe NovoVetor.java class VetorInicializado{ public static void main(String[] args){ double[] nota = {7,8, 8,4, 4,2, 1,8, 6,4}; for(int i=0; i < nota.length-1;i++) System.out.println(“Valor no vetor:”+nota[i]); } } Repare que no exemplo, o tamanho do vetor apresentado é definido pelos valores que serão armazenados e passados entre { }. No caso, teremos um vetor com 5 posições, no qual sua instanciação é realizada no momento de sua definição com valores do tipo double. Ainda no exemplo anterior, é importante mencionar que devido à nossa estrutura de repetição ‘for’ possuir apenas uma linha definida em seu bloco, no caso a impressão dos valores do vetor, não é necessária a delimitação do bloco de escopo da estrutura com os caracteres “{” “}”. Certo, mas e quanto às matrizes? Quando falamos sobre matrizes no Java, devemos ter em mente um aspecto importante que, porém, não impedirá ou dificultará seu trabalho. O fato é que a linguagem Java possui somente arrays unidimensionais, sendo que para a criação de arrays multidimensionais, ou seja, matrizes, o que podemos fazer é criar ou simular “pseudomatrizes” por meio da definição de um array de arrays. 24 t JAVA com Orientação a Objetos Obviamente que isso passa de maneira despercebida na construção de suas matrizes, sendo apenas uma estruturação interna da linguagem e que, como mencionado, não irá interferir em seu programa de maneira significativa. Você deve ter em mente apenas a constante necessidade de duas estruturas de repetições para percorrer todo o vetor, assim como em qualquer outra linguagem de programação. Para entender melhor o processo, vamos a um exemplo. //Classe Matrizes.java class Matrizes{ public static void main(String[] args){ int [][]matriz = new int[2][3]; for(int i=0; i< matriz.length; i++){ System.out.println(“Linha: “+i); for(int j=0; j< matriz[i].length; j++){ matriz[i][j]= i+1; System.out.println(“Valores da coluna”); System.out.println(matriz[i][j]); } } } } Após executar o programa, teremos como resultado a impressão da coluna, da qual cada uma das posições faz parte no momento. Como mencionamos, são necessárias duas estruturas de repetição para percorrer toda a matriz, lembrando que uma é responsável por fazer referência à linha e a outra estrutura a um novo vetor, no caso, uma pseudocoluna, conforme é impresso no programa anterior. No Java, existem outras estruturas de vetores que, no caso, são classes que fornecem mais ferramentas e funcionalidades, agregando valor ao programa. Em nosso sexto capítulo, iremos analisar mais e apresentar essas estruturas adicionais para o trabalho com vetores, tais como as classes Array, HashMap e Vector. Então, para a definição de vetores e matrizes, sempre estaremos envolvidos em três etapas: Capítulo 1 - Conceitos básicos da tecnologia Javat25 1-Declaração do vetor ou matriz: Ocorre da mesma forma como em uma variável, porém basta acrescentar antes ou depois da variável um par de colchetes. Exemplo: double posicao[][], controle[][]; int []indice; 2- Reservar o espaço de memória: No caso, é necessário definir o tamanho do espaço de memória que será mantido para a estrutura. Para isso, é utilizado o operador new. Exemplo: posicao = new double[10][20]; indice = new int[5]; 3- Armazenar valores nas posições: Para armazenar um valor ou informação em um dos elementos do vetor ou matriz, é necessário informar o índice, ou seja, o local no qual iremos manter os dados. Exemplo: nota[3] = 7.8; posição[4][6] = 3365; double[] nota = {7,8, 8,4, 4,2, 1,8, 6,4}; A manipulação de vetores e matrizes é essencial para o trabalho com as estruturas de dados. Diante disso, fica como sugestão que seja dada uma especial atenção ao trabalho com vetores e matrizes, refazendo os exemplos sugeridos e memorizando sua sintaxe. 1.6 Funções Caro leitor, chegamos ao último tema que será analisado neste capítulo, que se trata do trabalho com funções. Assim como nas demais linguagens, uma função consiste em uma rotina ou sub-rotina automatizada. As funções 26 t JAVA com Orientação a Objetos são interessantes para as atividades que serão utilizadas rotineiramente, ou seja, que se repetirão várias vezes e com isso, evitam que tenhamos de reimplementar tal trecho a cada momento de necessidade. Resumidamente, sempre que se quiser usar um mesmo trecho de código que realiza uma tarefa específica repetidamente, o melhor a fazer é criar uma função. O entendimento deste conceito é primordial para que, em nosso próximo capítulo, possamos lidar com o conceito de métodos. O primeiro passo para criar uma função gira em torno do fato de que a função deve ser sempre global, ou seja, deve ser enxergada por todo o código da classe e para tanto, também deve ser estática, sendo definida pela palavra reservada static. A palavra reservada static é responsável por garantir que ao ser realizada a execução de uma instância da classe mais de uma vez, somente haverá uma única referência para determinada variável ou função existente na memória em seu computador. Ou seja, ao declarar uma função como static, todas as instâncias de uma determinada classe irão acessar e compartilhar o mesmo endereço de memória da função. Outro detalhe ao qual devemos ficar atentos é que ao declararmos algum trecho, variável ou função como static, isso nos permitirá que sejam acessados diretamente sem a necessidade de criar uma instância da classe. Esses conceitos ficarão mais claros ao trabalharmos com a orientação a objetos, porém é importante saber que existem diversos padrões de projetos que fazem referência à utilização da palavra reservada static, como, por exemplo, o Singleton. As funções podem ser tanto sem argumentos quanto parametrizadas, porém a estrutura padrão que deve ser digitada dentro da classe, mas fora da função main, como é apresentada a seguir. static void nomeDaFunção(lista de parametros){ // código da função } Em nosso exemplo, a estrutura apresentada faz referência a uma lista de parâmetros, que pode ou não ser declarada. Assim, como mencionado, existem funções nas quais não existem argumentos, mas necessariamente devem existir os parênteses. Para ter uma melhor fixação da sintaxe, vamos a um exemplo da utilização de funções. Digite o código a seguir e verifique seu funcionamento, assim como nos demais exemplos averiguados até aqui. Capítulo 1 - Conceitos básicos da tecnologia Javat27 // Classe ExemploFuncao.java public class ExemploFuncao{ public static void mostrarMensagem() { System.out.println(“Chamei minha função”); } public static void main(String[] args) { // chamando a função dentro do // programa principal mostrarMensagem(); } } Em nosso exemplo, foi criada uma função chamada mostrarMensagem( ), que apenas imprime na tela. Nossa função é chamada dentro do método principal main( ), ou seja, poderíamos realizar a chamada quantas vezes fossem necessárias sem ter de reescrever todo o código novamente. Este exemplo é um bom começo para o entendimento da ideia de mensagens, mas isso é um assunto para o nosso próximo capítulo. É importante mencionar que a ideia de função em java muitas vezes é confundida com o conceito de métodos, sendo que iremos saber mais sobre eles em nosso segundo capítulo Bom pessoal, assim finalizamos nosso primeiro capitulo e espero que todos tenham assimilado, aprendido e gostado do fascinante e, ao mesmo tempo, importante mundo Java. Um detalhe que deve ser mencionado e deve fazer parte de sua rotina como programador é que a atividade de programação torna-se cada vez mais clara com a prática. Desta forma, sempre que possível, refaça todos os exemplos sugeridos neste capitulo. Nos vemos no próximo capítulo. Capítulo 2 Programação orientada a objetos A tecnologia Java ganhou espaço nos últimos anos considerando diversos aspectos. Vários deles já comentamos, como, por exemplo, a ideia de máquina virtual que possibilita a portabilidade entre os programas desenvolvidos com o Java ou mesmo o fato da tecnologia Java ser regida pela GPL. Porém, outro aspecto deve ser mencionado neste contexto. Ele consiste no fato da linguagem Java trabalhar sobre os conceitos oriundos do paradigma da programação orientação a objetos. Neste capítulo, trabalharemos com os conceitos básicos relacionados a este paradigma de programação, obviamente baseados na tecnologia Java. Fica como dica que é muito importante que os temas abordados em nosso primeiro capítulo tenham sido assimilados de maneira satisfatória, garantindo assim um melhor aproveitamento dos tópicos que virão a ser tratados não só neste capítulo, como nos seguintes também. Certo! Vamos ao que realmente interessa. O paradigma de programação orientada a objetos trata de uma forma diferente o enfoque até então trabalhado na maioria das linguagens de programa, que se sustentavam no paradigma de programação estruturada. A ideia por trás do paradigma da programação orientada a objetos baseia-se em uma contextualização mais humana e próxima da realidade, isso considerando que quase tudo o que temos e lidamos em nosso dia a dia são objetos. Por exemplo, o carro que você anda, a casa onde você mora. Poderíamos, então, imaginar que até mesmo as pessoas, animais e qualquer outro tipo de entidade podem ser vistos grosseiramente como objetos. Claro que existem situações nas quais esses “objetos” podem não ser concretos e palpáveis, tal como o veículo que mencionamos, mas no caso abstrato, como, por exemplo, os softbots (robôs de software), também podemos imaginar que eles existem e possuem elementos que os diferenciam, assim como atitudes e funções que possibilitam que possamos imaginar que estes podem servir de modelos computacionais. O paradigma de programação orientada a objetos é relativamente novo na concepção e na implementação de sistemas de software, considerado 30 t JAVA com Orientação a Objetos os demais disponíveis no mercado. Vários são os benefícios que podem ser vislumbrados com a utilização desse paradigma, entre eles estão o aumento da produtividade de programadores por meio de uma maior expansibilidade e a reutilização de código, ou mesmo o fato de controlar a complexidade e os custos com a manutenção do software. Neste caso, o objetivo central da orientação a objetos na criação de programas computacionais é permitir que os programas possam ser desenvolvidos de maneira a espelhar o modo como os objetos são organizados no mundo real, criando a figura de modelos que podem ser reutilizados quantas vezes forem necessárias, bem como criar uma estrutura modular, na qual os problemas possam ser resolvidos sem que o todo seja afetado. Tais benefícios oriundos do paradigma ficarão mais claros com o decorrer da leitura do livro e o conhecimento dos conceitos. Diante do exposto, caro leitor, é importante que você saiba que a programação orientada a objetos baseia-se nos seguintes conceitos: classes, atributos, métodos e objetos que serão apresentados neste capítulo, além dos construtores, encapsulamento, herança, polimorfismo e interface que serão apresentados em nosso próximo capítulo. 2.1 Conceitos da programação orientada a objetos Então, vamos lá amigo leitor! Aqui, iremos entender mais a fundo a Programação Orientação a Objetos (POO). Como citado, grande parte de nosso entendimento e relacionamento com as coisas do mundo real se dá através do conceito de objetos concretos ou mesmo abstratos. Observando ainda as coisas e seres que existem, há uma natural tendência a identificar o que é cada uma destas diferentes entidades, um móvel, uma pessoa e assim por diante. Relembrando um exemplo claro deste fenômeno e que já citamos, olhemos um carro. Logo, algo que se pode ver e reconhecer sua forma, tamanho, cor e aplicação. Porém, algo que acredito que muitos ainda não imaginaram é que tais observações podem ser feitas para todos os carros. Esse carro que acabamos de observar não é o único que existe. Inúmeros outros estão disponíveis com as mesmas características, porém sendo objetos completamente distintos. O que podemos começar a vislumbrar é que podemos ter computacionalmente uma estrutura que garanta que as informações possam ser mantidas e trabalhadas para inúmeras situações semelhantes. Capítulo 2 - Programação orientada a objetost31 Voltemos ao exemplo do carro. Se existem vários carros com as mesmas características e funcionalidades, é de se imaginar que exista uma forma ou molde que garanta que todos saiam iguais, de forma padronizada e serializada. Estes conceitos oriundos da administração foram absorvidos de maneira eficiente pela computação e pelo paradigma da orientação a objetos, que adotou a ideia de criação de modelos computacionais que podem ser reutilizados e fazer com que esses elementos se completem, modularizando a programação. O que temos inicialmente é a ideia de uma modelo, que a qualquer momento podemos dispor de seu uso para criar novos ‘objetos’ de um determinado tipo de entidade que será mantida na memória. É claro que cada objeto no mundo real é único, no caso, ninguém é igual a ninguém. Cada objeto possui uma característica ou mesmo uma função que o torna único, servindo como uma identidade que o diferencia dos demais, mesmo que sejam do mesmo tipo. Por exemplo, uma pessoa. Todas as pessoas possuem um nome, endereço, trabalho, mas todas são diferentes uma das outras, sendo que cada uma possui sua identidade própria, algo que a torna única e diferenciada. Poderíamos grosseiramente dizer que se trata do RG individual e pessoal de cada objeto. Assim pessoal, além das características genéricas vinculadas a todas as classes de objetos, existe também a identidade vinculada a cada objeto individualmente. Por meio dos exemplos citados, podemos descrever informalmente os conceitos fundamentais da programação orientada a objetos, sendo eles: t t t t t t t t t Classe; Atributos e métodos; Objeto; Referência a Objetos; Construtores; Encapsulamento; Herança; Polimorfismo; Interface. Como citado no início deste capítulo, iremos concentrar-nos agora apenas nos quatro primeiros conceitos mencionados. Os demais serão apresentados nos próximos capítulos. 32 t JAVA com Orientação a Objetos Caro leitor, mas o que você deve ter em mente quando iniciar o estudo sobre os conceitos da orientação a objeto é o fato de que tal paradigma irá auxiliá-lo a entender e visualizar de maneira mais ampla um problema a ser resolvido, conseguindo assim, uma maior independência entre a programação e o modelo do problema a ser trabalhado, o que obviamente irá facilitar possíveis manutenções e reutilizações com uma modularização, como já citei. 2.1.1 Classe Em nosso primeiro capítulo, já trabalhamos com a criação de classes. Mas, agora, vamos entender sua real contextualização, sendo o conceito primordial para o entendimento da POO. Uma classe é um modelo formado por propriedades ou características, que serão definidas mais à frente como atributos, operações e funcionalidades, que iremos conhecer como métodos e definem o comportamento básico dos objetos oriundos de uma classe. Podemos imaginar uma classe em seu sentido estrito e epistemológico, ou seja, uma representação genérica de um conjunto de entidades individuais e iguais. Por exemplo, uma classe de alunos, na qual todos são semelhantes dentro do contexto, com nome, matrícula e assim por diante. Em nosso caso, a classe é a representação comum ou padrão de um conjunto de entidades iguais. As classes são a modelagem das entidades do mundo real de uma forma mais natural computacionalmente, pois estamos acostumados a lidar com “objetos”, que possuem características, “atributos”, funcionalidades e “métodos”. Tomemos como exemplo o carro. Este poderia ser definido por uma classe que descreve de maneira comum um conjunto de objetos do tipo carro, independentemente de sua marca, cor ou tipo, atendo-se especificamente aos conceitos que, de alguma maneira, descrevem a estrutura de um carro de forma genérica. Então, vamos a um exemplo de uma classe, na qual podemos descrever um conjunto de entidades a partir de um modelo computacional genérico. // Classe Carro.java class Carro{ String cor; String marca; String modelo; Capítulo 2 - Programação orientada a objetost33 void andar(){ System.out.println(“Carro andando”); } void parar(){ System.out.println(“Carro parado”); } } Vale a pena novamente mencionar que os nomes das classes devem ser iniciados com letras maiúsculas, diferenciando assim dos atributos e das instâncias de objetos que utilizam letras minúsculas em suas iniciais. Caso o nome seja composto por mais de uma palavra, recomenda-se que as demais palavras também iniciem em letra maiúsculas. Exemplo: Carro, CarroDeCorrida. Observando o exemplo de classe acima, verifica-se que ela não possui um método principal, sendo assim, ela não retornará nenhuma ação ao ser executada, sendo apenas a descrição de um objeto do mundo real, ou seja, a ideia central e descrevendo o problema a ser implementado. Em nosso caso, descreve uma estrutura para manter os dados e descreve as funcionalidades de um carro como um modelo que será utilizado. Isso é interessante, pois sempre que quisermos manter informações ou mesmo utilizar suas funcionalidades, não precisaremos implementar novamente todo o código, necessitando apenas uma nova instanciação de um objeto da classe ou mesmo sua cópia para outro projeto. De maneira mais formal, podemos então dizer que uma classe é uma abstração que descreve as propriedades relevantes de um conjunto de elementos em termos de sua estrutura, atributos e comportamento - os métodos. 2.1.1.1 Qualificadores de acesso Outro aspecto interessante e que auxilia em uma programação mais ‘realista’ com a utilização do paradigma orientado a objetos consiste na ideia do qualificador de acesso. Tantos as classes que já conhecemos quanto os atributos e métodos que ainda iremos conhecer fazem uso e estão na maioria das vezes associados ao conceito de qualificador de acesso ou também conhecido como especificação de visibilidade. 34 t JAVA com Orientação a Objetos Vamos a uma analogia para tentar entender o conceito de visibilidade. Você resolve parar um pouco com a leitura de seu livro de programação com Java e resolve dar uma volta de carro. Diante disso, começa a lembrar dos conceitos que leu, rememorando que seu carro é um objeto dentro de uma classe que define todos os aspectos comuns de todos os carros. Mas, ao ligar seu carro, você começa a pensar: “Bom, eu tenho visíveis várias características e várias funcionalidades, mas existem outras que não consigo enxergar e que tenho certeza que contribuem para o bom funcionamento do veículo, tais como a partida, rotação do motor e assim por diante”. Pois bem, ao considerar esta situação, é possível verificar que, em diversos momentos em nosso cotidiano, existem objetos, características e funcionalidades que estão visíveis e outras não. Logicamente que isso irá depender dos aspectos funcionais e da utilidade de cada objeto. É aí que entram os especificadores ou os qualificadores de visibilidade. Eles permitem definir quem ou o que pode ser visível ou acessível no momento do desenvolvimento de suas classes. Na maioria das situações, com as quais você irá deparar-se, uma classe fará uso de outras classes e essas devem estar acessíveis por meio da utilização dos qualificadores. Para tanto, deve-se utilizar uma das estruturas apresentadas a seguir. No caso, os tipos de qualificadores básicos são: Tabela 3. Qualificadores de acesso. Qualificador Descrição public Define que o conteúdo da classe é público e pode ser utilizado livremente por outras classes do mesmo pacote ou de outro pacote. protected Define que o conteúdo da classe está protegido e que só pode ser utilizado por classes do mesmo pacote. private Define que o conteúdo é privado e só pode ser utilizado internamente na própria classe. Assim, poderíamos enriquecer nosso exemplo da classe carro apresentada anteriormente com os qualificadores de visibilidade. Verifique que tanto Capítulo 2 - Programação orientada a objetost35 a classe como suas variáveis que descrevem as características e as funções, que descrevem seu comportamento ou funcionalidades, podem fazer uso de tais especificadores. //Classe Carro.java public class Carro{ public String cor; public String marca; public String modelo; protected void andar(){ ligar(); System.out.println(“Carro andando”); } protected void parar(){ System.out.println(“Carro parado”); } private void ligar(){ System.out.println(“Carro ligado”); } } Os qualificadores de visibilidade são essenciais para o conceito de encapsulamento, com o qual trabalharemos em nosso próximo capítulo. Por padrão, quando não declarada a visibilidade de uma classe ou mesmo atributo e método, a máquina virtual Java irá interpretar que tais elementos estarão especificados com o operador protected, ou seja, protegidos e acessíveis apenas dentro do pacote do qual fazem parte. 2.1.1.2 Pacotes Com isso, temos outro conceito que interfere nas classes tanto quanto a visibilidade. No caso, consiste na ideia de pacotes, declarados no Java como package. A principal função desta diretiva no Java está na organização das classes e obviamente em sua visualização por outras classes de um projeto. O que podemos então verificar é que o pacote está diretamente 36 t JAVA com Orientação a Objetos relacionado aos qualificadores de visibilidade no acesso a classes, características e funcionalidades destas. Ou seja, a utilização da diretiva package na classe indica que todo o conteúdo público (public) pode ser utilizado por outras classes pertencentes ao mesmo pacote ou não. O conteúdo determinado como protegido (protected) na classe pertencente a um pacote só pode ser acessado ou utilizado por outras classes pertencentes ao mesmo pacote e o conteúdo definido como privado (private) só será acessível dentro da própria classe, independentemente do pacote do qual faz parte. Caso o pacote de uma classe não seja informado, essa classe passará a fazer parte do pacote default (src, abreviação de source, traduzindo, fonte). Do ponto de vista usual, dentro de seu projeto, um pacote de classes Java consiste em um diretório, no qual existem uma ou mais classes, ou seja, é um repositório de classes. Geralmente, colocam-se no mesmo package as classes com o mesmo propósito. Sob certos aspectos, os packages reproduzem a ideia de bibliotecas, que podem ser importadas em uma classe Java por meio do comando import. 2.1.1.3 Import O comando import é responsável por garantir a reutilização e a modularização dos programas no Java. Logicamente, tais conceitos são semelhantes ao include do C ou C++ e garantem, com os demais conceitos de visibilidade e pacotes, definições de segurança e organização. Certo pessoal! Diante do exposto até aqui, vamos a um exemplo de uma estrutura padrão de uma classe, considerando todos os conceitos apresentados. Além disso, apresentaremos um novo elemento da linguagem neste exemplo, que consiste nos comentários. package local.diretorio; // atributos da classe // métodos da classe } Capítulo 2 - Programação orientada a objetost37 Note que a estrutura apresentada apenas define os conceitos já vistos por você nos exemplos anteriores. Porém, deve-se tomar muito cuidado com a manipulação de pacotes e imports, pois podem ocorrer erros de referência, uma vez que as classes e os pacotes não existam. 2.1.1.4 Comentários Bom pessoal, os comentários são extremamente úteis na linguagem Java. Isso porque não somente permitem os simples comentários, mas ganham valor ao considerarmos as possibilidades que a própria máquina virtual define para tal elemento. No caso, a tecnologia Java define três tipos de comentários, sendo eles: com uma linha, múltiplas linhas e documentação. O primeiro, com uma linha, utiliza duas barras (//) para marcar seu início e tudo após as duas barras é considerado um comentário pela JVM. O segundo tipo de comentário utiliza a combinação /* e */ para delimitar as múltiplas linhas de comentários. E o último tipo consiste no comentário de múltiplas linhas, semelhante ao segundo, porém com o propósito de documentar a programação. No Java, recomenda-se como uma boa prática de programação, que todas as classes sejam documentadas e para isso, a tecnologia Java nos dá uma mãozinha, como pode ser observado no Apêndice II deste livro. Na Tabela 4, é demonstrada a utilização dos comentários apresentados. Tabela 4. Tipos de comentários Tipos de comentários // comentário de uma linha // tudo após as duas barras é um comentário /* comentário de múltiplas linhas */ /** comentário de documentação que também * podem ter múltiplas linhas */ 38 t JAVA com Orientação a Objetos Usualmente, o comentário com o objetivo de documentação é posicionado antes do elemento a ser documentado, sendo que seu conteúdo é extraído automaticamente pelo utilitário javadoc fornecido com o JDK. Como mencionado, o apêndice deste livro traz exemplos para a criação da documentação de suas classes, com a utilização do suporte fornecido pela JVM. Conforme já citado em nosso capítulo anterior e algumas vezes na seção onde definimos as classes, os atributos e os métodos são conceitos que estão intrinsecamente associados à ideia de classe. Então, mãos à obra! Vamos conhecer um pouco mais sobre esses conceitos. 2.1.2 Atributos Um atributo nada mais é que uma variável contextualizada, na qual representa uma característica ou uma propriedade da classe de objetos em questão. É uma variável destinada a armazenar informações associadas à classe. Por exemplo, vamos considerar novamente o famoso carro apresentado nas seções anteriores. Seja qual for o carro, várias características podem ser elencadas para ele, entre elas, sua marca. Trata-se de uma propriedade comum a todos os carros, pois todo carro possui uma marca ou mesmo uma cor. Assim, é natural que ao definirmos uma classe, por exemplo, CarroDeCorrida, ela possua um atributo ou no caso, uma variável contextualizada destinada a armazenar sua marca e sua cor. Podemos definir os atributos como “variáveis da classe que podem ser de tipos primitivos ou de outras classes destinadas a manter os dados dentro de um contexto”. A definição de um atributo dentro de uma classe Java é feita da mesma maneira como em uma declaração de variável, sendo definido o seu tipo e nome, que deve indicar qual seu propósito. Considere o exemplo da classe CarroDeCorrida a seguir. //Classe CarroDeCorrida.java package fabrica; Capítulo 2 - Programação orientada a objetost39 public class CarroDeCorrida{ public String cor; public String marca; } Mais uma vez, temos de lembrar que, em nossa classe, não existe um método executável, ou seja, o método main( ), sendo que tal classe representa uma descrição de um problema que, no caso, consiste em manter as informações sobre, por exemplo, um carro de corrida durante a execução. Poderíamos dizer grosseiramente que se trata de um mapeamento das informações a serem mantidas. Mas, de nada valem os atributos sem que estes possam ser trabalhados e transformados. É aí que entram os métodos. 2.1.3 Métodos Como verificamos no tópico anterior, enquanto os atributos permitem manter dados vinculados e contextualizados aos objetos, ou seja, valores que descrevem as características de um objeto, os métodos são responsáveis por realizar operações sobre os atributos, sendo capazes de especificar ações ou transformações para uma classe de entidades. A ideia central na construção de métodos está relacionada ao fato de conferir um caráter dinâmico aos objetos de uma classe, exibindo um comportamento que transforme e modifique seu estado atual. Na maioria das vezes, essa transformação se dá por meio da alteração de valores dos seus atributos, tentando imitar o comportamento de um objeto real ou abstrato. Bom, assim como nos demais conceitos, cada método possui uma assinatura e corpo definidos por: t t t t qualificador de acesso; tipo de retorno; nome; lista de parâmetros. Definido o corpo do método, vem seu escopo, que define a implementação à qual o método se propõe, que deve estar entre “{ }”. Apresento a seguir mais um exemplo, além de aproveitá-lo para demonstrar mais um elemento da linguagem Java. Vamos utilizar o exemplo de um caixa eletrônico. 40 t JAVA com Orientação a Objetos //Classe ContaCorrente.java package banco; public class ContaCorrente{ !" private String titular; !# $ % & "' this.saldo -= valor; return “Saque realizado com sucesso!”; } !# $ % ! & "' this.saldo = this.saldo + valor; return “Deposito realizado com sucesso!”; } } Repare que no exemplo, temos dois métodos, sacar e depositar. Por convenção, os métodos sempre devem ser declarados com o verbo no infinitivo, tal como: desenhar, andar, propor, correr e assim por diante. No caso de nomes compostos, o primeiro nome será no infinitivo e os demais declarados normalmente, tal como: gerarDocumentacao( ), depositarDinheiro( ) ou depositarCheque( ). Mas, um elemento deve ter chamado sua atenção no exemplo. No corpo dos métodos pode ser visualizado o identificador this. 2.1.3.1 Identificador this O identificador this é responsável por fazer referência a um atributo ou método da própria classe. No exemplo apresentado anteriormente, é feita a referência ao atributo saldo. Isso é muito útil, pois é comum que os parâmetros possuam nomes iguais aos já existentes nos atributos de uma classe, diferenciando-os. Uma alternativa para a utilização do identificador this é sua definição nas classes Java para a chamada de um método construtor dentro da própria Capítulo 2 - Programação orientada a objetost41 classe. Isso é feito por meio do método reservado this( ). É importante que você saiba que todos os conceitos apresentados até aqui devem ser sempre bem planejados e isso é geralmente feito no momento do projeto de suas aplicações. As classes, atributos e métodos podem ser representados de maneira gráfica, o que é extremamente útil para enxergar os possíveis gargalos ou deficiências em sua aplicação. Para tanto, existem várias notações, geralmente gráficas, para a representação de classes, atributos e métodos, mas com certeza a mais utilizada comercialmente trata-se da UML (Unified Modeling Language). Não iremos aprofundar-nos quanto à utilização da UML em nosso livro. Fica como sugestão para o leitor que, como forma de enriquecer sua leitura e aprendizado da programação orientada a objetos, procure conhecer mais sobre tal notação. 2.1.4 Mensagem e modularização de código Como mencionado no início deste capítulo, um dos pontos-chave da programação orientada a objetos consiste na possibilidade de modularizar o código. Com isso, podemos chegar mais próximo da realidade vivida no cotidiano, bem como possibilitar uma manutenção mais simples. É considerando tais perspectivas que surge um novo conceito relacionado à linguagem Java e ao paradigma de orientação a objetos: a mensagem. O conceito de mensagem entre as classes Java trabalha com a ideia de que as classes, ou melhor, os objetos ou as instâncias de uma classe podem invocar métodos de objetos de outras classes para desempenhar alguma tarefa de maneira a completar sua função e melhor definir o problema. O conceito garante que caso uma classe necessite de um serviço ou funcionalidade, mas a função faça parte de outro contexto, então será necessário apenas utilizar o que é proposto em outra classe, mantendo assim a organização de modo a garantir que todo o código fique em seu devido lugar. Obviamente, isso tornará seu código mais inteligível para as possíveis intervenções que venham a ocorrer no futuro, como no caso de uma manutenção. Dúvidas? Certo, vamos ao já mencionado no exemplo do carro. Imagine que se construa uma classe Carro que possui vários atributos, tais como: cor, marca e modelo. Além disso, na classe sejam definidos os métodos andar( ) e parar( ). Pois bem, até aqui nada de novidade. Porém, para que um veículo ande, é necessário que seu motor seja ligado. É aí que entra o conceito de mensagem como uma forma de melhor definir o problema. 42 t JAVA com Orientação a Objetos O correto seria a criação de uma nova classe, que defina a ideia do objeto motor, que teria como atributos características, tais como: potência, ignição e combustível. Além dos atributos, os métodos para a manipulação desses atributos, que seriam, por exemplo, ligar( ) e desligar( ). Desta forma, quando um carro anda, o que é acionado é o motor e para tanto, é ele que deve ter seu estado alterado. O motor, como parte do carro, deve receber uma mensagem oriunda da classe carro que informe tal solicitação, fazendo acesso ao método ligar( ). Logo, o conceito de mensagem trata da comunicação entre as classes. Para ter uma melhor fixação, vamos demonstrar tais conceitos da maneira como devem ser abordados, criando classes. Aqui, utilizaremos as classes que definem nosso problema e de forma legível para seu entendimento, caro leitor. Então, vamos lá? Transcreva as classes abaixo para um editor de texto. Entretanto, não é necessária a compilação e a execução do programa, já que não temos uma classe principal que garanta uma visualização dos resultados. // Classe CarroNovo.java package fabrica; public class CarroNovo{ // atributos String cor; String marca; String modelo; Motor novoMotor = new Motor(); // métodos public void andar() { novoMotor.ligar(); System.out.println(“ANDANDO”); } public void parar() { novoMotor.desligar(); System.out.println(“PARANDO”); } } Capítulo 2 - Programação orientada a objetost43 _____________________________________________________ __ // Classe Motor.java package fabrica; public class Motor { // atributos Boolean ignicao; String potencia; String combustível; // métodos public void ligar() { ignicao = true; System.out.println(“Ligado”); } public void desligar() { ignicao = false; System.out.println(“Desligado”); } } Neste exemplo, podemos notar que existe um novo elemento na classe CarroNovo, que consiste na utilização de uma INSTÂNCIA da classe Motor. Estarei abordando mais sobre a importância deste conceito na próxima seção com o uso da palavra reservada new. Outro exemplo que pode ser útil para o entendimento do conceito de mensagens entre classes é apresentado a seguir. No próximo exemplo, é implementada uma classe denominada ProgramaPrincipal, com um método principal onde se cria uma variável ou para que você vá acostumando-se, um objeto da classe Mensagem que possui o método imprimir( ) acessado durante a execução pela classe ProgramaPrincipal. Trata-se de um exemplo sem uma contextualização, mas que é útil para enxergar a divisão de tarefas e a modularização do código com os conceitos da orientação a objetos. Considerando as características do exemplo, podemos compilar e executá-lo para a visualização. 44 t JAVA com Orientação a Objetos // Classe ProgramaPrincipal.java class ProgramaPrincipal{ public static void main (String arg []){ Mensagem m = new Mensagem(); m.imprimir(); } } // Classe Mensagem.java class Mensagem{ public void imprimir(){ System.out.println(“Mensagem”); } } No exemplo, é feita a utilização do método imprimir( ) da classe Mensagem pela classe ProgramaPrincipal. Ainda no exemplo, é feita novamente a utilização do operador new. Trata-se de mais uma palavra reservada para a tecnologia Java que é responsável pela atribuição de uma instância a um atributo. A ideia de instanciação é essencial para compreendermos todo o paradigma da programação orientada a objetos. Então, vamos conhecer o conceito de objeto. 2.1.5 Objeto ou instanciação Como já deve ter sido percebido, a criação de novos objetos de uma classe se chama instanciação ou simplesmente criação do objeto. O objeto consiste em representar um único exemplar de uma determinada classe. Vamos voltar ao exemplo do carro. A classe CarroDePasseio representa os atributos (características) e os métodos (comportamento) de todos os carros de passeio. Logo, a classe consiste em um modelo para reservar um espaço de memória para manter e transformar os dados. A partir da classe CarroDePasseio, podemos criar vários objetos, ou seja, várias representações ou instâncias a partir do mesmo modelo, pertencente à classe. Por exemplo, podemos manter os dados de uma Mercedez, um Gol ou qualquer que seja o carro de passeio. Note, qualquer um dos veículos citados é um exemplar específico da Capítulo 2 - Programação orientada a objetost45 classe CarroDePasseio. Cada carro em específico possui características, tais como, cor, tamanho e marca, que o torna único e métodos que definem como ele pode ser ligado, desligado e como deve andar. Se fôssemos pensar grosseiramente, poderíamos ter uma classe Pessoa, da qual você, leitor, é um objeto ou uma instância da classe (conjunto) de pessoas. Pensemos, você é único e ninguém é igual ao outro, onde cada um possui características próprias e comportamentos distintos, ou seja, cada um é um objeto diferente pertencente a uma determinada classe. Pode-se definir um objeto como sendo uma instância de uma classe (grupo) que tem valores próprios para os atributos definidos na classe, tendo uma identidade única, ou seja, sendo único no conjunto de elementos do mesmo tipo. A notação para instanciar um novo objeto utiliza o operador new, destinado à sua criação. NomeDaClasse nomeDoObjeto = new NomeDaClasse(); Exemplo: CarroDePasseio mercedez = new CarroDePasseio(); Mensagem m = new Mensagem(); A primeira coisa que você deve ter reparado é que a declaração de um objeto é realizada de maneira semelhante a dos atributos de uma classe. Então, é valido mencionar que as classes podem conter instâncias de outras classes como atributos, com diversos elementos compondo um todo, assim como apresentado na classe Carro e Motor. Outro exemplo seria a mesma classe carro que pode ter como atributo um proprietário, que pode ser definido como sendo de uma classe Pessoa, como é apresentado no exemplo a seguir. Conforme já visto neste capítulo, o exemplo abaixo apresenta o conceito de importação de outras classes. O código é meramente ilustrativo, não sendo necessária sua execução. // Classe Carro.java package concessionaria; import concessionaria.Pessoa; public class Carro{ // atributos 46 t JAVA com Orientação a Objetos int velocidadeMaxima = 50; Pessoa proprietario = new Pessoa(); // métodos public void mostrarVelocidadeMaxima(){ proprietario.dirigir(); System.out.println(velocidadeMaxima); } } ______________________________________________________ // Classe Pessoa.java package concessionaria; public class Pessoa{ String nome; String endereco; public void dirigir(){ System.out.println(“DIRIGINDO”); } } Mas, quando falamos de objeto, o que realmente está por trás disso é outra importante característica da orientação a objetos, no caso, o reuso do código. Mais uma vez fazendo referência ao exemplo do carro, a mesma classe pode ser utilizada para definir vários objetos do mesmo tipo, sendo que somente os dados referentes às características de cada um diferem. Por exemplo, poderíamos ter um objeto denominado carroDePasseio e outro instanciado com sua referência sendo carroDeCorrida. Ambos podem ser instâncias da classe Carro, sendo que os dois objetos são oriundos da mesma classe, diferenciados apenas pelos valores associados aos atributos que cada um assume. Porém, ambos utilizam o mesmo modelo, mais especificamente, a classe Carro. Cabe ainda destacar outros detalhes da instanciação de objetos. Agora que já sabemos como os objetos são criados, com a utilização do operador new, é importante entender o que acontece quando criamos esses objetos. Capítulo 2 - Programação orientada a objetost47 Quando um objeto é criado por meio do comando new, o que é retornado à variável ou ao atributo, como já vimos, é uma referência. Referência? O que é isso? Vamos à explicação. Quando um objeto é criado, ele é uma referência ou uma indicação para um determinado espaço de memória reservado, sendo que o nome da variável torna-se apenas uma âncora para o acesso ao espaço de memória no momento da execução de seu programa, no qual estão dispostos nossos atributos e métodos. A ilustração apresentada a seguir torna isto mais claro. Figura 2. Referência para objetos. Após a explicação e as considerações feitas sobre o espaço de memória ser o objeto, é necessário também dizer que o operador new não é a única forma de atribuirmos referência a um objeto ou uma instância. Uma maneira de visualizar o objeto como uma referência, no caso uma âncora, é o fato de também se poder fazer a cópia de uma referência de um objeto para outro, com ambos passando a apontar para o mesmo endereço de memória. Se copiarmos uma referência para outro objeto, ele estará referenciando o mesmo objeto que a instância original está referenciando. Para ter um melhor entendimento do que foi dito, observe a Figura 3. Figura 3. Cópia de referência de objetos. 48 t JAVA com Orientação a Objetos Entendido o conceito, você deve perguntar: Mas, como fica isto em linhas de código no Java? Respondendo ao seu questionamento, observe e transcreva o código na sequência, onde são criadas duas classes que exemplificam o explicado. Conforme visto em nosso primeiro capítulo, utilizaremos o método executável main para imprimir os valores que foram referenciados aos atributos das classes. //Classe Carro.java package concessionaria; import concessionaria.Pessoa; public class Carro{ //atributos int velocidadeMaxima = 50; Pessoa proprietario = new Pessoa(); //métodos public void mostrarVelocidadeMaxima(){ proprietario.dirigir(); System.out.println(velocidadeMaxima); } } ___________________________________________________ //Classe Pista.java package concessionaria; public class Pista{ public static void main(String[] args){ Carro carroDeCorrida = new Carro(); Carro carroDePasseio = new Carro(); carroDeCorrida.velocidadeMaxima = 300; carroDePasseio.velocidadeMaxima = 60; carroDeCorrida.mostrarVelocidadeMaxima(); carroDePasseio.mostrarVelocidadeMaxima(); Capítulo 2 - Programação orientada a objetost49 Carro novoCarro = carroDeCorrida; carroDeCorrida.mostrarVelocidadeMaxima(); carroDePasseio.mostrarVelocidadeMaxima(); novoCarro.mostrarVelocidadeMaxima(); } } Entre as diversas linhas que devem ser observadas no código anterior, devemos dar mais importância à linha na qual a instância da classe Carro chamada novoCarro recebe a referência, ou seja, o endereço de memória do objeto carroDeCorrida, observe. Carro novoCarro = carroDeCorrida; Os atributos carroDeCorrida e novoCarro fazem referência ao mesmo espaço de memória, pois não foi atribuído um novo espaço de memória para o objeto novoCarro, mas sim, o mesmo espaço ao qual o objeto bolaGrande já fazia referência. Esta é uma característica fundamental da linguagem Java, sendo que sempre são passadas referências no Java, não valores. Tal fato torna o trabalho e a manipulação das estruturas de dados com o Java muito simples. Assim, finalizamos nosso segundo capítulo e no próximo, iremos continuar a conhecer outros conceitos vinculados à programação orientada a objetos. Capítulo 3 Construtores, destrutores e encapsulamento Dando continuidade à nossa análise sobre os conceitos relacionados ao paradigma da programação orientada a objetos, iremos, neste capitulo, descrever e aprender a lidar com três novos e importantes elementos para a tecnologia Java. É importante que os conceitos expostos nos capítulos anteriores tenham sido compreendidos para ter um melhor aproveitamento do conteúdo que será apresentado neste capítulo. Como visto nos capítulos anteriores, a ideia de classe na linguagem Java é o núcleo de todo o processo da orientação a objetos, no qual podemos representar qualquer objeto, seja ele concreto, tal como um carro, seja mesmo abstrato, tal como um softbot. Mas, não podemos deixar de mencionar outros conceitos importantes que auxiliam a concretizar o paradigma, como, por exemplo, os atributos e os métodos, conhecidos no capítulo anterior, ou mesmo os métodos construtores, destrutores e a ideia de encapsulamento na qual iremos trabalhar. Tais ferramentas tecnológicas possibilitam que a linguagem Java possa descrever com maior clareza o problema e com isso, obter uma solução melhor. O primeiro conceito no qual iremos trabalhar são os construtores e sua função no paradigma e na linguagem Java. 3.1 Construtores Em nosso segundo capítulo, analisamos a criação de objetos, que denominamos de instanciação de um objeto. Ali, obtínhamos uma referência para um espaço de memória, lembra? Pois bem, como vimos, isso nos obriga a seguir uma determinada sintaxe, conforme é apresentado a seguir. Carro novoCarro = new Carro(); Já foi analisada a utilização da palavra reservada new no ato da instanciação de um objeto ao verificar sua responsabilidade pela nova instância. Mas, podemos dizer que é uma meia verdade. Isto porque, como visto no exemplo anterior, o operador new antecede a chamada de um método. É esse 52 t JAVA com Orientação a Objetos método que denominamos de construtor do objeto ou apenas construtor. Sua função, como o próprio nome explicita, é construir, ou melhor, preparar o espaço de memória definido para um objeto para receber os dados conforme determinado na estrutura definida na classe. Sua invocação geralmente é feita no momento da criação de um objeto, da mesma maneira como é feita para qualquer método. Porém, um detalhe a ser considerado é que os métodos construtores possuem o mesmo nome da classe na qual são definidos. Por definição, os métodos construtores são métodos especiais invocados pelo sistema na instanciação de um objeto, sendo que o operador new apenas informa qual método construtor de uma determinada classe será utilizado pela JDK no momento da criação do objeto para inicializar os valores dos atributos. Neste caso, cabe um exemplo para ter uma melhor visualização dos conceitos. No exemplo a seguir, definimos um método para a classe Carro. Transcreva o código e tente visualizar as peculiaridades dos métodos construtores. // Classe Carro.java package conceitos; public class Carro { // atributos Boolean chave = true; // métodos construtores public Carro(){ this.chave = true; } public Carro(Boolean chave){ this.chave = chave; } // métodos funcionais public void ligar() { Capítulo 3 - Construtores, destrutores e encapsulamentot53 chave = true; System.out.println(“Ligar”); } public void desligar() { chave = false; System.out.println(“Desligar”); } } No exemplo apresentado, foi criada a classe Carro, sendo definidos dois métodos funcionais: ligar e desligar. Pois bem, mas o que realmente importa para nós está na definição dos métodos construtores. Repare que no exemplo, foi definido o método público Carro(). Ele consiste em um método construtor. A invocação de um método construtor nada mais é que uma referência para a área de memória onde foi criado o novo objeto, podendo ser trabalhados os valores de inicialização do objeto. Desta forma, podemos, então, dizer que o método construtor é responsável pela definição e pela alocação de memória para um novo objeto. Como mencionado, um método construtor deve possuir o mesmo nome da classe, porém verifique que, para tal método, não é definido nenhum tipo de retorno. Isso acontece sempre e com qualquer que seja o método construtor ou mesmo objeto a ser inicializado, pois nosso método construtor é responsável por estruturar o espaço de memória e inicializar os valores para os atributos de uma classe, não por fazer operações funcionais do objeto criado e retornar valores. O método construtor apresentado sem parâmetros também é conhecido como método construtor DEFAULT, sendo o mais comum. Nele, podem ser realizadas todas as inicializações citadas para os métodos construtores parametrizados. Outro aspecto interessante a ser destacado sobre os métodos construtores é a capacidade fornecida pela própria JVM, que no caso de não ser definido nenhum método construtor para uma classe, a própria JVM fica responsável por disponibilizar um método construtor para a inicialização do espaço de memória no ato da instanciação de um objeto. Esse método, porém, sempre será vazio, sem parâmetros e sem inicializações com valores para seus atributos. Um exemplo para tal citação é a criação do objeto ‘chave’ demonstrado no quadro anterior, no qual é utilizado o construtor default. 54 t JAVA com Orientação a Objetos Em vários problemas, é interessante que os valores de determinados atributos sejam inicializados no momento da instanciação dos objetos, como ocorreu no nosso exemplo. No caso, podem ser criados quantos métodos construtores forem necessários, diferenciados apenas pela lista de parâmetros definida para o método construtor PARAMETRIZADO. Assim, podem ser passados valores para a inicialização dos atributos da classe no momento de sua instanciação. Outra importante função dos métodos construtores é a possível realização operações que auxiliem e interfiram no funcionamento de um objeto, abrindo arquivos, estabelecendo comunicação, inicializando dispositivos que serão úteis ao programa. Carro novoCarro = new Carro(Boolean chave); Resumidamente, uma classe Java pode possuir diversos construtores, todos obrigatoriamente devem possuir o mesmo nome da classe, SENDO SOMENTE UM DEFAULT, diferenciados por sua assinatura, no caso, suas listas de parâmetros. Isto é o que denominamos de sobrecarga do construtor. Isto torna o conceito de orientação a objetos mais próximo da realidade, facilitando o uso de classes em diferentes contextos e circunstâncias. Detalhe importante a ser mencionado aqui é o fato de um método construtor poder fazer a chamada de outros métodos da classe ou mesmo instanciar novos objetos, realizando assim, a chamada de métodos construtores de outras classes e com isso, possibilitando a construção de objetos que se completam e definem de maneira mais realista a representação do problema, já que, conforme vimos, em sua maioria, um programa é composto por várias classes que se comunicam por meio de mensagens. Uma vez criados os objetos, é interessante para nossa programação entender a liberação dos recursos de memória utilizados. Para isso, iremos detalhar e trabalhar com os destrutores e o coletor de lixo. 3.2 Destrutores e Garbage Collector (Coletor de Lixo) Na tecnologia Java, existem os construtores que realizam as operações iniciais de um objeto, preparando-o para o correto funcionamento, além da alocação de memória para estas, mas existem também os métodos Capítulo 3 - Construtores, destrutores e encapsulamentot55 destrutores. Assim como os construtores, o destrutor é uma característica oriunda da linguagem C++. É um método especial que tem por finalidade liberar o espaço de memória utilizado por um objeto e com isso, finalizar qualquer operação que esteja em andamento, como, por exemplo, fechar arquivos, encerrar a comunicação e liberar os recursos alocados no sistema, e com isso, eliminar qualquer vestígio da instância do objeto. Os métodos destrutores seguem uma sintaxe padrão, conforme é apresentado a seguir. ! " ?&' // Aqui é geralmente utilizado // para liberar recursos. } Como apresentado, um método destrutor deve sempre ser identificado pelo nome finalize() e pode ser invocado a qualquer momento. Outro detalhe que pode ser percebido quanto aos destrutores é que ao contrário dos construtores, que podem conter parâmetros em sua assinatura, os destrutores não têm parâmetros na assinatura do método, porém sempre deve ser definido o valor de retorno como void. A real finalidade dos destrutores é liberar do sistema a memória usada durante a execução do objeto, sendo que este é um dos mais complexos problemas encontrados no desenvolvimento de sistemas. Isto é devido ao fato de que os recursos de um sistema são finitos. Importante detalhe a ser citado é que a invocação de um método destrutor não caracteriza a finalização concreta do objeto instanciado, mas sim, apenas uma sinalização para que o sistema, assim como definido pela JVM, libere os recursos recolhendo o espaço de memória do objeto destruído. É aí que surge outro conceito. 3.2.1 Garbage Collector Preocupados com o problema do estouro de memória comum e encontrado em diversas linguagens, os criadores da tecnologia Java incorporaram à maquina virtual um mecanismo que tem como objetivo a liberação da memória automaticamente, que é executado simultaneamente ao programa Java. Este recurso é denominado Garbage Collector ou mais popularmente conhecido como Coletor de Lixo. No Java, conforme mencionado no 56 t JAVA com Orientação a Objetos capítulo anterior, um objeto sempre referencia um endereço de memória, porém quando esse endereço deixa de ser referenciado pelo programa, no caso, sai do processo de execução do programa em questão, este espaço de memória é marcado para uma futura liberação pelo Garbage Collector. A ideia é que uma vez marcado como um objeto no qual seu espaço de memória pode ser devolvido para o sistema, o coletor de lixo devolve a memória ao sistema para uma futura utilização. Tal sinalização é feita por meio dos métodos destrutores já vistos. Outro aspecto interessante do coletor de lixo está no fato de que este mecanismo tenta sempre se antecipar aos possíveis erros, oriundos da falta de recursos do sistema. Para isso, quando não há mais memória disponível no sistema, o garbage collector faz uma varredura para obter os espaços de memória ociosos para sua liberação de imediato para o sistema, prevenindo erros de estouro de memória. Considerando tais elementos da tecnologia Java, um aspecto positivo é que não é necessário o tratamento para a liberação da memória, ficando a critério do programador a utilização dos métodos destrutores, diferentemente de outras linguagens de programação, como, por exemplo, o C++, que deve realizar tal tratamento. Um detalhe importante para finalizar nossa análise sobre o coletor de lixo e os métodos destrutores está no fato de que um objeto no Java pode ser eliminado com uma nova inicialização, por meio da invocação dos métodos construtores que irão reiniciar a referência de uma variável ou um atributo. No caso, o objeto perderá a referência ao espaço de memória anterior. Outra possibilidade de fazer com que o coletor de lixo libere recursos é atribuir o valor null a um objeto, sendo que com isso, sua referência de memória ficará à disposição. Tais possibilidades são importantes, pois garantem que não ocorram erros no sistema, devido à falta de recursos do hardware. 3.3 Encapsulamento Iremos, agora, trabalhar com outro importante elemento conceitual do paradigma de programação orientada a objetos - o encapsulamento. Este conceito é útil para garantir que a solução dos problemas propostos seja sempre realista e que cada elemento só enxergue o que realmente é importante. No caso, consiste em um mecanismo que é utilizado com a finalidade de esconder os detalhes de implementação das classes Java, de maneira a se Capítulo 3 - Construtores, destrutores e encapsulamentot57 assemelhar à realidade dos objetos reais. O ponto central do conceito é possibilitar uma maior segurança e domínio da complexidade, pois uma classe deve ofertar apenas o que ela pode fazer, não como ela faz. Vamos ser um pouco mais claros. O objetivo do encapsulamento é que uma classe impeça que seus atributos e métodos sejam acessados diretamente. Para isso, o certo é disponibilizar apenas métodos públicos para o acesso aos valores produzidos que realmente devem ser visualizados, ofertando apenas a real funcionalidade que a classe se propõe a realizar. Note que aqui é feita uma referência aos qualificadores de visibilidade apresentados em nosso segundo capítulo, sendo: public, protected e private. Tais características são oriundas da ideia cotidiana que encontramos e que geralmente passam despercebidas em nosso dia a dia na abstração de um objeto do mundo real. Vamos a um exemplo para ter um melhor entendimento. Ao assistirmos televisão ou mesmo ouvirmos um rádio, não temos nenhum conhecimento de como estes objetos trabalham internamente. Aqui, apenas são fornecidas interfaces ou maneiras de interagir com estes equipamentos, por exemplo: um botão para ligar/desligar ou mesmo aumentar/ diminuir o volume. Todos esses elementos são funcionalidades que mexem com parâmetros ou propriedades internas (atributos) do equipamento. Então, o encapsulamento é útil na proteção dos atributos da classe, disponibilizando, por meio de métodos, o acesso às propriedades da classe e com isso, a alteração do estado destas e do sistema. Definindo os métodos de encapsulamento, eles fornecem uma forma de comunicação entre os usuários de uma classe e a implementação disponível nesta, facilitando a escrita, manutenção e alteração dos programas. Existem métodos convencionados entre os programadores Java que criam uma interface entre os atributos e o usuário, sendo uma camada de comunicação publica. São denominados métodos get e set vinculados a cada atributo das classes e evitam o acesso direto a tais atributos, aumentando a segurança ao impedir que a implementação e a forma como os atributos são tratados estejam visíveis. Um exemplo desses métodos é apresentado no quadro a seguir. // Classe Carro.java package conceitos; public class Carro { // atributos 58 t JAVA com Orientação a Objetos private Boolean chave = true; // método construtor public Carro(){ setChave(true); } public Carro(Boolean chave){ setChave(chave); } // métodos de encapsulamento private void setChave(Boolean status){ this.chave = status; } private Boolean getChave(){ return this.chave; } // métodos de funcionamento public void ligar() { setChave(true); System.out.println(“###LIGADO###”); } public void desligar() { setChave(false); System.out.println(“###DESLIGADO###”); } // método destrutor ! " ?&' chave = null; } } Destrinchando o código apresentado no quadro anterior, observe que os métodos que descrevem o funcionamento da classe, aqui ligar e desligar, Capítulo 3 - Construtores, destrutores e encapsulamentot59 não fazem mais referência direta aos atributos, como ocorria nos outros exemplos apresentados até aqui. Algumas partes do código devem ser citadas. Note que o atributo-chave passou a ser declarado como privado, evitando o acesso de qualquer outra classe diretamente a ele. Foram implementados os métodos set e get, que garantem o encapsulamento dos atributos, no caso, as interfaces de acesso para a inserção de valores e recuperação. Assim, garantimos que qualquer requisição desse atributo deve ser realizada através dos métodos get e set definidos para o atributo. Nesta nova contextualização, os atributos devem obrigatoriamente ser acessados por meio de métodos. Os métodos set são utilizados para atribuir valores e os métodos get para a recuperação dos valores atuais dos atributos. Isso consiste em uma boa prática de programação que deve ser inserida em seu cotidiano, ainda mais no que tange à programação Java. Vamos a mais um exemplo para demonstrar os vários conceitos vistos até aqui. Transcreva o programa a seguir e execute-o, conforme já foi realizado em vários exemplos dos Capítulos 1 e 2. // Classe Carro.java package conceitos; import java.util.Scanner; public class Carro{ // atributos private Boolean chave = true; // métodos construtores public Carro(){ setChave(true); } public Carro(Boolean chave){ setChave(chave); } // métodos de encapsulamento private void setChave(Boolean status){ this.chave = status; } 60 t JAVA com Orientação a Objetos private Boolean getChave(){ return this.chave; } // métodos de funcionamento public void ligar() { setChave(true); System.out.println(“###LIGADO###”); } public void desligar() { setChave(false); System.out.println(“###DESLIGADO###”); } // método destrutor ! " ?&' chave = null; } // método executável public static void main(String[] args){ Scanner scn = new Scanner(System.in); Carro novoCarro = new Carro(); System.out.println(“Digite 1 para ligar \n 2 para“ + desligar); int opcao = scn.nextInt(); switch(opcao){ case 1: novoCarro.ligar(); break; case 2: novoCarro.desligar(); break; } } } Capítulo 3 - Construtores, destrutores e encapsulamentot61 No exemplo, é solicitada uma entrada ao usuário que define qual será a funcionalidade a ser acessada. Para tanto, a classe Scanner é responsável por capturar essas entradas em um console, sendo utilizado o método nextInt( ). O programa apresentado inicialmente importa a classe Scanner para que possamos capturar uma entrada do teclado, criando uma interação com o usuário. Após a definição da classe Carro, foi criado o atributo-chave, bem como os métodos de encapsulamento get e set para acesso a este. Foram criados os métodos construtores, configurando a sobrecarga dos construtores, com um possuindo parâmetros e o outro, não. Um importante detalhe a ser mencionado para os construtores é que, caso o programador defina pelo menos um método construtor, o método default disponibilizado pela própria JVM deixará de existir, sendo necessária a declaração do método construtor default pelo programador, conforme é exemplificado. Este é um conceito que está relacionado à ideia de polimorfismo que iremos explorar mais a fundo no próximo capítulo. Por enquanto, atente ao fato de que sempre que um método construtor for definido, o método default disponibilizado pela JVM deixará de existir e você deverá fornecê-lo, caso queira utilizá-lo. Finalizando, o exemplo anterior possui um método executável que define uma instância, no caso, o objeto da classe carro denominado ‘novoCarro’ que permite o acesso ao espaço de memória com seus atributos e métodos. Assim, finalizamos mais um capítulo, no qual conhecemos os elementos do paradigma da programação orientada a objetos. Fica como sugestão que todos os exemplos sejam refeitos para que todos os conceitos vistos sejam mais bem assimilados. Em nosso próximo capítulo, continuaremos a trabalhar com a orientação a objetos aplicada à tecnologia Java. Capítulo 4 Herança, polimorfismo e interface Caro leitor, neste capítulo iremos conhecer outros conceitos ainda vinculados ao paradigma da programação orientada a objetos. Sendo bem mais específico, poderíamos dizer que este é o mais importante dos capítulos estudados até aqui, pois daremos os princípios que tornam o paradigma e a tecnologia diferenciados. Como mencionado diversas vezes no decorrer do livro, o paradigma da Programação Orientada a Objetos (POO) trabalha determinando um retrato fidedigno dos objetos do mundo real. Logo, nada mais sensato e interessante que a existência de um conceito que denote a hierarquia existente entre os objetos reais. Tal conceito é de extrema importância e diferencia a linguagem de programação Java que, aliada à orientação a objetos, possibilita um melhor aproveitamento e reutilização do código por meio da ideia de herança. Outra característica da POO que iremos analisar está vinculada ao polimorfismo. A palavra polimorfismo é oriunda da palavra grega polimorfos, que significa diversas formas. Este conceito é fundamental, já que possibilita chamadas ou invocações semelhantes para diferentes implementações por meio da redefinição ou da sobrecarga de métodos. Finalizando, neste capítulo ainda conheceremos o conceito de interface, que nos possibilitará uma maior abstração da modelagem do mundo real para as classes no Java. Isso torna a linguagem Java mais extensível e reutilizável. 4.1 Herança Como mencionamos, iremos trabalhar nesta seção com um dos mais importantes conceitos da orientação a objetos, se não o mais importante: a herança. Este, por sua vez, consiste na aplicação de uma técnica muito simples e que segue os preceitos do mundo real. Um bom começo para o entendimento da herança é vislumbrar os aspectos já bem conhecidos da estrutura familiar, na qual existe uma hierarquização entre pais e filhos, normal a qualquer ser humano. Ao definir a herança, pode-se dizer que consiste em 64 t JAVA com Orientação a Objetos uma técnica na qual uma determinada classe, denominada subclasse, utiliza atributos e métodos já definidos em outra classe denominada superclasse (pai), especificada como ancestral da subclasse (filho). Sendo mais claro, a herança consiste no compartilhamento de atributos e métodos, assim como acontece num relacionamento genético do tipo pai e filho. A classe-pai, que é denominada superclasse ou classe base, possui definições gerais que podem ser utilizadas nas classes-filho, também conhecidas como subclasses. Isto significa que podem ser definidas classes mais genéricas, ou seja, em um sentido mais amplo, sendo refinadas sucessivamente em subclasses que herdam todas as características das classes mais genéricas, sendo assim mais específicas. Isso é conhecido como especificação de uma classe. A especificação, termo muito utilizado quando se fala de herança, centra no fato de que as subclasses possuem características próprias que as diferenciam das classes-pai ou mesmo de outras classes que também herdam das mesmas superclasses, das quais são herdados atributos e métodos. Um exemplo disso seria imaginarmos uma estrutura familiar, no qual um filho herda de seu pai diversas características, tais como a cor dos olhos, cabelos e voz. Porém, esse filho, que herda tais atributos, possui características que são especificamente suas, como, por exemplo, o jeito de andar ou mesmo de correr. Isso o torna mais especifico que o ente do qual herdou várias características, no caso, seu pai. Podemos considerar que sempre que uma classe herda de outra, a classe que herda passa a ser um tipo especial da classe-pai. Complicado? Veremos que não. Vamos a mais um exemplo, desta vez contextualizado a linguagem Java para clarear esta ideia. Tome como exemplo uma subclasse Paciente que herda todos os atributos e métodos de uma superclasse Pessoa, sendo assim, um paciente é um tipo de pessoa, porém mais especifico, já que além de herdar todos os atributos e métodos definidos na classe-pai, no caso a classe Pessoa, a Paciente tem seus próprios atributos e métodos que a diferenciam para o contexto que se predispõe a representar. Caro leitor, acredito que devidamente apresentado o conceito de herança, é necessário conhecermos a sintaxe para sua utilização na linguagem Java. O conceito de herança, no qual uma classe estende outra, é realizado por meio da utilização da palavra reservada extends, conforme é demonstrado hierarquicamente no quadro a seguir. Capítulo 4 - Herança, polimorfismo e interfacet65 // Classe SuperClasse.java public class SuperClasse{ //atributos e métodos da superclasse } // Classe SubClasse.java public class SubClasse extends SuperClasse{ //atributos e métodos da subclasse } No quadro anterior, é interessante notar que a superclasse não recebe nenhuma marcação que a diferencia. Porém, repare que para a subclasse, é definida a herança por meio da palavra reserva extends, que a torna uma extensão da superclasse. Com isso, podemos dizer que a subclasse, mais específica, possui o comportamento da superclasse. Outro ponto importante a mencionar, que é oriundo da utilização dos procedimentos de herança, é a utilização da palavra reservada super. Sua função é possibilitar à subclasse o acesso à superclasse. Assim, quando for necessário acessar algum método ou atributo na superclasse, a sintaxe para tanto será “super.nomeMétodo( )”. Desta forma, você está explicitando que deseja invocar um método da superclasse. Outro detalhe importante é quanto a invocar o método construtor da classe base e isto pode ser feito através da invocação do método “super( )”. Provavelmente, você ainda não deve ter percebido a importância do que foi dito até aqui. Pois bem, a importância está no fato de que, com a utilização dos princípios da herança, não é necessário que seja reescrita boa parte do código em um programa Java, pois tudo que já foi produzido na classe-pai fica à disposição da classe-filho. Outro detalhe importante a ser citado é que uma parte do código, que será comum a várias outras partes de um programa, pode ser escrita somente uma vez em uma classe-pai, como mencionamos. Com isso, caso seja necessária a manutenção de alguma parte desse código, não será necessário que seja alterado em todas as classes-filho, somente na classe-pai, e as demais subclasses herdarão a alteração. Do ponto de vista da manutenção do código, isso é um diferencial a ser considerado no processo de desenvolvimento com a tecnologia Java. Como nos demais conceitos trabalhados até aqui, vamos a um exemplo para fixar esse importante princípio da orientação a objetos que imita a realidade. Então, transcreva a classe a seguir. 66 t JAVA com Orientação a Objetos // Classe Pessoa.java package consultorio; public class Pessoa{ // atributos String nome; String endereco; // métodos public void setNome(String newNome){ this.nome = newNome; } public void setEndereco(String newEndereco){ this.endereco = newEndereco; } public String getNome(){ return this.nome; } public String getEndereco(){ return this.endereco; } public void andar(){ System.out.println("Estou andando"); } } Em nosso exemplo, foi definida a classe Pessoa, na qual estão detalhadas algumas características e comportamentos inerentes a uma pessoa real. Feito isto, contextualizando o problema, todos sabemos que um médico nada mais é que genericamente uma pessoa. Mas, não podemos deixar de considerar que ele, além de suas peculiaridades humanas, possui especificidades quanto ao exercício da profissão de um médico, que o diferem das demais pessoas, sendo assim, temos uma especificação da classe Pessoa. Desta maneira, podemos dizer que um médico tem uma relação “é um(a)” pessoa, com características e métodos específicos a este e que devem Capítulo 4 - Herança, polimorfismo e interfacet67 ser considerados. Ao trabalhar com as relações do tipo “é um(a)”, temos uma chave para identificar e determinar quando uma classe deve ou não ser descendente de outra existente. Para ter uma melhor visualização do processo, transcreva a classe Medico e note que ela explicita o conceito de herança de atributos e métodos da classe Pessoa por meio da palavra reservada extends após o nome da classe. // Classe Medico.java package consultorio; public class Medico extends Pessoa{ // atributos String horario; String especialidade; // métodos public void setHorario(String newHorario){ this.horario = newHorario; } public void setEspecialidade(String newEspecialidade){ this.especialidade = newEspecialidade; } public String getHorario(){ return this.horario; } public String getEspecialidade(){ return this.especialidade; } } Como dito, note que os atributos e os métodos que definem um médico como pessoa não foram novamente reescritos, pois já foram definidos na classe Pessoa, sendo reaproveitados pela classe Medico por meio da herança. Com isso, aquilo que foi definido para certa classe não precisa ser repetido para uma classe mais especializada originada da primeira, logo, seus atributos e métodos. Desta forma, o paradigma da orientação a objetos auxilia a 68 t JAVA com Orientação a Objetos reduzir a repetição do código em um programa e em sua manutenção. Para ter uma melhor visualização prática das vantagens obtidas com a herança, vamos a um exemplo. Nele, utilizamos as classes criadas anteriormente na definição de um programa executável, no qual é feito o cadastro de um médico. Note que aqui, começamos a fazer com que a solução de nossos problemas passe a ter uma característica modular, aumentando o reuso e facilitando posteriormente a manutenção. // Classe Consultorio.java package consultorio; import java.util.Scanner; public class Consultorio{ public static void main(String[] args){ // instância da classe Medico Medico novoMedico = new Medico(); Scanner scn = new Scanner(System.in); // entrada de dados System.out.println("#####Cadastro Clinico#####"); System.out.println("Entre com o nome do médico:"); novoMedico.setNome(scn.next()); System.out.println("Entre com o endereço do” + “médico:"); novoMedico.setEndereco(scn.next()); System.out.println("Entre com o horario do” + “médico:"); novoMedico.setHorario(scn.next()); System.out.println("Entre com a especialidade do” + ”médico:"); novoMedico.setEspecialidade(scn.next()); //impressão dos dados obtidos e mantidos para o //objeto System.out.println("\n####DADOS DO MÉDICO####"); System.out.println("Nome do médico:"+novoMedico. getNome()); System.out.println("Endereço do médico:"+novoMedico. Capítulo 4 - Herança, polimorfismo e interfacet69 getEndereco()); System.out.println("Horário do médico:"+novoMedico. getHorario()); System.out.println("Especialidade:"+novoMedico. getEspecialidade()); } } No exemplo anterior, o mais importante a ser mencionado é que, mesmo não definidos na classe Medico, os atributos e os métodos referentes ao nome e ao endereço, que são oriundos da classe Pessoa, estão disponíveis para a instância da classe Medico denominada novoMedico. Isto, relembrando, só é possível graças à herança existente na classe Medico da classe Pessoa, evitando a reescrita do código já existente na superclasse Pessoa para a subclasse Medico. A utilização do processo de herança também é muito importante para as estruturas correlatas ou mesmo as definições de melhores práticas, como no caso de vários padrões do projeto. Fica como sugestão a leitura sobre design pattern, que pode enriquecer seus programas, além de evitar, em muitos casos, o retrabalho. Finalizando nossa seção que trata dos princípios da herança na orientação a objetos com Java, é importante citar que a linguagem não suporta a herança múltipla, assim como o C++. Entretanto, existem alternativas para lidar com tal limitação, que serão consideradas ainda neste capítulo. Com isso, chegamos ao final desta seção, na qual trabalhamos a importante característica do paradigma de orientação a objetos, que é a herança. Mas, iremos analisar outra característica não menos importante para a orientação a objetos baseada na tecnologia Java, o polimorfismo, que consiste em uma estrutura fortemente dependente do processo de herança. 4.2 Polimorfismo Como já dito na introdução deste capítulo, a palavra polimorfismo advém do grego polimorfos e significa diversas formas. Como observado em sua essência epistemológica da palavra, o conceito segue os mesmo princípios. Sendo mais especifico, o polimorfismo nada mais é que a definição de métodos com o mesmo nome, que definem a solução de um mesmo problema, 70 t JAVA com Orientação a Objetos seja na própria classe, seja na superclasse. O conceito, mais um do paradigma da orientação a objetos, pode ser aplicado de duas formas: sobrecarga ou redefinição. Com certeza, a forma mais simples de obter o polimorfismo é por meio da sobrecarga de métodos ou também conhecido como overload. 4.2.1 Sobrecarga A sobrecarga, em sua essência, consiste na possibilidade de ter em uma mesma classe, vários métodos com o mesmo nome, “porém com assinaturas diferentes”. Mas, como assim com assinatura diferente? Certo, se você se lembra do conceito de assinatura visto em nosso primeiro capítulo, ele é aplicado aos métodos definindo o nome, tipo de retorno e lista de parâmetros ou argumentos. Neste caso, o que foi citado é que apesar do nome ser igual, o tipo de retorno ou mesmo a lista de parâmetros deve ser diferente para um método. Com isso, os métodos podem possuir o mesmo nome, sendo considerados diferentes por receberem um diferente número ou tipo de parâmetros, ou mesmo o tipo de retorno pode ser diferente. Este é um conceito muito simples de ser assimilado e para tanto, vamos utilizar nosso exemplo recorrente da classe Carro, na qual pode acontecer o polimorfismo. // Classe Carro.java package fabrica; public class Carro{ // atributos Combustivel combustivel = new Combustivel(); // métodos public void setCombustivel(Combustivel newCombustivel){ this.combustivel = newCombustivel; } public Combustivel getCombustivel(){ return this.combustivel; } Capítulo 4 - Herança, polimorfismo e interfacet71 KK U X public void abastecer(Alcool abstAlcool) { setCombustivel((Combustivel) abstAlcool); System.out.println(“Abastecido com álcool”); } public void abastecer(Gasolina abstGasolina) { setCombustivel((Combustivel) abstGasolina); System.out.println(“Abastecido com gasolina”); } public void abastecer(BioDiesel abstBioDiesel) { setCombustivel((Combustivel) abstBioDiesel); System.out.println(“Abastecido com biodiesel”); } } Aqui, é demonstrado um exemplo de sobrecarga dos métodos de uma mesma classe, no qual um carro pode ser abastecido de diversas maneiras, porém a essência da função é a mesma, no caso, abastecer o carro para que ele possa andar. É importante mencionar que o exemplo apresentado é apenas ilustrativo, uma vez que não foram apresentadas as classes Combustivel e suas especificações, no caso, Gasolina, Alcool e BioDiesel, que também foram utilizadas no exemplo. A ideia aqui é que seja entendida a essência do conceito de polimorfismo e sua abstração do mundo real. Conforme pode ser observado, em nosso dia a dia, uma mesma funcionalidade pode ser desempenhada de várias maneiras, porém devendo ser guardadas as devidas especificidades de funcionamento de cada uma. O exemplo disso é um carro bicombustível. Em nosso exemplo, mais um elemento importantíssimo que foi utilizado deve ser comentado - o cast. 4.2.2 Cast O cast é a forma mais comum de conversão de tipos, porém sua utilização possui algumas peculiaridades que devem ser observadas na hora da 72 t JAVA com Orientação a Objetos utilização do recurso. O cast no Java pode ser realizado de duas formas, considerando a conversão dos tipos, sendo: implícito e explícito. Estes recursos, por sua vez, estão geralmente associados à utilização do conceito de herança, no qual, como vimos, uma classe-filho (subclasse) é semelhante à classe-pai (superclasse), da qual foram herdados os atributos e os métodos. É deste ponto que podemos partir para o entendimento do cast implícito. Neste caso, toda subclasse pode ser associada à declaração de uma superclasse. Esta é a forma mais simples, pois não há a necessidade de utilização de nenhum recurso adicional para ser efetuado, sendo apenas descrito por meio da declaração convencional de um objeto da classe-pai e do método construtor da classe-filho. Para ter uma melhor visualização, vamos a um exemplo. Pai objeto = new Filho(); Em nosso exemplo, o objeto é declarado como uma instância da classe-pai, porém ele é ‘construído’ como sendo do tipo da classe filho, ou seja, recebe uma área de memória referente à subclasse. Note que há uma conversão natural, pois ambos são semelhantes devido ao processo de herança. Grosseiramente, poderíamos dizer que isso é possível, pois a classe-filho pode ser hierarquicamente inferior à classe-pai, conhece e sabe que ela própria é do tipo superclasse e, portanto, não precisa necessariamente ser informada que será convertida. Isto é feito no momento em que utilizamos a palavra extends na classe-filho, ou seja, a herança. Já o cast em sua forma explicita, é denominado assim, pois é necessária a informação para a Máquina Virtual Java de qual classe se deseja fazer a conversão, no caso o cast. Neste caso, de uma classe-pai, tenta-se convertê-la em uma classe-filho. Porém, ao fazer novamente uma análise hierárquica para ter um melhor entendimento, a superclasse não sabe e em momento algum é informada quais são suas classes-filho. Portanto, é necessário informar explicitamente em qual tipo de classe-filho a instância da classe-pai será convertida. Então, caro leitor, aqui o convido a verificar como isso pode ser realizado na linguagem Java, observando a sintaxe no exemplo a seguir. Pai objetoPai = new Filho(); Filho objetoFilho = (Filho)objetoPai; Capítulo 4 - Herança, polimorfismo e interfacet73 Em nosso exemplo, o objeto declarado como objetoPai é instanciado como sendo do tipo da classe Filho. Logo após, a referência do objetoPai é convertida por meio de um cast explicito no objetoFilho. Estes conceitos são importantes para que possamos também conhecer outro importante elemento da linguagem Java - o operador instanceof. 4.2.3 instanceof O instanceof é um operador utilizado para a realização de casts do tipo explícito, nos quais não se tem conhecimento de qual classe o define. Aqui, podemos trabalhar um pouco mais o exemplo apresentado da classe Carro quanto ao seu abastecimento. Vamos a ele então. ... public void encherTanque(Combustível c){ if(c instanceof Alcool){ abastecer((Alcool) c); }else{ abastecer((Gasolina) c); } } ... No código anterior, repare que temos um método ‘encherTanque’ que recebe como parâmetro um objeto da classe Combustível. Dentro desse método, é verificado, por meio da utilização dos operadores condicionais if/else, qual o tipo de combustível. Para isso, dentro do teste, é utilizado o operador instanceof, que realiza a verificação de qual é o combustível selecionado. De acordo com o valor da instância, é chamado o método abastecer usando a sobrecarga de métodos. Simploriamente, instanceof significa “instância de” ou “é do tipo”. Assim, em nossa verificação anterior, no teste condicional o que é feito nada mais é que uma comparação para definir se o objeto passado como parâmetro é uma instância da classe Alcool ou da classe Gasolina. 74 t JAVA com Orientação a Objetos 4.2.4 Sobrecarga de construtores Outro exemplo que pode ser citado para o polimorfismo é a sobrecarga dos métodos construtores, onde são definidos diversos métodos com o mesmo nome, porém com assinaturas diferentes. Tais considerações já foram feitas em nosso terceiro capítulo, no qual definimos dois métodos construtores para uma classe, pois, conforme visto, uma vez declarado um construtor, qualquer que seja sua assinatura, passa a ser obrigatória a definição do método construtor default para sua utilização, já que o método construtor fornecido pela JVM deixará de ser fornecido. 4.2.5 Redefinição Nossa segunda forma de utilização do conceito de polimorfismo consiste na redefinição de métodos ou também conhecido como sobrescrita (override), isso realizado sobre os métodos existentes nas superclasses. Tal conceito está baseado na herança, da qual as subclasses herdam atributos e métodos de uma classe-pai. Isso devido ao seguinte fato: Imagine que nem sempre os métodos fornecidos pela classe-pai são suficientes para resolver os problemas da subclasse, visto que a classe-filho é uma especialização, tendo suas particularidades a serem consideradas no processo. É aí que entra a sobrescrita dos métodos, na qual a subclasse reescreve um método utilizando a mesma assinatura definida na classe-pai, sendo que no momento da invocação do método, dentro da subclasse, será acionado o método ali definido que se sobrepõe ao da superclasse. Vamos a um exemplo: imagine um pai e um filho, ambos são pessoas e assim, possuem características e comportamentos de um ser humano. É natural que o filho herde os trejeitos de seu pai, porém existem características e comportamentos que, por mais inerentes ao processo de herança que sejam, precisam ser especializados, ou seja, por mais que exista o processo de herança, ao falar, por exemplo, o filho possui especificidades em sua voz que são só suas, apesar da aproximada semelhança com a de seu pai. Para o paradigma da orientação a objetos, uma operação, ação ou mesmo uma transformação que um objeto realiza por meio dos métodos são implementações específicas das operações desejadas para uma classe. Então, mesmo que esta venha a herdar os atributos e os métodos de outra classe, sempre terão prioridade as características e os comportamentos definidos Capítulo 4 - Herança, polimorfismo e interfacet75 para o escopo específico da classe. Para ter um melhor entendimento, vamos a uma implementação. Altere a classe Medico do exemplo apresentado na seção anterior, no qual essa classe herda os atributos e os métodos da classe Pessoa. // Classe Medico.java package consultorio; public class Medico extends Pessoa{ // atributos String horario; String especialidade; // métodos public void setHorario(String newHorario){ this.horario = newHorario; } public void setEspecialidade(String newEspecialidade) { this.especialidade = newEspecialidade; } public String getHorario(){ return this.horario; } public String getEspecialidade(){ return this.especialidade; } KK YZ X public void andar(){ System.out.println("Estou andando rápido"); } } Note que no exemplo apresentado, foi criado na classe Medico o método andar( ). Tal método já havia sido definido na classe Pessoa, na seção anterior, e devido ao processo de herança existente entre as classes Pessoa e 76 t JAVA com Orientação a Objetos Medico, não foi necessária sua reescrita na classe filho, ou seja, na classe Medico. Porém, devido às características específicas da classe Medico, esse método é redefinido e, então, passa a atender as particularidades dessa classe. Assim, a máquina virtual Java entenderá que ao ser invocado um método, no qual houve o processo de redefinição, o método da subclasse será o que deve ser acionado. Caro leitor, acredito que até aqui você deve ter verificado que o conceito de polimorfismo é importantíssimo para o paradigma de orientação a objetos, assim como os demais mecanismos de abstração de dados, herança e encapsulamento já apresentados. 4.3 Interface Amigo leitor, agora que já conhecemos os conceitos de herança e polimorfismo, vamos a outro conceito-chave no paradigma da orientação a objetos - as interfaces. O conceito de interface é um tanto amplo, já que ao trabalharmos com o Java, ele está constantemente presente. De um lado, como vimos, toda classe no Java tem um propósito específico, geralmente se relaciona com outras classes e aí, temos um sistema completo e funcional que se comunica por meio de mensagens. Essas mensagens são trabalhadas por meio dos métodos definidos segundo os modificadores de acesso e tais métodos são interfaces (entradas) para o acesso ao conteúdo de uma instância. Mas, a tecnologia Java ampliou o conceito, tornando-o ainda mais flexível. Poderíamos dizer grosseiramente que a tecnologia Java possui um tipo especial de classe que são as interfaces. Isso ocorre devido à sintaxe de definição de uma interface no Java ser semelhante à utilizada para uma classe. Entretanto, no lugar do identificador class, deve ser utilizada a palavra reservada interface. A ideia central do conceito de interface é a modelagem dos comportamentos que são esperados na definição de uma determinada classe. O nome interface coerentemente tem o objetivo de explicitar que esse conceito Java pretende disponibilizar um meio padronizado de acesso a diferentes tipos de implementação como uma interface propriamente dita. Para isso, em uma interface são definidos apenas métodos abstratos e variáveis finais. Um ponto importante a ser destacado é o fato de uma interface não ter uma implementação para seus métodos ou mesmo instâncias para suas variáveis. Logo, a utilização das interfaces garante que você sempre se concentre nos objetos e nos relacionamentos que existirão, Capítulo 4 - Herança, polimorfismo e interfacet77 construindo, desta forma, uma espécie de planta baixa do sistema para possibilitar a construção baseada nos modelos existentes, definindo assim uma camada extra de abstração para seu sistema que servirá de base para o restante. O código a seguir apresenta a sintaxe para a definição de interfaces nos Java. // Interface Aluno.java package escola; public interface Aluno{ !# ^`|~~U~ !# # &' public abstract int faltas(); !# # & ' !# # & ' } Algumas considerações devem ser feitas e estar sempre em mente quanto à utilização das interfaces. Uma interface não pode ser instanciada, sendo que seu objetivo é definir um modelo de comportamento abstrato para as classes. Tais classes que se propõem a implementar a interface devem fornecer a implementação dos métodos declarados na interface ou, ao menos, declará-los em seu escopo. Uma boa analogia para as interfaces é imaginá-las como sendo um contrato, no qual qualquer classe que se propõe a implementar a interface deve, ao menos, declarar métodos e variáveis estáticas e finais em seu escopo. Note que no exemplo anterior, é feita a utilização da palavra reservada abstract. Ela pode referenciar tanto métodos quanto classes ao apresentar apenas a ideia ou mesmo um modelo do todo, sem que seja fornecida a implementação para tais métodos ou classe, deixando explícita somente a abstração do objeto. O conceito de abstração muitas vezes se confunde com o conceito de interface. A função das classes abstratas é forçar o programador a implementar subclasses para a resolução dos problemas. Assim, os métodos da classe abstrata são declarados com o modificador abstract e sem corpo. Voltando ao conceito de interface, para que uma classe implemente uma interface, em sua definição deve ser colocado o identificador 78 t JAVA com Orientação a Objetos implements seguido da lista de interfaces separadas por vírgulas. Vamos a um exemplo de uma classe que implementa a interface aluno definida no quadro anterior. // Classe AlunoImp.java package escola; public class AlunoImp implements Aluno{ // atributos !" private int faltas; // métodos !# &' return nota1(nota1)+nota2(nota2)/2; } public int faltas(){ return faltas; } !# & ' this.nota1 = nota; return nota1; } !# & ' this.nota2 = nota; return nota2; } } Caro leitor, vamos entender o exemplo apresentado no quadro anterior. Conforme pode ser observado, a classe AlunoImp implementa a interface Aluno e, então, deve fornecer uma implementação para os métodos declarados na interface Aluno, que foi construída anteriormente. Além de fornecer a ideia de um contrato entre as classes, conforme mencionado, outro aspecto que pode ser explorado no conceito de interface, consiste na ideia da plugabilidade. Vamos a um exemplo para deixar tal perspectiva Capítulo 4 - Herança, polimorfismo e interfacet79 mais clara. Imagine uma classe que utiliza uma determinada interface que disponibiliza os métodos abstratos para a conexão com um banco de dados. Assim, existem diversas formas de implementar uma conexão. Logo, podemos ter uma interface que defina todas as maneiras como uma conexão pode ser implementada, cabendo ao desenvolvedor plugar o método que lhe for mais conveniente, isso sem a necessidade de alterações drásticas no programa, uma vez que todas as classes que implementam tal interface para a conexão, de alguma maneira, deverá comprometer-se a fornecer todos os métodos, como em um contrato, independentemente da forma como será implementado cada método. Vamos a outro exemplo clássico e bem simples para o entendimento da ideia de plugabilidade, que é elemento central em vários padrões de projetos utilizados. Pois bem, imagine uma empresa de software que possui um programa de controle médico. Este pode ser aplicado tanto a clínicas para tratar pessoas quanto a clínicas veterinárias. Tal sistema é relativamente simples e realiza apenas o controle de pacientes. Verificando o funcionamento de ambas as clínicas, os processos são idênticos, apesar de lidar com pacientes completamente diferentes. Resumindo, o sistema pode ser utilizado em ambos os casos. Mas, como? No caso, poderia ser criada uma interface Paciente que definiria os métodos comuns aos pacientes, restando as classes que implementam tal interface. Então, observe o código a seguir. // Interface Paciente.java package clinica; public interface Paciente { public void setNome(String nome); public void setHistorico(String historico); public String getNome(); public String getHistorico(); } Observe a seguir que nossas classes Pessoa e Animal, que implementam a interface Paciente, a partir deste momento deveriam ter as seguintes definições obrigatoriamente. 80 t JAVA com Orientação a Objetos // Pessoa.java package clinica; public class Pessoa implements Paciente { // atributos private String nome; private String historico; // Métodos public void setNome(String nome) { this.nome = nome; } public void setHistorico(String historico) { this.historico = historico; } public String getNome(){ return nome; } public String getHistorico(){ return historico; } } Definida a classe Pessoa, para a classe Animal teríamos uma semelhança, uma vez que ela deve também implementar a interface Paciente. // Animal.java package clinica; public class Animal implements Paciente { // atributos private String nome; private String historico; // Métodos public void setNome(String nome) { this.nome = nome; Capítulo 4 - Herança, polimorfismo e interfacet81 } public void setHistorico(String historico) { this.historico = historico; } public String getNome(){ return nome; } public String getHistorico(){ return historico; } } Isso permite uma padronização dos procedimentos ou mesmo que o código possa ser utilizado duas ou mais vezes, dando extensibilidade ao programa, bastando apenas que seja construída uma nova classe que se proponha a implementar a interface e assim, que se possam utilizar todos os demais benefícios de seguir tais regras. Para tanto, teríamos como declaração dos objetos das classes, por exemplo. // Clinica.java package clinica; public class Clinica { public static void main(String[] args){ Paciente p = new Pessoa(); Paciente a = new Animal(); // Dados para a pessoa p.setNome(“Alex Coelho”); p.setHistorico(“Apresentou problemas na” + “garganta.”); // Dados para o animal a.setNome(“Totó”); a.setHistorico(“Problemas na pata.”); // Imprimindo os relatórios 82 t JAVA com Orientação a Objetos Relatorios r = new Relatorios(); r.imprimirRelatorio(p); r.imprimirRelatorio(a); } } Note que em ambos os casos, o espaços de memória construídos são diferentes, um para a pessoa ou para o animal. Porém, quanto à sua definição, são do mesmo tipo, no caso instâncias da interface Paciente. Repare. Paciente p = new Pessoa(); Paciente a = new Animal(); Isso nos garante uma padronização nos processos, fazendo com que outras funcionalidades do sistema possam ser utilizadas de forma extensível a vários produtos. No nosso caso específico, a vantagem de utilizar a interface Paciente se caracterizaria na utilização dos procedimentos de geração de relatório, por exemplo, como é apresentado no código a seguir e já referenciado no exemplo anterior. // Relatorios.java package clinica; public class Relatorios { public void imprimirRelatorio(Paciente p) { System.out.println(“Relatório de Pacientes”); System.out.println(“Nome:”+p.getNome()); System.out.println(“Histórico:” +p.getHistorico()); System.out.println(“Histórico:” +p.getCadastro()); } } Para finalizarmos nosso capitulo, vamos a outra funcionalidade que o conceito de interface possibilita, sendo a manipulação da herança múltipla no Java. Capítulo 4 - Herança, polimorfismo e interfacet83 4.3.1 Herança múltipla Embora a linguagem Java não forneça um mecanismo explícito que possibilite a herança múltipla, ou seja, uma classe herdar de mais de uma classe, com a utilização de interfaces é possível obter algo próximo da essência do conceito, com a implementação de diversas interfaces por uma classe. Como uma classe pode implementar diversas interfaces e deve prover implementações para os métodos declarados nas interfaces, obtém-se uma pseudosobrescrita dos métodos e com isso, ela disponibiliza todos os métodos e atributos para as classes que a utilizam. Para ter um melhor entendimento, vamos a mais uma analogia. Um exemplo clássico é o do carro anfíbio, que possui características e comportamentos tanto de um carro quanto de um barco. Então, nosso primeiro passo é criar as interfaces que definem os métodos. // Carro.java package estaleiro; public interface Carro { public void puxarFreioDeMao(); } % ? na herança múltipla. // Barco.java package estaleiro; public interface Barco { public void navegar(); } Feito isso, agora é necessário criar a nossa classe CarroAnfibio que irá implementar as interfaces propostas e com isso, passará a ser obrigada a reescrever os métodos propostos nas interfaces. Assim, vamos ao que interessa. Transcreva o código a seguir e analise sua utilização. 84 t JAVA com Orientação a Objetos KK ~#" package estaleiro; !# ~# implements Carro, Barco { public void puxarFreioDeMao() { System.out.println(“Puxou o freio de mão!”); } public void navegar() { System.out.println(“Navegando!”); } } Repare no exemplo que com isso, garantimos o comportamento ambíguo do objeto real com a utilização das interfaces que mapearam o problema. Acredito que com isso, você tenha conseguido visualizar toda a vantagem de utilizar interfaces, que consiste na definição de um protocolo que seja comum entre as classes, além de criar uma especificação do que uma classe deverá oferecer e implementar em termos de métodos, o que resulta numa forma de abstração. Então pessoal, chegamos ao final dos principais conceitos do paradigma de programação orientada a objetos, porém isso não significa o final de nossa jornada. Nos próximos capítulos, iremos conhecer ferramentas interessantes que irão possibilitar a você, caro leitor, ter um melhor aproveitamento da linguagem Java e dos conceitos do paradigma de programação orientada a objetos. Assim como nos capítulos anteriores, aproveite para refazer todos os exemplos propostos. Capítulo 5 Ferramentas úteis para o dia a dia Neste capítulo, iremos considerar diversas ferramentas para um programador Java. Elencar quais seriam as prioridades de um programador em seu cotidiano não é algo tão simples assim, porém com certeza a manipulação de strings, trabalho com datas e horas, definição de operações matemáticas e manipulação de vetores especiais estão entre as funções que mais se destacam. Assim como mencionado no início de nosso livro, tudo no Java consiste em classes, exceto os tipos primitivos. Relembrando nossos tipos primitivos: int, float, boolean etc. Nossas classes derivam da classe Object, que tem as definições padrão para qualquer classe. Logo, todas as funcionalidades e operações que são fornecidas pela biblioteca padrão da tecnologia Java são definidas em classes que operam por meio de seus métodos. Diante disso, iremos abordar como a linguagem Java possibilita o trabalho com estas importantes ferramentas para o dia a dia. 5.1 Manipulação de strings Como já vimos desde o início de nossa leitura, a classe String trata-se de uma das mais utilizadas, ao considerarmos que no cotidiano computacional, em sua maioria, as operações dependem deste tipo de dado em sua entrada. Pois bem, diversas linguagens apresentam formas diferentes de lidar com as strings, seja no formato de um vetor de caracteres, seja mesmo fornecendo uma operação responsável por desempenhar a função de elemento agregador destes. A tecnologia Java fornece a classe String para a realização de operações e a manipulação de palavras e frases. Como você deve já ter percebido, diferentemente dos tipos primitivos, tais como int, float e assim por diante, sempre que instanciamos um objeto do tipo String, com ‘S’ maiúsculo em sua inicial, isso denota, segundo a convenção existente entre os desenvolvedores Java e já conhecida por você, que se trata de uma classe. Por se tratar de uma classe, ela nos fornece uma gama de serviços que podem ser acionados por meio de seus métodos. São tais métodos que tornam as classes 86 t JAVA com Orientação a Objetos diferenciadas dos tipos primitivos, como já citado. A tecnologia Java fornece classes que também auxiliam na manipulação dos principais tipos, como, por exemplo, Int, Float, classe Boolean, entre outras. Repare que aqui temos o mesmo padrão: todas as iniciais são maiúsculas, logo, todas são classes que agregam valores aos tipos de dados por meio de métodos. Mas iremos apenas nos concentrar aqui na manipulação de strings com as principais operações possíveis utilizando o Java. Desta forma, conheceremos os principais métodos disponibilizados pela classe String, começando pela utilização de seus métodos construtores. 5.1.1 Construtores da classe String Assim como em todas as classes Java, é necessária a existência de um método construtor responsável por garantir a instanciação dos objetos do tipo String. Pois bem, a classe String fornece dois métodos construtores para a instanciação de seus objetos, sendo um parametrizado e outro default. Outra forma, no entanto, de realizar a criação de nosso objeto é por meio da passagem de valor explícita, ao qual o objeto fará sua referência. Vamos observar como pode ser realizada a criação de nossos objetos da classe String. String s = “Palavra”; String s1 = new String(); String s2 = new String(“Palavra”); Repare que tudo que foi apresentado no quadro anterior não é novidade. Vários exemplos já apresentados a você, leitor, nos demais capítulos fizeram menção a alguma das possibilidades de criação ou instanciação de objetos da classe String. É vital para seus programas que o objeto seja construído com algumas das possibilidades mostradas, garantindo que não ocorra erro de pontos nulos, sem uma referência de memória. 5.1.2 Outros métodos Considerando inicializados nossos objetos, vários métodos são úteis para a manipulação de Strings, como segue na Tabela 5. Consideremos um objeto ‘s’ do tipo String para a sintaxe. Capítulo 5 - Ferramentas úteis para o dia a diat87 Tabela 5. Principais métodos da classe String. Método Função Sintaxe length( ) Retorna o número de caracteres da string. Int tamanho = s.lenght( ); charAt(int) Captura um caractere na posição especificada na string. char caractere = s.charAt(3); equals(String) Compara duas strings e retorna um valor booleano (verdadeiro ou falso) Boolean iguais = s.equals(s1); equalsIgnoreCase(String) Compara duas strings ignorando a diferença entre minúsculas e maiúsculas e retornando valores booleanos. Boolean iguais = s.equalsIgnoreCase(s1); compareTo(String) Compara duas strings e retorna 0 se ambas forem iguais. Se a string que chama o método for menor que a passada como parâmetro, será retornado um número negativo e caso contrário, um número positivo. Int result = s.compareTo(s1); substring(int) substring(int, int) Retorna um novo objeto String a partir do ponto especificado em valor inteiro ou, então, delimitando a posição inicial e final. indexOf(char) Retorna a posição da primeira ocorrência do caractere passado como parâmetro na string que invocou o método. String nova = s.substring(3); String nova = s.substring(3,5); Int posicao = s.indexOf(‘A’); 88 t JAVA com Orientação a Objetos toUpperCase( ) Retorna um novo objeto String com todos os valores em maiúsculo. String nova = s.toUpperCase( ); toLowerCase( ) Retorna um novo objeto String com todos os valores em minúsculo. String nova = s2.toLowerCase( ); Amigo leitor, como você deve ter percebido, a classe String fornece diversas possibilidades que podem tornar a vida do programador mais fácil. Outros métodos da classe String poderiam ser considerados aqui. Assim, fica como dica que você verifique na documentação oficial da classe String as demais possibilidades para a manipulação das strings. Porém, nem só de strings vive um programa, outras classes devem ser consideradas como elementos comuns e interessantes para o cotidiano de um desenvolvedor Java. Entre tais classes, podemos citar as classes que auxiliam na manipulação de datas e horas, sendo nosso próximo assunto. 5.2 Data e hora Um dos elementos que mais geram problemas e controvérsias na programação Java, com certeza consiste na manipulação de datas e horas. Isso pode tomar proporções ainda maiores quando falamos de aplicações que irão rodar e depender da Internet e de seus diversos servidores espalhados pelo mundo. Considerando tais problemas, a tecnologia Java fornece um conjunto de classes que, conforme já vimos, disponibilizam uma grande variedade de métodos que possibilitam o trabalho com datas e horas. As principais classes que são utilizadas para a manipulação de datas são: java.util.Date, java. util.Calendar e java.util.GregorianCalendar. É importante mencionar que a classe Date está em desuso e, portanto, em processo de depreciação, o que pode fazer com que nas próximas versões da máquina virtual Java, ela não seja mais fornecida e os sistemas que a utilizam tenham problemas devido à sua falta. Mas, ainda assim, é importante apresentá-la ao considerarmos que diversos sistemas ainda a utilizam. Certo, então, nada melhor que apresentar sua utilização com um código. Logo, transcreva o código a seguir e execute-o. Capítulo 5 - Ferramentas úteis para o dia a diat89 // ExemploData.java import java.text.SimpleDateFormat; import java.util.Date; import java.util.GregorianCalendar; public class ExemploData { public static void main(String[] args) { Date d = GregorianCalendar.getInstance(). getTime(); SimpleDateFormat format = new SimpleDateFormat(); System.out.println(format.format(d)); } } Com a execução do programa, você deve ter percebido outra possibilidade da classe Date, que consiste na manipulação de informações sobre o tempo, tais como: hora, minutos, segundos e milissegundos. No exemplo apresentado, note que a data impressa segue o padrão americano e para isso, é necessária a manipulação de sua formatação, que é feita no método construtor da classe SimpleDateFormat. Essa classe fornece um conjunto de caracteres padrão para a formatação do objeto Date. Podemos citar alguns exemplos de formatações de datas, como a seguir. dd/MM/yy = 21/08/11 dd/MMM/yyyy = 21/AGO/2011 Para obtermos a formatação no padrão brasileiro, criaremos uma classe alterada no trecho que se refere ao método construtor da classe SimpleDateFormat, como apresentado a seguir. Execute o código e verifique sua saída. // Data.java import java.text.SimpleDateFormat; import java.util.Date; import java.util.GregorianCalendar; public class Data { 90 t JAVA com Orientação a Objetos public static void main(String[] args) { Date d = GregorianCalendar.getInstance().getTime(); SimpleDateFormat format = new SimpleDateFormat(“dd/MM/yy”); System.out.println(format.format(d)); } } É importante mencionar aqui que a classe SimpleDateFormat conta com a utilização de diversos caracteres que auxiliam na formatação para a manipulação de datas e as mesmas, estão disponíveis em sua documentação padrão. Outra maneira de obtermos a data pode ser com a utilização das classes DateFormat e Locale. A classe Locale é importante, pois auxilia na designação do local e no processo de alteração dos padrões regionais. A mesma já vem com diversas regiões preconfiguradas como constantes, que apenas necessitam ser determinadas no momento de sua implementação. Já a classe DateFormat, define alguns padrões que podem ser aplicados a uma data ou hora. Vamos a um exemplo para sua fixação. // DataFormatada.java import java.text.DateFormat; import java.util.Locale; import java.util.Date; public class DataFormatada { public static void main(String args[]){ Date d = new Date(); Locale local = new Locale («pt»,»BR»); DateFormat df = DateFormat.getDateInstance( DateFormat.LONG, local); System.out.println(“Hoje são: “+ df.format(d)); } } Porém, como mencionado, a classe Date está em processo de descontinuação e dessa forma, a tecnologia Java passa a fornecer outras classes para Capítulo 5 - Ferramentas úteis para o dia a diat91 a manipulação de datas. A principal, com certeza, consiste na Calendar. A classe Calendar consiste em uma classe abstrata, ou seja, contém as abstrações que definem uma data e hora, e desta forma, disponibiliza atributos e métodos para a manipulação de datas, uma vez que a data e a hora existem em qualquer lugar. Mas, o fato de ser uma classe abstrata não permite a instanciação direta por meio do operador new. Assim, para instanciar um objeto Calendar, caro leitor, você deve utilizar o método estático sobrecarregado getInstance( ), que na maioria das vezes é herdado da classe GregorianCalendar. A classe Calendar permite que várias operações sejam aplicadas a uma data, enriquecendo o que já é ofertado pela classe Date. Para entender sua manipulação, vamos a um exemplo. // ExemploDataCalendar.java import java.text.DateFormat; import java.util.Calendar; import java.util.Date; public class ExemploDataCalendar { public static void main(String args[]){ Date d = new Date(); Calendar calendario = Calendar.getInstance(); calendario.setTime(d); DateFormat df = DateFormat.getDateInstance( DateFormat.SHORT); System.out.println( df.format(calendario.getTime())); } } Repare que no exemplo, utilizamos uma instância da classe Date para definirmos a data atual passada pelo sistema, porém poderíamos também ter, ao invés de utilizado o objeto do tipo Date, definido por meio de String os diversos parâmetros de uma data ou mesmo hora. Segue um exemplo para a visualização. 92 t JAVA com Orientação a Objetos ... calendario.set(ano,mês,dia); calendario.set(ano,mês,dia,hora,minuto); calendario.set(ano,mês,dia,hora,minuto,segundo); ... Porém, nem sempre é interessante utilizar a classe Date, dada sua atual situação, sendo que a própria classe Calendar com a utilização do método getInstance( ) já fornece todo o suporte necessário para a aquisição da data. Além disso, o objeto do tipo Calendar, no nosso caso, calendário, pode ser utilizado para a manipulação dos elementos vinculados à data por meio de suas constantes, além de realizar, como já dito, operações como, por exemplo, a soma ou a subtração dos dias em uma data. Como nos demais tópicos trabalhados até aqui, vamos a mais um exemplo. // DataCalendario.java import java.text.DateFormat; import java.util.Calendar; import java.util.Locale; public class DataCalendario { public static void main(String args[]){ Calendar calendario = Calendar.getInstance(); DateFormat df = DateFormat.getDateInstance( DateFormat.SHORT, Locale.UK); System.out.println(df.format (calendario.getTime())); System.out.println(Calendar.DAY_OF_MONTH); System.out.println(Calendar.DAY_OF_WEEK); System.out.println(Calendar.DAY_OF_YEAR); calendario.add(Calendar.DAY_OF_YEAR,10); //adiciona 10 dias à data atual calendario.add(Calendar.MONTH,5); //adiciona 5 meses à data atual calendario.add(Calendar.YEAR,2); //adiciona 2 anos à data atual System.out.println(df.format (calendario.getTime())); Capítulo 5 - Ferramentas úteis para o dia a diat93 } } No exemplo anterior, utilizamos duas classes conhecidas, DateFormat e Locale. Note que utilizamos a constante “Locale.UK” que define que seguiremos os aspectos regionais da Grã-Bretanha. Logo depois, é feita a impressão da data e algumas operações para, então, termos uma nova impressão da data com os valores alterados. É claro que existem diversos outros métodos e constantes que a classe Calendar oferta e, logo, para serem obtidas maiores explicações, podem ser acessadas na documentação oficial. 5.3 Operações matemáticas Outra classe importante para o cotidiano de um programador Java é a java.lang.Math. Ela disponibiliza diversos métodos e constantes para as operações matemáticas que podem ser acessadas de maneira estática, ou seja, não é necessário realizar a instanciação de um objeto. Exemplos das diversas possibilidades são as constantes π (pi) e ln (base dos logaritmos naturais, também chamado de número de Euler ou logaritmo neperiano), sendo respectivamente 3,141592653589793 e 2.718281828459045. Essas duas constantes podem ser acessadas conforme o exemplo a seguir explicita. // Matematica.java public class Matematica { public static void main(String args[]){ // Cálculo do comprimento de um círculo double comp = 2 * raio * Math.PI; System.out.println(comp); // Logaritmo neperiano System.out.println(Math.E); } } Foram apresentadas no código as constantes “Math.PI” e “Math.E” que, conforme já mencionado, simplificam a utilização dos valores para π 94 t JAVA com Orientação a Objetos e ln. Porém, nem só de constantes vivem as operações matemáticas e para isso, a classe Math disponibiliza vários métodos que auxiliam nas mais diversas operações. Entre os principais métodos ofertados pela classe, temos a comparação de valores maiores e menores, bem como operações como potências e raízes para os cálculos de Trigonometria. Considerando isso, vamos à apresentação de métodos para uma comparação para o conhecimento de sua sintaxe. // ComparaValores.java public class CamparaValores { public static void main(String[] args) { " System.out.println(“O maior valor é “ + Math.max(valores1[0], valores1[1])); System.out.println(“O menor valor é “ + Math.min(valores1[0], valores1[1])); } } Verifique que no exemplo, foi realizada a comparação de valores, sejam eles máximos, sejam mínimos por métodos da classe Math. Porém, a maioria dos métodos fornecidos pela classe Math auxilia na manipulação dos valores trigonométricos. Para verificarmos a utilização dos métodos da classe Math para a Trigonometria, nada mais interessante que utilizar o clássico exemplo da hipotenusa. Logo, transcreva o código e execute-o. // Hipot.java public class Hipot{ public static void main(String args[]) { double p1 = 12; double p2 = 16; System.out.println(“A distancia entre os pontos é “ + Math.hypot(p1, p2)); } } Capítulo 5 - Ferramentas úteis para o dia a diat95 Outros métodos, como os que calculam o seno, cosseno e tangente, além de suas variações, são fornecidos pela classe Math. Finalizamos nossa análise sobre os métodos da classe Math com um dos mais utilizados e importantes, que é o “Math.random( )”. O método trabalha com valores randômicos que variam de 0.0 até 1.0. Em geral, é necessária a manipulação de tais valores para se obter sua utilidade para a criação de aplicações do dia a dia, como podemos perceber no exemplo a seguir. // NumeroRandomico.java public class NumeroRandomico { public static void main(String[] args) { int valorMin = 0; int valorMax = 10; int ranger = valorMax - valorMin; double valorRandomico; for (int i = 0; i < 10; i++) { valorRandomico = Math.random(); System.out.println(“O número entre” +valorMin+” e “+valorMax+” é:” + Math.round( valorMin + valorRandomico * ranger)); } } } No exemplo, definimos os valores mínimos e máximos que definem o limite para os valores randômicos que queremos. Logo após, criamos uma variável que recebe o valor randômico. Finalizando, temos um laço de repetição, no qual realizamos o arredondamento da transformação dos valores fracionados. É importante mencionar que existem outros métodos da classe Math que podem ser utilizados para as operações matemáticas que agregam valor às suas aplicações. 5.4 Trabalho com vetores especiais Como temos visto desde o início de nosso trabalho, a tecnologia Java 96 t JAVA com Orientação a Objetos se destaca claramente das outras devido à sua diversidade. Diante isso, outra ferramenta que deve ser mencionada, entre as diversas que a linguagem Java disponibiliza, consiste nas classes para o trabalho com vetores. Em sua maioria, as diversas linguagens de programação disponíveis no mercado trabalham com vetores convencionais que delimitam um tamanho fixo. Isto é, impedem que estes sofram alteração de tamanho durante a execução, como, por exemplo, os que foram desenvolvidos em nosso primeiro capítulo. A linguagem Java fornece subsídios adicionais para a manipulação dos vetores, resolvendo problemas como o citado, no caso, a alteração de tamanho durante a execução. Além disso, disponibiliza classes para as operações de nossos vetores estáticos. Isso tudo obviamente é feito por um conjunto de classes. 5.4.1 Classe Vector Entre estas, temos a Vector, que possibilita a criação de uma estrutura de dados semelhante aos vetores convencionais, porém os objetos instanciados com a utilização dessa classe podem ser redimensionados dinamicamente durante a execução. Logo, a qualquer momento, quando é necessário mais espaço para o armazenamento, o próprio sistema, por meio da máquina virtual, encarrega-se de dobrar o tamanho do espaço inicialmente alocado para o vetor, no caso, um objeto da classe Vector. Pois bem, outra característica, que deve ser considerada e que torna a classe Vector diferenciada, está na capacidade de armazenar qualquer tipo de entidade, independentemente de seu tipo. Isto se dá devido ao fato da classe Vector fazer o armazenamento de referências para Object. Logo, como já sabemos, tudo no Java consiste nas classes que herdam de Object e isso, garante que possamos trabalhar com qualquer que seja o tipo do elemento a ser armazenado no vetor. Então, nada como a prática para entendermos melhor os conceitos apresentados. Vamos a eles. // Classe ExemploVector.java import java.util.Vector; public class ExemploVector{ public static void main(String[] args){ Capítulo 5 - Ferramentas úteis para o dia a diat97 Vector vetor = new Vector(); Carro mercedez = new Carro(); vetor.add(mercedez); Bola bolaFutebol = new Bola(); vetor.add(bolaFutebol); if(!vetor.isEmpty()){ for (int i = 0; i < vetor.size(); i++) { Object object = vetor.elementAt(i); } System.out.println(vetor.size()); } } } No exemplo anterior, um detalhe a ser observado está no fato de que, diferentemente dos vetores trabalhados até aqui, não definimos o tamanho de nosso vetor para a variável ‘vetor’. Isso graças à capacidade da classe Vector que determina o tamanho a ser alocado durante a execução, sendo que isso é ideal para a manipulação de grandes vetores, bem como estruturas heterogêneas. Por ser uma classe, Vector disponibiliza para seus objetos, no caso, vetores especiais, funcionalidades que não são comuns aos vetores tradicionais, tais como, a verificação do tamanho do vetor ou mesmo se eles estão vazios, conforme foi apresentado no exemplo, através dos métodos size( ) e isEmpty( ). Entretanto, isto não elimina um dos maiores problemas do trabalho com vetores, que é sua navegação e busca. Imagine um vetor que cresce e tem um tamanho de um milhão de posíções. No caso, isso tornaria o processo de busca computacionalmente inviável, já que para encontrar um elemento, seria necessário percorrer o vetor. Isto se torna mais crítico, caso o objeto procurado esteja no final do vetor. Para solucionar esse problema, a linguagem Java fornece outra classe para o trabalho com vetores - a HashMap. 98 t JAVA com Orientação a Objetos 5.4.2 Classe HashMap Seu diferencial está no fato de que ela permite a associação de índices que identificam e tornam o objeto único dentro do vetor. Tal índice se torna uma chave para a recuperação do valor de maneira pontual. Isto garante que possa ser realizada uma consulta de um vetor de um para um. Obviamente, isto é bem diferente do que ocorria com a definição dos vetores estáticos vistos em nosso primeiro capítulo ou mesmo nos definidos com a classe Vector. Depois destas considerações, vamos a um exemplo com a classe HashMap. // Classe NovoVetor.java import java.util.HashMap; public class NovoVetor{ public static void main(String[] args) { HashMap pessoas = new HashMap(); Pessoa newPessoa = new Pessoa(); newPessoa.nome = "Fulano da Silva"; String chave = "Fulano"; pessoas.put(chave, newPessoa); Pessoa outraPessoa = (Pessoa)pessoas.get(chave); System.out.println("Nome recuperado: "+ outraPessoa.nome); } } Note que no exemplo anterior, foi instanciado um objeto do tipo HashMap denominado pessoas, no caso um vetor, e por meio do método put( ), foi armazenado um objeto da classe Pessoa. Repare que foi necessária a atribuíção de uma chave para a identificação do objeto, que será utilizado em uma posterior recuperação. Isto, por sua vez, é feito com a utilização do método get( ) da classe HashMap, como visto no código anterior. É importante ainda mencionar que o toolkit Java possui ainda uma classe que disponibiliza métodos para a realização de operações nos vetores. Essa classe não é denominada por acaso de Arrays. Capítulo 5 - Ferramentas úteis para o dia a diat99 5.4.3 Classe Arrays A classe Arrays fornece suporte para as principais manipulações que um vetor pode sofrer, no caso a comparação, ordenação ou mesmo pesquisas binárias, além de outras. Para verificarmos o funcionamento da classe Array, transcreva o código e execute para verificar seu resultado. // Classe ManipulandoArrays.java import java.util.Arrays; public class ManipulandoArrays { public static void main(String[] args) { String[] nomes = new String[3]; nomes[0]= "Fulano"; nomes[1]= "Ciclano"; nomes[2]= "Beltrano"; Arrays.sort(nomes); for(int i=0;i<nomes.length;i++){ System.out.println(nomes[i]); } } } A classe Array, como pode ser observado, consiste em uma classe que manipula vetores estáticos. Além disso, é uma classe abstrata que não precisa ser instanciada para a utilização, sendo utilizada sua própria referência. Ela, como já dito, possui uma série de métodos, entre eles, provê o método sort( ) que foi utilizado no exemplo, responsável por ordenar o array passado como parâmetro, realizando internamente um mecanismo de comparação entre as strings e ordenando-as. Note que utilizamos o atributo length para que a impressão ordenada dos nomes fosse realizada através da estrutura de repetição for. Isto abre espaço para que seja comentado que mesmo os vetores sendo estruturas estáticas, estes possuem alguns atributos e métodos que auxiliam em sua manipulação, como, por exemplo, o método equals( ). Com isso, chegamos ao final de mais um capítulo. Nosso próximo compromisso é a necessidade de trabalharmos com o tratamento de exceções no Java, o que nos abrirá caminho para elementos mais avançados, tais como, o trabalho com arquivos ou a manipulação de streams. Capítulo 6 Tratamento de exceções e entrada de dados ! " # $ #%& # # & ' & $ ( $ ( $ ) ! * $ # # & $ $ # # + ! ( % , $ / ( # ! # ( + ! & 011 #! $ 0 % ( $ $ 23 $ ( $ $ Como mencionado, diversas classes fornecidas pelo JDK podem ser utilizadas para fornecer tal suporte para as aplicações desenvolvidas, diminuindo o tempo de possíveis manutenções que venham a ocorrer e aumentando a capacidade dos programas em Java. 102 t JAVA com Orientação a Objetos 6.1 Tratadores de exceções A exceção na linguagem Java é uma indicação de que um erro ou um problema aconteceu durante a execução de uma aplicação. Isto se torna mais suscetível considerando que a linguagem Java fornece uma grande quantidade de bibliotecas, no caso, APIs, ou mesmo classes próprias. A ideia nem chega perto da perfeição, mas, ao menos, tenta garantir à tecnologia, que as classes de exceção tratem as situações de erros moderados que podem ser encontrados e visualizados em seus programas e, então, recuperados. Uma maneira interessante de interpretar essas exceções é sempre trabalhar os trechos de código que apresentam um grau maior de possibilidade de que um erro possa vir a acontecer e geralmente, isto está vinculado à utilização de recursos externos à tecnologia. Isto, em um contexto mais recente e atualizado, no qual os sistemas são construídos sem critérios, torna a tecnologia Java um elemento diferenciado e um porto seguro para o desenvolvimento de soluções complexas na construção de aplicações críticas, tais como, sistemas hospitalares, de aviação e até mesmo espaciais e de robótica. O tratamento de exceções no Java nada mais é que um processo de gatilho, que ao ser disparado um erro como mensagem para o sistema, possibilita que seja trabalhada a exceção. Este processo demonstra ao sistema o erro, além de relatá-lo para o entendimento e o tratamento da melhor solução para a recuperação e a continuídade das próximas rotinas do programa. Assim, o tratamento de exceções geralmente deve ser utilizado ao: Processar situações excepcionais, nas quais um método seja incapaz de terminar sua função por razões que fogem ao seu controle; Processar exceções de componentes que não estão projetados para realizarem tais tarefas diretamente; Em projetos de grande porte para tratar as exceções de maneira uniforme em todo o projeto. A manipulação das exceções em Java é feita da mesma maneira como na linguagem C++, com a utilização das claúsulas try e catch. A cláusula try é utilizada para indicar o trecho de código que será executado e no qual poderá ocorrer uma exceção, no caso, um pedaço de código onde a possibilidade de que possíveis erros ocorram é maior, tal como a abertura de um arquivo ou mesmo a abertura de uma conexão de rede com outro computador, e como já mencionado, a conexão com um banco de dados. Capítulo 6 - Tratamento de exceções e entrada de dadost103 Já na declaração da cláusula catch, define-se o código a ser executado, caso algum erro ou, no caso, uma exceção venha a acontecer durante a execução da instrução principal, presente na cláusula try. O quadro abaixo demonstra a organização das cláusulas try/cath que devem ser utilizadas dentro do escopo de um programa Java. ... try{ ... // trecho do código a ser executado }catch(Classe que trata a excecao){ ... // tratamento da exceção } ... Um detalhe importante a ser considerado está na possibilidade de existirem quantas cláusulas catch forem necessárias para o melhor desempenho da aplicação. Isto se deve ao fato de poderem existir vários tipos de possíveis exceções e obviamente, é interessante que exista um tratamento específico para cada um desses gargalos do que, por exemplo, apenas um tratamento generalista que possa vir a comprometer sua recuperação e continuídade na execução do programa. Além disso, podem ocorrer situações nas quais é necessária a execução de alguma tarefa ou instrução, ocorrendo ou não uma falha no trecho de código principal, ou seja, com o perfeito funcionamento da aplicação ou não. Aqui, entra em cena a cláusula finally, na qual é definido o bloco de código que será executado havendo ou não o lançamento de uma exceção. Sua utilização deve ocorrer após a declaração das cláusulas try e catch. Assim, temos uma nova estrutura para o tratamento das exceções nos Java, como apresentado no trecho a seguir. ... try{ ... // trecho de código a ser executado 104 t JAVA com Orientação a Objetos }catch(Classe que trata a excecao){ ... // tratamento da exceção }catch(Classe que trata a excecao){ ... // tratamento da exceção ... /*código a ser executado com ou sem exceção */ } ... Aqui, é necessário que façamos algumas considerações para entendermos o tratamentos de exceções. Conforme você deve ter observado nos quadros, a cláusula catch obriga-nos a adotar uma classe que será responsável por tratar os possíveis erros que venham a ocorrer. As cláusulas vistas até aqui de nada serviriam se não existissem classes responsáveis pela identificação dos erros lançados pela JVM, além de possuir métodos que possibilitem o entendimento do problema e dos elementos para a sua recuperação. Existem algumas classes que devem ser conhecidas para a sua manipulação, em conjunto com as cláusulas para tratar as exceções mais comuns, sendo apresentadas na Tabela 6. Tabela 6. Principais exceções. Classe Função ArithmeticException Classe utilizada para tratar as exceções em operações aritméticas e lança uma exceção quando estas são impossíveis de ser realizadas. NullPointerException Utilizada para o controle de instâncias e ocorre quando um objeto é instanciado. Capítulo 6 - Tratamento de exceções e entrada de dadost105 NegativeArraySizeException Classe utilizada para o controle de vetores e ocorre quando um valor nulo é atribuído a uma posição do array. ArrayIndexOutOfBoundsException Utilizada para o controle de vetores e ocorre quando se tenta acessar uma posição do array que não existe. IOException Utilizada para tratar as exceções nas operações de entrada e saída de dados. Exception Classe geral para o tratamento de exceções, ou seja, qualquer que seja o erro. Além disso, utilizada pelas demais classes que tratam os erros por meio da herança. Bom pessoal, assim como qualquer classe, as de exceção devem ser importadas para a utilização dentro de um programa. Como feito nos demais capítulos trabalhados até aqui, nada como implementar alguns exemplos para a fixação dos novos e essenciais conceitos da linguagem Java vistos em nosso sexto capítulo. Assim, transcreva o código observando a utilização das cláusulas para o tratamento de exceções, então ao final, execute o programa para visualizar o resultado. // Classe Excecao.java public class Excecao{ public static void main(String[] args){ int a = 20; int b = 0; int c = 0; try{ c = a/b; }catch(ArithmeticException e){ System.out.println(“Problemas na operação”); System.out.println(e); } 106 t JAVA com Orientação a Objetos $ ! &!YZ ?' } } } No exemplo apresentado, foi criado um programa que contém um problema matemático, com a divisão por 0. Assim, obrigamos que a cláusula catch seja executada. Verifique que todas as informações necessárias para o entendimento do problema são disponibilizadas pela instância da classe ArithmeticException. Além disso, a cláusula finally é executada ao final do programa com o lançamento da exceção. Nos últimos anos, várias tecnologias têm contribuído para o avanço da tecnologia Java, tornando-a mais interessante em diversos aspectos, incluindo também o tratamento de exceções. Um novo conceito que vem ganhando espaço é a Programação Orientada a Aspectos. A orientação a aspectos é um paradigma de programação que vem sendo desenvolvido para somar valor à POO (Programação Orientada a Objetos) e assim, tornar todo o processo de programação mais eficiente. Fica a dica de uma leitura complementar sobre a Programação Orientada a Aspectos, que é algo facilmente encontrado na Web. Outro detalhe sobre o tratamento de exceções que deve ser comentado é que a tecnologia Java permite outra forma de realizar o tratamento. Este consiste na utilização da cláusula throws. 6.1.1 Throws Assim como nas cláusulas try/catch, a throws é a responsável por tratar os possíveis erros que venham a acontecer, porém a cláusula throws exige que sejam listadas as classes que tratam as possíveis exceções que podem ser disparadas na assinatura de um método. Para ter uma melhor compreensão, vamos à sintaxe da cláusula throws. ... tipoDeRetorno nomeDoMetodo() throws ClasseDeExcecao1, ClasseDeExcecao2, ...{ // corpo do método } ... Capítulo 6 - Tratamento de exceções e entrada de dadost107 Aqui, cabe dizer que a única diferença entre as duas sintaxes apresentadas é que as cláusulas try/catch definem seu bloco de código que será tratado, caso ocorra alguma exceção. A cláusula throws, por sua vez, é sempre definida para um método de uma classe, logo, a qualquer momento dentro do método ao ocorrer um erro, as classes definidas são executadas para tratá-lo. Vamos a um exemplo para o conceito apresentado. // Classe ExcecaoThrows.java public class ExcecaoThrows{ public static String divisao(int a, int b)throws ArithmeticException{ return “O valor da divisão é “ + a/b; } public static void main(String[] args){ int a = 20; int b = 0; int c = 0; divisao(a,b); } } Bom, conforme mencionado, o método de divisão da classe ExcecaoThrows.java utiliza a palavra reservada throws para invocar a classe ArithmeticException para realizar o tratamento do erro matemático. Com o conhecimento adquirido para o tratamento de erros, abrimos espaço para também apresentarmos uma nova maneira de obter os dados oriundos do teclado, que é por meio da utilização das classes BufferedReader e InputStream. 6.2 Entrada de dados Conforme você já deve ter percebido, o método apresentado, e que você conhece desde o início da leitura de nosso livro, no qual utilizamos objetos da classe Scanner, possui suas limitações,.além de ser instável e inviável para as grandes quantidades de dados digitados. Pois bem, agora iremos verificar uma nova forma de realizar a manipulação dos dados vindos do teclado. 108 t JAVA com Orientação a Objetos Como já foi mencionado no início de nosso livro, a classe System é responsável por controlar todas as funções do sistema. E por meio de System.in, estamos lidando diretamente com as entradas de dados. Até aqui, nenhuma novidade. Porém, existem outras classes que substituem a classe Scanner na manipulação dos dados, no caso, InputStream e BufferedReader. A primeira é reponsável pela leitura do fluxo de entrada. A segunda, pela manutenção no buffer dos dados que foram digitados até que seja pressionada a tecla Enter. Pois bem, para que você possa compreender e verificar o funcionamento dessa modalidade de entrada de dados, vamos a um exemplo. // ManipulacaoDados.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class ManipulacaoDados { public static void main(String args[]){ String dados = new String(); BufferedReader entr = new BufferedReader( new InputStreamReader(System.in)); try { System.out.println(“Entre com a sua frase:”); dados = entr.readLine(); System.out.println(“A frase digitada foi:”+dados); } catch (IOException e){ System.out.println(“Erro ao ler string”); e.printStackTrace(); } } } Você deve estar perguntando: Mas, por que isso não foi apresentado antes? No caso, esta metodologia de entrada de dados não foi demonstrada anterioremente, pois necessitava do tratamento de exceções, uma vez que estamos lidando com alguns elementos critícos, tais como, a manipulação de streams, buffer, e isso nos força a lidar com um tratamento prévio dos riscos que podem ser impostos à nossa aplicação. Capítulo 6 - Tratamento de exceções e entrada de dadost109 Entendendo o que foi feito em nosso exemplo, verifique que inicialmente foi declarada uma variável de dados do tipo String, que receberá os valores oriundos do teclado. Para isso, no entanto, foi necessária a utilização dos objetos da classe BufferedReader e InputStream. A primeira, como mencionado, apenas armazena os dados oriundos do teclado, obtidos por meio da InputStrem, que é responsável por recuperar os dados de entrada do sistema, no caso, o teclado, por meio de System.in. O método readLine( ), do objeto da classe BufferedReader, faz com que o sistema passe a esperar a entrada de dados e armazená-los na memória durante a execução. Encerrando este capítulo, vale resaltar que as duas classes citadas nesta seção também são úteis para a manipulação de arquivos, característica essa que consideraremos em nosso próximo capítulo. Capítulo 7 Manipulação de arquivos de texto Bom pessoal, chegamos ao nosso último capítulo e para finalizarmos os conceitos e as tecnologias introdutórias da tecnologia Java, nada como aprendermos a criar, realizar a entrada e a leitura de dados nos arquivos texto. Os conceitos trabalhados até aqui nos demais capítulos serão essenciais para uma melhor compreensão dos conceitos relacionados ao trabalho com arquivos, principalmente os referentes ao tratamento de exceções, considerados no capítulo anterior. Estudar a manipulação de arquivos é de extrema importância, pois geralmente todas as aplicações trabalham com algum tipo de persistência de dados, uma vez que todos os exemplos realizados e apresentados até aqui somente fazem uso da memória volátil de seu computador. Ou seja, todos os dados são perdidos assim que finalizamos nossa aplicação, como você já deve ter percebido. Diante de tais fatos, a maneira mais básica e simples de realizar a persistência dos dados é por meio da utilização de arquivos. Isso possibilita que as informações das aplicações possam ser recuperadas após sua finalização, abrindo novas possibilidades para seus programas. Obviamente, existem formas mais profissionais de manter os dados, como, por exemplo, a utilização de Sistemas Gerenciadores de Banco de Dados (SGBD), mas, em diversos momentos, a manipulação de arquivos é útil e interessante, mantendo, na maioria das vezes, neutralidade dos dados. Neste primeiro livro, não iremos abordar a manipulação do banco de dados, uma vez que o objetivo aqui é fazer com que este livro seja um referencial para as disciplinas introdutórias de programação. A tecnologia Java fornece diversas classes e com isso, formas para o trabalho com arquivos, no caso, a criação, armazenamento e recuperação de dados. Então, vamos apreciá-las. 7.1 Arquivos A primeira coisa que devemos tratar sobre a manipulação de arquivos 112 t JAVA com Orientação a Objetos com Java é a oferta de um pacote destinado a manipulá-los, denominado java.io. É esse pacote que mantém a maioria das classes que podem ser utilizadas para o trabalho com arquivos. Um detalhe importante que deve ser mencionado é que, em sua maioria, os programadores tratam a leitura e a escrita, respectivamente, como operações de entrada e saída de dados em um arquivo. Tal notação é uma formalização do processo e também é comum em outras linguagens de programação, tais como C e C++. Além disso, a notação apresentada demonstra uma relação do ponto de vista da aplicação, na qual o arquivo a ser lido passa ser uma fonte de entrada de dados a serem processados e depois, saindo da aplicação, eles são escritos e mantidos novamente no arquivo. O java.io é um dos pacotes mais extensos da tecnologia Java, com mais de 50 classes que fornecem suporte para o trabalho com arquivos, além de funcionalidades como, por exemplo, a compactação de arquivos, mas, em sua maioria, tais classes são classificadas nos dois grandes grupos já mencionados, no caso, a entrada e a saída de dados. É importante mencionar que as operações irão depender do tratamento de exceções para a realização da manipulação dos arquivos. Geralmente, para estes casos, é utilizada a classe IOException que também faz parte do pacote java.io e traz funcionalidades que auxiliam na recuperação dos erros que possam vir a acontecer. Outro importante detalhe a ser mencionado sobre o pacote java.io é o fato de que as classes oferecidas permitem também o trabalho com arquivos especiais como binários, de buffer, como vimos no capítulo anterior, ou mesmo stream de vídeo e som. Aqui, não iremos considerar tais classes, mas uma boa gama deste material pode ser encontrada na Web. Diante de tantas possibilidades, é necessário que façamos uma seleção das principais para que concentremos nossos esforços. No caso, estaremos trabalhando com FileReader, Scanner, PrintWriter, FileInputStream, FileOutputStream, RandomAccessFile e IOException. 7.2 Entradas Indo ao que realmente interessa, no caso, a leitura de dados em arquivos, que é considerada uma forma de entrada de dados para uma aplicação, a linguagem Java fornece uma classe que possibilita este processo - a FileReader. Sua utilização não apresenta nada fora do normal das demais classes consideradas até aqui. Assim, devemos realizar a criação de um objeto da FileReader Capítulo 7 - Manipulação de arquivos de textot113 que fornece atributos e métodos para manipulação do arquivo. Entretanto, a classe FileReader necessita da utilização de uma classe já conhecida para realizar o acesso e a manipulação dos dados vindos do arquivo. Essa classe consiste na Scanner, classe com a qual já trabalhamos no início de nosso livro. Pois bem, se você lembra, a Scanner é responsável por auxiliar na manipulação dos dados que vêm do teclado, acessando o atributo de entrada de dados da System.in. Em nosso caso, só mudaremos a fonte dos dados, que antigamente consistia no teclado e agora, é o arquivo. Iremos, agora, utilizar a classe Scanner para realizar operações no objeto da classe FileReader, que faz referência ao local onde se encontra o arquivo de texto. Nada como um bom exemplo para entender o processo. Transcreva o código abaixo e execute-o para verificar seu funcionamento. // LerArquivo.java import java.io.IOException; import java.io.FileReader; import java.util.Scanner; public class LerArquivo{ public static void main(String[] args) { FileReader arq = null; try { arq = new FileReader( "c:\\programas\\Arquivo.txt"); } catch (IOException e) { e.printStackTrace(); } Scanner leitor = new Scanner(arq); while(leitor.hasNextLine()){ String linha = leitor.nextLine(); System.out.println(linha); } } } 114 t JAVA com Orientação a Objetos É bom mencionar que, para nosso programa funcionar, o arquivo ao qual fizemos a referência com o objeto da classe FileReader deve estar no mesmo local onde a classe se encontra. Uma forma de resolver este problema é passando o caminho de referência completo para o local no qual o arquivo se encontra, no caso, “C:\programas\Arquivo.txt”. É importante citar que foi necessária a realização do tratamento de exceção que possa vir a acontecer, como, por exemplo, o fato do arquivo não ser localizado. Isso obviamente foi realizado com a utilização das cláusulas try/catch, além da classe IOException. Ainda em nosso exemplo, um objeto da classe Scanner foi instanciado, sendo passada, como parâmetro, a instância da classe FileReader, denominada arq, que define o caminho do arquivo, como já analisado. Após, é utilizada a estrutura de repetição while para a leitura linha a linha do arquivo e uma posterior impressão do conteúdo. Conforme vimos, a manipulação de arquivos no Java trata-se de uma rotina simples, sem muitos segredos, isso graças ao suporte que a tecnologia fornece. Entretanto, vale ressaltar que tais funcionalidades dependem da correta utilização das classes do pacote java.oi. Apresentado o conceito de entrada de dados, ou seja, a leitura de um arquivo, vamos ao processo de criação e escrita ou, conforme já é de seu conhecimento, à saída de dados utilizando a classe PrintWriter do pacote java.io. 7.3 Saídas Após verificarmos como funciona o processo de entrada de dados, iremos trabalhar com a criação e a gravação de dados em um arquivo no formato de texto, utilizando para isso, a classe PrintWriter. Tal classe fornece atributos e métodos para a manipulação de arquivos, entretanto, aqui também é necessária a utilização da classe IOException para o tratamento das exceções, assim como é feito para a leitura de dados. Mas aqui, trataremos os erros que venham a ocorrer no processo de criação e escrita dos dados. Vamos colocar a mão na massa e escrever mais um exemplo. Neste caso, um no qual iremos criar um arquivo e escrever uma mensagem dentro deste. // EscreverArquivo.java import java.io.IOException; import java.io.PrintWriter; Capítulo 7 - Manipulação de arquivos de textot115 public class EscreverArquivo{ public static void main(String[] args) { PrintWriter arq = null; try { arq=new PrintWriter("dados.txt"); arq.println("Mensagem escrita"); arq.close(); } catch (IOException e) { e.printStackTrace(); } } } Vários detalhes devem ser mencionados sobre o processo de criação e escrita em arquivos. Inicialmente, um importante detalhe a ser descrito, e que você deve levar em consideração sobre a criação e a escrita, é que se seu arquivo já existir, ele será recriado e com isso, todos os dados existentes anteriormente serão perdidos. Outro detalhe é que para que todos os dados sejam persistidos em um arquivo, é necessário que sempre ao final do processo de escrita, ou seja, ao final da utilização da instância da classe PrintWriter, seja feita a invocação do método close( ). Tal processo pode ser contemplado no exemplo anterior. Finalmente, no programa anterior, note que foi utilizado para a escrita do conteúdo, o método println( ), também da classe PrintWriter. Então, vamos tornar nosso exemplo mais interessante. Para isso, iremos, com a utilização da classe Scanner, capturar os dados do teclado e persisti-los em um arquivo. Este será nomeado com o nome definido pelo usuário. Então, transcreva o código e execute-o para verificar seu adequado funcionamento. // CriandoArquivo.java import java.io.IOException; import java.io.PrintWriter; import java.util.Scanner; public class CriandoArquivo{ public static void main(String[] args) { 116 t JAVA com Orientação a Objetos Scanner entrada=new Scanner(System.in); PrintWriter arq = null; System.out.println("Nome do arquivo"); String nome = entrada.nextLine(); System.out.println("Entre com os dados"); String dados = entrada.nextLine(); try { arq = new PrintWriter(nome); } catch (IOException e) { e.printStackTrace(); } arq.println(dados); arq.close(); } } Note que no exemplo, por meio do operador new e o construtor da classe PrintWriter, foi passado o nome do arquivo a ser criado. Entretanto, esse arquivo só será efetivamente criado após a execução do método close( ), ou seja, ao finalizar o programa. Com isso, verificamos o processo de criação e escrita em arquivos, sendo algo bem mecânico. 7.4 Streams Outra possibilidade de trabalhar com a manipulação de entrada e saída de dados consiste na utilização das classes FileInputStream e FileOutputStream. Essas classes são extremamente úteis, pois nem só de arquivos com texto natural vivem nossos programas. Estas são úteis para as manipulações de todos os tipos de arquivos. Assim como nas demais classes vistas até aqui, a FileInputStream e a FileOutputStream são fornecidas pelo pacote java.io. Elas são utilizadas para manipular fluxos de bytes, ou seja, possibilita uma manipulação em um nível mais baixo. Estes dados, na maioria das vezes, se alterados, comprometem sua estrutura e possível utilização. Ou seja, para uma manipulação mais segura e da qual dependemos de uma maior segurança, as classes são recomendadas. Como os próprios nomes sugerem, a FileInputStream é utilizada para a entrada de dados e a FileOutputStream, para a saída. Um aspecto Capítulo 7 - Manipulação de arquivos de textot117 importante a ser colocado aqui é o fato de que assim como nas classes FileReader e PrintWriter, já vistas, para as classes que auxiliam na manipulação de streams também é necessária a utilização do tratamento de exceção. Para ter uma melhor compreensão do que foi considerado, veja o exemplo a seguir e execute-o. // Classe ExemploStream.java import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class ExemploStream{ public static void main(String[] args) { FileOutputStream saida = null; FileInputStream entrada = null; try { saida = new FileOutputStream(“dados.txt”); saida.write(1010); entrada= new FileInputStream(“dados.txt”); System.out.print(entrada.read()); } catch (IOException e) { e.printStackTrace(); } } } No exemplo anterior, foi criado um arquivo e escrita uma linha de stream com a utilização do método write( ) da classe FileOutputStream. Logo após, é realizada a leitura dos dados escritos e a impressão destes por meio do método read( ) da classe FileInputStream. Repare que o processo de criação dos objetos para a manipulação dos arquivos de stream é semelhante aos realizados até aqui com outras classes pertencentes ao pacote java.io. 118 t JAVA com Orientação a Objetos 7.5 Acessos aleatórios em arquivos Em todos os exemplos apresentados até aqui, toda a leitura é realizada de maneira sequencial, ou seja, do início do arquivo até seu fim, sempre nessa ordem. No entanto, em diversas situações, assim como no caso dos vetores, isso pode ser caro computacionalmente. Imagine que você esteja buscando uma informação em um arquivo extenso e essa informação esteja apenas no final do arquivo. Pois bem, para que você chegue a essa informação, deve percorrer todo o arquivo. Isso muitas vezes complica todo o processo. No caso, é muito mais econômico computacionalmente realizar a leitura de uma posição especifica no arquivo ou mesmo a escrita em determinado ponto. Para isso, a tecnologia Java fornece a classe RandomAccessFile, que possibilita o trabalho de acesso aleatório a posições dentro dos arquivos. A RandomAccessFile é mais uma classe que também faz parte do pacote java.io. Bom, neste caso, a vantagem de manipular seus arquivos com as instâncias dessa classe é nítida, indo pontualmente no local no qual se deseja escrever ou ler. Para tanto, são utilizados métodos para as operações nos arquivos, tais como seek( ), length( ), read( ) e write( ), entre os principais. Aqui, oriento que o exemplo seja transcrito e executado. // Aleatorio.java import java.io.IOException; import java.io.RandomAccessFile; public class Aleatorio{ public static void main(String[] args) { try { ~ ~& “dados.txt”,”rw”); &' $ ! &&'' } catch (IOException e) { e.printStackTrace(); } } } Capítulo 7 - Manipulação de arquivos de textot119 Alguns aspectos do código anterior devem ter chamado a sua atenção. No caso, a classe RandomAccessFile trabalha com o conceito de definição do tipo e acesso, sendo que tal definição é realizada no momento da instanciação do objeto com a utilização de seu construtor. Obviamente, são utilizados parâmetros no momento da abertura do arquivo para a definição das permissões no arquivo. Os tipos de acesso que podem ser trabalhados consistem, respectivamente, nos atributos ‘r’ e ‘rw’, que indicam que o arquivo deve ser aberto somente para a leitura e quando podem ser realizadas operações tanto de leitura quanto de escrita, respectivamente. Para o posicionamento do cursor no arquivo na posição na qual se deseja realizar a escrita ou a leitura, é necessária a utilização do método seek( ). Para a leitura, continuamos a trabalhar com o método read( ), conforme demonstrado no exemplo. Da mesma forma, podemos realizar a escrita, sendo que para isso, utilizamos o método write( ) da própria classe RandomAccessFile. Aqui, entretanto, é importante mencionar que existem diversas vertentes do método write permitindo que sejam trabalhados vários tipos de dados disponibilizados pela tecnologia Java. Existem métodos, tais como writeInt( ), writeBoolean( ) e writeChars( ), que possibilitam o tratamento de cada tipo de dado. Isso auxilia o desenvolvedor, pois evita que seja perdido tempo realizando sua conversão ou mesmo manipulação para persistir os dados. Para visualizar o resultado destas operações, altere o exemplo anterior conforme é demonstrado a seguir. // Classe Aleatorio.java import java.io.IOException; import java.io.RandomAccessFile; public class Aleatorio{ public static void main(String[] args) { try { ~ ~& “dados.txt”,”rw”); &' $ ! &&'' &` ' &' 120 t JAVA com Orientação a Objetos } catch (IOException e) { e.printStackTrace(); } } } No caso em específico, a estrutura continua a mesma e deve ser realizado o tratamento de exceções, já que sua manipulação pode acarretar problemas para a aplicação. Caro amigo, assim finalizamos nossa leitura. Espero que este manuscrito tenha sido de proveito para que você entenda os detalhes e os conceitos básicos da programação baseada na tecnologia Java, principalmente os vinculados ao paradigma da programação orientada a objetos. Obviamente que a tecnologia não se restringe somente aos tópicos mencionados até aqui. Poderíamos citar a criação de aplicações desktop com a utilização da API Swing, além do acesso ao banco de dados com a JDBC (Java Database Connection) ou ainda a criação de programas para dispositivos móveis ou Web, tudo baseado no Java. Mas, obviamente que minha intenção até aqui sempre foi garantir que fossem apresentados os elementos que possam subsidiar o conhecimento dessas novas ferramentas que estão à sua disposição. Sinceramente, desejo-lhe sucesso nos seus desafios e espero revê-lo em breve. Referências Bibliográficas BOENTE, Alfredo. Aprendendo a Programar em Java 2: Orientado a Objetos. Rio de Janeiro: Brasport, 2003. ANSELMO, Fernando. Aplicando Lógica Orientada a Objetos em Java. 2. ed. Florianópolis: Visual Books, 2005. CARDOSO, Caíque. Orientação a Objetos na Prática: Aprendendo Orientação a Objetos com Java. Rio de Janeiro: Ciência Moderna, 2006. DEITEL, Harvey M. Java: Como Programar. 6ª ed. São Paulo: Pearson Prentice Hall, 2005. SANTOS, Rafael. Introdução à Programação Orientada a Objetos Usando Java. Rio de Janeiro: Campus, 2003. PUGA, Sandra; RISSETTI, Gerson. Lógica de Programação e Estruturas de Dados: com Aplicações em Java. São Paulo: Pearson Prentice Hall, 2004. Horstmann, Cay; Cornell, Gary. Core Java, Volume 1 – Fundamentos. 8 Edição. São Paulo: Pearson, 2010. Block, Joshua. Java Efetivo - 2ª Edição. Rio de Janeiro: Alta Books, 2008. Furgeri, Sergio. Java 7 - Ensino Didático. São Paulo: Editora Érica, 2010. Sierra, Kathy; Bates, Bert. Use a Cabeça Java. Rio de Janeiro: Alta Books, 2005. Apêndice I Instalação do Sdk e Configuração das Variáveis de Ambiente (Windows Xp) Para o download do JDK, siga os seguintes passos: 1. Acesse por meio de um navegador o endereço: http://java.oracle. com 2. Após acessar o endereço, clique no link Java SE em “Downloads” ou em “Top downloads”. 3. Logo em seguida, clique no botão “Java Platform” para selecionar a versão “Standard Edition” da tecnologia. 4. Após isso, aceite o termo e selecione o tipo de Sistema Operacional no qual a tecnologia será executada. No caso do Windows, selecione conforme apresentado a seguir. 124 t JAVA com Orientação a Objetos 5. Caso tudo tenha ocorrido conforme o esperado, será aberta a janela para o download do arquivo. 6. Depois, basta seguir as orientações do software no momento de sua instalação. Para as variáveis de ambiente, siga os seguintes passos: 1. Selecione Iniciar > Painel de Controle > Sistema. Apêndice It125 2. Selecione Avançado > Variáveis de Ambiente. 3. Clique em Nova. 126 t JAVA com Orientação a Objetos 4. Em “Nome da Variável”, digite JAVA_HOME. 5. Em “Valor da Variável”, digite o caminho onde foi instalado o Java em seu computador. Copie o caminho literalmente como é exibido, por exemplo, no Explorer. 6. Clique em OK. 7. Procure a variável PATH, selecione-a e escolha “Editar”. Apêndice It127 8. Em “Valor da Variável”, acrescente ao valor já existente em seu final: “;%JAVA_HOME%\bin”. 9. Clique em OK. 10. Selecione Avançado > Variáveis de Ambiente > Nova, assim como foi feito para a criação da variável JAVA_HOME. Se estiver em dúvida, observe o processo feito anteriormente. 11. Em “Nome da Variável”, digite CLASSPATH. 12. Em “Valor da Variável”, digite “.;%JAVA_HOME%\lib\tools.jar”. 13. Clique em OK. 14. Clique novamente em OK. Para que você tenha certeza dos efeitos das mudanças, reinicie o sistema. Apêndice II JAVADOC 1. Inicialmente, é necessário realizar o comentário de documentação em sua classe definindo e seguindo os elementos definidos pela tecnologia Java. Existem diversas tags especiais definidas pela tecnologia, que auxiliam na definição de informações importantes e na formatação padrão, tais como, @author, @see e @return. Veja o exemplo: /** * @see java.lang.Object * @author Alex Coelho */ public class Carro{ public String cor; public String marca; public String modelo; /** * Construtor Carro * @param Carro * @throws Sem Exceções */ public Carro(Carro novo){ this.cor = novo.cor; this.marca = novo.marca; this.modelo = novo.modelo; } /** * Método andar que imprime * o valor String na tela * @see java.lang.String 130 t JAVA com Orientação a Objetos */ protected void andar(){ ligar(); System.out.println(“Carro andando”); } /** * Método parar que imprime * o valor String na tela * @see java.lang.String */ protected void parar(){ System.out.println(“Carro parado”); } /** * Método ligar que imprime * o valor String na tela * @see java.lang.String */ private void ligar(){ System.out.println(“Carro ligado”); } } 2. Feitos os comentários necessários, digite o comando javadoc em seu prompt de comando para sua classe, como segue o exemplo: javadoc NomeDaClasse.java Veja o funcionamento: Apêndice IIt131 Se tudo correu conforme o esperado, você deve ter acesso a uma página html na mesma pasta onde se encontra sua classe, como é demonstrado a seguir. É importante mencionar que a maioria das IDEs disponíveis no mercado possui algum tipo de suporte para agilizar e garantir a documentação com Javadoc. Para obter maiores informações, acesse: http://www.oracle.com/technetwork/java/javase/documentation/ index.html. Java na Web $XWRU$QW¶QLR1HWR S£JLQDV lHGL©¥R )RUPDWR[ ,6%1 A complexidade e o crescimento da Internet fez com que surgissem vários modelos e propostas para a comunicação entre aplicações e que esses fossem apresentados como opções de computação distribuída. Este livro aborda as principais tecnologias da plataforma Java 2 Enterprise ! " ! !!#$%&'''%( vêm se tornando uma das mais bem sucedidas para desenvolvimento em ambiente Web. ) ' '* +, +! / +0/ !1 314 !!5*67 8'(9 !:';*<'! =>$<sentados. À venda nas melhores livrarias. Impressão e acabamento <=>?@BFBGFHJK=B&HLM@HB0KFN=MB/JFB Tel: (21) 2201-6662