Baixe Linguagem de programação Java e outras Notas de estudo em PDF para Informática, somente na Docsity! TUTORIAL: “A LINGUAGEM DE PROGRAMAÇÃO JAVAÔ” ORIENTAÇÃO A OBJETOS Instituto de Computação maio, 04 André Augusto Cesta. aacesta@dcc.unicamp.br Orientadora: Profa Dra Cecília Mary Fischer Rubira 2 INDÍCE 1. CLASSES E OBJETOS .......................................................................................................................................................10 1.1. ESPECIFICANDO UMA CLASSE.....................................................................................................................................10 1.2. OBJETOS EM JAVA..........................................................................................................................................................12 1.2.1. PROGRAMA HELLO INTERNET! ..............................................................................................................................13 1.2.2. ATRIBUTOS.................................................................................................................................................................15 1.2.3. ATRIBUTOS E MÉTODOS..........................................................................................................................................21 1.2.4. MÉTODOS QUE RETORNAM VALORES...................................................................................................................27 1.2.5. COMPARAÇÃO COM UM PROGRAMA EM PASCAL ..............................................................................................28 1.2.6. CONSTRUTORES ........................................................................................................................................................33 1.2.7. CONSTRUTORES E AGREGAÇÃO ............................................................................................................................36 1.2.8. DESTRUTORES OU “finalizers” ................................................................................................................................38 1.3. PONTEIROS, “POINTERS”, REFERÊNCIAS E OBJETOS .............................................................................................39 1.3.1. PASSAGEM POR REFERÊNCIA ................................................................................................................................39 1.3.2. VETORES E MATRIZES ..............................................................................................................................................40 1.3.3. COPIA , COMPARAÇÃO E DETERMINAÇÃO DA CLASSE EM OBJETOS .............................................................41 1.4. OBTENDO VALORES DO USUÁRIO..............................................................................................................................45 1.4.1. LENDO DO TECLADO ...............................................................................................................................................45 1.4.1.1. LEITURA DE STRINGS USANDO UM VETOR DE BYTES................................................................................................45 1.4.1.2. UMA VISÃO GERAL SOBRE PACKAGES E STREAMS.....................................................................................................46 1.4.2. ARGUMENTOS DE LINHA DE COMANDO ..............................................................................................................49 1.5. ENCAPSULAMENTO COM PRIVATE, PUBLIC, “PACKAGE” E PROTECTED..........................................................50 1.5.1. ENCAPSULANDO MÉTODOS E ATRIBUTOS...........................................................................................................51 1.5.1.1. ATRIBUTOS PRIVATE, MÉTODOS PUBLIC .......................................................................................................................52 1.5.1.2. UM ATRIBUTO É PUBLIC ....................................................................................................................................................54 1.5.2. ENCAPSULAMENTO E “PACKAGES”......................................................................................................................55 1.5.2.1. ENCAPSULAMENTO DE ATRIBUTOS E MÉTODOS COM PACKAGES .........................................................................55 1.5.2.2. ENCAPSULAMENTO DE CLASSES COM PACKAGES......................................................................................................59 1.6. TIPO ABSTRATO DE DADOS..........................................................................................................................................64 1.6.1. TAD FRAÇÃO..............................................................................................................................................................65 1.6.2. STRINGS, UM MODELO DE CLASSE........................................................................................................................71 1.6.3. TAD E ALOCAÇÃO DINÂMICA. ................................................................................................................................74 2. HERANÇA............................................................................................................................................................................80 2.1. HIERARQUIAS DE TIPOS................................................................................................................................................80 2.1.1. UMA HIERARQUIA SIMPLES. ...................................................................................................................................80 2.1.2. PROTECTED ...............................................................................................................................................................84 2.1.3. REDEFINIÇÃO DE MÉTODOS HERDADOS ............................................................................................................88 2.2. INTERFACES, UMA ALTERNATIVA PARA HERANÇA MÚLTIPLA .........................................................................90 3. POLIMORFISMO, CLASSES ABSTRATAS...................................................................................................................93 3.1. REDEFINIÇÃO DE MÉTODOS PARA UMA CLASSE HERDEIRA...............................................................................93 3.2. SOBRECARGA ( MÉTODOS E OPERADORES).............................................................................................................93 3.2.1. SOBRECARGA DE MÉTODOS, “COPY CONSTRUCTOR”......................................................................................93 3.2.2. SOBRECARGA DE OPERADOR.................................................................................................................................96 3.3. CLASSES ABSTRATAS E CONCRETAS.........................................................................................................................96 3.3.1. CLASSE ABSTRATA ITERADOR ................................................................................................................................99 3.3.2. ACOPLAMENTO DINÂMICO DE MENSAGENS.....................................................................................................103 3.3.2.1. UM EXEMPLO ESCLARECEDOR.......................................................................................................................................104 3.3.2.2. O QUE ACONTECE COM O QUE FOI ACRESCENTADO ................................................................................................105 3.3.3. LISTA HETEROGÊNEA DE FORMAS (geométricas)...............................................................................................106 4. CONCEITOS AVANÇADOS............................................................................................................................................109 4.1. ATRIBUTOS STATIC......................................................................................................................................................109 4.2. MÉTODOS STATIC........................................................................................................................................................110 4.3. TRATAMENTO DE EXCEÇÕES....................................................................................................................................111 4.3.1. TRATANDO AS EXCEÇÕES GERADAS POR TERCEIROS ....................................................................................112 4.3.2. GERANDO SUAS PRÓPRIAS EXCEÇÕES...............................................................................................................114 5 Prefácio: Este texto faz parte de um estudo comparativo de linguagens de programação orientadas a objetos. O conteúdo deste estudo também está disponível na “World Wide Web”, área multímidia da internet, sob o endereço http://www.dcc.unicamp.br/~aacesta . Neste endereço, você pode complementar seu aprendizado, rodando exercícios iterativos, acessando “links” para outros hipertextos sobre linguagens de programação, vendo exemplos de programas e interagindo com aplicações para a internet. A diferença entre este estudo e outros textos que você possa encontrar sobre o mesmo assunto é o caráter prático. Exemplos completos, dicas de programação, explicações sobre detalhes normalmente ignorados em livros, tornarão seu aprendizado mais fácil, principalmente na segunda parte onde tratamos da construção de aplicações para a internet. No inicio, os exemplos podem ser considerados fáceis, mas eles vão se complicando cada vez mais de modo que é importante que o leitor acompanhe o texto fazendo os exercícios. Forneceremos uma série de idéias de programas simples para que você possa testar seu conhecimento. Estes programas simples podem ser melhorados através do uso de interfaces gráficas, assunto que não é coberto neste tutorial. QUEM DEVERIA LER ESTE TUTORIAL Qualquer leitor que tenha experiência com pelo menos uma linguagem de programação. DIAGRAMAÇÃO DESTE TEXTO Apesar de Java ser uma linguagem que serve para vários propósitos, o seu sucesso atual (época do seu lançamento) se deve a possibilidade de elaboração de aplicações para a internet. Dada a importância deste aspecto da linguagem, este texto está organizado de maneira semelhante as páginas encontradas na WWW, frequentemente você encontrará diagramas como o seguinte: TUTORIAL http://www.dcc.unicamp.br/~aacesta “Estudo comparativo de linguagens de programação orientadas a objetos”. Nesta página você encontrará tutoriais sobre diversas linguagens orientadas a objetos, dentre elas: C++, Modula-3, Java. Quanto a Java, você terá a oportunidade de ver código de programas e testá-los, além de poder adquirir a versão mais nova deste texto.Encontrará também links para sites sobre orientação a objetos. Este diagrama representa um hipertexto que pode ser acessado de modo a complementar seu estudo. A parte escrita em letra maior é o endereço, o texto em itálico faz um resumo do conteúdo desta página. Usando estes “links” ou diagramas você encontrará uma maneira ordenada de aprender sem se perder no mar de informações da internet. URL: “Uniform Resource Locator”, é o endereço de um computador na internet O diagrama acima aparecerá toda vez que introduzirmos uma palavra nova. Caso você encontre alguma palavra desconhecida, basta usar o índice remissivo para obter sua definição. É importante lembrar que os hipertextos citados neste tutorial, não são de nossa responsabilidade. Como eles estão sujeitos a mudanças, contamos com a sua ajuda para efetuarmos atualizações, conte você também com a nossa ajuda na internet. Os programas exemplo deste texto são apresentados em caixas retangulares, e todo código é escrito em fonte diferente da usada neste texto comum. O trechos de código que aparecem 6 desmembrados de seus arquivos, tem fundo cinza claro. Todos os arquivos presentes dentro dos retângulos, são arquivos “text-only”, qualquer formatação (negrito) tem apenas função didática. Os resultados dos programas são indicados pelo diagrama ao lado: DIVISÃO DO TUTORIAL Este tutorial contém uma sequência de tópicos que permite apresentar a linguagem sob a ótica da teoria de orientação a objetos. A apresentação do modelo de objetos da linguagem e conceitos relacionados tais como polimorfismo, tratamento de exceções está em primeiro plano. Ao longo dessa apresentação, em segundo plano, você aprenderá os aspectos básicos da linguagem tais como “loops”, desvios condicionais, etc. Estes tópicos são frequentemente retomados, cada vez de maneira mais aprofundada. Quando terminamos o assunto métodos, você já está pronto para saber o que são contrutores, e é exatamente isto que ensinamos. Porém o assunto contrutores não é esgotado, você ainda vai aprender a usar construtores em conjunto com agregação e depois em conjunto com herança. A maioria dos leitores fica ansiosa para aprender como criar aplicações para a internet, mas depois de satisfeita esta ansiedade voltam para o ponto onde aprendem como programar na linguagem e não apenas experimentar com a criação de botões, caixas de diálogo, imagens, etc. Se esse é o seu caso, é recomendável um “tour” pela WWW antes de começar a programar, um bom “site” para começar a pesquisar com um “browser” compatível com Java (Netscape Navigator 2.0Ô ou superior) é : APPLETS http://www.Javasoft.com/applets “Links” para vários applets, divididos por categorias: games, sound, busines, animation... Divirta- se... APPLETS: São pequenos programas escritos em Java que podem ser embebidos em documentos hipetextos. São exemplos de applets: animações, imagens, botões, etc. Applets podem suportar efeitos de multimidia como sons, iterações com o usuário (mouse, teclado), imagens, animações, gráficos, etc. Ao longo do texto,você perceberá que Java é excelente para desenvolver aplicações comerciais e para ser usada em universidades. Java pode ser vista como uma fusão de várias técnologias que vêm sendo desenvolvidas na área de computação, de modo que estudantes dessa linguagem tem a oportunidade de tomar contato com vários tópicos recentes: programação concorrente, sitemas distribuídos, orientação a objetos, protocolos da internet, e uma série de outros assuntos fáceis de praticar nessa linguagem. É claro que os resultados dependerão de seu esforço, portanto depois de ler esta introdução, descanse um pouco, tome um cafezinho1. Pois durante o restante do texto esperaremos que você se envolva com a linguagem, reuse programas encontrados na W.W.W, se comunique com colegas programadores, participe de listas de discussões, newsgroups (comp.lang.Java & alt.www.hotJava), e o mais importante: PROGRAME, PROGRAME, RE-PROGRAME! ADQUIRINDO O SOFTWARE NECESSÁRIO, PLATAFORMAS SUPORTADAS 1Um dos símbolos da linguagem é uma xícara de café que aparece em animações com sua fumaça quente tremulando. 7 Um ambiente de programação Java é normalmente composto de um kit de desenvolvimento de aplicações Java e um “browser compatível com esta linguagem (recomendável). Se você não tem acesso a esse ambiente de programação, tente estes endereços: “DOWNLOAD” JAVA http://Java.sun.com . Raiz do hipertexto montado pelos criadores da linguagem. Sob este endereço você pode obter o compilador e outras ferramentas de desenvolvimento de aplicações Java para a sua plataforma de programação. Fique atento! Outros desenvolvedores estão criando ambientes de programação Java. “DOWNLOAD” “BROWSERS” http://www.netscape.com Raiz do hipertexto montado pelos criadores do Netscape NavigatorÔ. Sob este endereço você pode obter o browser “Java compatible” da “Netscape Comunications INC’.. Outros desenvolvedores estão lançando “browsers” compatíveis com Java. “DOWNLOAD” “BROWSERS” http://www.microsoft.com A microsoftÔ licenciou a tecnologia Java e a incorporou em seu novo browser: Internet Explorer versão 3.0 ou superior. BROWSERS: São uma categoria de programas que permitem você visualizar um documento criado em um certo padrão, no caso html (hipertext markup language). Atualmente os browsers tem se tornado complexos devido a quantidade de padrões existentes (ex. imagens .gif .jpg, etc). A linguagem Java pode contribuir para minimizar esta complexidade. CARACTERÍSTICAS DA LINGUAGEM Parecida com C, C++: Java tem a aparência de C ou de C++, embora a filosofia da linguagem seja diferente. Por este motivo estaremos frequentemente fazendo comparações alguma destas linguagens. O leitor que programa em qualquer uma delas, ou em uma linguagem orientada a objetos, se sentirá mais a vontade e se tornará um bom programador Java em menos tempo. Java também possui características herdadas de muitas outras linguagens de programação: Objective-C, Smalltalk, Eiffel, Modula-3, etc. Muitas das características desta linguagem não são totalmente novas. Java é uma feliz união de tecnologias testadas por vários centros de pesquisa e desenvolvimento de software. Compilada: Um programa em Java é compilado para o chamado “byte-code”, que é próximo as instruções de máquina, mas não de uma máquina real. O “byte-code” é um código de uma máquina virtual idealizada pelos criadores da linguagem. Por isso Java pode ser mais rápida do que se fosse simplesmente interpretada. Portável: Java foi criada para ser portável. O “byte-code” gerado pelo compilador para a sua aplicação específica pode ser transportado entre plataformas distintas que suportam Java (Solaris 2.3Ò, Windows-NTÒ, Windows-95Ò, Mac/Os etc). Não é necessário recompilar um programa para que ele rode numa máquina e sistema diferente, ao contrário do que acontece por exemplo com programas escritos em C e outras linguagens. 10 PROGRAMAÇÃO ORIENTADA A OBJETOS NOTA AOS PROGRAMADORES “C” A leitura deste hipertexto é fortemente recomendada para os programadores que tem “C” ou ”C++” como sua principal linguagem de programação, mesmo antes de iniciar a leitura deste tutorial: “DOWNLOAD” “BROWSERS” http://www.dcc.unicamp.br/~aacesta/java/group.html (Clique em brewing Java tutorial) “Brewing Java: A Tutorial”, Um tutorial de Java que segue o estilo do livro inicial sobre “C”, escrito por Kernighan & Ritchie [[2]], é a referência básica para este texto. Cobre principalmente sintaxe de loops e outros assuntos fáceis não abordados aqui. Iniciaremos com um pouquinho de teoria sobre orientação a objetos. Se você quiser algum hipertexto para aprofundamento ou para ler em paralelo com esta introdução sobre classes e objetos e use: “TUTORIAIS” http://www.dcc.unicamp.br/~aacesta/java/group.html “Esta é a homepage do grupo de estudos em Java”. Você vai encontrar links atualizados para diversos lugares na internet onde se estuda Java. 1. CLASSES E OBJETOS Uma classe é um tipo definido pelo usuário que contém o molde, a especificação para os objetos, algo mais ou menos como o tipo inteiro contém o molde para as variáveis declaradas como inteiros. A classe envolve, associa, funções e dados, controlando o acesso a estes, definí-la implica em especificar os seus atributos (dados) e seus métodos (funções). Um programa que utiliza uma interface controladora de um motor elétrico provavelmente definiria a classe motor. Os atributos desta classe seriam: temperatura, velocidade, tensão aplicada. Estes provavelmente seriam representados na classe por tipos como int ou float. Os métodos desta classe seriam funções para alterar a velocidade, ler a temperatura, etc. Um programa editor de textos definiria a classe parágrafo que teria como um de seus atributos uma String ou um vetor de Strings, e como métodos, funções que operam sobre estas strings. Quando um novo parágrafo é digitado no texto, o editor cria a partir da classe Parágrafo um objeto contendo as informações particulares do novo texto. Isto se chama instanciação ou criação do objeto. 1.1. ESPECIFICANDO UMA CLASSE Suponha um programa que controla um motor elétrico através de uma saída serial. A velocidade do motor é proporcional a tensão aplicada e esta proporcional aos bits que vão para saída serial e passam por um conversor digital analógico. Vamos abstrair todos estes detalhes por enquanto e modelar somente a interface do motor como uma classe, a pergunta é que métodos e que atributos deve ter nossa classe, que argumentos e valores de retorno devem ter os métodos? Representação da velocidade: 11 A velocidade do motor será representada por um atributo inteiro (int). Usaremos a faixa de bits que precisarmos, caso o valor de bits necessário não possa ser fornecido pelo tipo , usaremos então o tipo long, isto depende do conversor digital analógico utilizado. Representação da saída serial: O motor precisa conhecer a sua saída serial, a sua ligação com o “motor do mundo real”. Suponha uma representação em hexadecimal do atributo endereço de porta serial, um possível nome para o atributo: enderecomotor. Não se preocupe em saber como usar a representação hexadecimal. Alteração do valor da velocidade: Internamente o usuário da classe motor pode desejar alterar a velocidade, cria-se então o método: public void altera_velocidade(int novav);. O código anterior corresponde ao cabeçalho do método ele é definido junto com a classe motor, associado a ela. O valor de retorno da função que “implementa” o método é void, poderia ser criado um valor de retorno (boolean) que indicasse se o valor de velocidade era permitido e foi alterado ou não era permitido e portanto não foi alterado. O ato de invocar um método também é chamado de passar uma mensagem para o objeto que está executando este método. Não faz sentido usar, chamar, este método separado de uma variável do tipo motor, mas então porque na lista de argumentos da função não se encontra um motor? Este pensamento reflete a maneira de associar dados e código (funções) das linguagens procedurais. Em linguagens orientadas a objetos o código e os dados são ligados de forma diferente, a própria declaração de um tipo definido pelo usuário já engloba as declarações das funções inerentes a este tipo, isto será explicado em 1.2. O objeto ao qual é aplicado o método é passado de outra forma. Note que não fornecemos o código do método, isto não é importante, por hora a preocupação é com a interface definida pela classe: seus cabeçalhos de métodos e atributos. Apenas pense que sua interface deve ser flexível de modo a não apresentar entraves para a criação do código que seria feita numa outra etapa. Nesta etapa teríamos que imaginar que o valor numérico da velocidade deve ir para o conversor onde irá se transformar numa diferença de potencial a ser aplicada nos terminais do motor, etc. Um diagrama simplificado da classe motor com os atributos e métodos: Este e outros diagramas deste texto foram elaborados com uma ferramenta case para “object oriented modeling and design” segundo a metodologia descrita em [[1]] Exercícios: 1- Lembre-se de algum programa em que você trabalhou, cite que tipos de classes seriam criadas se esse programa fosse escrito em Java, que atributos e que métodos estariam associados a esses objetos? 12 Exemplo: “Eu trabalhei em um programa de contas a pagar e contas a receber. Se esse programa fosse escrito em Java eu definiria a classe conta_bancaria. Os atributos seriam: saldo, taxa_de_juros, limite_de_saque, etc. Minha opção seria por representá-los como variáveis do tipo double (não se preocupe em usar os tipos da linguagem inda)”. “Dentre os métodos desta classe estariam funções para efetuar saques, depósitos e computar juros.” 1.2. OBJETOS EM JAVA Objetos são instâncias de uma classe. Quando um objeto é criado ele precisa ser inicializado, ou seja para uma única classe de nome EstudanteDeGraduacao podemos ter vários objetos durante a execução de um programa. Estudante de graduação Andre; Identificação 940718; Curso Computacao | Estudante de graduação Luiza , Identificação 893249, Curso Medicina... A classe representa somente o molde para a criação dos objetos, estes sim contém informação, veja tópico CLASSES E OBJETOS. O atributo Identificação tem valor 940718 para a instância (objeto) André da classe Estudantes de Graduação. INSTANCIAS: Um objeto existente durante um momento da execução de um programa é uma instancia de uma classe. Uma classe e suas instancias: Cada estudante (ou instancia) poderia ser modelado, desenhado como: Objetos podem conter objetos, ou seja os atributos de um objeto podem ser objetos, da mesma classe ou não. Objetos podem ser passados pela rede, armazenados em meio físico. Objetos possuem um estado e um comportamento. Métodos podem receber objetos como argumentos, podem declarar objetos como variáveis locais, podem chamar outros métodos. Você pode chamar um método (mandar uma mensagem) para objetos em outras máquinas através de sua rede. Um objeto pode ser visto como um RECORD, só que com uma tabela de funções que podem ser chamadas para ele. Na verdade esta definição não é muito teórica, mas é um bom começo para os programadores que estão acostumados com linguagens procedurais. Na verdade podemos fazer com objetos muito mais do que fazemos com records e procedimentos em Pascal. Em Java, ao contrário de C++ e Modula-3, não existem funções desvinculadas de classes, funções isoladas. Isto implica que todo trecho de código que for escrito deve pertencer a uma classe, mais precisamente deve ser um método desta. O programa mais simples em Java deve conter pelo menos uma classe e um método de início de programa, e é este programa que faremos agora. Esta filosofia é simples e semelhante a adotada em Eiffel, tudo o que se pode fazer com procedimentos, funções isoladas e variáveis de procedimentos, também se pode fazer com classes e métodos. C++ tinha que permitir a criação de funções isoladas para manter a compatibilidade com “C”, mas Java não. Quando neste texto usarmos o termo função no lugar de métodos estaremos mais interessados em enfatizar a parte de implementação em detrimento da interface, você pensar que em Java toda função implementa um método de uma classe. 15 Semelhante ao void C++ ou C, é o valor de retorno da função, quando a função não retorna nenhum valor ela retorna void, uma espécie de valor vazio que tem que ser especificado. main Este é um nome particular de método que indica para o compilador o início do programa, é dentro deste método e através das iterações entre os atributos, variáveis e argumentos visíveis nele que o programa se desenvolve. (String args[]) É o argumento de main e por consequência do programa todo, ele é um vetor de Strings que é formado quando são passados ou não argumentos através da invocação do nome do programa na linha de comando do sistema operacional, exemplo: Java HelloInternet argumentotexto1 argumentotexto2 No nosso caso, ignoramos a possível passagem de argumentos via linha de comando, retornaremos a este assunto em 1.3. { ... } “Abre chaves” e “fecha chaves”. Para quem não conhece C ou C++, eles podem ser entendidos como algo semelhante ao BEGIN END de Pascal ou Modula-3, ou seja: delimitam um bloco de código. Os programadores Pascal notarão que variáveis locais dos métodos podem ser declaradas em qualquer local entre as chaves. Mas por motivos de clareza do código declararemos todas no início do abre chaves. System.out.println("Hello Internet!"); Chamada do método println para o atributo out da classe ou objeto System, o argumento é uma constante do tipo String. println assim como writeln de Pascal, imprime a String e posiciona o cursor na linha abaixo , analogamente print não avança linha. Por hora você pode guardar esta linha de código como o comando para imprimir mensagens na tela, onde o argumento que vem entre aspas é a String a ser impressa. O ; “ponto e vírgula” separa operações. } Finalmente o fecha chaves termina com a declaração da classe HelloInternet. Conclusão: Normalmente o volume de conceitos presentes num primeiro programa de uma linguagem orientada a objetos como Java ou Eiffel é grande se comparado com o de um primeiro programa em C ou Pascal. Esses conceitos ainda serão aprofundados e são citados aqui apenas por curiosidade, é normal que você não tenha entendido tudo. De agora em diante não explicaremos mais como compilar os programas. Exercícios: 1- Experimente fazer modificações no programa HelloInternet. Imprima outras mensagens na tela, adicione comentários. 1.2.2. ATRIBUTOS 16 No programa anterior, não observamos a criação de nenhum objeto, apenas a declaração da classe HelloInternet que continha o método main. O nosso programa funcionou, porque o método main não precisa de um objeto específico para ser invocado. Este exemplo declara uma classe (Circulo) e em seguida cria um objeto deste tipo em main e altera o conteúdo desta variável. Uma classe é parecida com um record de Pascal, a nossa representa um círculo com os atributos raio e x , y, que são coordenadas cartesianas. Note que este objeto não possui métodos ainda. A classe círculo é especificada em um arquivo separado do arquivo da classe que contém o método main (início do programa), um arquivo neste texto é representado pelo retângulo envolvendo um trecho de código, até o tópico encapsulamento de classes com packages cada classe será especificada em um arquivo. É importante entender este exemplo, quando você estudar interfaces gráficas, poderá usar a classe círculo pré-definida na linguagem para desenhar círculos que se movem na tela. Embora não tenhamos explicado com detalhes os tipos básicos da linguagem, usaremos neste exemplo o tipo float (real), e nas explicações o tipo String e o tipo int (inteiro). No final deste tópico forneceremos uma explicação detalhada sobre tipos. //Classe circulo, arquivo Circulo.Java public class Circulo { //so atributos entre as chaves public float raio; //atributo raio do circulo public float x; //posicoes em coordenadas cartesianas public float y; } 17 //Classe principal, Arquivo Principal.Java public class Principal { public static void main(String args[]) { Circulo umcirc; //declaracao de uma variavel circulo no metodo main. umcirc=new Circulo(); //alocacao dessa variavel System.out.println("("+umcirc.x+","+umcirc.y+","+umcirc.raio+")"); umcirc.x=umcirc.x+17; System.out.println("("+umcirc.x+","+umcirc.y+","+umcirc.raio+")"); } } (0,0,0) (17,0,0) Porque os matemáticos não gostam de C, C++, Java e Basic: A declaração umcirc.x=umcirc.x+17 presente em nosso programa deixa os matemáticos doidos pois de você subtrair umcirc.x de cada um dos lados da “igualdade” a expressão se torna 0=17. Ocorre que = não é o operador de teste de igualdade e sim de atribuição, ele tem a mesma função do := de Pascal a qual os matemáticos adoram. O operador de teste de igualdade em Java é : == Mais sobre arquivos: Como pode ser observado, cada arquivo texto do programa está envolvido em uma moldura retangular. Neste caso as duas classes criadas: Circulo e Principal, estão em arquivos separados. A sequência de alterações em seu diretório ou folder é: Inicio: Após Javac AsDuasJuntas.Java: O compilador deve ser chamado para ambos arquivos. Ou você pode usar os chamados “wildcards”1 javac *.java. Sem Includes: 1Wild-Card, do inglês: Coringa , carta de baralho. O * substitui os nomes de todos os arquivos, assim como o coringa pode susbtituir todas as cartas em jogos de baralho. 20 mas a=1+0; também tem como resultado o valor 1, mas agora com o significado inteiro, numérico. O compilador não sabe distinguir entre os significados numérico e booleano. Essa ambiguidade faz com que programadores (C ou C++) não acostumados com esta convenção de operadores = e ==, incorram no erro de escrever j=1 quando na verdade queriam dizer j==1, mas para o compilador ambas as expressões tem valor inteiro de modo que se esse engano ocorrer num teste de parada de um loop, pode ocorrer que ele nunca pare, pois 1 tem o mesmo valor de true. Java elimina este tipo de erro introduzindo o tipo boolean com os valores true e false, que não tem nenhuma relação com o tipo int e qualquer outros tipos. TIPOS BÁSICOS E CONSTANTES OU VALORES LITERAIS (para nossa sorte os tipos básicos são os mesmos para qualquer ambiente de programação Java, porque a linguagem é portável): char Caractere UNICODE 16-bit O tipo char (caractere UNICODE) é representado com 16-bits sem sinal, o que permite endereçar de 0 a 65535. O objetivo desta opção é permitir internacionalização da linguagem, bem como a padronização. Constantes do tipo caractere aparecem entre apóstrofes: ‘a’, ‘1’, ‘$’. Tabela de caracteres especiais: (ao contrário de C/C++, não existe um caractere especial para o som de beep ou bell ) Representação visual: Função, significado: \n Pula linha, linefeed \r Retorno de carro \b Backspace \t Tabulação \f Formfeed \’ Apóstrofe \” Aspas \\ Barra inversa \u223d Caractere unicode \gfa Octal \fff Hexadecimal boolean Valor true ou false, diferente representação de C++, sem conversão em outros tipos. O tipo boolean não tem relação nenhuma com outros tipos (coerção). Eliminando problemas que surgiram por exemplo em C++ que usa inteiros para representar valores booleanos. Os possíveis valores são true e false que são os resultado dos testes lógicos. boolean pertenceAoConjunto; //declara variavel pertenceAoConjunto=true; //exemplo byte Inteiro 8-bit , complemento de 2, faixa:-128 até 127 short Inteiro 16-bit, complemento de 2, faixa:-32768 até 32767 int Inteiro 32-bit, complemento de 2, faixa:-2147483648 até 2147483647 long Inteiro 64-bit, compl. de 2, faixa:-9223372036854775808 até 9223372036854775807 21 Não existem especificadores de tipos como unsigned, todos os tipos “inteiros” tem sinal. Valores literais: O que você vê nem sempre é o que você tem: Um valor como 299792458 é considerado int como padrão, se você quer atribuir esta constante a um long, faça o “type cast” explicitamente: long a; a=(long)299792458; //a recebe PI Ou então use uma terminação em L para indicar que o número deve ser representado como long: long a=299792458L; //ou L minusculo Para indicar valores octais anteceda-os com um zero: 0444 , já para valores hexadecimais anteceda-os com 0X ou 0x, exemplo: 0xBCD4 float Ponto flutuante 32-bit IEEE754 double Ponto flutuante 64-bit IEEE754 Um valor como 3.14159265 é considerado double como padrão, se você quer atribuir esta constante a um float, faça o type cast explicitamente: float a; a=(float)3.14159265; //a recebe PI Ou então usar uma terminação em f para indicar que o número deve ser representado como float: float a=3.14159265f //ou F maiusculo Expoentes podem ser escritos usando o caracter e ou E: 6,02E23 ou 1.380658e-23 . Onde e-1, significa multiplicado por dez elevado a menos 1 ou *0.1. O separador de casas decimais é o ponto. Apesar de todas estas regras para uso de valores literais, quando dois números de tipo diferentes como double e int são usados em um calculo do lado direito de uma atribuição, você não precisa fazer o type cast desses valores. O compilador promove o número do tipo mais fraco para o tipo mais forte antes de fazer o calculo. Você pode desejar fazer o type cast do resultado, para atribuir a uma variável long por exemplo. Exercícios: 1- Repita o mesmo exemplo só que agora mova o círculo alterando as componentes x e y. Coloque o círculo na posição (1.0,1.0), através de atribuições do tipo acirc.x=1.0; . Acompanhe todas as modificações do objeto imprimindo seus atributos na tela. 2- Simplifique o programa anterior retirando o atributo raio. Você pode dar o nome de Ponto ou Ponto_geometrico para esta classe. Não se esqueça de compilar, use o compilador como ferramenta para verificar se você aprendeu corretamente a sintaxe da linguagem. 3- Reescreva a classe Circulo para trabalhar com atributos do tipo int. 1.2.3. ATRIBUTOS E MÉTODOS Os métodos determinam o comportamento dos objetos de um classe. Quando um método é invocado, se diz que o objeto está recebendo uma mensagem (para executar uma ação). Programas 22 complexos formam conjuntos de objetos que trocam mensagens entre si gerenciando inclusive os recursos do sistema. O programa a seguir exemplifica chamadas de métodos, para tal define um objeto que serve como contador, a implementação representa a contagem no atributo num que é um número inteiro. Os métodos são simples: incrementa adiciona um ao contador em qualquer estado e comeca inicializa a contagem em zero. Decrementa faz o oposto de incrementa. //Classe Contador, arquivo Contador.Java public class Contador { public int num; //este é o atributo //numero do contador public void incrementa() //incrementa contador { num=num+1; //acesso ao atributo } public void decrementa() //decrementa contador { num=num-1; } public void comeca(int n) //comeca a contar { num=n; } } //Classe principal, Arquivo Princ.Java public class Principal { public static void main(String args[]) { Contador umcont; //declaracao de atributo contador umcont=new Contador(); //alocacao umcont.comeca(0); System.out.println(umcont.num); 25 //Classe circulo public class Circulo { public float raio; public float x; //posicoes em coordenadas cartesianas public float y; public void move(float dx,float dy) //move o circulo de lugar { this.x+=dx; y+=dy; } public void mostra() //imprime na tela estado do objeto { System.out.println("("+x+","+y+","+raio+")"); } } //fim da declaracao da classe //Classe principal, Arquivo Principal.Java class Principal { public static void main(String args[]) { Circulo umcirc; //declaracao de atributo circulo umcirc=new Circulo(); umcirc.x=0; umcirc.y=0; umcirc.raio=12; umcirc.mostra(); umcirc.move(10,10); umcirc.mostra(); umcirc.x=100; umcirc.mostra(); } } 26 (0,0,12) (10,10,12) (100,10,12) Como funcionam no compilador as chamadas de métodos: É possível imaginar que as definições de métodos ocupam um grande espaço na representação interna dos objetos, mas lembre-se que elas são todas iguais para uma classe então basta manter para cada classe uma tabela de métodos que é consultada no momento da chamada . Os objetos só precisam ter uma referência para esta tabela. Exercícios: 1- Faça as seguintes modificações no programa HelloInternet: Adicione a declaração e inicialização de variável String logo após o abre chaves do método main: String nomequalquer; nomequalquer=new String(“Uma constante do tipo string”); Modifique o argumento de println para nomequalquer. Não use aspas em nomequalquer, temos uma variável agora. Execute o programa, qual o resultado? 2- No programa deste exemplo, crie um método chamado inicializa para a classe Circulo. Este método deve ter como argumentos um valor para x, um para y e outro para o raio, e deve alterar os atributos inicializando-os com os valores passados. Você pode abstrair o uso desse método como uma maneira de inicializar o objeto de uma só vez embora isto seja feito seqüencialmente. Comente as vantagens desta abordagem, comparando com as outras opções, tenha sempre em mente a questão de segurança quando avaliar técnicas diferentes de programação. 3- No programa anterior, verifique que nada impede que você acesse diretamente os valores de x , y e raio e os modifique, como alias foi feito nos exemplos anteriores. Como se pode criar um número enorme de métodos : altera_x(float a); move_raio(float dr); seria desejável que somente essas funções pudessem modificar x, y e raio. Você verá que isso é possível em encapsulamento 1.5. Por hora, crie esses métodos se preocupando em permitir através chamadas a eles tudo o que for possível fazer com acesso direto aos atributos. Comente também as duas opções equivalentes de implementação abaixo (os nomes são auto explicativos), tenha em mente o número de métodos a serem criados para garantir flexibilidade a classe: umcirculo.multiplica_atributo_x(10); ou umcirculo.altera_atributo_x(umcirculo.retorna_atributo_x()*10); //chamadas aninhadas de metodos //o resultado de uma chamada compoe o argumento da outra 4- Teste o método move com argumentos negativos, exemplo ac.move(-1.0,-1.5);. O resultado é coerente? 5- 27 ”Há uma tendência em definir o maior número de métodos em uma classe, porque nunca se pode prever exatamente o seu uso em programas futuros”. Comente esta frase, tendo em vista o conceito de portabilidade. Você já é capaz de citar outras medidas que tornem suas classes mais portáveis, flexíveis e genéricas? Leia o exercício anterior e o exercício 3. 1.2.4. MÉTODOS QUE RETORNAM VALORES. Até agora só havíamos visto métodos com valor de retorno igual a void. Um método, assim como uma função comum, pode retornar um único elemento de qualquer tipo, inclusive os definidos pelo usuário ou seja: objetos. Sendo assim, sua chamada no programa se aplica a qualquer lugar onde se espera um tipo igual ou equivalente ao tipo do seu valor de retorno, seja numa lista de argumentos de outro método , numa atribuição ou num operador+ em System.out.println( variavel+chamada_de_metodo_que_retorna_valor); Classe trava: O estado da trava é representado no atributo public boolean travado; e pode ser obtido através de uma chamada ao método estado(). Este exemplo é bastante simples e não tem a pretensão de ser uma implementação completa de uma classe que modele uma trava que possa ser usada para evitar o acesso a um arquivo, por exemplo. public class Trava { public String quem; public boolean travado; public void atrave(String q) //move o circulo de lugar { this.travado=true; this.quem=q; } public void adestrave(String q) { this.travado=false; this.quem=q; } public boolean estado() //estado(true/false) do objeto { return travado; } } 30 ac.inicializa(0,0,10); ac.mostra(); ac.move(1,1); ac.mostra(); ac.x=100; ac.altera_raio(12); ac.mostra(); } } Disposição dos métodos e atributos na declaração de uma classe: Em uma declaração de uma classe normalmente se coloca a declaração de métodos depois da declaração dos atributos, porém podemos fazer intercalações ou adotar qualquer ordem que nos convenha. Uma boa técnica de programação, que aconselhamos você adotar em seus programas é usar linhas de comentários para delimitar uma área do código de suas classes que englobe tudo o que interessa a um usuário desta. Nestes programas exemplos praticamente tudo é de interesse de um usuário (cliente) de sua classe, mas nos exemplos mais avançados isto não será verdade. Comentários: Observe que o método mostra chama o método public float retorna_raio(void) que é da mesma classe. Fica claro da definição de mostra que this.retorna_raio() se aplica ao mesmo objeto instanciado que recebeu a chamada de mostra. Isto foi feito somente para revelar que chamadas aninhadas de métodos também são permitidas, pois nesse caso esta chamada de método poderia ser substituída pelo próprio atributo raio, o que seria mais eficiente. Programação orientada a objetos e interfaces gráficas com o usuário: Existem “libraries” de classes que permitem o programador C++ desenvolver aplicações para ambientes como o Microsoft Windows® de uma maneira bastante abstrata, este é um exemplo claro de reuso de código, afinal não é preciso saber de detalhes da interface para programar nela. Na segunda parte falaremos sobre a “Java Aplication Programming Interface” que permite programar de maneira bastante abstrata sistemas de interfaces gráficas com o usuário seja para aplicações para a internet (rodando em browsers) ou para sistemas como o WindowsÒ ou Mac/OsÒ e X-WindowsÒ . (0,0,10) (1,1,10) (100,1,12) Pascal: Um programa parecido só que em Pascal: 31 PROGRAM Comparacao; {COMPARACAO COM UM PROGRAMA Java} TYPE Circulo=RECORD x:real; {COORDENADAS X E Y} y:real; r:real; {somente dados} END; var ac:circulo; leitura:integer; PROCEDURE Inicializa(var altereme:Circulo;ax,by,cr:real); {COLOCA O CIRCULO EM DETERMINADA POSICAO} BEGIN altereme.x:=ax; altereme.y:=by; altereme.r:=cr; END; PROCEDURE Altera_Raio(var altereme:Circulo;ar:real); {ALTERA O RAIO DO CIRCULO} BEGIN altereme.r:=ar; END; FUNCTION Retorna_Raio(copieme:Circulo):real; BEGIN Retorna_Raio:=copieme.r; END; PROCEDURE Move(var altereme:Circulo;dx,dy:real); {MODE AS COORDENADAS X E Y ACRESCENTANDO DX E DY} BEGIN altereme.x:=altereme.x+dx; altereme.y:=altereme.y+dy; END; PROCEDURE Mostra(copieme:Circulo); {MOSTRA O CIRCULO NA TELA} BEGIN writeln('X:',copieme.x,' Y:',copieme.y,' R:',copieme.r); END; BEGIN {TESTES} Inicializa(ac,0.0,0.0,10.0); Mostra(ac); 32 Move(ac,1.0,1.0); Mostra(ac); ac.x:=100.0; Altera_Raio(ac,12.0); Mostra(ac); read(leitura); END. X: 0.0000000000E+00 Y: 0.0000000000E+00 R: 1.0000000000E+01 X: 1.0000000000E+00 Y: 1.0000000000E+00 R: 1.0000000000E+01 X: 1.0000000000E+02 Y: 1.0000000000E+00 R: 1.2000000000E+01 //COMENTARIOS JAVA: As classes em Java são compostas de atributos e métodos. Para executar uma ação sobre o objeto ou relativa a este basta chamar um método : ac.mostra(); O método não precisa de muitos argumentos, porque é próprio da classe e portanto ganha acesso aos atributos do objeto para ao qual ela foi associado: public float retorna_raio(void) { return raio; //tenho acesso direto a raio. } { COMEntArios Pascal: } Em Pascal os procedimentos e os dados são criados de forma separada, mesmo que só tenham sentido juntos. A junção entre os dados e procedimentos se dá através de passagem de parâmetros. No caso de uma linguagem procedural, o que normalmente é feito se assemelha ao código seguinte: Move(ac,1.0,1.0);. Nesse caso AC é um “record”, algo semelhante ao struct de C (não C++). Move, acessa os dados do “record” alterando os campos. O parâmetro é passado por referência e o procedimento é definido a parte do registro, embora só sirva para aceitar argumentos do tipo Circulo e mover suas coordenadas. Segurança: Em ambos programas (Pascal, Java) o programador pode obter acesso direto aos dados do tipo definido pelo usuário: ac.x:=100.0; (Pascal) ou ac.x=100.0; (Java). Veremos em 1.5 ENCAPSULAMENTO maneiras de proibir este tipo de acesso direto ao atributo, deixando este ser modificado somente pelos métodos. Isto nos garante maior segurança e liberdade pois podemos permitir ou não o acesso para cada atributo de acordo com nossa vontade. Eficiência: Alguém pode argumentar que programas que usam bastante chamadas de métodos podem se tornar pouco eficientes e que poderia ser melhor obter acesso direto aos dados de um tipo definido pelo usuário ao envés de passar por todo o trabalho de cópia de argumentos, inserção de função na pilha, etc. Em verdade não se perde muito em eficiência, por que tal metodologia de programação nos leva a organizar o código de maneira mais compacta. E além disso muitas vezes não se deseja 35 (0,0) (1,1) (100,1) //COMENTARIOS: Note que com a definição do construtor, você é obrigado a passar os argumentos deste no momento da alocação do objeto. Se você precisa ter a opção de não passar esses valores ou passar outros, as possíveis soluções serão dadas em 3. (float)0.0 indica que é para ser feita a conversão de 1.0 para ponto flutuante. 1.0 sozinho é considerado double. (int)1.0 é igual a 1. (int) 2.3 é igual a dois. Esta operação indicada por (nometipo)tipo_a_ser_convertido é também chamada de “type cast”. A ocorrência de rotinas de criação de objetos em diversos locais de um programa é muito comum. Objetos podem ser criados dentro de estruturas condicionais, armazenados em arquivos, passados como parâmetros, inseridos em estruturas dinâmicas dentro de outros objetos, etc. Exercícios: 1- Um método pode chamar outro método da mesma classe. Parta do exemplo de 1.2.5 e crie um construtor que chama o antigo método inicializa(float a,float b) repassando os argumentos do construtor: ponto(float a, float b) { inicializa(a,b); } Na chamada de inicializa() fica implícito que ela se aplica ao objeto cujo construtor foi chamado. Isto é válido também para outros métodos que chamam métodos, ou seja, não é necessário o operador identificador.inicializa(a,b); , veja 1.2.4 uso de this. Este exercício é útil para mostrar outro recurso de Java, o ponteiro this. this é uma palavra reservada e dentro de qualquer método this é um “ponteiro” para o objeto em questão, então o código descrito acima poderia também assumir a seguinte forma equivalente: ponto(float a, float b) { this.inicializa(a,b); } //Verifique! É lógico que neste exemplo this não tem muita utilidade, mas existem casos onde um objeto precisa passar seu “ponteiro” para alguma função que o modifica, ou fica possuindo uma referência para ele, então usa-se this. Veremos outras aplicações mais adiante. 2- Introduza mensagens no construtor tipo: System.out.println(“Objeto instanciado”); introduza trechos parecidos em outros pontos de seu programa. O objetivo é acompanhar visualmente a seqüência de criação e modificação dos objetos. 3- Esta classe Ponto pode ser adaptada para funcionar como representação de vetores em duas dimensões, para tal forneça outros métodos úteis: métodos para tornar o vetor unitário, retornar o módulo do vetor, a componente x e y. Para tal você terá que fazer uso de Math.cos(double a); Math.sin(double a); e Math.sqrt(double a); , respectivamente o cosseno, o seno e a raiz quadrada de um ponto flutuante de dupla precisão. Não é necessário fazer nada semelhante a um include de C++ para usar esses métodos, basta adicioná-los ao seu código, outro método útil é 36 Math.max(a,b); que retorna o maior valor entre a e b (float, double, int. long, etc). Use “type cast” explicado nos comentários deste exemplo. 4- Crie uma classe reta que tem como atributos dois objetos da classe ponto. Dica: Não use construtores, use funções do tipo inicializa(), já apresentadas. Quando seu programa ficar pronto acrescente métodos para esta reta tais como inclinação, coeficiente linear, etc. Para acrescentar construtores leia 1.2.7. Existem maneiras mais eficientes e compactas de representar uma reta, porém faça como foi sugerido, neste caso o objetivo não é eficiência. 1.2.7. CONSTRUTORES E AGREGAÇÃO Este exemplo é o resultado do exercício anterior (exercício 4 tópico construtores), ele cria uma classe Reta com dois atributos da classe Ponto. Ou seja você estará reutilizando a classe Ponto na classe que representa uma Reta, a forma como essa reutilização de código ocorre é chamada de agregação. O nome reutilização de código é bastante apropriado, pois a classe Reta estará utilizando todos os métodos escritos para a classe Ponto. Em casos como este é comum dizer que a classe Reta é cliente da classe Ponto em uma analogia com as relações comerciais. Este programa busca chamar sua atenção para o fato de que objetos são alocados dinamicamente e que caso você se esqueça de alocá-los, eles ficam possuindo o valor null. Obter acesso a uma variável com valor null é um erro que geralmente é verificado em tempo de execução. Nos programas anteriores alocar os objetos era fácil, mas agora que temos objetos funcionando dentro de outros objetos há necessidade de adotar técnicas melhores. Veremos a seguir duas alternativas para alocar os atributos Ponto presentes na classe Reta: Alternativa 1: Primeiro aloca-se o objeto da classe Reta, depois chama-se para cada atributo da classe Ponto a rotina de alocação: Reta r1; r1=new Reta(); r1.ponto1=new Ponto((float)1.0,(float)2.0); r1.ponto2=new Ponto((float)3.0,(float)4.0); Esta alternativa é em muitos casos pior que a seguinte. Alternativa 2: Passamos para o construtor da classe Reta a tarefa de alocar e inicializar os atributos de forma coerente. Os argumentos do construtor da classe Reta passam a conter valores úteis para a chamada dos construtores de seus atributos. Esta alternativa é executada pelo programa a seguir: //Reutilize o arquivo da classe Ponto definida anteriormente. //Classe Reta class Reta { public Ponto a,b; public Reta(float ax,float ay,float bx,float by) 37 { a=new Ponto(ax,ay); //chamadas dos contrutores da classe Ponto b=new Ponto(bx,by); } public void mostra() { a.mostra(); b.mostra(); } } //Classe principal, Arquivo Principal.java class Principal { public static void main(String args[]) { Reta ar; ar=new Reta((float)0.0,(float)0.0,(float)1.0,(float)1.0); ar.mostra(); } } Alguns podem argumentar que esta maneira de representar uma reta não é muito eficiente, mas não é do escopo deste texto tratar de problemas dessa natureza, o que aliás complicaria muito o código , desviando-nos do objetivo principal: simplicidade na apresentação de conceitos de orientação a objetos. O leitor deve ampliar os modelos aqui sugeridos em aplicações na sua área de interesse. Como ainda faltam conceitos importantes para serem apresentados este tipo de aplicação deve ser feita em paralelo com a leitura. Exercícios: 1- Implemente um programa que use a mesma lógica do exemplo anterior para criar uma classe composta de outras . Você estará usando agregação. Por exemplo um triângulo precisa de três pontos para ser definido. 2- Uma implementação mais eficiente da classe Reta seria feita armazenando apenas um coeficiente angular e um linear, mas esta reta deveria prover também um construtor que aceitasse dois pontos como argumentos. Como você não aprendeu sobrecarga de construtores (definir vários construtores), use só um construtor que tem coeficiente angular e linear como argumentos e implemente esta nova classe reta (sem usar agregação agora). 3- Defina um método para a classe Reta que retorna o ponto de intercessão com outra reta: public Ponto intercessao(Reta a);. Não se esqueça de tratar os casos degenerados, tais como 40 //chame o metodo troca de outro objeto, usando OBJ como argumeto //verifique a alteracao nos atributos desse argumento O código acima mostra como definir um método que usa a existência implícita de ponteiros na linguagem para criar um método que troca os atributos de dois objetos da mesma classe. Implemente este método e faça os testes sugeridos nos comentários desse código. 1.3.2. VETORES E MATRIZES Vetores são objetos, eles possuem papel importante no estilo de programação desta linguagem que exclui ponteiros. Por serem objetos, vetores são obrigatoriamente alocados de maneira dinâmica. O exemplo a seguir aloca um vetor de inteiros com três posições, seguindo uma sintaxe semelhante a de alocação de objetos: class VetorTest { public static void main (String args[]) { int vetor[]=new int[3]; vetor[0]=0; //indexacao semelhante a C , C++ vetor[1]=10; vetor[2]=20; System.out.println(vetor[0]+" "+vetor[1]+" "+vetor[2]+" "); } } 0 10 20 Resumo da sintaxe de vetores: int a[]; //declara vetor de inteiros a a=new int[10]; //aloca vetor a com dez posicoes //as duas linhas anteriores podem ser abreviadas por: int a[]=new int[10]; //alem disso se voce quiser inicializar o vetor a, ja’ na declaracao: int a[3]={0,10,20}; O análogo para matrizes é: int a[][]; //declara matriz de inteiros a a=new int[3][3]; //aloca matriz 3x3, 9 celulas //as duas linhas anteriores podem ser abreviadas por: int a[]=new int[3][3]; //alem disso se voce quiser inicializar a matriz a ja na declaracao: int a[3][3]={{0,10,20},{30,40,50},{60,70,80}}; Em métodos, argumentos e valores de retorno que são vetores, são escritos da seguinte forma: int[] , ou tipo[] nomedavariavel //no caso de argumentos. 41 Diagrama do vetor: Perceba que a faixa útil do vetor vai de 0 até (n-1) onde n é o valor dado como tamanho do vetor no momento de sua criação, no nosso caso 3. O mesmo ocorre com matrizes. Esta convenção pode confundir programadores Pascal onde a indexação vai de 1 até n. Java checa se você usa corretamente os índices do vetor. Se ocorrer um acesso ao vetor[i] onde i é um índice inválido (fora da faixa de valores permitidos), você receberá uma mensagem parecida com: java.lang.ArrayIndexOutOfBoundsException: #. Retornaremos ao assunto desta mensagem mais adiante, ela é uma exceção gerada pelo código que acompanha a linguagem. Existe um atributo muito útil quando se trabalha em um vetor de dados: a.length; //armazena o numero de elementos do vetor a Declarar um vetor de objetos, por exemplo objetos da classe Ponto, não implica que os objetos de cada posição do vetor já estarão inicializados, para inicializar os elementos, siga seguinte sintaxe: Ponto a[]; //declaracao, todas as posicoes com null a = new Ponto[3]; //alocacao for (int i = 0; i < a.length(); i++) { a[i] = new Ponto(0,0); } //inicializacao (o código acima representa um dos usos de vetores no lugar de ponteiros) Exercícios: 1- Escreva um programa simples que toma um vetor de preços e um de descontos (50%=.5), e altera o vetor de preços de modo que estes produtos já incluam os descontos no seu valor de venda. Exemplo: Preços : 40,5 30,3 12,6 100 Descontos: .5 .3 .2 .5 Vetor calculado: 20.25 9.09 2.52 50 A linguagem Java será muito usada no comércio da internet, tabelinhas assim, usadas para cálculos de preços de produtos já estão se tornando comuns. Os nomes que você pode dar as classes e métodos são: Classe: TabelaPrecos Atributo: boolean ComDesconto; Atributo: double[] Precos; Método: void AplicaDescontos(int[] descontos); 1.3.3. COPIA , COMPARAÇÃO E DETERMINAÇÃO DA CLASSE EM OBJETOS Este tópico ainda não está completo, devido a indefinições sobre o que estará contido nas packages e interfaces da Java API. Objetos são implicitamente referências, portanto sua cópia (através do operador =) está sujeita ao problema de “reference aliasing” e efeitos colaterais. A comparação de objetos através do 42 operador == tem o significado da comparação de referências, ou seja ela verifica se os objetos1 compartilham o mesmo espaço alocado na memória. Observe que com as operações conhecidas até agora, não conseguimos comparar dois objetos quanto ao conteúdo a não ser que comparemos atributo por atributo, o mesmo vale para a cópia. Seria útil dispor de um conjunto de operações de igualdade, desigualdade e copia que se aplicasse ao conteúdo dos objetos e não ao endereço de memória de suas variáveis. Uma outra necessidade seria verificar a classe de um objeto em tempo de execução. Antes de ver a solução procure sentir o problema no exemplo a seguir: Usaremos o arquivo da classe Ponto, já apresentado em construtores 1.2.6., mas com uma modificação no método mostra. No lugar de mostra criaremos um método chamado public String toString(). Este método é padrão em muitas classes e deve ser definido de modo a retornar uma String descritiva do objeto. Fazendo isto você pode concatenar uma variável Ponto com uma String no argumento do método System.out.println(meuPonto1+ “ Na vizinhanca de “+meuPonto2);. Esta decisão de implementação é certamente mais genérica que mostra, visto que nem sempre estaremos imprimindo através de System.out.println(), por exemplo na segunda parte ocorrerão casos em que temos que “pintar” Strings em áreas especiais na tela. Apresentação do problema //Classe ponto class Ponto { public float x,y; public Ponto(float ax,float ay) //omita o valor de retorno! //garante o estado do objeto { this.x=ax; this.y=ay; } public void move(float dx,float dy) { this.x+=dx; this.y+=dy; } public String toString() { return "("+this.x+","+this.y+")"; //(x,y) } } //Classe principal, Arquivo Principal.java class Principal { public static void main(String args[]) { 1Strings e vetores são objetos de classes pré definidas na linguagem, portanto as afirmações feitas aqui se aplicam a eles também. 45 System.out.println("Copia2:"+pCopia2); System.out.println("Modificando Copia1.x para 2"); pCopia1.x=2.0f; System.out.println("Veja como o original ficou:"+pOriginal); System.out.println("Copia2 nao se modifica:"+pCopia2); //comparacao de objetos System.out.println("Original==Copia2:"+(pOriginal==pCopia2) ); System.out.println("Original.igual(Copia2):"+pOriginal.igual(pCopia2) ); System.out.println("Deixando atributos de Copia iguais aos de Original"); //verificacao da classe dos objetos System.out.println("Obtendo a classe dos objetos"); System.out.println(pOriginal.getClass().getName()); System.out.print("Original e da classe String?"); boolean result=(pOriginal instanceof String); System.out.println(result); } } Este programa exemplo não opera corretamente, isto se deve a não implementação destes métodos na versão beta (a disponível atualmente). 1.4. OBTENDO VALORES DO USUÁRIO Este tópico foi introduzido porque os programas seguintes são mais complexos e precisam ser testados iterativamente. Mesmo que você só vá fazer programas em interfaces gráficas é interessante saber como testar certas classes via teclado ou linha de comando, sem contar que sem o conteúdo deste tópico os programas seguintes não serão possíveis. Além do que você pode desejar fazer algum programa de linha de comando (batch) em Java, por exemplo um programa de comunicação entre as diversas máquinas de uma rede ou um programa que receba argumentos via linha de comando. 1.4.1. LENDO DO TECLADO Este tópico contém dois exemplos que ensinam como ler Strings do teclado, como convertê- las para números, e como controlar melhor a entrada e saída de dados. 1.4.1.1.LEITURA DE STRINGS USANDO UM VETOR DE BYTES. Em Java é praticamente um padrão ler dados em bytes, seja do teclado, da rede, do disco ou de qualquer outro lugar. Por este motivo o primeiro exemplo lê em um vetor de bytes. Como sabemos que você quer mais do que isso( ex. ler outros tipos da linguagem ), o segundo exemplo tratará desse caso. 46 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.io.*; class EntraDados { public static void main (String args[]) { byte vetortexto[] = new byte[200]; //declaracao de um vetor de bytes int byteslidos = 0; System.out.println("Escreva algo:"); try { byteslidos = System.in.read(vetortexto); System.out.print("Voce escreveu:"); System.out.write(vetortexto,0,byteslidos); } catch (IOException e) { // Alguma acao de recuperacao da falha } } } Escreva algo: Como assim escreva algo? Voce escreveu:Como assim escreva algo? //COMENTARIOS: Este programa usa o método System.in.read(vetortexto); para ler do teclado. Este método precisa de um vetor de bytes como argumento(onde serão lidos os caracteres) e além disso retorna o número de bytes lidos, para que você possa usar o vetor corretamente. Para descarregar o vetor no vídeo use o método System.out.write(vetortexto,0,byteslidos); que imprime na tela as posições de 0 até byteslidos do vetor de bytes passado como o primeiro argumento. Você deve estar se perguntando qual a função dos blocos de código try {} catch {} deste programa exemplo. Eles são importantes no tratamento de exceções, tópico que será abordado no final deste texto. Por enquanto apenas veja estes blocos de código como necessários para escrever dentro do bloco try{} as operações de leitura de teclado, que são operações que podem gerar exceções. 1.4.1.2. UMA VISÃO GERAL SOBRE PACKAGES E STREAMS A maneira ensinada nos tópicos anteriores (leitura de bytes) é suficiente para que você leia do teclado, mas aproveitaremos agora que você já está mais experiente para ensinar uma outra maneira 47 elegante que serve para ler do teclado, de um arquivo ou de uma conexão da rede que aceita por exemplo a leitura direta para uma String e outros tipos desta linguagem e não só para um vetor de bytes. Nestes exemplos usaremos um conceitos que somente serão explicados a fundo nos próximos tópicos, são os conceito de packages e Streams. Packages são conjuntos de classes. Até agora só tínhamos utilizados elementos da package java.lang que é importada implicitamente em todos os programas, permitindo por exemplo escrever na tela (System.out é uma classe presente em java.lang). A package java.io fornece a abstração de streams para lidar com a complexidade de entrada e saída de dados. Esta abstração é poderosa e será utilizada aqui para ler do teclado. Um stream é como um cano por onde passam os bytes, a vantagem deste cano é que não precisamos nos preocupar de onde vem esses bytes, para nós eles vem do cano. Para quem escreve no stream também vale a mesma idéia do que agora do outro lado, empurrando os bytes para dentro. Neste exemplo usamos um stream para ler do teclado, esta poderosa abstração será estendida mais tarde para tratar a entrada e saída de dados através de portas de comunicação e programação cliente servidor, explorando as capacidades de “networking” da linguagem, além da leitura e escrita em arquivos, no tópico adequado. Este programa simplesmente lê uma linha do teclado e a imprime novamente. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.io.DataInputStream; //classe DataInputStream para a entrada de dados public class ReadString { public static void main(String args[]) { String linha=""; DataInputStream meuDataInputStream; meuDataInputStream = new DataInputStream(System.in); try{ linha = meuDataInputStream.readLine(); } catch (Exception erro) { System.out.println(“Erro de leitura”); } //antes de imprimir ou armazenar a string, e’ obvio que voce poderia executar algum 50 public static void main(String args[]) { for (int i=0; i < args.length; i++) { System.out.println("Argumento" + i+": "+ args[i]); } } } Resultado do programa para a chamada: java MostraArgumentos Passando 3 “argumento s” Argumento0: Passando Argumento1: 3 Argumento2: argumento s args.length; Retorna o comprimento do vetor de Strings, este valor é usado para iterar sobre os argumentos que são Strings armazenadas em args[i]. Perceba que as aspas fazem com que nomes separados por espaços sejam considerados só um argumento. Os argumentos passados para seu programa são gravados em um vetor de Strings, para usar o argumento 3 que ficou guardado como String, na forma de inteiro, é preciso primeiro convertê-lo para inteiro. Isto pode ser feito no caso do nosso exemplo através de uma chamada a: Integer.parseInt(args[1]); A classe Integer usada no código acima é um dos “wrappers” que descrevemos em 1.3.1. A operação contrária a que foi feita é Integer.toString(int a);. O método parseInt aceita também um segundo argumento que é a base em que o número está representado na String. Exercícios: 1- Mude o primeiro programa em Java (HelloInternet) para imprimir “Hello” seguido do primeiro argumento de linha de comando (se existir: args.length>0 ). 2- Construa um programa simples que recebe argumentos da linha de comando e os imprime através de cout. Normalmente é isso que deve ser feito antes de usar um recurso da linguagem pela primeira vez, experimentá-lo em programas simples. 1.5. ENCAPSULAMENTO COM PRIVATE, PUBLIC, “PACKAGE” e PROTECTED Encapsulamento, “data hiding” é um conceito bastante importante em orientação a objetos. Neste tópico vamos falar das maneiras de restringir o acesso as declarações de uma classe e a própria classe, isto é feito através do uso das palavras reservadas public, private e protected1 2 que são qualificadores. Alguém pode estar se perguntando o porquê de se restringir o acesso a certas partes de uma classe. A idéia é simples, devemos fornecer ao usuário, cliente de uma classe, o necessário e somente o necessário para que ele tire proveito da funcionalidade desta classe. Os detalhes devem ser omitidos, somente a lista de operações a qual uma classe deve atender fica visível. 1Protected será explicada juntamente com herança na seção 2. 2PACKAGE será explicado juntamente com packages. 51 Os benefícios são muitos: clareza do código, minimização de erros, facilidade de extensão. Talvez a facilidade de modificação seja o mais importante dos benefícios. Como a classe é conhecida pela sua interface, é muito fácil mudar a representação interna sem que o cliente, usuário, perceba a diferença Estaremos preocupados em separar design de implementação, Java é uma linguagem boa de se programar em termos de design e em termos de implementação. Programar tendo em vista o design é também chamado de “programming in the large”, enquanto que programar tendo em vista implementação, codificação é chamado de “programming in the small”. Alguns programadores experientes afirmam que Java se parece com C quando estamos preocupados com codificação, mas quando estamos preocupados com design, Java se assemelha a Smalltalk. Com encapsulamento você será capaz de criar componentes de software reutilizáveis, seguros, fáceis de modificar. 1.5.1. ENCAPSULANDO MÉTODOS E ATRIBUTOS Até agora não nos preocupávamos com o modo de acesso de declarações de uma classe pois, mesmo sem saber porque, você foi avisado para qualificar todos os atributos e métodos de suas classes como public o que significa que eles são acessíveis, visíveis, em qualquer local de seu código. Por visível entenda o seguinte: se o atributo x do objeto UmPonto não é visível por exemplo fora de sua classe, então não faz sentido escrever em main: UmPonto.x=0; . Mas então como controlar o acesso de atributos e métodos em uma classe? Simples, através das palavras reservadas private, public e protected cujos significados quando qualificando métodos e atributos (private e public podem também qualificar classes) são descritos abaixo: public Estes atributos e métodos são sempre acessíveis em todos os métodos de todas as classes. Este é o nível menos rígido de encapsulamento, que equivale a não encapsular. private Estes atributos e métodos são acessíveis somente nos métodos(todos) da própria classe. Este é o nível mais rígido de encapsulamento. protected Estes atributos e métodos são acessíveis nos métodos da própria classe e suas subclasses, o que será visto em Herança 2. Nada especificado, equivale “package” ou “friendly” Estes atributos e métodos são acessíveis somente nos métodos das classes que pertencem ao “package” em que foram criados. Este modo de acesso é também chamado de “friendly”. (existem outros qualificadores, não relacionados com encapsulamento que serão explicados depois) Package e friendly: Aparecem entre aspas porque não são palavras reservadas da linguagem, são apenas nomes dados para o tipo de encapsulamento padrão (default), que ocorre quando não existe um especificador. São nomes fáceis de memorizar. Friendly significa amigável, no sentido de que as classes que permitem este tipo de acesso possuem um encapsulamento mais relaxado com relação as classes do mesmo package (amigas). Package é um grupo de classes relacionadas. Protected será explicada em 2.1 pois está relacionada com herança, por hora vamos focalizar nossa atenção em private e public que qualificam os atributos e métodos de uma classe quanto ao tipo de acesso (onde eles são visíveis) . Public, private e protected podem ser vistos como qualificadores ou “specifiers”. 52 Para facilitar a explicação suponha a seguinte declaração de uma classe: 1) class Ponto { private float x private float y public void inicializa(float a,float b) {x=a; y=b;}; public void move (float dx,float dy); } Fica fácil entender essas declarações se você pensar no seguinte: esses qualificadores se aplicam aos métodos e atributos que vem imediatamente após eles. Os elementos da classe qualificados como private aparecem com fundo cinza escuro indicando que sua “visibilidade” é mais limitada que os atributos qualificados como public (cinza claro). Agora vamos entender o que é private e o que é public. Vamos supor que você instanciou (criou) um objeto do tipo Ponto em seu programa: Ponto meu; //instanciacao meu=new Ponto(); Segundo o uso da definição da classe Ponto dada acima você não pode escrever no seu programa: meu.x=(float)5.0; //erro ! ,como fazíamos nos exemplos anteriores, a não ser que x fosse declarado como public na definição da classe o que não ocorre aqui. Mas você pode escrever x=5.0; na implementação (dentro) de um método porque enquanto não for feito uso de herança, pode-se dizer que um método tem acesso a tudo que é de sua classe, veja o programa seguinte. Você pode escrever: meu.move(5.0,5.0); ,porque sua declaração (move) está como public na classe, em qualquer lugar se pode escrever meu.move(5.0,5.0);. Visibilidade das declarações de uma classe, fora dela ,de sua hierarquia e de seu PUBLIC package. Veja que só a parte public é visível neste caso: PRIVATE PROTECTED “PACKAGE” Visibilidade das declarações de uma classe, dentro dela mesma: PUBLIC PRIVATE PROTECTED “PACKAGE” 1.5.1.1.ATRIBUTOS PRIVATE, MÉTODOS PUBLIC Aplicando encapsulamento a classe ponto definida anteriormente, deixando os atributos encapsulados e definindo a interface publica da classe somente através de métodos. //Classe ponto public class Ponto { private float x,y; //atributos private Área private. 55 public float x; public float y; public void inicializa(float a, float b) {x=a; y=b;}; public void move(float dx, float dy) ; {x+=dx; y+=dy; }; }; b) public class Ponto { public void inicializa(float a, float b) {x=a; y=b;}; public void move(float dx, float dy) ; {x+=dx; y+=dy; }; private float x; private float y; }; c) public class Ponto { public void inicializa(float a, float b) {x=a; y=b;}; private void move(float dx, float dy) ; {x+=dx; y+=dy; }; public float x; private float y; }; 1.5.2. ENCAPSULAMENTO E “PACKAGES” Neste tópico explicaremos o recurso de packages com ênfase nas opções de encapsulamento relacionadas , em herança explicaremos este recurso com ênfase no reuso de código das packages oferecidas com a linguagem. Packages são conjuntos de classes relacionadas, estes conjuntos são determinados incluindo uma linha no topo de cada arquivo indicando a qual package pertencem as classes ali declaradas. Se nenhuma linha é inserida assume-se que todas as classes pertencem a uma package só. 1.5.2.1. ENCAPSULAMENTO DE ATRIBUTOS E MÉTODOS COM PACKAGES O encapsulamento de atributos e métodos atingido com o uso de packages é muito semelhante ao encapsulamento com private e public, só que agora o “limite de visibilidade” é mais amplo do que a classe. A questão aqui levantada é se a classe é visível fora do “package” ou não. Só para lembrar, a questão que do item anterior: 1.5.1, era se os métodos e atributos eram visíveis fora da classe ou não. Visibilidade das declarações de uma classe, dentro de seu package: PUBLIC PRIVATE PROTECTED “PACKAGE” Quando explicarmos protected forneceremos um diagrama completo dos modos de encapsulamento de métodos e atributos, este diagrama é muito útil para a memorização. 56 “Packages” é um recurso da linguagem que permite formar grupos de classes relacionadas entre si de forma que elas ofereçam facilidades umas as outras. Facilidades estas que nem sempre são oferecidas ao usuário. Vamos montar uma package de nome Geometria contendo classes que representam elementos gráficos tais como retas, círculos e pontos. A estrutura de diretório abaixo descreve a disposição dos arquivos deste exemplo: Inicio: Após Javac: Note que a package Geometria está toda sob um diretório ou folder de mesmo nome, isto ocorre porque a estrutura de packages deve ser mapeada em uma estrutura de diretórios para que suas classes possam ser achadas. Assim a classe java.awt.Color está dois níveis, portanto dois diretórios abaixo na hierarquia de packages fornecida com a linguagem. //Classe ponto package Geometria; public class Ponto { float x,y; //nenhu especificador, default=package public Ponto(float ax,float ay) //omita o valor de retorno! //garante o estado do objeto { this.x=ax; this.y=ay; } public float retornaX() { return x; } public void move(float dx,float dy) { this.x+=dx; this.y+=dy; } public void mostra() 57 { System.out.println("("+this.x+","+this.y+")"); } } //Classe circulo package Geometria; public class Circulo { float raio,x,y; //nenhum especificador, defaul=package public Circulo(float ax,float ay,float ar) //garante o estado do objeto { this.x=ax; this.y=ay; this.raio=ar; } public void altera_raio(float a) { this.raio=a; } public float retorna_raio() { return this.raio; } public void move(float dx,float dy) { this.x+=dx; this.y+=dy; } public float distancia(Ponto ap) { float dcp; //distancia do centro do circulo ao ponto dcp=(float)Math.sqrt((double) ((x-ap.x)*(x-ap.x)+(y-ap.y)*(y-ap.y)) ); //acesso direto //aos atributos de ap, isto porque as classes pertencem ao mesmo package if (dcp<raio) {return raio-dcp; } else {return dcp-raio; } } public void mostra() { System.out.println("("+this.x+","+this.y+","+this.raio+")"); } } 60 Os qualificadores são: public Estas classes são sempre acessíveis em todos os packages do seu código. Somente uma classe publica é permitida por arquivo, e o arquivo deve ter o mesmo nome da classe. private Estas classes são acessíveis somente pelas classes declaradas no mesmo arquivo. Um arquivo pode possuir várias classes private, mas uma única classe public. Nada especificado “package” Estas classes podem ser acessadas no package que elas pertencem, se nenhum package é especificado, elas pertencem ao programa. (existem outros qualificadores, não relacionados com encapsulamento que serão explicados depois) Seus programas não precisam necessariamente fazer uso destes recursos de encapsulamento, mas todo código escrito para terceiros deve utilizá-los intensamente. Você teve ter notado que sempre definimos uma classe por arquivo, isto é feito porque a linguagem só permite uma classe public por arquivo, as outras tem que ser private ou package. Um exemplo de utilização: ao criar uma classe lista ligada alguém pode achar conveniente definir uma classe nó para ser usada somente pela classe listaligada. Uma opção é definir a classe nó com modo de encapsulamento: package. O cliente de sua classe lista ligada não precisa saber que esta classe se baseia em uma classe No. Vamos implementar esta idéia no exemplo a seguir, tomarei o cuidado de fazê-lo bem simples pois estamos no começo do tutorial: Inicio: Após Javac: package listas; //classe Lista, classe No arquivo Lista.java class No { //sem especificador de modo de acesso na classe private char info; //se eu ja tivesse ensinado protected usaria em lugar de private private No prox; //"ponteiro" para o proximo no No(char i,No p) //construtor { info=i; prox=p; } char retorna_info() //retorna valor do campo { 61 return info; } void altera_info(char i) //altera valor do campo { info=i; } void altera_prox(No p) //altera valor do proximo no { prox=p; } No retorna_prox() //retorna referencia ao proximo no { return prox; } } public class Lista { private No cabeca; //inicio da lista private int elementos; //numero de nos na lista public Lista() //construtor { cabeca=null; elementos=0; } public void insere(char a) { //realizada em muitos mais pacos para facilitar apredizado elementos++; No temp; if (cabeca==null) cabeca=new No(a,null); else{ temp=new No(a,null); temp.altera_prox(cabeca); cabeca=temp; } //se cabeca == null tambem funciona } 62 public char remove() { No removido; if (cabeca==null) return '0'; //elementos==0 else { elementos--; removido=cabeca; cabeca=cabeca.retorna_prox(); return removido.retorna_info(); } } public int retorna_elementos() { return elementos; } public void mostra() //nao deveria estar aqui, e so para debugar { No temp=cabeca; while (temp!=null) { System.out.print( "[" + temp.retorna_info() + "]-" ); temp=temp.retorna_prox(); } System.out.print("null"); System.out.println(); } } Agora você pode escolher entre duas versões do programa principal, a versão da esquerda implementa um loop de entradas do teclado que permite testar iterativamente a classe Lista, não explicaremos ainda detalhes deste loop, apenas como usá-lo. Dê uma olhada no código desta versão de programa principal, se você acha-lo complicado pode usar a segunda versão presente a direita deste arquivo e em uma segunda leitura retornar a versão da esquerda. Na versão com o loop (1-esquerda) a letra ‘i’ indica o comando inserção, se você digitar i<enter> inserirá o caractere <enter> na sua lista, o que normalmente não é desejado, digite ic<enter> para inserir o caractere c. ‘r’ indica remoção, e ‘m’ indica que a lista deve ser mostrada na tela. Pelo resultado do programa você entenderá melhor esses comandos rudimentares de teste. Você pode achar rudimentar programar assim, mas é um bom método, verá como esta mesma lista depois de testada assim pode ficar bonita se inserida em um applet e mostrada graficamente. Escolha uma implementação para entender e compilar: //Classe principal, Arquivo Principal.java versao 1 import java.io.DataInputStream; import listas.*; //Classe principal, Arquivo Principal.java versao 2 import java.io.DataInputStream; import listas.*; 65 Tipo abstrato de dados é um conceito muito importante em programação orientada a objetos e por este motivo é logo apresentado neste tutorial. Os exemplos seguintes são simples por não podermos usar todos os recursos da linguagem ainda. Dada esta importância, a medida em que formos introduzindo novos conceitos exemplificaremos com aplicações na implementação tipos abstratos de dados. Exercícios: 1- Use a estratégia da lista de compras (“shopping list approach”) para modelar a interface do tipo abstrato de dados Ponto, tente pensar nas operações que geralmente se aplicam a pontos em geometria, tais como distância a outros elementos, rotação em torno de outro ponto. Reimplemente este TAD adicionando as inúmeras alterações. 1.6.1. TAD FRAÇÃO Neste exemplo implementaremos o tipo abstrato de dados fração. Baseado no conceito de número racional do campo da matemática. Algumas operações não foram implementadas por serem semelhantes às existentes. Uma aplicação deste TADs consiste em alguns cálculos onde pode ocorrer muita perda de precisão ao longo do programa devido ao uso de aritmética de ponto flutuante. Por exemplo: faça exatamente o seguinte em sua calculadora: 5 / 3 * 3 , qual é o resultado? Ao terminar este programa teste a seguinte operação com frações (5/3)*(3/1), qual será o resultado? RESUMO DAS OPERAÇÕES MATEMÁTICAS ENVOLVIDAS: · Simplificação de fração: (a/b)=( (a/mdc(a,b)) / (b/mdc(a,b)) ) · Onde mdc(a,b) retorna o máximo divisor comum de ab. · Soma de fração: (a/b)+(c/d)=( (a.d+c.b) / b.d ) simplificada. · Multiplicação de fração: (a/b) * (c/d)= ( (a*c) / (b*d) ) simplificada. · Igualdade: (a/b)== (c/d) se a*d == b*c. · Não igualdade: (a/b) != (c/d) se a*d != b*c · Maior ou igual que: (a/b)³(c/d) se a*d ³ b*c “SHOPPING LIST APPROACH” PARA O TAD FRAÇÃO: (O conjunto de operações implementadas esta marcado com þ. A finalização dessa shopping list bem como do programa é deixada como exercício, o qual não deve ser difícil pois vendo a implementação da soma, o leitor obtém quase que de maneira direta a implementação da subtração, o mesmo ocorre para as demais operações): þ Construtor (recebe dois argumentos numéricos inteiros) þ Simplificação da fração (divisão do numerador e denominador por máximo divisor comum)þ Soma de fração (método recebendo argumento do próprio tipo fração)ý Subtração de fraçãoþMultiplicaçãoý Divisão 66 þ Teste de igualdadeþ Teste de desigualdadeþ Teste de maior ou igual que ý Teste de menor ou igual que ý Teste de maior queý Teste de menor queþ Impressão na telaþ Rotina de criação com entrada de numerador e denominador pelo tecladoþ Conversão para doubleþ Conversão para longþ Operação de alteração do numeradorý Operação de alteração do denominadorþ Retorno do valor do numerador e denominadorý Outras operações que o leitor julgar necessárias TÓPICOS ABORDADOS: Construtores em geral, criação de métodos de conversão de tipos, chamadas de métodos do mesmo objeto, operador % que retorna o resto da divisão de dois inteiros. CONSIDERAÇÕES DE PROJETO: A representação escolhida para o numerador e o denominador da fração será baseada no tipo int. O formato escolhido para os métodos que implementam as operações é: TipoDoValorDeRetorno NomedaOperacao(TipoDoOperando ValorDoOperando); Nesse formato um dos operandos é a própria fração que está recebendo a chamada de método o outro é passado como argumento. Outros formatos equivalentes poderiam ter sido adotados. Um dos possíveis formatos faz com que os dois operandos sejam passados como argumentos e a fração que está recebendo a chamada de método executa a operação para esses argumentos retornando o valor. Voltaremos a discutir essas alternativas de implementação. Se durante o processo de construção de seu programa, ocorrer a repetição de um certo trecho de código nos diversos métodos (repetição da rotina de simplificação nos métodos de soma, subtração), considere a opção de definir este trecho de código como um método em separado. Se este método não for um método que deva compor, participar, da interface, mas que ainda assim tem seu padrão muito repetido, considere a possibilidade de defini-lo como private (método mdc). Não existe uma regra de ouro que diga exatamente como projetar as suas classes para que elas preencham requisitos de portabilidade, robustez, flexibilidade. Todavia uma recomendações importantes podem ser feitas para evitar reformulações durante o processo de programação: “Não economize tempo na fase de projeto. Procure antes de programar, simular o uso de uma classe, seja mentalmente ou através de um protótipo.” O cálculo do máximo divisor comum (mdc) de dois inteiros não tem nada a ver com as operações a serem oferecidas por frações, teria a ver com as operações oferecidas por um objeto facilidades de cálculos (mdc(a,b) , fatorial(b) , fibonaci(x), combinacoes(n,k) ). No entanto a classe fração precisa da operação mdc. Ocorre que já estudamos uma maneira de implementar um método em uma classe e não oferecê-lo através da interface, é o qualificador private. Em C++ provavelmente o programador implementaria mdc como uma função isolada. IMPLEMENTAÇÃO: 67 //TAD fracao. //File Fracao.java class Fracao { private int num,den; //numerador, denominador private int mdc(int n,int d) //metodo private maximo divisor comum //metodo de Euclides +- 300 anos AC. { if (n<0) n=-n; if (d<0) d=-d; while (d!=0) { int r=n % d; //%=MOD=Resto da divisao inteira. n=d; d=r; } return n; } public Fracao(int t,int m) //construtor comum { num=t; den=m; this.simplifica(); //chamada para o mesmo objeto. } public void simplifica() //divide num e den pelo mdc(num,den) { int commd; commd=mdc(num,den); //divisor comum num=num/commd; den=den/commd; if (den<0) { den=-den; num=-num;}; //move sinal para cima } //operacoes matematicas basicas public Fracao soma (Fracao j) { Fracao g; g=new Fracao((num*j.den)+(j.num*den),den*j.den); return g; } public Fracao multiplicacao(Fracao j) { 70 Esta e’ a fracao a: (5/3) Esta e’ a fracao b: (1/3) c de a+b: (2/1) a*b: (5/9) a+b: (2/1) a>=b: true a==b: false a!=b: true (int)a 1 (double)a 1.66667 //COMENTARIOS: Uma implementação completa do tipo de dados fração tem que checar por “overflow” do numerador e denominador o que ocorre frequentemente quando se trabalha com números primos entre si (não podem ser simplificados), uma possível solução para este problema é fazer uma aproximação e/ou alterar a representação interna da fração para um tipo com maior numero de bits (de int para long). De qualquer forma estas extensões são exercícios avançados pois a última delas envolve uso de herança. Exercícios: 1- Complete o tipo fração com os métodos faltantes da “shopping list approach” long retorna_den(void) {return den;} long altera_den(int a) {den=a;} Considerando que os atributos declarados em private não são acessíveis fora da classe, descreva a utilidade desses métodos. Eles são úteis se usados pelos próprios métodos de fração? 12- Implemente o tipo abstrato de dados número complexo com as operações matemáticas inerentes. Faça antes um projeto dos métodos que serão implementados, descreva (detalhadamente) as operações matemáticas necessárias. Que forma de representação você escolherá: coordenadas polares ou retangulares? 3- Pesquise sobre matrizes em Java: 1.3.2. Crie um tipo abstrato de dados matriz que suporte atribuições e leituras de células contendo elementos do tipo float. Crie outros métodos para este tipo abstrato de dados como multiplicação por uma constante. 4- Implemente o tipo abstrato de dados relógio, pesquise as operações normalmente oferecidas por relógios reais, o único objetivo é marcar as horas. ¿ Se você precisar de inspiração para este exercício, consulte o exemplo da classe Contador e seus exercícios. 1Estes exercícios são considerados difíceis. É recomendável somente esboçar o projeto deles e depois, a implementação pode ser deixada como exercício das próximas seções. 71 1.6.2. STRINGS, UMMODELO DE CLASSE Agora que já estamos programando alguns tipos abstratos de dados, está na hora de apresentar um exemplo que mostre como comportamento é importante para um TAD. Só que desta vez ficaremos do lado do cliente, do usuário desse tipo abstrato de dados e não do lado do programador. Estudaremos a classe String oferecida pela linguagem. Nos tutoriais desta série, feitos para outras linguagens (C++, Modula-3), recomendávamos como exercício a implementação do tipo abstrato de dados string, que deveria suportar operações de concatenação, substring, acesso a elemento, etc. Este exercício não faz sentido em Java porque o tipo String, é fornecido com a linguagem como uma classe da package java.lang que é importada implicitamente em todos os programas além disso a sua implementação desta classe é bastante completa. A declaração de Strings se dá da mesma forma que os outros objetos: String minhaString; . O compilador oferece uma facilidade sintática para a inicialização com valores literais: String teste=“Ola meu amigo”; //objeto instanciado com valor Ola meu amigo Para concatenar Strings use o operador +. Os operandos podem não ser Strings, nesse caso serão convertidos para objetos desta classe, por exemplo se um dos argumentos for um inteiro, o objeto String correspondente conterá o valor literal deste inteiro. System.out.println(teste + “ Andre!”); //Ola meu amigo Andre! teste+=“ Andre!”; //atalho para concatenacao seguida de atribuicao: teste=teste+” Andre!” System.out.println(teste); //totalmente equivalente a primeira Para obter o comprimento em número de caracteres de uma String, chame o método length() para a String em questão. Para obter o caractere presente na posição 6 da String, chame o método charAt(); . Note que o primeiro caractere da String está na posição zero: char umChar=teste.charAt(6); //um char recebe ‘u’ Para obter uma substring, chame o método substring(int a,int b); onde o primeiro argumento é o índice do início da substring e o segundo é o índice do fim da substrings, os caracteres em a e b também são incluídos: String aStr=teste.substring(0,2); //aStr recebe ola Para transformar todos os caracteres de uma String em letras maiúsculas basta chamar o método toUpperCase(); teste=teste.toUpperCase(); //teste fica igual a OLA MEU AMIGO Um método interessante para usar em checagem de padrões em texto é indexOf(String busque); . Este método retorna o índice posição inicial de ocorrência de busque na String para a qual foi chamado o método: teste.indexOf(“MEU”); //retorna 4 Analogamente, lastIndexOf(String busque), retorna o índice de ocorrência da substring, só que agora do procurando do fim para o começo. teste.indexOf(“M”); //resulta em 9 (logo a seguir do ultimo A que esta na posicao 8) Para comparação de igualdade use: test.equals(“OLA MEU AMIGO”); //retorna valor booleano Além disso, a classe String define métodos para conversão dos tipos básicos para seus valores na forma de String, você pode achar esses métodos um pouco estranhos, pois eles tem todos os mesmos nomes e não precisam de um objeto para serem chamados, eles são chamados para a classe: String.valueOf(3); //argumento e naturalmente um inteiro, retorna “3” String.valueOf(3.1415); //argumento e double, retorna “3.1415” Os métodos de nome valueOf são uma padronização de métodos de conversão entre tipos encontrados em algumas das classes pré-definidas na linguagem, principalmente nas classes “wrappers” que foram exemplificadas com a classe Integer. 72 class StringTest { public static void main (String args[]) { String teste="Ola meu amigo"; System.out.println(teste + " Andre!"); //Ola meu amigo Andre! teste+=" Andre!"; //atalho para concatenacao seguida de atribuicao System.out.println(teste); //totalmente equivalente a primeira char umChar=teste.charAt(5); //um char receber ‘e’ System.out.println("Andre "+umChar+teste.substring(3,13)); teste=teste.toUpperCase(); //teste fica igual a OLA MEU AMIGO ANDRE! for (int i=0;i<teste.length();i++) //imprimindo caracteres um a um { System.out.print(teste.charAt(i)); } System.out.println(); //pula uma linha System.out.println(teste.indexOf("AMIGO")); //retorna 8 System.out.println(teste.indexOf("biba")); //nao acha, retorna -1 System.out.println(teste.lastIndexOf("AMIGO")); //retorna 8 System.out.println(String.valueOf(3.1415f)); //Metodo chamado para a classe } } Ola meu amigo Andre! Ola meu amigo Andre! Andre e meu amigo OLA MEU AMIGO ANDRE! 8 -1 8 3.1415 //COMENTARIOS O código fonte disponível com o compilador constitui uma ótima fonte de aprendizado sobre como construir componentes de software reutilizáveis no estilo de programação orientada a objetos, leia-o sempre que estiver disponível e quando não estiver, preste atenção na sua interface. Você deve ter notado que exceto pelo método toUpperCase() a classe String não permite alterações no seu estado depois de ter sido criada. Isto decorre da decisão do “Java team” de implementar duas classes para garantir a funcionalidade e segurança no uso de strings, são elas: 75 private int lc[]; //=new int[linhas*colunas]=vetor[0..(tam-1)]=~matriz[l][c] þ Construtor (recebe dois argumentos numéricos inteiros, número de linhas e de colunas)þ Conversão de linha e coluna para índice linearþ Conversão de índice linear para coluna.þ Conversão de índice linear para linha.þ Operação de troca de elementos da matriz com dois argumentos do tipo índice linear.ý Operação de troca de elementos da matriz com argumentos do tipo (linha e coluna).þ Operação de atribuição a elemento da matriz indicado por índice linear.ý Operação de atribuição a elemento da matriz indicado por linha e coluna.þ Operação de retorno do conteúdo de posição da matriz indicada por um índice linear.ý Operação de retorno do conteúdo de posição da matriz indicada por linha e coluna. þ Representação do número de colunas, permitindo acesso de leitura ao cliente.þ Representação do número de linhas, permitindo acesso de leitura ao cliente. Programa exemplo da classe Matriz2DInt. class Matriz2DInt { private int linhas; //numero de linhas da matriz private int colunas; //numero de colunas da matriz private int tam; //=linhas*colunas private int lc[]; //=new int[linhas*colunas]=vetor[0..(tam-1)]=~matriz[l][c] public Matriz2DInt(int l,int c) //cria matriz LxC { lc=new int[l*c]; //l,c dimensoes ; lc vetor[l*c] linhas=l; colunas=c; tam=linhas*colunas; } //qualquer uma das funcoes abaixo retorna int negativo se nao obteve sucesso public int linear(int alin,int acol) //ind linear a partir de linha e coluna //nao modifica nenhum atributo { int result; //valor de retorno para todos os metodos ... if ( (0<alin) && (alin<=linhas) && (0<acol) && (acol<=colunas) ) { result=(alin-1)*colunas+acol; } else { result=-1; } return result; } public int col(int indlin) //coluna a partir do indice linear //nao modifica nenhum atributo da classe { 76 int result; if ( (0<indlin) && (indlin<=tam) ) { result=(indlin % colunas); if (result==0) { result=colunas; } } else { result=-1; } return result; } public int lin(int indlin) //linha a partir do indice linear //nao modifica nenhum atributo da classe { int result; if ( (0<indlin) && (indlin<=tam) ) { result=(int)( ( (indlin-1)/colunas )+1 ); } else { result=-1; } return result; } public boolean trocaindlin(int i,int j) //argumentos: 2 indices lineares // retorna se conseguiu,ou nao conseguiu(false) { int aux; //auxiliar na troca if ( (0<i) && (i<=tam) && (0<j) && (j<=tam) ) { aux=lc[i-1]; //efetua a troca lc[i-1]=lc[j-1]; //embora para usuario a matriz vai de 1 ate l*c lc[j-1]=aux; //para mim vai de o ate l*c-1 return true; //sucesso } else { return false; } //falhou } public boolean atribuiindlin(int i,int v) //atribui v ao indice i //retorna true se conseguiu, false nao conseguiu { if ( (0<i) && (i<=tam) ) { lc[i-1]=v; //efetua a atribuicao return true; } else { return false; } //falhou 77 } public int retornaindlin(int indlin) //retorna conteudo do indice i //retorna -1 se nao conseguiu { int result; if ( (0<indlin) && (indlin<=tam) ) { result=lc[indlin-1]; } else { result=-1; } return result; } public int getl() //retorna numero de linhas { return linhas; } public int getc() //retorna numero de colunas { return colunas; } public int gett() //retorna tamanho { return tam; } } //Classe principal, Arquivo Principal.java class Principal { public static void main(String args[]) { Matriz2DInt teste; teste=new Matriz2DInt(5,10); //5 linhas 10 colunas for(int i=1;i<teste.gett();i++) {teste.atribuiindlin(i,0); } System.out.println("linear(5,5)="+ teste.linear(5,5) ); System.out.println("Atribuindo 2 a posicao (5,5)"); teste.atribuiindlin(teste.linear(5,5),2); System.out.println("Atribuindo 4 a posicao (4,2)"); teste.atribuiindlin(teste.linear(4,2),4); System.out.println("Trocando estas posicoes"); teste.trocaindlin(teste.linear(5,5),teste.linear(4,2)); System.out.println("Conteudo da posicao (5,5):"+teste.retornaindlin(teste.linear(5,5))); 80 2. HERANÇA Existe uma visão um pouco tacanha de orientação a objetos como uma simples maneira de organizar melhor o seu código. Essa visão é facilmente desmentida pelos conceitos de encapsulamento, interfaces, packages e outros já apresentados. Neste tópico apresentaremos o conceito de herança, fundamental para programação orientada a objetos e um dos fatores de sucesso desta como muito mais que uma simples maneira de organizar melhor seu código. Um dos aspectos que distinguem objetos de procedimentos e funções é que o tempo de existência de um objeto pode ser maior do que o do objeto que o criou. Isto permite que em sistemas distribuídos objetos criados em um local, sejam passados através da rede para outro local e armazenados lá quem sabe na memória ou mesmo em um banco de dados. Curiosidade: Existem classes que podem ser obtidas na Internet para fazer interface com bancos de dados SQL, servindo principalmente para facilitar esta faceta da programação na internet que é bastante limitada pelas restrições de segurança da linguagem. 2.1. HIERARQUIAS DE TIPOS Neste tópico mostraremos como construir hierarquias de tipo por generalização / especialização. Para entender o que é generalização especialização e as regras de atribuição entre elementos dessas hierarquias, acompanhe a seguinte comparação: Se você vai a um restaurante e pede o prato de frutos do mar, é natural que você aceite uma lagosta com catupiry, ou então filé de badejo. Mas se o garçom lhe serve uma salada de tomates isto não se encaixa no pedido. Por outro lado, se o seu pedido for peixe, uma lagosta com catupiry, embora muito saborosa não serve mais1, assim como a salada. Note que peixe e lagosta são especializações de frutos do mar. Generalização e Especialização são ferramentas para lidar com complexidade, elas são abstrações. Os sistemas do mundo real apresentam complexidade muito maior que ordenar um prato listado em um cardápio. O uso de generalização e especialização permite controlar a quantidade de detalhes presente nos seus modelos do mundo real, permite capturar as características essenciais dos objetos e tratar os detalhes de forma muito mais organizada e gradual. Existe muito mais para falar sobre herança, principalmente no que diz respeito a polimorfismo de inclusão e acoplamento dinâmico de mensagens, tópicos estes que serão abordados em separado. 2.1.1. UMA HIERARQUIA SIMPLES. Construiremos uma hierarquia de tipos simples para demonstrar herança pública em Java. 1lagosta é crustáceo não peixe. 81 Comentários: O diagrama acima representa a hierarquia de classes implementada neste exemplo e foi obtido a partir da janela de edição de uma ferramenta case para programação orientada a objetos. A classe ponto que está no topo da hierarquia é chamada de classe base, enquanto que as classes ponto_reflete e ponto_move são chamadas classes filhas ou herdeiras. As classes da hierarquia são simples, ponto_move apresenta o método move, já abordado neste tutorial em 1.2.6, ponto_reflete apresenta o método (reflete) que inverte o sinal das coordenadas. Dada a simplicidade das classes o leitor poderia se perguntar, porque não juntar as três em uma só. A pergunta faz sentido, mas e se quiséssemos criar uma classe Ponto que não se movesse, apenas refletisse e outra que só se movesse? E se quiséssemos projetar nosso programa segundo uma hierarquia de especialização / generalização da classe Ponto? O exemplo mostra como fazê- lo. Na herança as classes filhas passam a atender pelos mesmos métodos e atributos public da classe pai, as classes filhas podem acrescentar métodos, atributos e até redefinir métodos herdados (veremos mais tarde). Por isso é que se diz que as classes subclasses garantem pelo menos o comportamento “behaviour” das superclasses, podendo acrescentar mais características. Os atributos encapsulados (private) da classe pai não são acessíveis diretamente na classe filha a não ser que sejam qualificados como protected ou public, veja 2.1.2. Diagrama de acesso, visibilidade, dos elementos da classe pai para uma classe filha ou PUBLIC herdeira. Os atributos e métodos da classe pai são classificados quanto ao PRIVATE encapsulamento. A parte sombreada significa não visível, encapsulado. PROTECTED *As duas são consideradas como sendo do mesmo package. “PACKAGE”* Construtores e herança: No construtor de uma classe filha o programador pode incluir a chamada do construtor da classe pai existente nela. Para referenciar a classe pai use a “keyword “ super de modo analogo a this (objeto corrente). Hierarquia de generalização e especialização. //Classe Ponto class Ponto { private float x,y; public Ponto(float ax,float ay) //omita o valor de retorno! 82 //garante o estado do objeto { this.x=ax; this.y=ay; } public void inicializa(float a,float b) { this.x=a; this.y=b; } public float retorna_x() { return x; } public float retorna_y() { return y; } public void altera_x(float a) { this.x=a; } public void altera_y(float b) { this.y=b; } public void mostra() { System.out.println( "(" + this.x + "," + this.y + ")" ); } } //Classe PtoMove class PtoMove extends Ponto { //adicione algum atributo private se quiser public PtoMove(float a,float b) { super(a,b); //chamada do construtor da classe pai } public void move(float dx,float dy) { this.altera_x(retorna_x()+dx); 85 Outra frase sobre protected: “A herança permite que uma subclasse ganhe acesso a declarações protected de sua superclasse, mas o usuário não percebe isso, para o usuário (uma classe externa) o que continua existindo é o que é public”. Diagramas de acesso, visibilidade, de atributos e métodos de uma classe pai para uma classe filha ou herdeira: Para uma classe filha em outro package (você herdando de uma classe pronta em Java) PRIVATE PROTECTED “PACKAGE” PUBLIC O que o restante do programa vê das declarações da classe pai na classe filha. PRIVATE (por restante do programa entenda: outros packages e outras hierarquias) PROTECTED “PACKAGE” PUBLIC O mesmo exemplo só que usando protected. //Classe Ponto class Ponto { protected float x,y; public Ponto(float ax,float ay) //omita o valor de retorno! //garante o estado do objeto { this.x=ax; this.y=ay; } public void inicializa(float a,float b) { this.x=a; this.y=b; } public float retorna_x() { return x; } public float retorna_y() { return y; } public void altera_x(float a) { this.x=a; } 86 public void altera_y(float b) { this.y=b; } public void mostra() { System.out.println( "(" + this.x + "," + this.y + ")" ); } } //Classe PtoMove class PtoMove extends Ponto { //adicione algum atributo private se quiser public PtoMove(float a,float b) { super(a,b); } public void move(float dx,float dy) { x=x+dx; //aqui continuam acessiveis, em main nao y=y+dy; //acesso direto, sem passar por metodo } } //Classe PtoReflete class PtoReflete extends Ponto { //adicione algum atributo private se quiser public PtoReflete(float a, float b) { super(a,b); //chamando o construtor da classe pai } void reflete() { x=-x; y=-y; } } 87 //Classe principal, Arquivo Principal.java class Principal { public static void main(String args[]) { PtoReflete p1=new PtoReflete(3.14f,2.72f); System.out.println("Criando PontoReflete em 3.14,2.72"); p1.reflete(); System.out.println("Refletindo este ponto."); p1.mostra(); PtoMove p2=new PtoMove(1.0f,1.0f); System.out.println("Criando PontoMove em 1.0,1.0"); p2.move(.5f,.5f); System.out.println("Movendo este ponto de 0.5,0.5"); p2.mostra(); } } Criando PontoReflete em 3.14,2.72 Refletindo este ponto. (-3.14,-2.72) Criando PontoMove em 1.0,1.0 Movendo este ponto de 0.5,0.5 (1.5,1.5) O qualificador protected termina com os modos de encapsulamento de atributos e métodos, portanto faremos uma revisão final, utilize os números dessa tabela para checar visualmente o tipo de encapsulamento no diagrama a seguir. Você vai aprender a olhar para este diagrama e enxergar tudo o que aprendemos nesse assunto, os atributos e métodos que estamos preocupados em encapsular são os da classe mais escura: MODO REPRESENTAÇÃO LIMITE DE VISIBILIDADE 4)private Representação no diagrama: a própria classe escura. Este é o nível de encapsulamento mais restritivo. A visibilidade das declarações limita-se ao envoltório da classe. 3) protected Representação no diagrama: a hierarquia abaixo da classe escura. A visibilidade das declarações se limita própria classe e as classes herdeiras dela. 2)Nada especificado “package” Representação no diagrama: retângulo envolvendo as classes pintadas. A visibilidade das declarações se limita a própria classe e as classes do mesmo package, mas não às classes herdeiras, . Classes herdeiras não precisam ser do mesmo package. 1 )public Representação no diagrama: todas as classes. Estas declarações são sempre acessíveis. 90 System.out.println(“Metodo redefinido na classe X, chamado.”) ; 2.2. INTERFACES, UMA ALTERNATIVA PARA HERANÇAMÚLTIPLA Herança múltipla: Herança múltipla é a capacidade de uma classe herdar de duas ou mais classes, por exemplo a classe radio-relógio herdar da classe rádio e da classe relógio. C++ apresenta herança múltipla, e também maneiras de tratar os problemas decorrentes de seu uso. Um dos problemas que podem surgir é o conflito de nomes de atributos ou métodos herdados desse tipo de herança. Uma das estratégias adotadas para resolver estes conflitos é o “renaming” ou renomeamento desses nomes iguais presentes nas superclasses. Tendo o seguinte significado: A classe herdeira tem comportamento, “behaviour”, semelhante ao das duas classes pais. Um outro exemplo de interface seria a classe audio-vídeo que herda da classe audio e da classe vídeo. Java por motivos de simplicidade, abandona a idéia de herança múltipla, cedendo lugar ao uso de interfaces. Interfaces são um conjunto de métodos e constantes (não contém atributos). Os métodos definidos na interface são “ocos” ou desprovidos de implementação. Classes podem dizer que implementam uma interface, estabelecendo um compromisso, uma espécie de contrato, com seus clientes no que se refere a prover uma implementação para cada método da referida interface.. Ao cliente, pode ser dada a definição da interface, ele acaba não sabendo o que a classe é, mas sabe o que faz. Quem programa em Objective C, deve ver as interfaces como algo semelhante ao conceito de protocolos. Neste exemplo usaremos uma interface de nome imprimível para capturar as características comuns as classe que podem ser imprimidas em algum dispositivo de saída de dados. Interfaces public interface Imprimivel { //alem das classes, so interfaces pode ocupar um arquivo final char nlin='\n'; //nova linha public String toString(); //forma preferida para impressao na tela public void toSystemOut(); } public class Produto implements Imprimivel { //um produto comercial qualquer protected String descricao; protected int quantidade; public Produto(String d,int q) { descricao=d; quantidade=q; } 91 public String toString() { return new String(" "+descricao+" "+quantidade); } //forma preferida para impressao na tela public void toSystemOut() { System.out.print(descricao + quantidade); } } //Classe principal, Arquivo Principal.java class Principal { public static void main(String args[]) { Produto ump=new Produto("macarrao", 100); ump.toSystemOut(); System.out.println(); System.out.println(ump.toString()); } } macarrao100 macarrao 100 //COMENTARIOS O paradigma de orientação a objetos está refletido na capacidade de herança e encapsulamento das interfaces. No caso deste exemplo, a interface foi declarada como public, mas se nada fosse especificado ela pertenceria ao package dela, ou seja os modos de encapsulamentos são semelhantes aos de classes. Uma interface poderia estender a interface Imprimivel: interface Imprimivel2 extends Imprimivel { } Interfaces tem sido representadas por retângulos de bordas arredondadas ligadas as classes que as implementam por linhas tracejadas. Muitos confundem interfaces com classes e o ato de implementar uma interface com o ato de estender ou herdar uma classe. Por isso a relação entre interfaces e herança será explicada só agora, depois que você já pensou no assunto. Uma classe Produto2 herda da classe Produto(nosso exemplo) que implementa a interface Imprimivel. A classe produto já fez a parte difícil que é implementar a interface, agora a classe 92 Produto2 pode optar por aceitar ou redefinir os métodos herdados, ou seja: “A interface é um dos ítems que é herdado de uma classe, assim como os atributos e métodos”. Exercícios: 1- Defina uma interface para um conjunto de classes que representam figuras geométricas que podem ser desenhadas na tela. 95 c=a.soma(b); c.mostra(); System.out.print("a>=b: "); System.out.println(a.maiorouigual(b)); System.out.print("a==b: "); System.out.println(a.igual(b)); System.out.print("a!=b: "); System.out.println(a.diferente(b)); System.out.print("(int)a "); System.out.println(a.converteint()); System.out.print("(double)a "); System.out.println( a.convertedbl()); } } Esta e' a fracao a: (5/1) Esta e' a fracao b: (1/3) c de a+b: (16/3) a*b: (5/3) a+b: (16/3) a>=b: true a==b: false a!=b: true (int)a 5 (double)a 5 Teste o “copy constructor” para o tipo abstrato de dados fração apresentado acima. Quando um só número for passado para o construtor desta classe, subentende-se que o construtor chamado é o de um só argumento inteiro e que portanto o denominador será igual a 1. Agora vamos falar do “copy constructor”, que embora implementado, não foi testado em main() . Esse método, pertence a outro objeto que não o argumento copieme, então para distinguir o atributo num deste objeto, do atributo num de copieme usamos copieme.num e simplesmente num para o objeto local, objeto em questão, ou objeto dono do método chamado. Exercícios: 1- Faça um “copy constructor” para uma das classes já implementadas neste texto. 2- Sobrecarregue o método move da classe Ponto para aceitar um Ponto como argumento, subentende-se que devemos mover a distância x e a distância y daquele ponto a origem. 3- 96 Crie um método de nome unitarizado para a classe Ponto. Este método deve interpretar o Ponto como um vetor e retornar um novo Ponto que contém as coordenadas do vetor unitarizado. Unitarizar é dividir cada coordenada pelo módulo do vetor. O módulo é a raiz quadrada da soma dos quadrados das componentes. 3.2.2. SOBRECARGA DE OPERADOR Java não fornece recursos para sobrecarga de operador, o que é perfeitamente condizente com a filosofia da linguagem. Seus criadores que acreditavam que a linguagem deveria ser pequena, simples, segura de se programar e de se usar (simple, small, safe and secure). A ausência de sobrecarga de operadores pode ser contornada definindo apropriadamente classes e métodos. 3.3. CLASSES ABSTRATAS E CONCRETAS Em um dos exercícios anteriores (no tópico sobre herança) pedíamos que você definisse uma hierarquia composta de três classes. A classe pai tinha o nome de Forma, e as classes herdeiras desta eram Ponto e Retangulo. Embora a classe forma não possuísse sentido prático, ela permitia certas operações como move, altera_x(int nx), entre outras (retorne a este exercício). Na verdade o que desejávamos era que esta classe Forma se comportasse como um esqueleto para as suas classes filhas, nós não queríamos instanciá-la. Classes abstratas permitem exatamente isto pois não podem ser instanciadas embora possam ser usadas de outras maneiras. Classes abstratas são poderosas, elas permitem: criação de listas heterogêneas, ocorrência de “dynamic binding” e maior clareza no projeto de sistemas. Os packages que vem com a linguagem estão repletos de exemplos de classes abstratas. Métodos abstratos, obrigatoriamente pertencem a classes abstratas, e são métodos desprovidos de implementação, são apenas definições que serão aproveitadas por outras classes da hierarquia. Voltando ao exemplo da hierarquia Forma, Ponto e Retangulo. O método mostra poderia ter sido definido na classe base abstrata (Forma) como um método abstrato. Classes abstratas //Classe Forma abstract class Forma { 97 protected float x,y; //visivel hierarquia abaixo public void move(float dx,float dy) { this.x+=dx; this.y+=dy; } abstract public void mostra(); //metodo abstrato } //Classe ponto class Ponto extends Forma { public Ponto(float ax,float ay) //omita o valor de retorno! //garante o estado do objeto { this.x=ax; this.y=ay; } //move nao precisa ser redefinido public void mostra() { System.out.println("("+this.x+","+this.y+")"); } } //Classe Retangulo class Retangulo extends Forma { protected float dx,dy; //delta x e delta y //protected acaba sendo menos inflexivel e mais eficiente que private public Retangulo(float ax,float ay,float dx,float dy) //garante o estado do objeto { x=ax; y=ay; this.dx=dx; this.dy=dy; //this usado para eliminar ambiguidade } //metodo move precisa ser redefinido public void move(float dx,float dy) { this.x+=dx; this.y+=dy; this.dx+=dx; this.dy+=dy; //this distingue o argumento do atributo de mesmo nome