Você está na página 1de 154

Alexandre Michael Souza de Melo Thiers Garretti Ramos Sousa

Programação de Computadores II

Alexandre Michael Souza de Melo Thiers Garretti Ramos Sousa Programação de Computadores II

Jouberto Uchôa de Mendonça Reitor

Amélia Maria Cerqueira Uchôa Vice-Reitora

Jouberto Uchôa de Mendonça Junior Pró-Reitoria Administrativa - PROAD

Ihanmarck Damasceno dos Santos Pró-Reitoria Acadêmica - PROAC

Domingos Sávio Alcântara Machado Pró-Reitoria Adjunta de Graduação - PAGR

Temisson José dos Santos Pró-Reitoria Adjunta de Pós-Graduação e Pesquisa - PAPGP

Gilton Kennedy Sousa Fraga Pró-Reitoria Adjunta de Assuntos Comunitários e Extensão - PAACE

Jane Luci Ornelas Freire Gerente do Núcleo de Educação a Distância - Nead

Andrea Karla Ferreira Nunes Coordenadora Pedagógica de Projetos - Nead

Lucas Cerqueira do Vale Coordenador de Tecnologias Educacionais - Nead

Equipe de Elaboração e Produção de Conteúdos Midiáticos:

Alexandre Meneses Chagas - Supervisor Ancéjo Santana Resende - Corretor Andira Maltas dos Santos – Diagramadora Claudivan da Silva Santana - Diagramador Edilberto Marcelino da Gama Neto – Diagramador Edivan Santos Guimarães - Diagramador Fábio de Rezende Cardoso - Webdesigner Geová da Silva Borges Junior - Ilustrador Márcia Maria da Silva Santos - Corretora Marina Santana Menezes - Webdesigner Matheus Oliveira dos Santos - Ilustrador Pedro Antonio Dantas P. Nou - Webdesigner Rebecca Wanderley N. Agra Silva - Designer Rodrigo Otávio Sales Pereira Guedes - Webdesigner Rodrigo Sangiovanni Lima - Assessor Walmir Oliveira Santos Júnior - Ilustrador

Redação:

Núcleo de Educação a Distância - Nead Av. Murilo Dantas, 300 - Farolândi Prédio da Reitoria - Sala 40 CEP: 49.032-490 - Aracaju / SE Tel.: (79) 3218-2186 E-mail: infonead@unit.br Site: www.ead.unit.br

Impressão:

Gráfica Gutemberg Telefone: (79) 3218-2154 E-mail: grafica@unit.br Site: www.unit.br

Banco de Imagens:

Shutterstock

M528p

Melo, Alexandre Michael Souza de Programação de computadores II / Ale- xandre Michael Souza de Melo, Thiers Gar- retti Ramos Sousa. –Aracaju : UNIT, 2011. 152 p.: il.

Inclui bibliografia

1. Informática 2. Programação de com-

putadores. I. Sousa, Thiers Garretti Ramos Universidade. II. Tiradentes – Educação à Distância III. Titulo

CDU : 004.42

Copyright © Sociedade de educação Tiradentes.

Apresentação

Prezado(a) estudante,

A modernidade anda cada vez mais atrelada ao tempo,

e a educação não pode ficar para trás. Prova disso são as

nossas disciplinas on-line, que possibilitam a você estudar com o maior conforto e comodidade possível, sem perder a qualidade do conteúdo.

Por meio do nosso programa de disciplinas on-line você pode ter acesso ao conhecimento de forma rápida,

prática e eficiente, como deve ser a sua forma de comunicação

e interação com o mundo na modernidade. Fóruns on-line,

chats, podcasts, livespace, vídeos, MSN, tudo é válido para

o seu aprendizado.

Mesmo com tantas opções, a Universidade Tiradentes optou por criar a coleção de livros Série Bibliográfica Unit como mais uma opção de acesso ao conhecimento. Escrita por nossos professores, a obra contém todo o conteúdo da disciplina que você está cursando na modalidade EAD e representa, sobretudo, a nossa preocupação em garantir o seu acesso ao conhecimento, onde quer que você esteja.

o seu acesso ao conhecimento, onde quer que você esteja. Desejo a você bom aprendizado e

Desejo a você bom aprendizado e muito sucesso!

Professor Jouberto Uchôa de Mendonça Reitor da Universidade Tiradentes

Sumário

Parte 1: Princípios da Programação Orientada a Objetos em

11

Tema 1: Introdução à Orientação a Objetos

 

13

1.1 Revisando a Linguagem

 

14

1.2 Conceitos Básicos da OO

 

.

.

.

.

.

.

.

.

.

.

.

.

.

23

1.3 Classes e

Objetos

 

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

29

1.4 Atributos e Métodos

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

37

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

45

Tema 2: Herança

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.47

2.1 Construtores e o Gerenciamento da Memória

 

48

2.2 Membros de Instância X Membros de Classe

 

.

.

.

.

.

.

.

.

.

54

2.3 Herança

no

Java .

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.61

2.4 A classe

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

68

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

75

Parte 2: Aspectos Avançados da Programação Orientada a Objetos em Java

 

77

Tema 3: Polimorfismo

 

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.79

3.1 Do Conceito à Prática

 

.

.

.

.

.

.

.

.

.

.

.

80

3.2 Final

Abstract X

 

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

87

Interfaces

3.3 .

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

94

3.4 Interfaces Especiais

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

102

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.110

Tema 4: Exceções, Coleções e Fluxos

 

111

4.1 Tratamento de Exceções

 

112

4.2 Avançando nas Exceções

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

120

4.3 Coleções da Java API

 

.

.

.

.

.

.

.

.

.

.

.

.

128

4.4 Fluxo de Dados .

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.136

144

Referências

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

145

Concepção da Disciplina

Ementa

Introdução à Orientação a Objetos: Revisando

a

Linguagem Java, Conceitos Básicos da OO, Classes

e

Objetos, Atributos e Métodos. Herança: Constru-

tores e o Gerenciamento da Memória, Membros de Instância X Membros de Classe, Herança no Java, A classe Object. Polimorfismo: Do Conceito à Prática, Abstract X Final, Interfaces, Interfaces Especiais. Exceções, coleções e fluxos: Tratamento de Exceções, Avançando nas Exceções, Coleções da Java API, Fluxos de Dados.

Objetivos:

Geral

Apresentar ao aluno os conceitos do paradigma da Programação Orientada a Objetos na linguagem de programação Java.

Específicos

• Desenvolver a capacidade de abstração do mundo real para implementação em aplicações computacionais;

• Escrever trechos de código reutilizáveis e de fácil manutenção, viabilizando a cons- trução de aplicações mais complexas.

Orientação para Estudo

A disciplina propõe orientá-lo em seus proce-

dimentos de estudo e na produção de trabalhos científicos, possibilitando que você desenvolva em seus trabalhos e pesquisas o rigor metodo- lógico e o espírito crítico necessários ao estudo.

Tendo em vista que a experiência de estu- dar a distância é algo novo, é importante que você observe algumas orientações:

• Cuide do seu tempo de estudo! Defina um horário regular para acessar todo o conteúdo da sua disciplina disponível neste material impresso e no Ambiente Virtual de Apren- dizagem (AVA). Organize-se de tal forma para que você possa dedicar tempo sufi- ciente para leitura e reflexão;

• Esforce-se para alcançar os objetivos pro- postos na disciplina;

• Utilize-se dos recursos técnicos e huma- nos que estão ao seu dispor para bus- car esclarecimentos e para aprofundar as suas reflexões. Estamos nos referindo ao contato permanente com o professor e com os colegas a partir dos fóruns, chats e encontros presenciais. Além dos recursos disponíveis no Ambiente Virtual de Aprendizagem – AVA.

Para que sua trajetória no curso ocorra de forma tranquila, você deve realizar as atividades propostas e estar sempre em contato com o professor, além de acessar o AVA.

Para se estudar num curso a distância deve- se ter a clareza que a área da Educação a Distân- cia pauta-se na autonomia, responsabilidade, coo- peração e colaboração por parte dos envolvidos, o que requer uma nova postura do aluno e uma nova forma de concepção de educação.

Por isso, você contará com o apoio das equipes pedagógica e técnica envolvidas na ope- racionalização do curso, além dos recursos tecno- lógicos que contribuirão na mediação entre você e o professor.

PRINCÍPIOS DA PROGRAMAÇÃO ORIENTADA A OBJETOS EM JAVA

Parte 1

1
1

Introdução à Orientação a Objetos

Neste nosso primeiro tema, veremos uma introdução ao conceito de Orientação a Objetos (OO). Inicialmente, comentaremos o novo paradigma de desenvolvimento de aplicações e em seguida breves comentários sobre os conceitos básicos. Haverá ainda a apresentação dos conceitos de objetos e também de classes, com seus atributos e métodos. Após este capítulo, estaremos preparados para aprofundar os pilares da programação Orientada a Objetos. Caso você já possua alguma informação a respeito da lingua- gem orientada a objetos (como Java, por exemplo), este livro pode ajudá-lo a aprofundar os seus conhecimentos, porém não fi- que preocupado caso você não possua nenhuma experiência com a programação OO. Como a linguagem Java será usada para ensinar conceitos de OO, neste primeiro momento será feita uma breve revisão da linguagem Java (sinta-se à vontade para rever o que achar necessário sobre a linguagem no livro anterior “Programação de Computadores I”).

14

Programação de Computadores II

1.1 Revisando a Linguagem Java

História A tecnologia Java foi criada em 1991, a partir de um projeto desenvolvido pela empresa Sun Microsystems (este projeto foi lide- rado por James Gosling), e, poucos anos depois, em 1995, ganhou notoriedade mundial. Isto aconteceu a partir do momento em que a linguagem de programação passou a ser o foco principal da empresa. A plataforma Java, desde o momento inicial, quando foi anunciada pela Sun, é composta pela Máquina Virtual Java (JVM) e pela API JAVA; logo em seguida, esta plataforma passou a ser incorporada ao navegador Netscape Navigator (principal navegador da época). Esse fato impulsio- nou a grande aceitação no uso da plataforma Java, fato verificado nos anos seguintes.

Características Vejamos a seguir algumas características que propiciaram o crescimento e a grande aceitação da linguagem Java no cenário de desenvolvimento web.

a) Simplicidade: a linguagem Java é fácil de aprender e de pro- gramar (ainda mais fácil para desenvolvedores da linguagem C++, já que tem muita semelhança com ela); para não se tor- nar complexa, Java não incorporou mecanismos mais sofisti- cados como ponteiros e herança múltipla; o gerenciamento de memória ainda está presente na linguagem Java, mas não é responsabilidade do desenvolvedor, pois o controle é totalmente automático.

b) Orientada a objetos: Java é uma linguagem totalmente OO, (quase) tudo é considerado um objeto (a exceção fica por conta dos tipos primitivos, os quais não são objetos por uma questão de desempenho); são oferecidos todos os mecanis- mos presentes nas linguagens orientadas a objetos, como por exemplo polimorfismo, encapsulamento e herança.

Tema 1

|

Introdução à orientação a objetos

15

c) Portabilidade: a Máquina Virtual Java (JVM – Java Virtual Ma- chine) garante a portabilidade de todos os programas escri- tos em Java; estes programas escritos no formato texto (com a extensão .java) quando são compilados geram automatica- mente um outro arquivo bytecode com a extensão .class. Este bytecode pode ser executado em qualquer ambiente desde que lá contenha a JVM, garantindo a veracidade da frase wri- te once, run anywhere(traduzindo: escreva uma vez, rode em qualquer lugar).

” (traduzindo: escreva uma vez, rode em qualquer lugar). Figura 01 - A portabilidade permite que

Figura 01 - A portabilidade permite que o seu código seja executado em qualquer sistema operacional.

d) Alto desempenho: por executar um código que já foi ana- lisado e convertido para um bytecode, obtém-se um bom desempenho. O coletor de lixo é outro fator que melhora a performance por liberar memória que não está mais sendo usada. Além disso, o fato de compilar no próprio ambiente que será posteriormente interpretado, faz com que a perfor- mance seja melhor.

e) Interpretada: em um primeiro momento ela é compilada, gerando um arquivo bytecode intermediário que pode ser executado em qualquer sistema operacional (desde que con- tenha a máquina virtual Java).

16

Programação de Computadores II

1 6 Programação de Computadores II Figura 02 - Após ser compilado, o arquivo fonte (*.java)

Figura 02 - Após ser compilado, o arquivo fonte (*.java) se transforma em um arquivo binário (*.class) e em seguida pode ser interpretado (podendo antes disso ainda ser verificado).

f) Robustez: entre algumas características que tornam lingua- gem Java robusta, podemos citar: possuir no momento da compilação uma varredura por código não alcançável; procu- rar e identificar variáveis não inicializadas; ser uma lingua- gem fortemente tipada; inicializar de maneira implícita todos os tipos primitivos e possuir uma eficiente manipulação de exceções.

g) Segurança: ser uma linguagem segura é muito importante, já que foi projetada para ambientes distribuídos; a JVM garante em um ambiente web que todos as aplicações Java fiquem protegidas de programas maliciosos escritos em qualquer ou- tra linguagem. Além disso, a coleta automática de lixo tam- bém evita erros de gerenciamento de memória frequentes em outras linguagens. Há também mecanismos de tratamento de erros e o fato de todas as variáveis terem obrigatoriamente os tipos informados no momento da declaração fortalecem ainda mais o fator segurança.

Tema 1

|

Introdução à orientação a objetos

17

h) Multithread: técnica que projeta e implementa aplicações paralelas de forma eficiente e que permite a utilização de mecanismos de sincronização entre processos.

Por que usar Java ? Há muito tempo Java é a linguagem mais utilizada em todo o mundo e a plataforma Java é o mecanismo que permite aproximar o usuário das informações, através dos inúmeros sistemas utilizados na Internet.

através dos inúmeros sistemas utilizados na Internet. Figura 03 - Ranking tiobe A API Java fornece

Figura 03 - Ranking tiobe

A API Java fornece aos desenvolvedores uma vasta biblioteca tanto para ambiente desktop (SWT, Swing) como para aplicações web (JSP, JSF, servlet). A curva de aprendizado desta linguagem é muito suave, mais ainda para conhecedores das linguagens C ou C++, das quais Java apresenta muita semelhança; ou mesmo para programadores que co- nhecem o conceito de orientação a objetos.

Elementos Básicos da Linguagem Java Como falamos nas características da Linguagem Java, ela é for- temente tipada (suas variáveis têm obrigatoriamente que informar o tipo de dado no momento da declaração). Para esta declaração ser feita, podem ser utilizados os tipos primitivos, os quais estão

18

Programação de Computadores II

presentes na primeira coluna da tabela abaixo; nas demais colunas, podemos verificar os tamanhos, intervalos e valores iniciais:

verificar os tamanhos, intervalos e valores iniciais: Figura 04 - Os tipos primitivos da linguagem Java.

Figura 04 - Os tipos primitivos da linguagem Java.

Para usar uma variável, devemos informar o seu nome, tipo e valor inicial, pois o tipo determina o conjunto de valores que esta variável pode assumir. Devemos lembrar o conceito de escopo de variável, ou seja, partindo do princípio que uma variável deve ser definida dentro de um método, fora deste método esta variável não será mais visível. Agora devemos comentar sobre os operadores, os quais podem ser categorizados em:

a) aritméticos: + (soma), - (subtração), * (multiplicação), / (divisão) e % (módulo);

b) unários: ++ (pré-incremento ou pós-incremento), -- (pré-de- cremento ou pós-decremento);

c) relacionais: == (igualdade), != (desigualdade), < (menor), <= (menor ou igual), > (maior), >= (maior ou igual);

Tema 1

|

Introdução à orientação a objetos

19

e) de atribuição: = (atribuição simples),

+= (x += y; equivale a x = x + y;)

-= (x -= y; equivale a x = x - y;)

*= (x *= y; equivale a x = x * y;)

/= (x /= y; equivale a x = x / y;)

%= (x %= y; equivale a x = x % y;)

Para comentar um trecho de código, existem três possibilidades:

a) comentário de linha

// isto está comentado

b) comentário de bloco

/* estas linhas estão comentadas */

c) iniciando com barra e dois asteriscos, semelhante ao co- mentário de bloco mas podendo gerar um arquivo html de documentação automaticamente.

/** estas linhas estão comentadas e podem gerar um html de documentação */

As instruções de um código são geralmente executadas de maneira sequencial, de cima para baixo, na ordem em que apare- cem. Sentenças de controle de fluxo, no entanto, podem alterar este fluxo de execução. As estruturas de controle disponibilizadas pela linguagem Java são agrupadas da seguinte forma: declarações de tomada de deci- são (if, if-else, switch), declarações de laço (for, while, do-while) e as declarações de interrupção (break, continue, return). A declaração if é a mais simples das declarações de controle de fluxo. Um bloco de instruções será executado somente se um

20

Programação de Computadores II

determinado teste (expressão booleana) for verdadeiro. É possível executar outro bloco de instruções quando o teste for falso, para isso basta usar a opção else.

if (teste) { // instruções quando o teste for verdadeiro

}

else { // instruções quando o teste for falso

}

Já a instrução switch permite vários caminhos de execução possíveis. Simula um código com múltiplos if’s. A única restrição é que o tipo de dado da variável a ser analisada seja byte, short, int ou char; na versão Java7 já é possível usar String também.

switch (variável) { case x: instruções A; break; case y: instruções B; break; case z: instruções C; break;

}

As declarações while e do-while executam repetidamente um bloco de instruções enquanto uma expressão booleana (teste) for verdadeira.

while (expressão booleana) { // instruções

}

A diferença entre estas declarações é que do-while avalia sua expressão na parte inferior do loop ao invés do topo (como no whi- le). Consequentemente, as instruções dentro do bloco do-while são sempre executados pelo menos uma vez.

do {

// instruções

} while (expressão booleana);

Tema 1

|

Introdução à orientação a objetos

21

A declaração for fornece uma maneira compacta para iterar sobre uma faixa de valores. A forma mais comum segue a orientação do código abaixo:

for(initialização;teste de término; ação) { bloco de instruções

}

A API Java disponibiliza várias estruturas de armazenamento de dados, uma das mais utilizadas é o array. Esta estrutura é relativa- mente simples por apresentar um número fixo de valores e todos eles devem ser de um único tipo. O comprimento de um array é estabe- lecido no momento da criação. Depois da criação, seu tamanho não pode ser mais alterado.

da criação, seu tamanho não pode ser mais alterado. Figura 05 - Estrutura de um array.

Figura 05 - Estrutura de um array.

Outra característica da linguagem Java que merece ser citada é o gerenciamento de memória. Em várias outras linguagens, o desen- volvedor é responsável por desalocar um recurso não mais utilizado; porém o gerenciamento da memória não é uma tarefa simples, sendo comum ocorrer estouro da pilha (stack overflow) ou outros problemas semelhantes. Em Java, o desenvolvedor não precisa se preocupar com um controle deste tipo, uma vez que o gerenciamento da memó- ria é feito automaticamente pela máquina virtual.

22

Programação de Computadores II

INDICAÇÃO DE LEITURA COMPLEMENTAR

HORSTMANN, C.S.; CORNELL, G ed., São Paulo: Alta Books, 2005.

Core Java 2. Vol 1 - Fundamentos. 7.

No primeiro capítulo (pág. 1 a 8) é feita uma breve introdução ao Java, fazendo um breve histórico e comentando a plataforma Java; o segun- do capítulo (pág. 9 a 18) fala sobre o ambiente de programação Java;

o capítulo 3 faz uma explanação sobre as estruturas fundamentais de programação em Java.

MENDES, D. R

tos. São Paulo: Novatec Editora Ltda, 2009.

Programação Java com Ênfase em Orientação a Obje-

O capítulo 1 faz uma introdução à linguagem Java (pág. 16 a 54) com

destaque às suas características, enquanto que o capítulo 2 apresenta os elementos básicos da linguagem Java (pág. 77 a 90).

PARA REFLETIR

Como é possível escrever e compilar um programa na linguagem de

programação Java em um computador com o Sistema Operacional Windows instalado e em seguida pegar o arquivo compilado (*.class)

e este arquivo funcionar em uma outra máquina que tenha uma ver-

são qualquer do Linux instalada ? Reflita sobre o que faz com que isto aconteça e como isto acontece.

Tema 1

1.2 Conceitos Básicos da OO

|

Introdução à orientação a objetos

23

História As linguagens orientadas a objetos que conhecemos atualmen- te surgiram por volta do ano de 1960, porém demorou mais de dez anos para que as empresas de desenvolvimento de software come- çassem a adotar este novo estilo de linguagem de programação.

Naquela época, as linguagens utilizavam o paradigma da pro- gramação estruturada, com pouca reutilização de código e muita re- dundância. Neste paradigma, também chamado de procedural, havia um alto acoplamento, ou seja, uma simples alteração em uma das partes do sistema poderia forçar uma modificação em vários locais da aplicação e, consequentemente, exigir que todo o sistema fosse testado de novo, o que aumentava o custo do software e acabava diminuindo o lucro da empresa. Mas, com o passar do tempo, as linguagens orientadas a obje- tos (como Java e C++) passaram a ser aceitas no mercado, passando

a integrar o processo de desenvolvimento de sistemas da maioria das empresas.

Paradigma

O paradigma da orientação a objetos possui uma ótica diferen-

te da programação estruturada, uma vez que passa a adotar maneiras de enxergar um sistema muito mais próximo da visão humana. Como,

por exemplo, em um sistema de escola, teríamos uma classe Aluno, outra classe Professor, outra classe Disciplina, todas elas interagindo entre si. No paradigma procedural, eram as funções; no paradigma OO, existe o objeto como centro das atenções. O mundo real, então, é in- terpretado como se fossem objetos interagindo entre si e possuindo os seus respectivos estados e comportamentos. Sendo que os esta- dos são as características (como por exemplo o nome de um Aluno)

e os comportamentos correspondem às ações vinculadas ao objeto (por exemplo, um Aluno pode trancar uma Disciplina).

É de fundamental importância esta mudança de paradigma

(passando de programação estruturada para programação orientada

a objetos), pois seria uma péssima prática utilizar a linguagem Java,

24

Programação de Computadores II

por exemplo, pensando de maneira estruturada; desprezando total- mente os recursos e potenciais da linguagem, que foi concebida de fato como uma linguagem OO. Hoje em dia, a grande maioria das empresas adota fortemente

a Programação OO (OOP). O que se nota é que a OOP é apenas mais uma etapa natural da evolução do desenvolvimento de software.

Surgimento Vamos analisar uma história que a princípio pode parecer des- conexa, mas que tem sim um relacionamento com o conceito OO. Analisando os grandes fabricantes de computadores da atualidade, percebemos que eles tiveram uma curva de crescimento acelerada, isto é, o sucesso foi atingido de forma relativamente rápida. Claro que os seus produtos apresentavam uma ótima qualidade e que também conseguiam vendê-los a preços satisfatórios, além de que a procura por PC crescia muito rapidamente. Mas a questão é: como estes fa- bricantes conseguiam produzir tantos modelos de computadores de forma rápida e ainda ser ágeis o bastante em uma época de constan- tes evoluções? Podemos citar alguns fatores que explicam o que aconteceu: es- tes fabricantes conseguiram aperfeiçoar as suas linhas de produção, passaram a comprar peças de outros fabricantes confiáveis e concen- traram os esforços apenas na linha de montagem dos computadores. Isto economizava tempo e recursos na criação e desenvolvimento dos diversos componentes adquiridos. Quando estes fabricantes compra- vam um componente, estavam garantindo que estas partes (com suas propriedades e suas funcionalidades) já estavam pré-testadas, o que tornava o processo inteiro mais eficiente. Por exemplo, ao invés de fabricar um disco rígido com uma determinada capacidade de arma- zenamento e com o papel definido para armazenamento de dados, a empresa de PC comprava este HD de outro fabricante, especializado exclusivamente na construção deste componente. No momento em que estas fábricas deixaram de produzir todas as partes e passaram

a comprar componentes, os seus faturamentos cresceram de maneira acentuada.

Tema 1

|

Introdução à orientação a objetos

25

Tomando este caso como referência, podemos fazer um pa- ralelo e perceber que a OOP segue o mesmo princípio. Um sistema desenvolvido seguindo o conceito OO é composto por vários objetos (cada um deles com as suas respectivas propriedades e funcionalida- des bem definidas). Desta forma o sistema espera apenas que cada objeto faça a sua parte, sem se preocupar em como isto será feito, ou seja, não importa a implementação interna de cada um deles, isto

é responsabilidade do desenvolvedor de cada objeto.

Objetivo

O principal objetivo da programação orientada a objeto é faci- litar o desenvolvimento de software através da reutilização de obje- tivos anteriormente desenvolvidos. Esta prática faz com que os sis- temas sejam desenvolvidos mais rápido, de maneira mais confiável

e ainda com custos mais baixos. Este objetivo é conseguido através

diversas características de programação, sendo possível utilizar recur- sos como instanciação, polimorfismo, herança, interface, entre outros.

Conceitos básicos Assim como no paradigma procedural as funções fazem o papel principal, no paradigma OO, o conceito de objeto é o núcleo principal. Um objeto pode ser entendido como uma representação codificada de qualquer entidade do mundo real, onde este objeto é transferido para linhas de código inclusive com suas características e seus com- portamentos. A programação orientada a objetos parte do princípio que os sis- temas são construídos se espelhando ao máximo nos próprios objetos da vida real. Antes de fazermos um aprofundamento em cada um dos pilares da orientação a objetos, devemos ver de maneira simplificada alguns dos conceitos principais. Como acabamos de citar, fica evidente que o elemento conside- rado ponto de partida para o entendimento do conceito OO é o próprio objeto. Entendemos um objeto do mundo real como algo concreto (ex:

um livro) ou como algo conceitual (ex: uma viagem). Definimos que um objeto possui duas características: o seu estado e o seu comportamen- to. Portanto, podemos entender o objeto como qualquer entidade real existente no nosso mundo.

26

Programação de Computadores II

Outro conceito importante é classe, que consiste em um mo- delo para a “fabricação” dos objetos. Entenderemos a classe como sendo uma abstração do objeto, algo que define todas as caracte- rísticas comuns a todos os objetos. Estas características podem ser divididas em estado (que chamamos no momento da codificação de atributos) e em comportamentos (chamados nos nossos códigos de métodos). Quando um objeto é criado, diz-se haver construído uma instância de uma classe. Para que este processo aconteça, é necessário fazer uso do conceito de construtor (maneira de criar a instância), conceito que veremos mais adiante (no conteúdo 2.1). Classes e objetos serão explorados de maneira mais aprofundada no conteúdo seguinte. Uma instanciação consiste em utilizar classes servindo como modelos para que os objetos possam ser criados. Um objeto pré- definido passa a exisitr quando um programa é executado, passando a se chamar então de instância de classe. Objetos trocam informações entre eles através de mensagens, esta é a maneira como um objeto invoca um outro objeto ou então um método de um outro objeto. Falaremos um pouco mais sobre o conceito de mensagens no conteúdo 1.4 deste mesmo tema. Há três tipos de comunicação entre os objetos, os quais são brevemente comentados a seguir:

a) associação: são relacionamentos entre classes (ou entre ob- jetos). Há dois tipos: agregação e composição;

b) generalização: também conhecida como herança (um dos pilares da POO), será brevemente comentada ainda neste mesmo conteúdo, porém o conceito de herança será bastan- te comentado no tema seguinte (conteúdo 2.3);

c) dependência: uma alteração em um elemento altera diretamente o seu dependente.

A utilização de uma estrutura bem definida, arrumada em pas- tas ou pacotes (no inglês package), facilita bastante a organização de um sistema composto por um grande número de classes. Além de

Tema 1

|

Introdução à orientação a objetos

27

uma organização lógica, o uso de pacotes faz com que um conjunto de classes relacionadas entre si fiquem agrupadas no mesmo diretó- rio, ou seja, também acaba propiciando uma organização física. Fala- remos mais sobre pacotes (packages) também no conteúdo seguinte.

Conceitos fundamentais Iremos explorar mais profundamente nos conteúdos posteriores os pilares da programação OO – encapsulamento, herança e polimor- fismo – faremos, neste momento, uma breve apresentação sobre cada um deles:

a) encapsulamento: é a arte de ocultar uma informação (infor- mation hiding), escondendo o que não precisa ou não deve ser visto; em outras palavras, consiste em controlar o acesso aos detalhes de implementação de uma classe através do uso de palavras reservadas (modificadores); como o usuário não tem acesso à implementação, esta pode ser alterada, corrigida ou melhorada sem que ele tome conhecimento ou mesmo tenha que fazer qualquer adaptação.

b) herança: é comum termos mais de uma classe compartilhan- do estado e comportamentos de uma classe de referência (estas classes serão chamadas de super-classes enquanto que as que herdam serão chamadas de sub-classes). O con-

ceito de herança aproveita estas semelhanças para melhorar

o projeto (reduzindo a repetição de código), criando super-

classes que serão compostas justamente por estas simila- ridades, enquanto que as sub-classes terão como objetivo refinar e ampliar as características e comportamentos da classe superior. Não há limites para níveis de herança, po- rém, especificamente na linguagem Java, há apenas a he-

rança simples (uma sub-classe só pode herdar de uma úni- ca super-classe), não havendo portanto a herança múltipla (lembrando que este conceito existe em outras linguagens orientadas a objetos). De maneira geral, pode ser dito que

a herança é o conceito no qual uma sub-classe herda auto- maticamente todos os estados e comportamentos de uma super-classe.

28

Programação de Computadores II

c) polimorfismo: como o nome sugere, significa várias formas diferentes para a mesma funcionalidade. Uma classe pode possuir um método com o mesmo nome (ex: ligar), mas com diferentes assinaturas (quantidade e tipos de parâme- tros distintos) para diversos fins (ex: ligar o rádio; ligar o carro). Não será preciso tomar conhecimento de todas as implementações deste mesmo método, apenas deve ser su- ficiente escolher qual delas é a mais apropriada para a sua necessidade; este processo contribui para a simplificação do desenvolvimento do software e facilita a reutilização de métodos com os mesmos nomes.

Outro conceito que merece destaque é o de interface. Um grande motivo para a importância do uso de interface é o fato de ela preencher a lacuna deixada pela ausência da herança múltipla, que, como foi falado anteriormente, não existe na linguagem Java. Uma interface define um conjunto de funcionalidades que precisam ser fornecidas por qualquer classe que venha a implementar esta interface. Há uma grande proximidade da interface com a classe abstrata, sendo que uma interface abriga exclusivamente métodos abstratos. Voltaremos a falar sobre este assunto no tema 3 (conte- údos 3.2 e 3.3). Não podemos deixar de falar sobre o conceito de abstração, que consiste em impedir que o desenvolvedor conheça detalhes da implementação de uma determinada funcionalidade. Com a uti- lização da abstração, permanece explícita apenas a assinatura do método, ficando oculto todo o código. Ou seja, não precisa tornar público ‘como’ fazer, apenas ‘o que’ se faz. O importante é o que um objeto ‘é’ e o que ele ‘faz’ sem se preocupar em ‘como’ é a sua implementação.

Tema 1

|

Introdução à orientação a objetos

29

INDICAÇÃO DE LEITURA COMPLEMENTAR

SINTES, A. Aprenda Programação Orientada a Objetos. São Paulo:

Pearson Education do Brasil, 2010.

A leitura da página 1 até a 16 é muito interessante, pois faz uma im- portante introdução à programação orientada a objetos.

CADENHEAD, R.; LEMAY, L. Aprenda em 21 dias Java 2. 4. ed. São Paulo: Elsevier Editora Ltda, 2005.

Para revisar e aprimorar seus estudos neste assunto, é sugerida uma leitura nos capítulos 5 e 6 (da pág. 73 a 106).

PARA REFLETIR

Reflita junto com seus colegas sobre a importância dos conceitos fundamentais da Orientação a Objetos, que são encapsulamento, herança e polimorfismo.

1.3 Classes e Objetos

Classe Uma classe pode ser entendida como um modelo, uma espécie de forma (ou ainda de gabarito) para a definição de objetos. Quando nos voltamos para o mundo dos softwares, usamos o conceito de classes para agrupar estes objetos que possuem uma relação entre si (características que os objetos têm em comum). A premissa da OO é trazer para o mundo dos softwares todos os objetos do mundo real.

30

Programação de Computadores II

Desta maneira, entendo que, de acordo com o nosso mundo real, aviões, navios, carros e motocicletas são classificados como meios de transporte. Apesar de serem objetos de tamanhos, formas e valores diferentes, eles possuem características comuns inerentes a todos os meios de locomoção (quantidade de passageiros, tipo de combustível etc). Assim sendo, a meta passa a ser codificar os objetos do mundo real acima citados em uma linguagem de programação. Uma classe constrói a especificação de todas as informações comuns a objetos da mesma espécie. Estas informações são dividi- das em duas categorias: atributos e comportamentos. Os “atributos da classe” são as características, tais como marca, cor, velocidade. Podemos chamar de “propriedades da classe” o con- junto de todos os atributos. Vale ressaltar que cada atributo precisa ser implementado com um nome e um tipo de dado correspondente. Por sua vez, os comportamentos são ações que de maneira geral al- teram os atributos. Por exemplo, os comportamentos acelerar e frear que alteram o atributo velocidade; estes comportamentos são imple- mentados através de métodos (que podem ser funções ou procedi- mentos); cada método é implementado com uma assinatura (nome do método, tipo de retorno e lista de parâmetros) e um conjunto de instruções.

Objetos Como não poderia deixar de ser, o conceito de objeto é de grande importância para a Programação Orientada a Objetos. É uti- lizado em praticamente todo processamento de uma aplicação. É o elemento utilizado para representar qualquer coisa, seja ela real (ex:

carro) ou mesmo abstrata (ex: viagem). Objetos são simplesmente instâncias de uma classe previamente definida. A partir do momento que temos uma classe chamada Pessoa com os atributos nome, idade e telefone, podemos criar uma ins- tância desta classe que representa um objeto real, ou seja, teremos um objeto pessoa com o nome João, idade igual a 25 anos e com o telefone 9876-1234.

Tema 1

|

Introdução à orientação a objetos

31

Uma aplicação orientada a objetos é construída por vários ob- jetos que interagem entre si. Cada interação desta é vista como uma troca de “mensagens”, que ocorre através da chamada de métodos uns dos outros. Para assimilar o conceito de classe e objetos, faça a seguinte atividade, agrupar em classes os seguintes objetos: cachorro, note- book, navio, cavalo, bicicleta, impressora, pato, avião e celular.

Codificando Toda classe deve começar com letra maiúscula e deve ter o nome igual ao do arquivo; se a classe for Pessoa, o arquivo tem que ser obrigatoriamente Pessoa.java. Todo código escrito na linguagem Java fica dentro desta classe. A sintaxe da assinatura de uma classe consiste em um modificador de acesso + palavra reservada class + nome da classe, de acordo com o exemplo abaixo:

public class Pessoa { .

.

.

}

Após termos criado a classe Pessoa, podemos em qualquer outra classe criar uma instância de Pessoa, chamada pessoa X com os valores citados anteriormente. Para tanto, utilizaremos a palavra reservada new, de acordo com o código abaixo:

Pessoa pessoaX = new Pessoa();

Da maneira acima, precisaremos atribuir as informações reais (“João”, 25 anos e o telefone 9876-1234) posteriormente. Ou pode- mos fazer da maneira a seguir:

Pessoa pessoaX = new Pessoa(“Joao”, 25, 98761234);

A diferença entre as duas construções acima é apenas no cons- trutor, ou seja, na forma que a instância é criada. Falaremos mais sobre construtores no conteúdo 2.1.

32

Programação de Computadores II

Pacotes O Kit do Desenvolvedor Java (JDK) contém um conjunto pré-defi- nido de classes, todas estas classes juntas compõem a API Java. Elas são organizadas por afinidade em várias pastas chamadas de pacotes. Cada pacote agrupa classes que apresentam propósitos semelhantes, como operações matemáticas, manipulações de datas e textos, trata- mento de arquivos etc. Outro motivo para a utilização de pacotes é que assim fica possível a existência de classes com o mesmo nome, desde que localizadas em pacotes diferentes. Isto é possível, uma vez que quando se deseja referenciar uma classe, faz-se necessário infor- mar o nome completo da mesma, o que inclui o caminho inteiro do pacote seguido pelo nome da própria classe. Mas é claro que, além de ter à disposição uma enorme biblio- teca de classes prontas para os mais variados fins, o próprio desen- volvedor está livre para crias as classes necessárias para a construção do seu software, preocupando-se exclusivamente com as regras de negócio próprias da sua aplicação. Neste momento, quando nós desenvolvedores criamos nossos próprios sistemas computacionais, precisamos criar várias classes e então podemos organizá-las também em pastas, também chamadas de pacotes (ou packages). Os pacotes servem para organizar tanto logicamente (por afini- dade), como fisicamente (ficando nas mesmas pastas no sistema de arquivos). Esta organização melhora a estruturação da aplicação e também otimiza o processo de publicação. A maneira utilizada para informarmos o pacote ao qual uma classe pertence é identificando na primeira linha da classe a sua de- claração de pacote, de acordo com a sintaxe a seguir:

Sintaxe:

package <nome da package>[.<nome da subpackage>];

Por exemplo, para a classe Funcionário, podemos informar que o pacote seria como indicado abaixo:

package br.com.empresa;

Tema 1

|

Introdução à orientação a objetos

33

Como falado anteriormente, esta classe deve estar localizada fisicamente na pasta br/com/empresa, que fica na raiz do projeto. Caso o pacote não seja citado no início da classe, a mesma deve estar localizada no raiz do projeto e diz-se que está no “pacote default”; mas esta prática não é recomendada. Sempre que precisarmos fazer uso de qualquer classe que faça parte da API, devemos fazer uma referência para a mesma com o nome completo (pacote + nome). Isto pode ser feito de duas maneiras:

a) em toda ocorrência da classe, a classe deve esta acionada através do nome completo;

b) fazer uma única referência de importação no início do arqui- vo (após a declaração “package”, caso exista) utilizando a sintaxe abaixo:

import <nome da package>[.<nome da subpackage>].<nome da classe>;

Exemplo:

import java.util.Random;

Utilizando a segunda maneira (com a palavra reservada ‘im- port’), sempre que for preciso, a classe (ex: Random) pode ser refe- renciada sem o nome completo do pacote em toda parte do código. Estas mesmas regras se aplicam quando precisamos usar clas- ses que nós criamos, caso não estejam evidentemente no mesmo pacote (já que neste caso não se faz necessário a referência).

Exemplo:

import br.com.empresa.Funcionario;

Caso seja necessário fazer referência a várias classes dentro de um mesmo pacote, podemos utilizar uma espécie de “coringa” (‘*’), o qual referencia todas as classes naquele pacote específico; isso evita que seja feita a referência individual para cada uma das classes que se deseja utilizar.

Exemplo:

import br.com.empresa.*;

34

Programação de Computadores II

Em apenas um caso não é necessário fazer a importação, esta exceção acontece quando a classe que se deseja utilizar está dentro do pacote java.lang. As classes localizadas neste pacote são essenciais à linguagem de programação e, por este motivo, precisam estar sem- pre disponíveis para uso; podemos entender a instrução “import java. lang.*;” como sendo implícita para todas as classes.

Classes Wrappers São classes Java que possuem os mesmos serviços dos tipos primitivos, porém com várias outras funcionalidades acrescentadas. Em inglês, wrap significa envolver, então temos classes ‘que envol- vem’ as classes originais (que no caso são os tipos primitivos) e oferecem novas funções. Lembrando que os tipos primitivos em Java não são objetos (todo o resto é objeto), então temos para cada um dos tipos primiti- vos, uma respectiva classe Wrapper, sendo esta um objeto. Existem então oito classes wrappers para os tipos primitivos e, entre as novas funcionalidades, podemos destacar os métodos de conversão entre tipos, mudança de bases decimais e outras operações que são per- mitidas apenas a objetos (como por exemplo manipulações com con- juntos). Isto se deve ao fato de tornar possível o uso da super classe Object implicitamente através das classes wrappers. Falaremos mais sobre a classe Object no conteúdo 2.4.

Falaremos mais sobre a classe Object no conteúdo 2.4. Figura 06 - Os tipos primitivos e

Figura 06 - Os tipos primitivos e suas respectivas classes wrappers.

Tema 1

|

Introdução à orientação a objetos

35

Cada uma das classes wrappers acima fornece dois construtores (formas para se criar uma instância); na primeira forma, um String é es- perado um parâmetro e na segunda forma é esperado um tipo primiti- vo correspondente ao que está sendo ‘envolvido’. A exceção é a classe Character com apenas um construtor com um parâmetro do tipo char.

Exemplos:

Integer valorA = new Interger(“10”); Integer valorB = new Interger(10);

Entre os métodos adicionais das classes wrappers falados ante- riormente, podemos destacar os mais relevantes, que são:

a) valueOf ( ) : fornece mais outra maneira para a instanciação, conforme exemplos abaixo;

Integer valorA = Interger.valueOf(“11”); Integer valorB = Interger.valueOf(11);

Integer valorC = Interger.valueOf(“11”, 2);

// o segundo parâmetro indica que o valor está

// escrito na

base 2; ou seja 11 na base 2 (igual a 3)

b) xxxValue ( ) : permite converter um objeto das classes wra- ppers numéricas para qualquer tipo primitivo.

exemplos:

Integer valorA = new Interger(“12”);

int valorB

long valorC = valorA.longValue(); float valorD = valorA.floatValue();

= valorA.intValue();

c) parseXxx() : convertem String em um tipo primitivo numérico;

exemplos:

byte valorA

= Byte.parseByte(“13”);

double valorB = Double.parseDouble(“13.4”);

36

Programação de Computadores II

d) toString() : facilita a conversão de um tipo primitivo qualquer em String.

exemplos:

String strA = Interger.toString(14); String strB = Float.toString(14.5);

A partir da versão Java 5, quando surgiu o conceito de autobo- xing (ou boxing e unboxing), é possível manipular objetos das classes wrappers como se fossem tipos primitivos.

Exemplos:

Integer valorA = 15; valorA = 16; int valorB = valorA; valorA++;

Vamos observar agora as facilidades detectadas nas linhas aci- ma em virtude do autoboxing. Na primeira linha, foi criado um ob- jeto Integer sem utilizar o construtor com a palavra reservada new; já nas linhas seguintes, o objeto valorA foi tratado como se fosse realmente um tipo primitivo. É importante destacar apenas que os objetos das classes wrappers são imutáveis, isto é, ao alterar um va- lor, o autoboxing de maneira transparente cria um novo objeto com o novo valor, descarta o anterior e a variável de referência (valorA no nosso exemplo) assume este novo objeto.

INDICAÇÃO DE LEITURA COMPLEMENTAR

HORSTMANN, C.S.; CORNELL, G ed., São Paulo: Alta Books, 2005.

Core Java 2. Vol 1 - Fundamentos. 7.

Uma boa recomendação de leitura sobre este conteúdo é o quarto ca- pítulo deste livro, da página 51 a 63, que orienta a utilização de classes predefinidas e ensina a criação e definição das suas próprias classes.

Tema 1

|

Introdução à orientação a objetos

37

BARNES, D., KOLLING M. Programação Orientada a Objetos com Java. São Paulo: Makron Books, 2006.

No capítulo 1, há uma interessante apresentação ao estudo de objetos (criação e interação) e de classes das páginas 3 a 15.

PARA REFLETIR

Se os tipos primitivos foram construídos para oferecerem uma melhor performance, qual a vantagem no uso das classes Wrappers (que emulam os tipos primitivos)?

Neste momento, é preciso que você faça uma reflexão sobre a preocu- pação com o desempenho na tecnologia Java. Faça uma ponderação sobre o fato dos tipos primitivos permanecerem presentes na espe- cificação da linguagem Java e também sobre a existência das classes Wrappers (que emulam os tipos primitivos).

1.4 Atributos e Métodos

Introdução Antes de começarmos a falar sobre atributos e métodos, deve- mos lembrar inicialmente do conceito de Classe. Conforme podemos retirar da definição feita no conteúdo anterior, temos que uma classe é composta por duas partes: atributos e comportamentos. Comentare- mos cada uma destas partes separadamente nos próximos parágrafos.

Atributos Também chamados de membros de uma classe, os atributos representam as características (propriedades) das classes. São eles

38

Programação de Computadores II

os responsáveis pelo armazenamento da informação, mantendo as particularidades de cada um dos objetos. Cada classe pode possuir vários atributos, cada um deles deve ser representado por um nome (identificador). Além disso, para cada um dos atributos, faz-se necessá- rio informar qual o tipo de dado desejado; podendo ser um tipo de dado primitivo ou até mesmo algum outro objeto (a linguagem Java não per- mite a criação de um atributo sem que seja informado o tipo de dado). Tomando como exemplo uma classe chamada Pessoa, podemos assumir a existência de vários atributos, cada um deles representado pelo seu tipo e seu identificador.

public class Pessoa { // atributos String nome; int idade;

}

Métodos Lembrando que uma classe é composta por atributos e por comportamentos. A partir de agora passaremos a chamar estes com- portamentos de métodos. Um método é simplesmente um processo que realiza uma ação, podendo em muitas vezes manipular o valor de um ou mais atributos. Além de iniciar sempre com letra minúscula, a recomendação para a criação de um método é conter um verbo no infinitivo (termi- nação ar, er, ir, or), imediatamente seguido de um substantivo. Por exemplo: calcularIdade(), imprimirDados(). Sintaxe da assinatura de um método:

<modificador> <tipo de retorno> <nome> (<parâmetros>) Sendo que:

<modificador>: palavra reservada que determina o nível de acesso ao método; <tipo de retorno>: indica se haverá ou não um tipo de retorno; caso haja um retorno, será informado o tipo de dado deste retorno; <nome>: identificador do método (seguir a recomendação acima); <parâmetros>: valores esperados pelo método.

Tema 1

|

Introdução à orientação a objetos

39

Tema 1 | Introdução à orientação a objetos 39 Figura 07 - Objeto Pessoa. Mensagens Os

Figura 07 - Objeto Pessoa.

Mensagens Os objetos precisam se relacionar, precisam trocar informações para realizar tarefas, não fazem nada sozinhos. Para que esta comuni- cação ocorra, deve existir uma troca de mensagens. Estas mensagens identificam as operações que precisam ser executadas através de três componentes:

a) objeto para o qual a mensagem é acionada;

b) método dentro do objeto que será executado;

c) parâmetros necessários para a execução do método.

Considerando um objeto Veículo, é preciso que outro objeto Motorista lhe envie uma mensagem, por exemplo, acionando o mé- todo abrirPorta(), passando ainda um parâmetro: qual porta deseja abrir. Há ainda uma grande vantagem na utilização da troca de men- sagens: uma vez que as interações são feitas através destas men- sagens, não é necessário que todos os objetos estejam dentro da mesma aplicação, ou seja, é possível acionar métodos de objetos que estão até mesmo em outro servidor de aplicações web. Temos então o entendimento que um programa OO consiste simplesmente em um conjunto de objetos, sendo especificado em

40

Programação de Computadores II

cada um deles o que o mesmo é capaz de fazer. No momento que se desejar utilizar algum método de qualquer objeto, basta que seja enviada uma mensagem solicitando a sua execução.

que seja enviada uma mensagem solicitando a sua execução. Figura 08 - Utilizando uma mensagem, o

Figura 08 - Utilizando uma mensagem, o objeto Motorista acessa um método do objeto Veículo.

Acessabilidade Cada uma das linguagens de programação possui a sua própria lista de palavras reservadas para a definição do controle de acesso a vari- áveis e métodos, mas basicamente existem os seguintes níveis:

a) público: permite o acesso por todos os métodos de todas as classes, não havendo restrições. Este é o nível mais ‘libe- rado’ possível, já seu uso em um atributo significa que não está sendo utilizado o conceito de encapsulamento.

b) protegido: acesso permitido aos métodos da própria classe, de outras classes do mesmo pacote e também por todas as classes que herdam dela (o conceito de herança será estuda- do no tema seguinte, mais precisamente no conteúdo 2.3).

c) privado: o acesso é liberado apenas aos métodos da própria classe. Este é o nível mais protegido possível e os atributos devem estar neste nível de acesso de acordo com a teoria do encapsulamento.

Tema 1

|

Introdução à orientação a objetos

41

Na linguagem Java, são utilizadas as seguintes palavras-chaves:

public, protected e private para definir os respectivos itens acima. Porém, há ainda outro nível de acesso: o default (ou package- private), que é caracterizado justamente pela ausência das palavras reservadas acima citadas. O seu acesso é permitido apenas para a própria classe e para as outras classes do mesmo pacote.

a própria classe e para as outras classes do mesmo pacote. Figura 09 - Tabela de

Figura 09 - Tabela de níveis de acesso.

Encapsulamento De acordo com a teoria de orientação a objeto, encapsular signi- fica ocultar, esconder informações de um objeto. Consiste em construir classes que não permitem o acesso direto aos seus atributos, estes não poderão ser lidos ou alterados por outras classes. Como então manipular tais informações? A única maneira para fa- zer isso é utilizar métodos da própria classe construídos especificamente para esta finalidade. Esses métodos são divididos em dois tipos:

a) configurador: responsável pela alteração dos dados, chamado também de setter ou mutator, permite a atualização do valor de um atributo. A recomendação é que o seu nome inicie com o prefixo set e seja acompanhado do nome do atributo a ser atualizado, ex: setNome( ).

b) capturador: método que recupera a informação, também chama- do de getter ou accessor, retorna o valor de um atributo. A re- comendação é que o seu nome comece pelo prefixo get e seja acompanhado do nome do atributo solicitado, ex: getNome( );

Além de esconder a complexidade do código, o encapsulamento também tem a função de proteger os dados de classes externas,

42

Programação de Computadores II

evitando que os atributos recebam valores indevidos (tornando-os inconsistentes), uma vez que tais atributos só podem ser acessados pela sua própria classe. O uso de palavras-reservadas modificadoras de acesso permite limitar o acesso às informações de uma classe. Isso evita que os seus dados se tornem inconsistentes ou até mesmo indevidos, garan- tindo consequentemente a confiabilidade destas informações. Isso acontece de fato, pois o acesso a um atributo através de um método permite que a informação que se pretende atribuir seja avaliada, ve- rificando se realmente é possível executar tal atribuição. Para que uma classe siga corretamente todas as orientações da teoria do en- capsulamento, todos os seus atributos devem ser privados (palavra reservada private), tornando-os de acesso exclusivo da própria classe (nem uma eventual sub-classe pode ter acesso), e também fornecer métodos configuradores e capturadores para cada um destes atri- butos, sendo assim possível a manipulação (leitura e gravação) de todas as suas características.

public class Pessoa { // atributos private String nome; private int idade; // métodos configuradores public void setNome(String nome) { this.nome = nome;

}

public void setIdade(int idade) { this.idade = idade;

}

// métodos capturadores public String getNome() { return this.nome;

}

public int getIdade() {

return this.idade;

}

}

Tema 1

|

Introdução à orientação a objetos

43

A palavra-chave this utilizada no exemplo acima serve para fa-

zer referência ao objeto atual, pode ser utilizada em qualquer ponto do código e pode acionar tanto os atributos como os métodos. Em alguns casos, o seu uso é dispensável, podendo ser usado apenas como o reforço, como nos casos dos métodos capturadores (exem- plo: getNome), mas, mesmo assim, o seu uso é recomendado por simplesmente aumentar a legibilidade do código. Entretanto, em ou- tras situações, a presença da palavra this é imprescindível, como nos métodos configuradores (exemplo: setNome), com o propósito de se diferenciar o atributo do parâmetro. Claro que este caso poderia ser resolvido de outra forma, por exemplo, alterando-se o nome do parâmetro, porém o mais comum é realmente a construção acima. Existe ainda outra finalidade para a palavra this, ela pode ser usada quando se deseja passar como parâmetro o próprio objeto atual para um método qualquer.

Sobrecarga de Métodos

A programação orientada a objetos permite a existência de mé-

todos com o mesmo nome, mas com a lista de parâmetros diferentes, onde esta diferença pode acontecer tanto na quantidade dos argu- mentos como nos tipos dos mesmos. Esta situação é conhecida como sobrecarga (ou overload) de métodos.

O uso deste mecanismo não cria um conflito, pois o compilador

será capaz de entender qual método está sendo acionado justamente a partir da verificação da lista de parâmetros. Para que seja caracterizada uma sobrecarga de métodos, os

seguintes fatos devem acontecer:

a) os métodos em questão devem estar na mesma classe ou em classes com relacionamento de herança;

b) os nomes dos métodos devem ser o mesmo;

44

Programação de Computadores II

No conteúdo 2.1 (já no próximo tema), veremos que os constru- tores de um objeto podem ser considerados com uma sobrecarga de métodos, apesar de alguns autores não considerarem os construtores como sendo métodos normais, mas esta é uma discussão para o próximo conteúdo. Vejamos então um exemplo de sobrecarga. A seguir, temos um método somarNumeros( ) com dois parâmetros do tipo inteiro e um retorno do tipo inteiro.

public int somarNumeros(int nA, int nB) { return nA + nB;

}

Agora veremos o mesmo método somarNumeros( ), só que com dois parâmetros do tipo real e um retorno também do tipo real.

public float somarNumeros(float nA, float nB) { return nA + nB;

}

Quando formos invocar o método somarNumeros( ), a imple- mentação a ser acionada dependerá dos tipos de parâmetros infor- mados. Se forem passados dois inteiros, o primeiro código será exe- cutado, caso sejam passados dois valores reais, o segundo trecho vai ser acionado.

INDICAÇÃO DE LEITURA COMPLEMENTAR

HORSTMANN, C.S.; CORNELL, G ed., São Paulo: Alta Books, 2005.

Core Java 2. Vol 1 - Fundamentos. 7.

Uma leitura recomendada sobre atributos e métodos encontra-se nas páginas 65 a 71 desta obra, que fala ainda faz uma importante abor- dagem sobre o conceito de mensagens.

Tema 1

|

Introdução à orientação a objetos

45

BARNES, D., KOLLING M. Programação Orientada a Objetos com Java. São Paulo: Makron Books, 2006.

O capítulo 2 é imprescindível para um maior aprofundamento tanto

no conceito de atributos como no de métodos (inclusive abordando sobrecarga de métodos), esta recomendação de leitura acontece nas páginas 27 a 40.

PARA REFLETIR

Convide os seus colegas para debaterem sobre o encapsulamento. Analise alguns pontos como o acesso restrito empregado a toda in- formação, fazendo inclusive uma espécie de camada de proteção, onde apenas os métodos getters / setters são públicos. Aproveite e apresente também as suas reflexões para o tutor.

RESUMO

Você está de parabéns por ter concluído o nosso primeiro tema, no qual fizemos uma introdução a orientação a objetos. No conteúdo 1.1, fizemos uma breve revisão da linguagem Java, com suas caracte- rísticas (como portabilidade, robustez e segurança) e seus elementos principais (entre eles as estruturas condicionais e as de controle).

O conteúdo 1.2 foi inteiramente dedicado aos conceitos básicos da

orientação a objetos. Foi feita inicialmente uma breve história do surgimento do paradigma OO e depois foram introduzidos temas bá- sicos como objeto (que representa através de código qualquer coisa do mundo real) e classes (que são os moldes para a construção dos

46

Programação de Computadores II

objetos). Além de uma apresentação de importantes conceitos: en- capsulamento, herança e polimorfismo.

O conteúdo seguinte foi o responsável por explorar profundamente

os temas objetos e classes, sendo mencionado a sua organização em pacotes (responsáveis pela organização física e lógica das classes), explicando o funcionamento através de trechos de códigos, além de abordarmos das classes Wrappers (que simulam os tipos primitivos).

E no último conteúdo, 1.4, acabamos de explorar os atributos e méto- dos de uma classe, sem deixar de citar os níveis de acesso (público, protegido e privado), encapsulamento (proteção da informação) e sobrecarga de métodos.

Agora que fizemos este passeio introdutório no mundo orientado

a objetos, chegou o momento do aperfeiçoamento, prepare-se para uma ótima leitura sobre herança na linguagem Java.

2
2

Herança

Nesta segunda parte do curso veremos o conceito de herança do paradigma de orientação a objetos. A utilização de herança possui algumas particularidades que até então não vimos em outras partes do curso. Uma dessas particularidades é a reutilização de classes. Aqui veremos o conceito do que é uma herança, qual a diferença entre membros de instância e membros de classe, entre outros.

48

Programação de Computadores II

2.1 Construtores e o Gerenciamento da Memória

Os construtores são utilizados para realizar a inicialização do objeto. Todas as vezes que utilizamos a palavra reservada new, esta- mos criando na memória uma nova instância do objeto. É neste mo- mento que os construtores das classes são executados. Como já estamos acostumados a escrever métodos, devemos ter cuidado para não visualizar um construtor como um método. Um construtor não deve ser considerado um método, isto porque o cons- trutor só pode ser invocado quando o objeto está sendo criado, diferentemente dos métodos, que podem ser invocados a qualquer momento. Os construtores podem ser criados em qualquer região da clas- se. Porém, obrigatoriamente o construtor deve possuir o mesmo nome

que o nome da classe. Por exemplo, se a classe possui o nome Carro,

o construtor deve-se chamar Carro. A sintaxe de um construtor deve seguir a seguinte regra:

<modificador> <nome do construtor>( [<lista de parâmetros>] ) { [<implementação>];

}

Você deve estar atento à sintaxe apresentada. O modificador indica o nível de visibilidade que este construtor irá possuir, podendo ser private, public ou protected. Já o nome do construtor, como dito anteriormente, deve ter o mesmo nome da classe que está sendo criada. E por último temos a lista de parâmetros que pode ser op- cional. Existem algumas diferentes formas de utilizar os construtores.

A primeira forma é a padrão, onde nenhum argumento é passado

como parâmetro. Na linguagem Java, caso nenhum construtor seja criado, automaticamente é criado o construtor padrão, sem nenhum parâmetro. O construtor padrão indica que o objeto será iniciali-

zado sem nenhum valor, ou seja, todos os atributos que o objeto

possui são inicializados com seus valores padrões. Vamos analisar

a seguinte classe:

Tema2

|

Herança

49

public class Computador{ private String marca; private int qtdMemoria; private int qtdDisco;

}

2 Para esta situação, ao iniciarmos o objeto computador, através

do seu construtor padrão, todas as variáveis da classe serão iniciadas

com o valor padrão. No caso da String o valor padrão será zero. Já

para as variáveis do tipo int, o valor padrão será zero. Vamos a um

exemplo de chamada a um construtor.

Computator computador = new Computador();

Neste momento estamos realizando a chamada ao construtor padrão criado pelo Java. Porém, podemos criar construtores padro- nizados de acordo com a nossa necessidade. Por exemplo, digamos que todos os computadores quando criados, deve ser informada qual é sua marca. Nesta situação devemos criar um construtor que receba como parâmetro uma variável do tipo String para que possamos ini- ciar o atributo marca que possuímos. A partir deste momento nossa classe vai possuir a seguinte configuração:

public class Computador{ private String marca; private int qtdMemoria; private int qtdDisco;

public Computador (String pMarca)

{

this.marca = pMarca;

}

}

Nesta situação, estamos passando um parâmetro denominado de pMarca. Este parâmetro vai ser responsável por iniciar um objeto Computador com o atributo Marca já pré-definido. Automaticamente

50

Programação de Computadores II

quando criamos um construtor, o construtor padrão que antes era criado pelo Java, deixa de existir. Então, a partir deste momento, toda vez que formos iniciar um objeto através do seu construtor devemos passar como parâmetro deste novo construtor criado. A chamada ao construtor agora será feita da seguinte maneira:

Computador computador = new Computador(“CCE”);

Perceba que agora estamos passando a String CCE como parâ- metro do construtor. Isto indica que o atributo Marca, ao ser iniciado automaticamente já vai ter o valor CCE. Este valor pode ou não ser alterado. Isto vai depender se foi criado o método set deste atributo. Porém, isto não quer dizer que todas as vezes que formos criar construtores devemos passar no mínimo um parâmetro. Podemos criar um construtor igual ao construtor padrão que é criado pelo Java. Mas, para que vamos precisar criar um construtor sem parâmetros? Em algumas situações precisamos criar o objeto com valores predefinidos. Vamos imaginar a seguinte situação. No objeto compu- tador, caso não seja informado, todos os computadores devem ter como valores básicos 512 de memória e 500 de disco. Então podería- mos criar um construtor com as seguintes características:

public class Computador{ private String marca; private int qtdMemoria; private int qtdDisco;

public Computador ()

{

this.qtdMemoria = 512; this.qtdDisco = 500;

}

}

Nesta situação deveremos chamar o construtor da forma pa- drão, deixando a responsabilidade de iniciar os valores do atributo de acordo com o que foi definido.

Tema2

|

Herança

51

Computador computador = new Computador();

Quando formos usar este objeto computador que acabamos de criar, os atributos qtdMemoria e qtdDisco já terão seus valores que foram predefinidos, porém, nada impede que estes valores sejam modificados. Podemos, também, dentro de uma única classe, possuirmos mais de um construtor. Isto indica que, dependendo da situação, podemos inicializar o objeto de maneira diferente. Quando em uma classe tiver- mos mais de um construtor, o compilador vai conseguir identificar di- ferentes construtores de acordo com os parâmetros que estão sendo utilizados, seja pela quantidade de parâmetros ou pelos tipos dos pa- râmetros. Vamos analisar a nossa classe Computador. Vamos colocar um construtor que não recebe nenhum parâmetro e outro construtor que recebe a marca do objeto computador que está sendo criado.

public class Computador{ private String marca; private int qtdMemoria; private int qtdDisco;

public Computador ()

{

this.qtdMemoria = 512; this.qtdDisco = 500;

}

public Computador (String pMarca)

{

this.marca = pMarca;

}

}

Quando formos criar o objeto Computador poderemos chamar qualquer um dos dois construtores. Por exemplo:

Computador computadorEscritorio = new Computador(); Computador compudatorSala = new Computador(“HP“);

52

Programação de Computadores II

Nesta situação temos dois objetos com valores de atributos diferentes. O objeto computadorEscritorio não possui um valor específico para a marca, isto porque o construtor que foi utilizado foi o construtor sem parâmetros. Este construtor irá apenas alterar o valor dos atributos qtdMemoria e qtdDisco. Já o objeto computadorSala terá apenas o valor da marca atribuído e seus outros dois parâmetros com o valor padrão, que neste caso será zero. Apesar de os construtores não serem considerados métodos, podemos realizar a chamada de um construtor para outro, ou seja, um construtor pode realizar apenas uma tarefa e realizar a chamada a outro construtor já existente.

public class Computador{ private String marca; private int qtdMemoria; private int qtdDisco;

public Computador ()

{

this.qtdMemoria = 512; this.qtdDisco = 500;

}

public Computador (String pMarca)

{

this.marca = pMarca; this();

}

}

Nesta situação, podemos continuar utilizando os dois constru- tores. Porém, quando utilizarmos o construtor que possui um parâ- metro, automaticamente o construtor que não possui parâmetros será invocado. Isto porque, a referência this indica que estamos invocando um construtor, neste caso sem parâmetros. Para esta construção de nossa classe, quando criarmos o ob- jeto computador utilizando apenas o construtor que possui um parâ- metro, estaremos atribuindo todos os valores ao objeto, isto porque um construtor realizou a chamada ao outro.

Tema2

|

Herança

53

Toda vez que estamos criando um objeto específico, estamos alocando espaço em memória para armazenar as informações dos objetos criados. Com isso, em determinados momentos podemos alocar espaço de memória desnecessariamente. Para solucionar este problema o Java possui ferramentas para realizar o gerenciamento de memória. A JVM (Java Virtual Machine) possui um mecanismo de coleta de lixo (garbage collector). Esta coleta consiste em realizar a reciclagem de memória não mais utilizada. Esta reciclagem é realizada através de um algoritmo específico. Este algoritmo realiza a busca de objetos que não podem ser mais alcançados. O algoritmo é executado sem que os programadores percebam que ele está sendo executado. O tempo de execução de uma aplicação é interferido diretamente pelo coletor de lixo. Isto por- que o coletor precisa a todo o momento estar realizando a verificação de objetos não mais utilizados.

INDICAÇÃO DE LEITURA COMPLEMENTAR

DEITEL, P.J.; DEITEL, H. M

Pearson Education do Brasil, 2010.

Java Como Programar. 8. ed. São Paulo:

Leia o capítulo 3, páginas 68 e 69. No trecho deste livro poderão ser encontrados exemplos de diversas maneiras de se utilizar os cons- trutores. O livro também demonstra, com figuras, como funciona o gerenciamento de memória para as instâncias das classes.

CADENHEAD, R. ; LEMAY, L. Java 2. 4. ed. São Paulo: Editora Campus, 2005.

No capítulo 3 do livro podemos encontrar na página 39 a importân- cia do gerenciamento de memória quando estamos trabalhando com Objetos. Na mesma página temos ainda uma explicação de como funcionam os construtores dos objetos.

54

Programação de Computadores II

PARA REFLETIR

O gerenciamento de memória é algo muito importante dentro do con- ceito de herança, já que podemos estar criando objetos sem a real necessidade de utilização. Discuta com seus colegas de turma como devem ser criados objetos que realmente sejam necessários, podendo também discutir como é o funcionamento do coletor automático de lixo (garbage collector).

2.2 Membros de Instância X Membros de Classe

Os membros de instância de uma classe são totalmente diferen- tes dos membros de uma classe. Quando falamos de membros, estamos falando dos atributos e métodos que esta classe possui. Vamos começar falando sobre os membros de uma instância. Os membros deste tipo só podem ser acessados através de uma ins- tância de uma determinada classe, ou seja, precisamos primeiro criar um novo objeto da classe que queremos acessar os membros. Todos os atributos e métodos só poderão ser acessados quando estivermos trabalhando com um objeto em memória. É importante ve- rificar que quando a instância de um objeto deixa de existir, automati- camente todos os seus métodos e atributos também deixam de existir. Vamos a um exemplo:

public class Computador{ private int qtdMemoria; private int qtdDisco;

Tema2

|

Herança

55

public Computador (int pQtdMemoria, int pDisco)

{

this.qtdMemoria = pQtdMemoria; this.qtdDisco = pDisco;

}

public void AdicionarMemoria(int pQntMemoria)

{

this.qtdMemoria = this.qtdMemoria + pQnt-

Memoria;

}

}

Agora vamos criar um objeto do tipo Computador e vamos ma- nipular os seus métodos e seus atributos.

Computador computador = new Computador(512,500); computador. AdicionarMemoria(512);

No exemplo acima, criamos um objeto computador. No momen- to de sua criação o computador foi criado com 512 de memória e 500 de disco rígido. Esses valores só serão válidos para o objeto compu- tador que foi criado. Quando adicionamos um valor à memória do computador, teremos o mesmo comportamento descrito. A adição de memória ao computador só será válida para este objeto. Quando criarmos um novo objeto, automaticamente os valores que estão armazenados no objeto anterior deixam de existir e novos valores são assumidos. Vamos a um exemplo:

Computador computadorSala = new Computador(1024,160);

Neste segundo caso, os valores que são atribuídos ao computa- dor são de 1024 de memória e de 160 de disco. Ou seja, dois valores diferentes para duas instâncias também diferentes.

56

Programação de Computadores II

Sendo assim, os membros de instância, sejam os atributos ou os métodos, só vão existir quando houver um objeto instanciando determinada classe, e automaticamente deixam de existir quando estes objetos não estiverem mais em memória. Já os membros de classe possuem uma característica bem di- ferente dos de instância. Todos os membros de classe são também conhecidos como membros estáticos. No Java os membros estáticos são representados pela palavra reservada static. É através desta palavra reservada que indicamos para o compilador que estamos criando um membro estático. Este tipo de membro nunca irá pertencer a uma instância de uma classe. Ele sempre estará ligado diretamente a uma classe. Toda vez que criarmos uma classe, mesmo que ela não seja instanciada, todos os membros estáticos são os primeiros a serem carregados. Vamos incluir, na classe de computador, um atributo es- tático que vai representar um membro de classe para o computador.

public class Computador{ private int qtdMemoria; private int qtdDisco; private static int qtdComputadores;

public Computador (int pQtdMemoria, int pDisco)

{

this.qtdMemoria = pQtdMemoria; this.qtdDisco = pDisco;

}

}

Podemos ver que agora existe um atributo estático dentro da classe, o atributo qtdComputadores. Este atributo vai pertencer a toda a classe, não mais a alguma instância específica. Quando preci- samos utilizar a variável estática, não precisamos mais declarar uma variável do tipo da classe nem criar um novo objeto. Basta apenas chamar o atributo diretamente da classe. Por exemplo:

Computador.qtdComputadores++;

Tema2

|

Herança

57

No exemplo anterior, estamos apenas aumentando a quanti- dade de computadores. Se analisarmos a situação acima, podemos perceber que, apesar de não termos nenhum objeto criado, pode- mos manipular a quantidade de computadores. Precisamos, então, de muita atenção quando formos utilizar este tipo de atributo. Como o atributo estará presente durante todo o ciclo de vida da classe, as variáveis estáticas também estarão presentes e manten- do o seu valor. Assim como os atributos de uma classe, nós podemos ter os métodos estáticos. Estes métodos irão funcionar da mesma maneira que os atributos, ou seja, vão existir sem a dependência de uma instância de uma classe. Porém, é importante lembrar que quando temos um método estático não iremos sobrescrever este método na subclasse, iremos ver o conceito de subclasse no tópico de hierar- quia. Caso, na subclasse, formos usar o mesmo método estático, teremos que redefinir este método. Para utilizar um método como estático, precisamos colocar a palavra reservada static logo após o modificador de visibilidade. Por exemplo, iremos colocar o método estático na classe computador.

public class Computador{ private int qtdMemoria; private int qtdDisco; private static int qtdComputadores;

public Computador (int pQtdMemoria, int pDisco)

{

this.qtdMemoria = pQtdMemoria;

this.qtdDisco = pDisco;

}

public static void Imprimir()

{

System.out.Println(“Quantidade de com- putadores cadastrados: ” + qtdComputadores);

}

}

58

Programação de Computadores II

Da mesma maneira que os atributos de classe são utilizados sem ser necessário instanciar uma classe. Porém, é importante obser- var que em métodos estáticos só podemos utilizar atributos também estáticos. Isto faz sentido, já que os membros de instância só existem quando possuímos uma instância da classe em memória. Neste caso, se usamos o método imprimir diretamente da classe como no exem- plo abaixo, poderíamos tentar imprimir alguma variável de uma ins- tância, sendo que esta instância ainda não existe.

Computador computador = new Computador(512,160); Computador.qtdComputadores++; Computador.Imprimir( );

Da mesma maneira que não podemos utilizar os atributos não estáticos, ou seja, de instância de uma classe, também não podemos utilizar a palavra reserva this. Vale lembrar que esta palavra reservada é utilizada para fazer algum tipo de referência a uma classe instanciada. É importante também perceber que, quando possuirmos uma instância de uma classe iremos conseguir utilizar os métodos estáticos.

Computador computador = new Computador(512,160); computador.Imprimir();

Apesar deste tipo de chamada ser possível, ou seja, não possuir um erro de sintaxe, não devemos utilizá-lo. Isto porque se criamos um método ou atributo estático nós devemos acessá-lo de forma estática. Quando falamos de acessar de forma estática estamos querendo dizer em acessá-lo diretamente através da classe e não de uma instância. Na linguagem Java ainda podemos criar um tipo de atributo que depois de iniciado não pode ter seu valor alterado. Este tipo de atributo pode ser tanto um membro de classe como um mem- bro de instância. Estes atributos são também conhecidos como constantes. Este tipo de atributo vai ser representado através da palavra reservada final.

Tema2

|

Herança

59

Vamos ao primeiro exemplo utilizando um membro de instância. Neste exemplo, a variável será inicializada no construtor do objeto.

public class Computador{ private static int qtdComputadores; private final int qtdMaxComputadores;

public Computador (int pQtdMemoria, int pDisco)

{

this.qtdMaxComputadores = 10 this.qtdMemoria = pQtdMemoria; this.qtdDisco = pDisco;

}

}

Neste exemplo, após iniciado o objeto, a variável qtdMax- Computadores irá ser inicializada com o valor 10 (dez) e não poderá ter seu valor alterado durante o ciclo de vida deste objeto. A inicia- lização do atributo deve, obrigatoriamente, ser realizada dentro do construtor da classe. Para utilizarmos este atributo iremos primeiro instanciar um objeto para depois utilizá-lo.

Computador computador = new Computador(512,160); System.out.println(“Quantidade máxima de computadores:”, compu- tador.qtdMaxComputadores);

Porém, ainda podemos criar os atributos estáticos. Neste caso o valor do atributo é informado na sua declaração e não pode ter seu valor alterado em nenhum método da classe. Vamos a um exemplo.

public class Computador{

private static int qtdComputadores; private static final int qtdMaxComputadores = 10; public Computador (int pQtdMemoria, int pDisco)

{

this.qtdMemoria = pQtdMemoria;

this.qtdDisco = pDisco;

}

}

60

Programação de Computadores II

Neste exemplo, o valor do atributo qtdMaxComputadores já está sendo inicializado com o valor 10 (dez) e em nenhum momento pode ter seu valor alterado. Como todos os outros atributos de classe o atri- buto final estático pode ser acessado diretamente da classe sem a ne- cessidade de uma nova instância. Vamos demonstrar com um exemplo:

System.out.println(“Quantidade máxima de computadores:”, Computador.qtdMaxComputadores);

Neste exemplo estamos acessando diretamente o atributo qtd- MaxComputadores da classe computador. Lembrando que esse valor vai estar presente até o momento em que o coletor de lixo do Java retire a classe da memória.

É importante entender que no Java apenas os métodos e os atributos podem ser estáticos. Não é possível declarar uma classe como estática.

INDICAÇÃO DE LEITURA COMPLEMENTAR

DEITEL, P.J.; DEITEL, H. M. Java Como Programar. 8. ed. São Paulo:

Pearson Education do Brasil, 2010.

Para aumentar seu conhecimento sobre os membros de classe e de instância, estude as páginas 262 e 263 do capítulo 8. Neste trecho do livro, Deitel apresenta as diferenças na utilização dos membros de instância para os membros de classes. O trecho também exemplifica as diferenças entre os dois tipos de membros.

CADENHEAD, R. ; LEMAY, L. Java 2. 4. ed. São Paulo: Editora Campus,

2005.

Os autores demonstram, no capítulo 6, nas páginas 99 e 100, os con- ceitos de membros de classe. Na página 99 é demonstrado um exem- plo para que seja visto como devemos utilizar variáveis de classe.

Tema2

|

Herança

61

PARA REFLETIR

A partir deste momento, começamos a perceber que existem diferen- tes tipos de criação de variáveis, sendo que estas vão depender de como estamos as declarando. Devemos então construir atributos de classes e de instância de acordo com a nossa necessidade, sempre tomando cuidado na sua utilização.

2.3 Herança no Java

Um dos principais objetivos do paradigma da Orientação a Obje- tos se refere à reutilização dos objetos. A herança é um dos principais conceitos de reutilização. Com herança podemos criar subclasses que herdem características e comportamento de superclasses. Em determinadas situações, precisamos criar objetos com algu- mas características específicas e com outras características comuns a outros objetos. Vamos a um exemplo para demonstrar o que está sen- do comentado. Por exemplo, digamos que nós possuímos uma classe Funcionário, sendo que este funcionário tem as seguinte características.

public class Funcionario{ private int idade; private String nome;

public Funcionario(int pIdade, String pNome)

{

this.idade = pIdade; this.nome = pNome;

}

public void imprimir() { System.out.println(“Funcionário: “ + nome + “ “+ idade);

}

}

62

Programação de Computadores II

Vamos agora à seguinte situação. A minha empresa tem em seu

quadro funcional vários funcionários, entre eles médicos, enfermeiros

e engenheiros. Se analisarmos as informações que precisamos arma-

zenar dos funcionários podemos perceber que alguns atributos serão iguais para todos eles, independentemente da atividade que eles realizam, sendo essas características (idade, nome). Porém, no caso

do médico precisamos informar qual é o numero do CRM (Conselho Regional de Medicina), o do enfermeiro é necessário o número do CO- REN (Conselho Regional de Enfermagem) e o número do CREA (Conse- lho Regional de Engenharia, Arquitetura e Agronomia) do engenheiro. Para informar todas estas características, seria necessário que criássemos um atributo para cada uma dessas novas informações. Porém, sempre teríamos atributos desnecessários, ou seja, quando criamos um objeto médico não vamos ter informações de enfermeiros

e engenheiros. Com o intuito de não acontecer este problema, o paradigma

orientado a objetos possui uma capacidade denominada de herança.

O conceito de herança está diretamente ligada ao conceito de

reutilização. Ou seja, um objeto genérico pode ser utilizado por um objeto mais especializado. Este, por vez, herda todas as característi- cas do objeto genérico e acrescenta mais atributos ou métodos. Na orientação a objetos a classe genérica é conhecida como superclasse e a classe especializada é conhecida como subclasse. No nosso exemplo, a classe Funcionário é considerada a superclasse, en- quanto uma outra classe a ser criada, por exemplo, a classe Médico, será considerada a subclasse. Na herança, uma superclasse pode possuir várias subclasses.

Porém, uma subclasse pode somente possuir uma superclasse. Na lin- guagem Java não é permitido heranças múltiplas, ou seja, uma classe herdar comportamentos de mais de uma classe. Para simular herança múltipla podemos utilizar as interfaces. O conceito de interfaces será visto no próximo tema.

O conceito de herança ainda permite que uma subclasse seja

uma superclasse de uma terceira classe. Ou seja, podemos ter uma herança de uma classe filha. Todos os métodos e atributos que a subclasse herda da superclasse também são herdados da superclasse referente.

Tema2

|

Herança

63

No Java, para indicarmos que uma classe herda da outra usamos a palavra reservada extends. Ou seja, uma classe estende o compor- tamento da outra. A sintaxe para que seja utilizada a herança é a seguinte:

<visibilidade> class <nome> extends <superclasse> // definição da classe

}

{

Vamos agora criar uma classe Médico, que herda da classe funcionário.

public class Medico extends Funcionario{ private String CRM;

}

Na classe Medico nós teremos todos os atributos relativos ao médico juntamente com os atributos que já existem na classe Funcio- nario. Podemos então dizer que a classe Medico é uma especialização da classe Funcionario, ou seja, Medico é um Funcionario. Toda vez que estivermos utilizando a herança vamos ter esta relação “X é um Y”. Para nosso exemplo, “Médico é um Funcionário”. Com isso todas as informações (atributos e métodos) que já existiam no funcionário poderão ser utilizadas pela classe médico. Sendo assim, quando instanciarmos uma classe do tipo Medico, te- remos uma classe com os atributos básicos de um funcionário com adição do atributo CRM. Para que a subclasse consiga utilizar todos os métodos e atri- butos da superclasse, é necessário que os membros da superclasse possuam modificadores de visibilidade que permitam a utilização, embora eles sejam herdados. Por exemplo, a superclasse Funcionário tem seus atributos como private. Automaticamente a subclasse não terá acesso aos métodos e atributos. Isto faz sentido. Se as subclasses pudessem modificar os atributos estaríamos ferindo o princípio do encapsulamento. Como as subclasses herdam o comportamento das superclasses, todos os métodos e atributos definidos na superclasse poderão ser

64

Programação de Computadores II

utilizados na subclasse. A herança ainda permite que as subclasses modifiquem o comportamento do que foi determinado na superclasse. Através da herança podemos criar um método com uma funcio- nalidade diferente da superclasse. Mesmo que este método possua o mesmo nome, tipo de retorno e lista de parâmetros. Quando isto acontece dizemos que o novo método sobrescreve (override) o método da superclasse. Vamos demonstrar um exemplo através da classe Medico. Iremos sobrescrever o método imprimir para que possa ser impresso o CRM do médico.

public class Medico extends Funcionario{ private String CRM;

public void imprimir() {

System.out.println(“Médico: “ + nome + “ CRM:”+ this.CRM);

}

}

Desta forma, o método imprimir() será invocado dependendo

de sua instância. Ou seja, vai depender do objeto que esteja invo-

cando o método imprimir. exemplo.

Vamos demonstrar isto através de um

Funcionario e = new Funcionario(35,”João”); e.imprimir(); //método imprimir de Funcionario Medico m = new Medico(); m.imprimir(); //método imprimir de Médico

Quando instanciamos a classe funcionário o objeto que chama o método imprimir vai imprimir as informações genéricas do funcio- nário. Quando criamos o objeto Medico, como esta classe herda de Funcionario, mas sobrescreve o método imprimir(), o que será impres- so será as informações do Medico, ignorando todas as definições do método imprimir() do Funcionario. Porém, para realizar a sobrecarga dos métodos herdados, algu- mas regras devem ser seguidas. Sendo elas:

Tema2

|

Herança

65

• O tipo de retorno deve ser exatamente o mesmo;

• A lista de parâmetros deve ser igual;

• O método da subclasse não pode ser “menos acessível” que o método da superclasse.

Quando falamos menos acessíveis, estamos falando dos modi- ficadores de visibilidade. Ou seja, caso a subclasse esteja herdando de um método public, o método que está sobrescrevendo não pode ser nem private nem protected. A herança ainda permite que, além de sobrescrever os méto- dos, nós possamos nos referir a atributos e métodos da superclasse. Para realizar esta referência, utilizamos a palavra reservada super. Para realizar uma chamada a um método da superclasse devemos fazer o seguinte:

Super.método( )

Vamos modificar a classe Medico para a utilização da referência super.

public class Medico extends Funcionario{ private String CRM;

public Medico (String pCRM, String pNome, int pIdade)

{

this.CRM = pCRM; super(pIdade, pNome);

}

public void imprimir() {

System.out.println(“Médico: “ + nome + “ CRM:”+ this.CRM);

}

}

66

Programação de Computadores II

No exemplo acima estamos utilizando o método super para rea- proveitar o que foi definido no construtor da classe Funcionario. Com isso, não precisamos reescrever todas as definições do Medico. Basta apenas definir o que foi acrescentado na herança e realizar a chamada ao construtor que possui a mesma característica. A chamada ao método continua sendo realizada da mesma ma- neira. Por exemplo, como agora definimos um construtor para a clas- se médico, quando formos instanciar um objeto deste tipo faremos da seguinte maneira:

Medico m = new Medico(“3245”, “Pedro“, 35); m.imprimir();

No exemplo apresentado, o construtor da classe Medico irá de- finir o atributo CRM e irá invocar o construtor da classe Funcionário para que os outro dois atributos (nome e idade) sejam iniciados. Apesar de opcional, podemos indicar ao compilador que esta- mos realizando uma sobrescrita de um método. Através da diretiva @Override nós conseguimos garantir, em tempo de compilação, que o método que estamos criando é uma sobrescrita de um método da superclasse.

public class Medico extends Funcionario{ private String CRM;

public Medico (String pCRM, String pNome, int pIdade)

{

this.CRM = pCRM; super(pIdade, pNome);

}

@Override

public void imprimir() { System.out.println(“Médico: “ + nome + “ CRM:”+ this.CRM);

}

}

Tema2

|

Herança

67

Com a herança podemos ainda utilizar membros de instâncias. Este tipo de membro deve ser declarado na superclasse. Com isso podemos garantir que, se o valor for alterado na superclasse, todas as classes que herdam desta superclasse também herdam as caracte- rísticas da superclasse.

INDICAÇÃO DE LEITURA COMPLEMENTAR

DEITEL, P.J.; DEITEL, H. M

Pearson Education do Brasil, 2010.

Java Como Programar. 8. ed. São Paulo:

Faça uma leitura no capítulo 9, especialmente nas páginas 279 a 297, onde poderão ser encontrados vários exemplos do conceito de herança.

Neste capítulo do livro, Deitel apresenta, por meio de exemplos, a hierarquia dos objetos do Java API. Além de descrever boas práticas de utilização da coleção.

CADENHEAD, R. ; LEMAY, L. Java 2. 4. ed. São Paulo: Editora Campus,

2005.

No capítulo 1, especialmente nas páginas 13 a 16, os autores demons- tram como podemos utilizar, na Orientação a Objetos as heranças e he- ranças múltiplas, ou seja, como são realizadas as hierarquia de heranças.

PARA REFLETIR

Com a utilização da herança podemos perceber que, quando utilizada de maneira correta e bem modularizada, a herança nos traz uma pro- dutividade maior. Isto porque podemos reutilizar bastante as classes que já foram criadas anteriormente. Reflita com o tutor e com os seus colegas sobre o assunto.

68

Programação de Computadores II

2.4 A classe Object

Depois de demonstrar como funciona a herança, vamos falar um pouco sobre a classe Object. Esta classe faz parte do pacote java. lang. Este é o pacote básico da linguagem Java e é neste pacote que estão contidas as classes String e System. Se observarmos, nunca precisamos importar este pacote, isto porque ele é automaticamente importado e é o único pacote que possui esta característica. A classe Object é uma classe base, em que todas as outras clas- ses herdam. No Java, em todas as classes que criamos, é necessário que esta classe herde algum comportamento de outra classe. Ou seja, para toda classe criada iremos ter uma superclasse. Se não utilizarmos a herança explicíta, ou seja, colocarmos a palavra reservada extends, automaticamente o compilador Java irá herdar todas as características da classe Object. Por exemplo:

public class Conta {

}

Para o compilador do Java, esta declaração da classe Conta é visto como:

public class Conta extends Object{

}

Esta característica não é apenas para as classes que são criadas. Todos os tipos, incluídos os tipos genéricos como int e double também herdam da classe Object. Tipos derivados de tipos inteiros, por exem- plo, vetores também possuem a mesma característica de herança. A utilização do tipo Object nos traz alguns benefícios. Isso por- que estamos tratando de um tipo genérico. Por exemplo, se estamos utilizando um método em que não sabemos qual será o tipo de obje- to que será passado como parâmetro, podemos utilizar um parâmetro do tipo Object. Isso vai ser possível pelo fato de que todos os objetos são heranças da classe Object. Vamos a um exemplo:

public class Conta {

Tema2

|

Herança

69

private int qtdTitulares; private Object[] titularesConta = new Object[3];

public void AdicionarTitular(Object pTitular)

{

this. titularesConta [qtdTitulares] = pTitular; qtdTitulares++;

}

}

No exemplo acima estamos criando uma classe Conta que pode possuir no máximo três titulares. Vale observar que o parâmetro que está sendo passado é do tipo Object, ou seja, não existe um tipo específico. Isso pode ser útil, no caso em que o titular de uma conta pode ser um funcionário ou uma pessoa. Quando formos utilizar a classe conta teremos a seguinte im- plementação:

Conta conta = new Conta(); Pessoa pessoa = new Pessoa(); pessoa.setNome(“João”);

pessoa.setCPF(“123123123“);

Funcionario funcionário = new Funcionario(); funcionario.setNome(“Maria“);

funcionário.setPIS(“12346343“);

conta.AdicionarTitular(pessoa);

conta.AdicionarTitular(funcionario);

No exemplo é visível que estamos utilizando o mesmo método de adição para dois objetos de tipos diferentes. Mas, como nosso mé- todo possui um parâmetro genérico, um parâmetro do tipo Object, isso torna possível. Porém, não podemos utilizar diretamente um item do vetor de Object. Isto porque, apesar de genérico, o objeto não vai ter nenhum método do objeto que estamos realmente querendo acessar. Por exemplo, se o objeto conta possui o método getSaldo e tentarmos fazer o seguinte

70

Programação de Computadores II

Object objetoConta = conta.titularesConta[0]; objetoConta.getSalto();

A linha que chama o método getSaldo não irá compilar. Isto porque o método não existe na classe Object. Este método pertence apenas à classe Conta. No próximo tema, polimorfismo, este tipo de diferença será demonstrada com mais detalhes. Porém, como nós temos a certeza que o objeto do vetor na posição zero é do tipo Conta, nós podemos fazer uma conversão de tipo para que o compilador identifique que o tipo Object da posição zero seja convertido para a classe Conta. Essa conversão necessita ser feita de forma explícita. Vamos a um exemplo.

Pessoa objetoConta = (Pessoa) conta.titularesConta[0]; objetoConta.getSalto();

A partir do momento em que colocamos a conversão através do “(Conta)”, estamos informando para o compilador que o meu objeto da posição zero é um objeto do tipo Conta. Com isso conseguimos utilizar normalmente todos os métodos e atributos que a classe Conta possui. Agora precisamos ter cuidado com este tipo de conversão, pois, se convertemos para um tipo diferente do objeto que é realmente uma instância, teremos um erro em tempo de execução. O compilador não consegue identificar este tipo de erro no processo de compilação. Para o mesmo exemplo acima vamos mudar o tipo da classe em que está sendo realizada a conversão.

Funcionario objetoConta = (Funcionario)Conta.titularesConta[0]; objetoConta.getSalto();

Nesta situação, continuamos tendo um objeto Pessoa na po- sição zero do vetor. Porém, estamos informando ao compilador que este objeto é do tipo Funcionário. Com isso quando formos tentar utilizar o objetoConta em tempo de execução iremos nos deparar com erros. Isto porque o objeto que realmente está na posição zero é um objeto do tipo Pessoa.

Tema2

|

Herança

71

A classe Object possui dois métodos que são bastante utilizados, sendo eles: equals e toString. Vamos falar um pouco de cada um deles.

O método equals é o responsável por verificar algum tipo de igualdade entre os objetos. Para os tipos primitivos, nós realizamos a comparação através do operador “==”. Com ele conseguimos analisar se o valor de uma variável possui o mesmo valor que está armazenado em outra variável. Porém, se formos comparar objetos? Estaremos comparando as suas referências? Vamos começar com um exemplo.

Aluno aluno_a = new aluno(“roberto”, 7.0); Aluno aluno_b = new aluno(“maria”, 5.0); Aluno aluno_c = new aluno(“roberto”, 7.0);

aluno_a == aluno_b é falso aluno_b == aluno_c é falso aluno_a == aluno_c é falso

Possuímos três instâncias diferentes da classe Aluno. Porém, os valores contidos nos objetos aluno_a e aluno_c e porque os objetos são diferentes? São diferentes porque as suas instâncias são diferen- tes. Como devemos então comparar os objetos? Por meio do método equals( ) da classe Object. A princípio o método object trabalha exata- mente igual ao operador (==). Ou seja, ele retorna true se e somente se x e y se referem ao mesmo objeto.

Porém, este método pode ser reescrito e ensinado a comparar dois objetos.

72

Programação de Computadores II

public class Conta {

private int qtdTitulares; private int codTitular;

public boolean equals(Object object)

{

Conta conta = (Conta) object;

if (conta.codTitular == this.codTitular)

}

}

{

else

{

}

return false;

}

return true;

No exemplo, criamos uma lógica para o método equals em que, para verificar se dois objetos são iguais, verificamos se o código do titular é o mesmo. Algumas regras para a sobrecarga do método de- vem ser respeitadas. Para referências não nulas:

• É reflexivo: a.equals(a) tem sempre que ser verdadeiro, ou seja, um objeto é sempre igual a si mesmo.

• É simétrico: a.equals(b) retorna verdade se e somente se b.equals(a).

• É transitivo: a, b e c, se a.equals(b) e b.equals(c) são ver- dade então a.equals(c) tem que ser verdade.

• É consistente: para múltiplas invocações de a.equals(b) terá sempre que retornar true ou sempre retornar false, en- quanto informações usadas na comparação do equals não sejam alteradas.

Tema2

|

Herança

73

O resultado de equals() entre um objeto e nulo deve re- tornar sempre falso, ou seja, a.equals(null) é sempre falso.

o método toString é utilizado para retornar a representação

string do objeto.

O toString() da classe Object retorna um string com o nome da

classe + ‘@’ + representação hexadecimal sem sinal do código hash do objeto. Por exemplo:

Conta conta = new Conta(); System.out.println(conta.toString()); //Valor retornado

aluno@457321245

Com isso é interessante que sempre que precisarmos utilizar o método toString() que seja feita a reescrita deste método para que ele retorne alguma informação que podemos mostrar como final.

public class Conta {

private int qtdTitulares; private int codTitular;

public boolean toString(Object object)

{

System.out.println(“O titular da conta possui o código: “ + this.codTitular;

}

}

Com isso, automaticamente quando utilizarmos o método toString( ), teremos como resultado final o que ficou definido dentro da reescrita do método.

74

Programação de Computadores II

INDICAÇÃO DE LEITURA COMPLEMENTAR

DEITEL, P.J.; DEITEL, H. M

Pearson Education do Brasil, 2010.

Java Como Programar. 8. ed. São Paulo:

Uma maneira de aprofundar sobre a classe Object pode ser encontra- da nas páginas 298 e 299 do capítulo 8. Neste capítulo a definição da classe Object é demonstrada pelo autor com alguns exemplos de como podemos utilizar esta classe. Também são demonstrados os principais métodos da classe.

HORSTMANN, C.S.; CORNELL, G ed. São Paulo: Alta Books, 2005.

Core Java 2. Vol 1 - Fundamentos. 7.

Uma produtiva leitura pode ser feita nas páginas 95 a 101, onde po- dem ser encontradas mais informações sobre os principais métodos da Super Classe Object como equals (responsável por testes de igual- dade), hasCode e toString.

PARA REFLETIR

Na orientação a objetos sempre utilizamos tipos criados por nós. Porém, nunca tínhamos parado para pensar que o Java já possuía um objeto genérico para todas as nossas classes. Com este tópico po- demos perceber que podemos comparar objetos através de reescrita de métodos.

Tema2

|

Herança

75

RESUMO

No capítulo 2.1, podemos aprender como são criados os construtores de um objeto. Através da criação destes construtores podemos ver que, ao criar um objeto, podemos já criá-los com seus atributos já definidos. Neste capítulo também aprendemos que o Java possui um gerenciador de memória, sendo que este é o responsável por descar- tar objetos que não estão sendo mais utilizados.

Enquanto, no capítulo 2.2, nós estudamos a diferença entre os mem- bros de instância e os membros de classe. Vimos que a principal diferença é que os métodos de um membro de instância só podem ser utilizados quando tivermos uma instância da classes enquanto os membros de classe podem ser utilizados a qualquer momento.

Já no capítulo 2.3, nós aprendemos o conceito de herança. Através deste conceito podemos perceber que com a reutilização do código fica mais fácil, já que podemos criar objetos com características seme- lhantes a objetos já criados, sendo que sempre podemos reaproveitar métodos e atributos já criados.

Por fim, no capítulo 2.4, aprendemos que todos os objetos criados e os objetos existente no Java são uma herança da classe Object. Vimos que esta classe é responsável por alguns métodos que todos os ou- tros objetos possuem, como o toString() e o equals( ).

ASPECTOS AVANÇADOS DA PROGRAMAÇÃO ORIENTADA A OBJETOS EM JAVA

Parte 2

3
3

Polimorfismo

Já estamos no nosso terceiro tema e o momento é de fazermos um estudo sobre o conceito de polimorfismo. Inicialmente faremos um aprofundamento teórico sempre com exemplos comentados. Veremos na sequência a importância das palavras abstract e final na linguagem Java. Também, neste tema, falaremos sobre o impor- tante conceito de interfaces, com suas definições e implementações e, no último conteúdo deste tema, ainda iremos comentar sobre algumas interfaces especiais.

80

Programação de Computadores II

3.1 Do Conceito à Prática

Introdução Polimorfismo é uma palavra de origem grega que quer dizer várias formas. Isto é, mais de uma maneira de fazermos a mesma coisa. Como estamos falando de uma linguagem de programação, temos que, o que pode ser feito de diferentes maneiras, concentra-se especificamente em um ponto, em como fazemos as chamadas aos métodos. Podemos definir então o polimorfismo como um mecanis- mo que permite que duas ou mais classes derivadas de uma mesma classe ancestral possuam métodos com a mesma assinatura, porém com comportamentos distintos, havendo uma especificidade para cada classe derivada. Esta importante característica presente nas linguagens OO permite que uma mesma mensagem enviada a um objeto tenha comportamento distinto, variando de acordo com o tipo de objeto instanciado. Aperfeiçoando nossos conhecimentos de herança (que foi as- sunto do conteúdo 2.3), percebemos que um objeto de qualquer subclasse pode ser manipulado como sendo um objeto da sua super- classe. Este comportamento faz com que objetos sejam tratados de forma genérica, mas para isto acontecer, deve haver uma hierarquia de classes. Existe também uma operação conhecida como typecast, que consiste em realizar uma conversão explicita de um objeto de uma classe para outra. Para isto, basta colocar entre parênteses e antes do objeto o tipo da classe a ser convertido, como no exemplo:

(ClasseDestino) meuObjeto;

Faz-se necessário que as classes origem e destino da conversão tenham uma relação de herança. Devemos lembrar que o typecast de uma subclasse para uma superclasse é implícito e automático, ficando transparente para o desenvolvedor, enquanto o inverso não é garantido, sendo para isso necessário o typecast explícito.

Funcionamento Como vimos, o polimorfismo consiste em várias maneiras de se fazer a mesma coisa, então podemos entender que temos a pos- sibilidade de escrever um método de mesmo nome com diferentes assinaturas. Isto faz com que, dependendo das circunstâncias, este

Tema 3

|

Polimorfismo

81

mesmo método possa ter comportamentos diferentes. Quem decide qual comportamento é o mais adequado para a situação é o objeto que possui o método acionado. O que acabamos de falar é muito importante para o entendimento do conceito de polimorfismo e, se ainda não está seguro se entendeu bem a frase anterior, é recomen- dável uma releitura acompanhada de uma reflexão. Temos então que a decisão sobre a escolha de qual método será selecionado ocorrerá de acordo com o tipo do objeto. Como esta decisão acontecerá apenas em tempo de execução, ocasionando um vínculo posterior, este procedimento passa a ser chamado de ligação tardia (late binding ou dynamic binding). Vejamos a seguinte situação: um determinado objeto “Pessoa” aciona um método abastecer() de um outro objeto “Veiculo”; este objeto é o responsável por decidir como será a implementação do método abastecer(). Analisando mais profundamente, quem vai de- cidir qual implementação será escolhida é o tipo do objeto “Veiculo”. Em outras palavras, se o Veículo é um Carro, vai ser de uma maneira; se for uma Lancha, vai ser de outra; se for uma Motocicleta, será de uma terceira maneira e assim por diante. Podemos constatar esta forma de tratamento polimórfico de maneira exemplificada. Vejamos

o trecho de código abaixo:

Veiculo meuVeiculo;

if (

)

{

meuVeiculo = new Carro(); } else { meuVeiculo = new Lancha();

}

meuVeiculo.abastecer(); //método polimórfico

Neste momento, é válido lembrarmos a frase que mereceu aten-

ção poucos parágrafos atrás: quem decide qual comportamento é

o mais adequado para a situação é o objeto que possui o método

acionado. Para comprovar a veracidade desta afirmação, podemos constatar que foi o objeto Veiculo quem decidiu qual método foi acio- nado. Isto aconteceu de acordo com o seu tipo, que poderia ser Carro, Lancha ou qualquer outra classe desde que herdasse de Veiculo.

82

Programação de Computadores II

Apesar de entendermos que, dependendo da circunstância, o objeto chamado vai agir de uma maneira ou de outra, vale ressaltar que o objeto acionado não se modifica nem se transforma. O que acontece é que, em tempo de execução, o compilador Java vai iden- tificar qual classe de fato será chamada, mas todas as classes com seus métodos já foram previamente criadas. Como no exemplo aci- ma, apesar da variável meuVeiculo ser do tipo Veiculo, a instanciação de verdade será de Carro ou de Lancha. No momento que uma mensagem é enviada para um objeto de uma subclasse, os seguintes passos são executados:

-

subclasse acionada verifica se possui um método com aquele nome e com a mesma quantidade e tipos de parâmetros;

a

-

caso realmente possua, este mesmo método é executado;

-

caso contrário, a mensagem é enviada para a classe imediata- mente superior no nível hierárquico;

-

percorrendo toda a hierarquia, chegando até a classe Object,

e não sendo encontrado um método que atenda as condições

necessárias, acontece neste momento um erro de execução.

Vamos analisar detalhadamente um modelo simples contendo as seguintes classes a seguir:

- uma classe Funcionario que contem um método calcularSalario( ), como esta classe não é capaz de fato de calcular o salário, ela retorna o valor zero;

public class Funcionario { public double calcularSalario() { return 0;

}

}

Tema 3

|

Polimorfismo

83

- uma classe FuncionarioHorista herda de Funcionario, apresenta os atributos salario- Hora e horasTrabalhadas e ainda um método calcularSalario( );

public class FuncionarioHorista extends Funcionario { private double salarioHora; private int horasTrabalhadas; public double calcularSalario() { return (salarioHora * horasTrabalhadas);

}

}

- uma classe FuncionarioMensalista que tam- bém herda de Funcionario e contém o atribu- to salarioMensal e o método calcularSalario( ).

public class FuncionarioMensalista extends Funcionario { private double salarioMensal; public double calcularSalario()

{

return salarioMensal;

}

}

O modelo descrito acima ilustra como a em-

presa categoriza os seus funcionários. Independen- temente do tipo, todo funcionário deve ser capaz de calcular o seu próprio salário. Caso seja um horista, o salário será a multi- plicação do atributo salarioHora pelo atributo horas- Trabalhadas, o que acontece no trecho de código na próxima página.

84

Programação de Computadores II

Funcionario funcH = new FuncionarioHorista(); System.out.println(funcH.calcularSalario());

Já se o funcionário for um mensalista, o salário será o próprio atributo salarioMensal, que é impresso no trecho abaixo.

Funcionario funcH = new FuncionarioMensalista(); funcH.calcularSalario();

Entendendo o polimorfismo como um conceito amplo, podemos aceitar que uma sobrecarga (overload) de métodos (assunto que já foi brevemente comentado no conteúdo 1.4) possa também ser conside- rada como um tipo de polimorfismo. Na sobrecarga de métodos temos a possibilidade de escrever várias assinaturas diferentes, desde que o nome do método permane- ça sempre o mesmo. A diferença nas assinaturas será responsabilida- de então da lista de parâmetros e do tipo de retorno. Esta aplicação não cria uma situação de conflitos entre os métodos uma vez que o compilador irá analisar a lista de argumentos e descobrir qual método será o escolhido. Qualquer alteração na lista dos argumentos de entrada, seja na quantidade ou nos tipos, implica automaticamente em uma nova assinatura. Exemplificando: podemos ter uma assinatura com dois parâmetros do tipo inteiro, outra com três parâmetros inteiros, outra ainda com dois parâmetros reais e assim por diante. É possível fazer, em assinaturas distintas, alguma alteração no tipo de retorno, por exemplo, quando são dois parâmetros de entrada do tipo inteiro, o parâmetro de saída pode ser do tipo inteiro, quando forem dois parâmetros de entrada do tipo real, o parâmetro de saída pode ser do tipo real. Porém, não é possível fazer uma alteração exclusivamente no tipo de retorno, isto é, não podem existir duas assinaturas que sejam diferenciadas pelo tipo do parâmetro de retorno, mantendo a quantidade e tipo de parâmetros de entrada exatamente iguais. A seguir, alguns exemplos de sobrecarga de métodos, onde apesar do nome do método ser o mesmo (maiorValor), a lista de argumentos é diferente (no primeiro exemplo são dois inteiros e no segundo são dois reais):

Tema 3

|

Polimorfismo

85

public int maiorValor(int nA, int nB) { int maior = nA; if (nB > maior) { maior = nB;

}

return maior;

}

public float maiorValor(float nA, float nB) { float maior = nA; if (nB > maior) { maior = nB;

}

return maior;

}

Outra aplicação possível e bastante utilizada é a sobrecarga de construtores. Para uma determinada classe, podemos disponibilizar vários construtores, cada um deles com a sua lista de parâmetros correspondente. Claro que, neste caso, não se aplica falar em tipo de retorno, pois como vimos no conteúdo 2.1, um construtor não tem parâmetro de retorno. Quando se aplica sobrecarga em um construtor, o objetivo é oferecer uma variedade de opções no momento da criação da ins- tância de uma classe. É possível criar um construtor sem parâmetro, com alguns ou até mesmo todos os parâmetros, onde cada parâmetro representa um atributo da classe. Vejamos algumas possibilidades de construtores para uma de- terminada classe Aluno, onde a diferenciação acontece, como foi dito, na lista de parâmetros:

public Aluno()

{ }

public Aluno(int matricula)

{ }

public Aluno(int matricula, String nome)

{ }

public Aluno(int matricula, String nome, int codigoCurso)

{ }

86

Programação de Computadores II

INDICAÇÃO DE LEITURA COMPLEMENTAR

DEITEL, P.J.; DEITEL, H. M. Java Como Programar. 8. ed., São Paulo:

Pearson Education do Brasil 2010.

Uma ótima leitura para aprimorar o seu conhecimento em polimorfis- mo pode ser encontrada nas páginas 304 a 308 (capítulo 10), onde é mostrado o mecanismo e as principais vantagens no uso do poli- morfismo.

SINTES, A

Pearson Education do Brasil, 2010.

Aprenda Programação Orientada a Objetos. São Paulo:

Leitura recomendada da página 121 a 144 para sedimentar o que foi lido sobre polimorfismo neste conteúdo, desde as principais técnicas de uso, benefícios e aplicações nos construtores e nos métodos gerais.

PARA REFLETIR

Neste momento de reflexão, realize um debate com seus colegas sobre a importância de dois conceitos: a sobrecarga de métodos (overload) e sobreposição de métodos (overriding), analisando os benefícios de cada um e as suas aplicações práticas.

3.2 Abstract X Final

Tema 3

|

Polimorfismo

87

Introdução Neste conteúdo, serão abordadas duas palavras reservadas da linguagem Java com importante papel para a boa utilização da pro- gramação OO. No primeiro momento falaremos da palavra abstract e em seguida será a vez da palavra final.

Modificador Abstract A palavra reservada abstract pode ser utilizada tanto para mo- dificar a característica de um método como de uma classe. Tornar uma classe abstrata (ou um método abstrato) significa omitir uma determinada implementação, deixando a preocupação do desenvol- vedor concentrada, neste momento, apenas na estruturação da clas- se. Neste momento, o importante é saber o que a classe (ou método) faz e não como isso será feito, em outras palavras, servirá como uma criação de modelo a ser seguido. A implementação propriamente dita ocorrerá apenas em um momento posterior (nas subclasses).

Método abstrato A linguagem Java permite que haja um método com sua assina- tura bem definida, contudo sem uma implementação presente. Um método dito abstrato possui todas as características de um método normal: nome do método, parâmetros de entrada e tipo de retorno normalmente como qualquer outro método, mas não há nenhuma linha de código dentro deste método. Sequer pode haver o par ‘{‘ e ‘}’, logo após a assinatura deste método, deve haver um ‘;’ que encerra o método. Vejamos um exemplo da construção de um método abstrato:

public abstract void alterarSalario(float valor);

Percebemos que a assinatura do método acima recebe um novo modificador: a palavra reservada abstract; notamos também que ao final da assinatura, observamos que não há implementação, imedia- tamente após a assinatura é necessário concluir o sinal ‘;’.

88

Programação de Computadores II

A implementação deste método só irá acontecer nas subclasses

que herdarão da classe onde este método se encontra. Qualquer classe que contenha no mínimo um método abstrato passa a ser considerada uma classe abstrata. Não pode existir um método abstrato privado (com o uso das palavras reservadas private e abstract), pois, como vimos, o método abstrato precisa ser implementado pela subclasse. Como um método

privado não é acessado pela subclasse, esta situação, portanto, não poderia ser resolvida.

O objetivo da existência de um método abstrato dentro de uma

classe é justamente torná-la também abstrata. Veremos na sequência o papel de uma classe abstrata.

Classe abstrata Uma classe abstrata pode ser entendida como se fosse uma classe incompleta e por este motivo não pode ser plenamente utiliza- da. Quando falamos que ele é incompleto, isso se deve ao fato desta classe conter pelo menos um método incompleto (veremos que este método incompleto será um método abstrato). Já quando falamos que esta classe ainda não está pronta para uso, queremos dizer que ela nunca pode ser instanciada. Lembre-se que é possível compilar normalmente uma classe abstrata, a única ressalva é realmente não poder criar uma instância a partir dela. Caso você tente instanciar uma classe abstrata qualquer (por exemplo Funcionario) , irá receber uma mensagem de erro do compilador Java em inglês semelhante à frase abaixo:

Funcionario is abstract; cannot be instantiated

O objetivo de uma classe abstrata é criar atributos e métodos comuns a todas as suas subclasses, mas com a garantia de que só poderá haver instâncias a partir das próprias subclasses, nunca a partir da superclasse abstrata; sua única razão para existir é ser estendida (herdada) por outras classes. Uma premissa essencial para uma classe abstrata é a presença de algum método abstrato, ou seja, toda classe abstrata deve conter pelo menos um método abstrato, caso contrário, ela não será

Tema 3

|

Polimorfismo

89

abstrata. Não há problema em conter métodos não-abstratos, o que não pode acontecer é que eles sejam a totalidade. Vejamos a seguir um exemplo de uma classe abstrata:

public abstract class Funcionario { // atributos private int código; private String nome; private String cargo; private float salario; // métodos public int getCodigo() {

}

public void setCodigo(int codigo) {

}

… // demais métodos não abstratos public abstract void alterarSalario (float valor);

}

No exemplo acima, destacamos a presença do método abstrato alterarSalario( ). Se ele não estivesse presente na classe Funcionario, ou ainda se tal método não fosse abstrato, a classe não poderia ser abstrata, pois não haveria outro método abstrato que garantisse a classe como abstrata. Quando uma subclasse herda de uma superclasse sendo esta abstrata, todos os métodos não abstratos já estarão automatica- mente presentes na subclasse e disponíveis para serem aciona- dos. Entretanto, precisamos analisar o caso do método abstrato no momento da herança. O que acontece é que o método abstrato também será herdado (continua sendo abstrato). Neste momento, precisamos tomar uma decisão: a primeira opção é não fazer nada referente ao método, desta maneira a presença de um método abs- trato implica em indicarmos a classe também como abstrata, o que impede de criarmos instâncias também a partir desta subclasse (o

90

Programação de Computadores II

mesmo que aconteceu na superclasse abstrata); apenas as classes que, por sua vez, herdassem desta poderiam ter instâncias criadas (desde que não tomassem a mesma atitude que a primeira subclasse tomou). A segunda opção consiste basicamente em fazermos uma redefinição (overriding) do método abstrato herdado, o objetivo des- ta redefinição nada mais é que apenas repetir a assinatura completa do método, mas desta vez retirando a palavra reservada abstract e também o ‘;’ no final da instrução e, além disso, agora sendo imple- mentado de fato (com as suas devidas instruções) o mesmo método. Abaixo segue um exemplo de como seria a implementação do método abstrato alterarSalario() em uma subclasse.

public void alterarSalario (float valor) { this.setSalario(valor);

}

Caso uma classe qualquer contenha exclusivamente métodos abstratos, ela passa a ser chamada de classe abstrata pura (veremos no conteúdo seguinte o conceito de interface e faremos uma compa- ração com o conceito de classe abstrata pura).

Modificador Final A palavra reservada final é utilizada tanto para modificar o com- portamento de uma variável, de um método, de uma classe ou de uma interface.

Variável final Uma variável final só pode ter o seu valor atribuído uma única vez. Uma vez recebido o seu valor inicial, tal valor não pode mais ser alterado. Esta característica acaba transformando funcionalmente tal variável em uma constante. Inclusive uma variável com o modi- ficador final, quando é do tipo primitivo ou ainda do tipo String, é formalmente chamada de variável constante. Acontece um erro de compilação ao tentar alterar o valor de uma variável final. Conforme exemplo abaixo, a palavra reservada final deve prece- der o tipo e nome da variável.

Tema 3

|

Polimorfismo

91

É possível não atribuir nenhum valor a uma variável final no momento da declaração, desta maneira, é possível a qualquer mo- mento posterior do código realizar uma atribuição a tal variável. Neste caso, não há problema, pois fica evidente que esta seria a primeira operação de atribuição. Mas após este ponto, nenhuma alteração pode ser realizada. Resumindo: a primeira atribuição a uma variável fi- nal pode acontecer a qualquer momento (não é obrigatório que seja no instante da declaração), entretanto uma troca de valor não é permitida. Quando se trata de um atributo de uma classe utilizando o mo- dificador final, temos duas possibilidades para realizar a atribuição (que, claro, só pode ocorrer uma única vez). Ou a atribuição de valor ocorre no próprio instante da declaração do atributo (ex: ”private final int valor = 100;”); ou, caso não seja escolhida a opção anterior, fica sendo estritamente obrigatório proceder à atribuição em todos os construtores existentes na classe. Neste último caso, o atri- buto é conhecido como blank final. Caso não seja utilizada nenhuma das duas opções (nem atribuir o valor do atributo na inicialização nem nos construtores), acontece um erro em tempo de compilação.

Método final Um método que utiliza a palavra reservada