Vous êtes sur la page 1sur 91

Java Básico +

Orientação a Objetos

JavaRN - http://javarn.dev.java.net
J2EEBrasil - http://www.j2eebrasil.com.br

1
Sumário

1. Introdução 4

2. Estrutura de um Código Java 6


2.1 Arquivo Fonte 6
2.2 Classe 6
2.3 Método 7

3. Primeiro Programa em Java 7

4. Elementos Básicos de um Arquivo Fonte .java 9

5. Identificadores e Palavras Reservadas 10


5.1 Convenção da Linguagem 11

6. Variáveis 11
6.1 Declaração de Variáveis 12
6.2 Variáveis que Referenciam Objetos 13

7. Outras Seqüências de Escape 16

8. Separadores 16

9. Operadores 17
9.1 Operadores Unários 17
9.2 Operadores Aritméticos 19
9.3 Operadores de Deslocamento 20
9.4 Operadores de Comparação 21
9.5 Operadores Bit-a-Bit 22
9.6 Operadores Lógicos de Curto-Circuito 22
9.7 Operador de Atribuição 24

10. Estruturas de Controle e Fluxo 24


10.1 Estrutura de Seleção if 24
10.2 Estrutura de Seleção if/else 25
10.3 Estrutura de repetição while 27
10.4 Estrutura de repetição do/while 27
10.5 Estrutura de repetição for 28
10.6 Estrutura de seleção múltipla switch 29
10.7 Interrompendo os Fluxos de Controle 30

11. Vetores/Arrays 30
11.1 Vetores/Arrays Multidimensionais 33
11.2 Estrutura for Aprimorada 34

12. Programação Orientada a Objetos 36


12.1 Abstração 36
12.2 Objetos 37
12.3 Classes 37
12.4 Passagem de Argumentos para Métodos 39
12.5 Lista de Argumentos de Comprimento Variável (Varargs) 43
12.6 Retorno de Métodos 44
12.7 Construtores 45

2
12.8 Sobrecarga de Métodos 46

13. Igualdade entre Objetos 47


13.1 Método equals() 49

14. Encapsulamento 52

15. Herança 54
15.1 Como Definir o Relacionamento de Herança 56
15.2 Exemplo de Implementação de Herança em Java 61

16. Polimorfismo 65
16.1 Redefinição de Métodos 68

17. Classes e Métodos Abstratos 69

18. Interfaces 73
18.1 Breve Comparação entre Classe Abstrata e Interface 79

19. Modificadores 79
19.1 Private 79
19.2 Public 80
19.3 Protected 80
19.4 Package 80
19.5 Static 80
19.6 Final 82
19.7 Abstract 82
19.8 Transient 82
19.9 Synchronized 82

20. Redefinição de Métodos e Modificadores 82

21. Wrappers de Tipos 83


21.1 Autoboxing e Auto-Unboxing 85

22. Constantes em Java 86

23. Referências Bibliográficas 91

3
1. Introdução

Java é uma linguagem de programação que começou a ser desenvolvida pela Sun
Microsystems[1] por volta de 1991. Possui uma estrutura bastante parecida com as
linguagens C e C++.
Java é orientada a objetos, portanto, inclui os benefícios de reutilização de
código, extensibilidade e aplicações dinâmicas. Sintetizando, programar em Java
significar criar classes (formadas por variáveis e métodos) que encapsulam
funcionalidades em objetos reutilizáveis e que podem ser carregados dinamicamente.
Uma outra característica bastante importante dessa linguagem é o fato dela ser portável,
ou seja, uma aplicação implementada em Java pode ser executada em diversos sistemas
operacionais.
Java é uma linguagem que é primeiramente compilada e em seguida
interpretada. A Figura 1 apresenta um esquema de compilação e execução de um
programa escrito nela. O primeiro passo é a construção de um arquivo com o código
fonte, que deve ter como extensão .java. No caso do exemplo da Figura 1, o arquivo foi
chamado de HelloWorld.java. Em seguida, o código fonte é compilado e um programa
fonte em bytecodes (HelloWorld.class) é gerado. Durante a compilação, há uma
checagem de erros do código fonte. O fonte em bytecodes só será gerado se nenhum
erro tiver sido detectado. Por fim, qualquer dispositivo que execute Java será capaz de
interpretar este novo arquivo fonte e executar a aplicação. Os bytecodes são lidos e
executados (ou seja, interpretados) pela Máquina Virtual Java (JVM – Java Virtual
Machine) em um computador, em um celular, etc., além de serem independentes de
plataforma.

Figura 1 - Esquema de compilação e execução de um programa Java

4
A geração dos bytecodes ou, para o exemplo, do arquivo HelloWorld.class é
feita a partir da execução do comando javac HelloWorld.java . Então, o próximo
passo é fazer com que a JVM execute o arquivo HelloWorld.java, através do comando
java HelloWorld.class . Dessa maneira, a JVM traduz o código compilado para uma
linguagem que a máquina entenda e o programa é executado.
Resumindo, um programa Java é um conjunto de instruções a serem interpretadas por
uma JVM. Como já citado, os bytecodes são independentes de plataforma. Dessa forma,
os programas em Java podem executar em qualquer plataforma de hardware ou
software que possua uma versão da JVM.
A Máquina Virtual Java é definida como uma máquina imaginária implementada
através da emulação em um software executado em uma máquina real. Ela possui uma
arquitetura que permite garantir segurança. Quando um programa Java é executado,
seus bytecodes são verificados pela JVM para que estejam de acordo com os seus
requisitos de segurança, impedindo a execução de código com alguma irregularidade.
Assim, códigos fonte com instruções que acessem áreas restritas da memória ou até
mesmo recursos do hardware não são executados pela JVM.

5
2. Estrutura de um Código Java

A estrutura geral de um código Java está apresentada na Figura 2.

Figura 2 - Estrutura Geral de um Código em Java

2.1 Arquivo Fonte

Um arquivo de código fonte possui a definição de uma classe. Uma classe


representa uma parte do programa. Ela define atributos (variáveis), o comportamento
de um objeto e possui a declaração básica apresentada na Listagem 1. Os limites da
definição da classe são dados pelas chaves {}.

Listagem 1 - Formato Geral da Declaração de uma Classe


class Passaro{

2.2 Classe

Uma classe é formada por um ou mais métodos. Os métodos definem as


funcionalidades da classe, ou seja, o que se pode fazer com os objetos dessa classe. No
caso da classe da Listagem 1, a classe Passaro possui dois métodos: voar e cantar,
como mostrado na Listagem 2. Os métodos devem ser declarados dentro da classe e
seus delimitadores também são as chaves {}.

6
Listagem 2 - Métodos da Classe
class Passaro{
void cantar(){

}
void voar(){

}
}

2.3 Método

Um método é formado por declarações e instruções que juntas representarão


uma funcionalidade da classe. Como se pode observar na Listagem 3, as declarações e
instruções são separadas por ponto-vírgula.

Listagem 3 - Instruções do Método


class Passaro{

void cantar(){
declaracao;
instrução1;
}
void voar(){
declaração;
instrução1;
instrução2;
}
}

3. Primeiro Programa em Java

O primeiro exemplo a ser apresentado deve ser escrito em um arquivo


denominado PrimeiroExemplo.java, que conterá uma classe também chamada de
PrimeiroExemplo. Quando o comando java PrimeiroExemplo.class é executado, a
JVM inicia e procura um método que parece com o descrito na Listagem 4. Esse método
é denominado método principal da classe.

7
Listagem 4 - public static void main
public static void main (String[] args){
//Código do programa a ser executado
}

O próximo passo é a JVM executar o código que se encontra entre as chaves ({})
do método principal. Toda aplicação Java tem ao menos uma classe e no mínimo um
método principal ou método main. É importante saber que não existe um método main
por classe, mas sim por aplicação.
A Listagem 5 apresenta o primeiro exemplo deste material escrito em Java.

Listagem 5 - PrimeiroExemplo.Java
public class PrimeiroExemplo{
public static void main (String[] args){
System.out.println (“Primeiro Exemplo em Java”);
}
}

Agora será explicado o código fonte presente na Listagem 5. Algumas coisas


ainda ficarão obscuras, mas tenha em mente que este é o primeiro exemplo a ser
apresentado e explicações futuras esclarecerão as possíveis dúvidas. A primeira linha
declara o nome da classe implementada:

public class PrimeiroExemplo{

A palavra public indica que a classe terá um nível de acesso público, ou seja, que
ela será acessível de qualquer classe. A palavra class indica que uma classe está sendo
declarada e seu nome é PrimeiroExemplo. A { delimita o limite inicial da classe.
A segunda linha declara o método principal da classe:

public static void main (String[] args){

A palavra public já foi descrita, portanto permite que o método seja acessado
publicamente. A palavra static será descrita posteriormente. O lugar onde fica a palavra
void é onde se deve indicar o tipo de retorno da classe. Neste caso, não há tipo de
retorno, dessa maneira o método é declarado como void. O conjunto String[] args
presentes entre () são os argumentos do método. Neste exemplo, o método possui um

8
único argumento (um vetor de Strings denominado args. A { delimita o limite inicial do
método.
A terceira linha consiste no conteúdo do método principal, formado apenas por
um comando:

System.out.println (“Primeiro Exemplo em Java”);

O System.out.println é usado para exibir algo na saída padrão, por padrão, na


linha de comando. Será exibido o que tiver entre (“”), no caso, Primeiro Exemplo em
Java. No final deste comando, tem-se um ; , que o finaliza.
Por fim, há duas “fecha chaves” } } , que delimitam o fim do método e da classe,
respectivamente.

4. Elementos Básicos de um Arquivo Fonte .java

Os elementos básicos (porém não obrigatórios) de um arquivo fonte .java são:


declaração de pacotes, declaração de importação de classes e definições de classes.
Os pacotes são criados para organização dos arquivos fontes da aplicação Java.
Servem para agrupar classes afins. Declarar um pacote em uma classe significa definir a
que pacote ela pertence. A sintaxe da declaração de um pacote é package
nome_pacote; ,ou seja, inicia com a palavra chave package, seguida do nome do
pacote (pode ser formado por várias partes separadas por ponto) e finalizada por um
ponto e vírgula (;).
É bastante comum empresas colocarem como nomes de pacotes o nome do seu
domínio invertido, como: package br.com.j2eebrasil.meuPacote; . Assim, os
arquivos que contém as classes devem estar dentro de um diretório
br\com\j2eebrasil\meuPacote .
A declaração de importação de classes é feita de duas maneiras: importando uma
classe específica ou importando todas as classes pertencentes a um pacote. A sintaxe
básica da declaração de importação de uma classe específica é import
nome_pacote.nome_classe; . Semelhante à declaração de pacotes, inicia-se com a
palavra chave import; seguida do nome do pacote, um ponto e do nome da classe; e
finalizada por um ponto e vírgula (;). No caso da importação de todas as classes de um
pacote a sintaxe é import nome_pacote.*; , ou seja, o nome da classe é substituído
pelo asterisco (*).
A Listagem 6 exemplifica a declarações de pacotes, de importação e definição de
classe.

9
Listagem 6 - Elementos de um Arquivo Fonte
// Declaração de Pacote
package br.com.j2eebrasil.meuPacote

// Declaração de Importação de Classe


import java.util.Random; //Importação de classe específica
import java.sql.*; //Importação de um pacote inteiro
import java.util.Date;
import java.sql.Date;

// Definição da Classe
public class NomeClasse {...}

5. Identificadores e Palavras Reservadas

Um identificador em um programa Java consiste em uma palavra (de tamanho


ilimitado) que nomeia uma variável, um método, uma classe ou um rótulo/label.
Identificadores podem ser formados por letras e números, mas apenas iniciar com um
dos três caracteres: letra, cifrão ($), ou sublinhado (_). Os demais caracteres que
formam o identificador podem ser desses três tipos ou um dígito.
A Listagem 7 apresenta exemplos de identificadores válidos. Já a Listagem 8
mostra exemplos de identificadores inválidos. Observe que os identificadores são case
sensitives, ou seja, há diferença quando formados por letras maiúsculas e minúsculas.

Listagem 7 - Identificadores Válidos


x
y
z
America
_9_i$to_EH_meio_esquisito
total_1+2+3
$4outroExemplo
exemploCOMmuitasPALAVRAS

Listagem 8 - Identificadores Inválidos


4_naoPodeComecarComDigito
!naoPodeComecarComCaracteresEspeciais

10
As palavras reservadas ou palavras chaves são aquelas que não podem ser
utilizadas como identificadores. Java possui diversas palavras reservadas, entre elas as
listadas na Listagem 9.

Listagem 9 - Palavras Reservadas


abstract boolean break byte
case catch char class
continue default do double
else extends final finally
float for if implements
import instanceof int long
native interface new null
package private public protected
return short static super
switch this throw synchronized
throws transient try void
volatile while

5.1 Convenção da Linguagem

Na linguagem Java é utilizada a seguinte convenção para formação de identificadores:

• Constantes com todas as letras em maiúsculo: CONSTANTE ;

• Variáveis começam com letra minúscula: variável ;

• Classes começam com letra maiúscula: Classe ;

• Métodos começam com letra minúscula: metodo(), metodo2(int a);

• Se o nome for composto, cada nome começa com letra maiúscula:


variávelComNomeComposto .

6. Variáveis

Variáveis são valores que são atribuídos dinamicamente, ou seja, não têm um
valor pré-definido. Podem ser definidos pelo programador, bem como por qualquer
método.
As variáveis podem pertencer à classe (variáveis globais) ou aos métodos
(variáveis locais). Também podem ser utilizadas como argumentos de métodos e como
tipos de retornos.

11
6.1 Declaração de Variáveis

Para evitar confusões na manipulação das variáveis de um programa, como por


exemplo, atribuir o valor de um objeto Cadeira a uma variável que corresponde a um
valor numérico, os tipos das variáveis devem ser declarados. Declarar significa identificar
se as variáveis são números inteiros, números de ponto flutuante, etc., ou até mesmo se
são objetos de uma outra classe.
As variáveis enquadram-se em duas categorias: primitivas ou referências a
objetos. As variáveis primitivas representam valores fundamentais, isto é, inteiros,
booleanos (verdadeiro ou falso), de ponto flutuante. Já o segundo tipo diz respeito às
variáveis que referenciam outros objetos diferentes.
Toda variável é formada por um tipo e um tamanho. Na Tabela 1 estão listados
os tipos de variáveis primitivas com seus respectivos tamanhos em bits e faixa de
valores assumidos.

Tabela 1- Tipos Primitivos


Tipo Tamanho Faixa de valores assumidos
(Em bits)
boolean 8 true ou false
char 16 2 bytes – Unicode (0 – 65535)
byte 8 -128 a 127
short 16 -32768 a 32767
int 32 -2.147.483.648 a 2.147.483.647
long 64 -9.223.372.036.854.775.808 a
+ 9.223.372.036.854.775.808
float 32 -3.40292347E+38 a +3.40292347E+38
double 64 -1.79769313486231570E+308
a +1.79769313486231570E+308

É importante saber que o compilador Java não permitirá que um valor de


tamanho grande seja atribuído a uma variável de tipo de tamanho menor. Por exemplo,
na Listagem 10, o valor da variável x é 24 que é um valor que caberia em uma variável
do tipo byte. Conceitualmente é fácil de perceber isso, porém o compilador não sabe
que o valor 24 cabe em uma variável byte, ele sempre vai achar que esse tipo de
atribuição (de um valor int a um valor byte) vai significar em atribuir um valor a uma
variável byte de tamanho maior do que esse tipo suporta, o que provocaria overflow.

12
Listagem 10 - Exemplo de Atribuição de Valor de Tamanho Maior em Variável de
Tamanho Menor
int x = 24;
byte b = x;

6.1.1 Exemplos

A Listagem 11 apresenta declarações de tipos primitivos com suas respectivas


atribuições de valores. As variáveis podem ser declaradas e inicializadas em uma única
linha de comando ou então, inicialmente declaradas e posteriormente receberem
valores. Na última declaração, float f = 72.5f, após o valor de ponto flutuante tem a
letra f. Esse f é necessário para que o Java saiba que o valor é do tipo float, pois para
ele números de ponto flutuante são interpretados como double.

Listagem 11 – Exemplo: Atribuição de Valores


int x;
x = 234;
byte b = 89;
boolean isPermitido = true;
double d = 1235.56;
char c = ‘R’;
int z = x;
boolean isAcessado;
isAcessado = false;
boolean ligado;
ligado = isPermitido;
long big = 3456789;
float f = 72.5f;

6.2 Variáveis que Referenciam Objetos

A declaração de variáveis primitivas já foi apresentada. Agora é hora de


apresentar como declarar variáveis que referenciam objetos.
A Listagem 12 mostra um segundo programa em Java. Antes de explicar como se
declara objetos, observe no código da Listagem 12 que Java não impõe formatação de
código fonte, ou seja, a separação entre tokens é feita apenas por espaços em branco,
por exemplo, é possível declarar cada variável do programa em uma linha ou então
declarar todas em uma única linha.

13
Antes da declaração da variável numPortas aparece a palavra chave private,
fazendo com que a variável tenha acesso privado, ou seja, tornando-a acessível apenas
dentro da definição da própria classe. Já antes da declaração da variável numJanelas
aparece a palavra chave public, que é um modificador de acesso público, permitindo
que a variável seja acessada de qualquer classe.
O código fonte abaixo também mostra como se comentar o código Java. O
comentário a ser escrito em uma única linha deve ser precedido por duas barras //. Já
um comentário que se estenda por várias linhas, deve ser escrito entre /**/.
A classe Casa do código da Listagem 12 é formada por 4 variáveis (duas inteiras,
uma boleana e uma de ponto flutuante), por dois métodos (um que define o número de
portas da casa e o outro que retorna o número de janelas) e um método principal.
A primeira linha do método principal exibe na tela a String “ Criação de uma
casa” seguida de uma quebra de linha. O espaço em branco antes da palavra “Criação”
corresponde a um TAB. Normalmente, os caracteres em uma String são exibidos
exatamente como são colocados entre as aspas duplas. Entretanto, na mensagem a ser
exibida pelo método principal os caracteres \t e \n não são enviados para a saída.
Quando uma barra invertida encontra-se juntamente com uma string de caracteres, o
caractere seguinte a \ é combinada à mesma para formar uma seqüência de escape. A
seqüência de escape \t é o caractere do TAB, já a seqüência \n é o caractere de nova
linha.
O segundo comando do método principal refere-se à declaração de um objeto. É
importante distinguir que uma variável primitiva possui um tamanho em bits que a
representa, porém uma variável que referencia um objeto não, ela possui um tamanho
em bits que representa um caminho para se obter o objeto, ou seja, um endereço de
memória.
O último comando do método principal, int numJan = c.getNumJanelas(),
atribui a variável inteira numJan o número de janelas do objeto Casa obtido a partir da
chamada ao método getNumJanelas() (que retorna um valor também inteiro). A
chamada a um método de um objeto é feita utilizando o operador ponto (.), ou seja,
c.getNumJanelas() . Nesse caso, significa que o objeto referenciado pela variável c
invoca o método getNumJanelas().

Listagem 12 - Segundo Programa em Java


public void class Casa (){

//Declarações das variáveis da classe


private int numPortas = 3;
public int numJanelas;

14
boolean grande = true; double valor;

/* Declarações dos métodos


da classe */

void setNumPortas(int numero){


numPortas = numero;
}
int getNumJanelas(){ return numJanelas; }

public static void main (String args[]){


System.outr.print(“\t Criação de uma casa \n”);
Casa c = new Casa();
int numJan = c.getNumJanelas();
}
}

6.2.1 Declarar, Criar e Atribuir Valor a um Objeto

Declarar, criar e atribuir valor a um objeto significa, por exemplo, escrever a


seguinte linha de código:

Casa casa = new Casa();

A declaração consiste no trecho Casa casa, o que faz a JVM alocar espaço em
memória para a referência ao objeto do tipo Casa.
A criação do objeto é feita a partir do trecho new Casa(); . Neste ponto, a JVM aloca
espaço em memória para o novo objeto Casa que acaba de ser criado.
O operador igual (=) faz a ligação entre a referência em memória ao objeto Casa.

6.2.2 Inicialização de Variáveis

As variáveis de uma classe, também conhecidas como variáveis globais, se, após
a sua declaração, não forem inicializadas pelo programador (como por exemplo, a
variável numPortas da Listagem 12 que recebe o valor 3), recebem valores padrões.
Os valores padrões estão apresentados na Tabela 2.

15
Tabela 2 - Valores Padrões de Variáveis Globais
Tipo Valor Inicial
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char ‘\u0000’
boolean false
reference null

7. Outras Seqüências de Escape

Esta seção destina-se apenas a mostrar outras seqüências de escape que não
foram apresentadas na seção anterior. Veja a Tabela 3.

Tabela 3 - Seqüências de Escape


Seqüência Descrição
de Escape
\n Indica nova linha, posicionando o cursor de tela no início da próxima linha.
\t Indica tabulação horizontal, movendo o cursor da tela para a próxima
parada de tabulação.
\r Posiciona o cursor de tela no início da linha atual. Qualquer saída de
caracteres após essa seqüência sobrescreve a saída anterior de caracteres
na linha atual.
\\ Usada para imprimir o caractere de barra invertida.
\” Usada para imprimir o caractere de aspas duplas.

8. Separadores

A Tabela 4 apresenta os separadores que podem ser utilizados ao se programar


em Java.

16
Tabela 4 - Separadores
Separador Descrição
() Separador utilizado para: delimitar parâmetros em chamadas de métodos;
modificar precedência em expressões; identificar tipo-alvo em comandos;
delimitar condicionais em comandos.
{} Separador para delimitar blocos de código e inicialização de vetores.
[] Separador para declarar e manusear vetores.
; Finalizar de comando.
. Separador utilizado para selecionar campos e métodos de um objeto.
, Utilizado na declaração de variáveis e na estrutura de controle for.

9. Operadores

A linguagem Java possui diversas categorias de operadores, entre eles: unário,


aritmético, deslocamento, comparação, bit-a-bit, lógico de curto-circuito, atribuição.

9.1 Operadores Unários

Os operadores unários são aqueles que trabalham apenas com um único


operando. São sete os operadores unários, como pode ser visualizado na Tabela 5.

Tabela 5 - Operadores Unários


Operador Descrição
++ Operador de incremento de variável. A posição do operador em relação à
variável deve ser considerada.
-- Operador de decremento de variável. A posição do operador em relação à
variável deve ser considerada.
+ Mais unário. Não afeta o valor numérico.
- Menos unário. Afeta o valor numérico, negando-o.
~ Operador de inversão de bit-a-bit. Em um valor binário, converte todos os
bits com valor 1 no valor 0 e vice-versa.
! Operador do não booleano. Inverte o valor de uma expressão do tipo
boolean.
() Operador para realização de cast. A operação de casting é utilizada quando
é desejado converter tipos de dados em uma expressão. Esse tipo de
conversão só é possível quando os tipos de dados são tipos compatíveis
entre si. Pode ser aplicado tanto a tipos primitivos como a objetos.

17
A Listagem 13 apresenta exemplos de usos dos operadores unários.

Listagem 13 - Exemplos de Uso de Operadores Unários


int a = 2; //Declaração da variável inteira a. Inicializada com o valor 2.

System.out.println(a++); /*Escreve na tela o valor 2 e depois incrementa o valor da


variável a para 3. Primeiro utiliza a variável, depois
incrementa*/

int b = 3; //Declara variável inteira b inicializada com valor igual a 3.

System.out.print(++b); /*Escreve na tela o valor 4. Primeiro incrementa o valor da


variável, depois utiliza*/

a = 10; //Variável a recebe novo valor 10.

int w = a--; /*A variável w é declarada e inicializada com o valor 10 e o


valor da variável a passa a ser 9. Primeiro utiliza a
variável, depois decrementa*/

System.out.println(--a); /*Escreve na tela o valor 8. Primeiro decrementa, depois


Utiliza. A possuía valor igual a 9.*/

double c = 43.5; //Declara variável doublé c inicializada com valor igual a


//43.5

double d = +c; //Variável double d recebe valor da variável c, mantendo o sinal.

d = -d; /*Variável d assume seu próprio valor com sinal negativo, ou seja
-43.5*/

boolean bT = true; //Declaração da variável booleana com valor true


boolean bF = false; //Declaração da variável booleana com valor false

!bT; //bT passa a assumir valor false


!bF; //bF passa a assumir valor true

18
double numD = 10.0;
int numero = (int)numD; //Casting do tipo double para o tipo int

9.2 Operadores Aritméticos

Os operadores aritméticos são aqueles utilizados na programação de cálculos


aritméticos. Veja a Tabela 6.

Tabela 6 - Operadores Aritméticos


Operador Descrição
+ Adição. Realiza a adição entre valores numéricos. O operador + também
pode ser utilizado para concatenar Strings.
- Subtração. Realiza a subtração entre valores numéricos.
* Multiplicação. Realiza a multiplicação entre valores numéricos.
/ Divisão. Realiza a divisão entre valores numéricos.
% Módulo. Retorna o resto da divisão de dois números.

A Listagem 14 apresenta exemplos de usos dos operadores aritméticos.

Listagem 14 - Exemplos de Uso de Operadores Aritméticos


int a = 10;
int b = 3;
int c = 17;
int d = a + b; //A variável d é declarada e inicializada com o valor 13 (10 + 3).
int e = c – b ; //A variável e é declarada e inicializada com o valor 14 (17 - 3).
int f = c % 5; //f é declarada e recebe o valor 2 (Resto da divisão 17 / 5).
int g = 23 / 5; /*g é declarada e recebe o valor 4. O resultado da divisão é na
realidade 4.6, porém a divisão inteira produz um quociente
inteiro. Não há arredondamento. A parte fracionária é
simplesmente truncada.*/
double h = (a + b) * g; //h é declara e inicializada com 52 ( (10 + 3) * 4)

Os operadores aritméticos possuem a precedência mostrada na Tabela 7. A


ordem da precedência em Java é feita da esquerda para direita.

19
Tabela 7 - Precedência dos Operadores Aritméticos
Operador Precedência
() Os operadores em expressões contidas dentro de pares de parênteses
são avaliados primeiro. Se os parênteses estiverem aninhados, a
expressão no par mais interno será avaliada primeiramente. Se existe
pares não aninhados em um mesmo nível, eles são avaliados da
esquerda para direita.
*, /, % São avaliados em segundo lugar. Havendo vários, são avaliados da
esquerda para direita.
+ ou - Avaliados por último. Havendo vários, são avaliados da esquerda para
direita.

9.3 Operadores de Deslocamento

A operação de deslocamento consiste simplesmente em mover bits para a direita


ou para a esquerda. Por exemplo:

Dado Original: 0001100110


Deslocamento de um bit para a direita: 0000110011
Deslocamento de um bit para a esquerda: 0011001100

A Tabela 8 apresenta os operadores de deslocamento.

Tabela 8 - Operadores de Deslocamento


Operador Descrição
<< Operador de deslocamento à esquerda.
>> Operador de deslocamento à direita com sinal.
>>> Operador de deslocamento à direita sem sinal.

O valor resultante do deslocamento possui a mesma quantidade de bits do valor


original. Os bits que forem deslocados para fora da representação são descartados. Já
aqueles novos que entram na representação assumem o valor 0, caso os operadores
sejam << ou >>>. No caso do operador >>, os novos bits adicionados assumem o
valor do bit mais significativo antes do deslocamento, por exemplo:

Dado Original: 1001100110


Deslocamento >>: 1100110011

20
9.4 Operadores de Comparação

Os operadores de comparação retornam valores booleanos. Veja a Tabela 9

Tabela 9 - Operadores de Comparação


Operador Descrição
== Compara se dois valores são iguais.
!= Compara se dois valores são diferentes.
> Compara se um valor é maior que o outro.
< Compara se um valor é menor que o outro.
>= Compara se um valor é maior ou igual a o outro.
<= Compara se um valor é menor ou igual a o outro.

Os operadores descritos acima, em relação aos tipos primitivos, comparam os


seus valores. Já em relação aos objetos, os valores comparados são as referências dos
objetos (tipicamente endereços em memória). A comparação semântica entre objetos,
por exemplo, se duas Strings são iguais, é feita a partir do método equals(). Veja
exemplo do uso dos operadores de comparação na Listagem 15.

Listagem 15 - Exemplos de Uso de Operadores de Comparação


int a = 10;
int b = 3;
if (a == b){} //Compara se a é igual a b
if (a != b){} //Compara se a é diferente de b
if (a > b){} //Compara se a é maior que b
if (a < b){} //Compara se a é menor que b
if (a >= b){} //Compara se a é maior ou igual a b
if (a <= b){} //Compara se a é menor ou igual a b

String s1 = new String(“Teste”);


String s2 = new String(“Teste”);
System.out.println(s1 == s2); /*Compara se o endereço em memória da variável
s1 é igual ao endereço da variável s2. Escreve na
tela false*/
System.out.println(s1.equals(s2)); /* Compara se o conteúdo da variável s1 é igual ao
conteúdo da variável s2. Escreve na tela true */

21
9.5 Operadores Bit-a-Bit

Os operadores bit-a-bit realizam, respectivamente, as operações lógicas AND,


XOR e OR bit-a-bit. Eles estão listados na Tabela 10.

Tabela 10 - Operadores Bit-a-Bit


Operador Descrição
& Operador do AND lógico.
^ Operador do XOR lógico.
| Operador do OR lógico.

Quando esses operadores são aplicados a operandos booleanos, os operados são


considerados como um único bit.

9.6 Operadores Lógicos de Curto-Circuito

Os operadores de curto-circuito são utilizados para realizar também operações


AND e OR, porém apenas para valores booleanos. Esses operadores estão na Listagem
11.

Tabela 11 - Operadores Lógicos de Curto-Circuito


Operador Descrição
&& Conjunção. Operador de curto-circuito do AND lógico.
|| Disjunção. Operador de curto-circuito do OR lógico.

A diferença desses dois operadores para os operadores & e | bit-a-bits é que em


algumas situações o operando que se encontra do lado direito pode não ser avaliado. Por
exemplo, os códigos presentes na Listagem 16 e na Listagem 17.

Listagem 16 - Exemplo Operando de Curto-Circuito: & e &&


String a = null; //Declara uma String nula de nome a
String b = new String(“Testando”); //Declara e inicializa uma String de nome b

if (a != null & b.equals(“Testando”) ){ //Teste com operador &


System.out.println(“Olá Pessoal”);
}

22
if (a != null && b.equals(“Testando”) ){ //Teste com operador &&
System.out.println(“Olá Pessoal”);
}

Em todos os testes da Listagem 16, o resultado será o mesmo, ou seja, nada será
escrito na tela. Considere o primeiro operando como sendo a != null e o segundo
operando como sendo b.equals(“Testando”). As diferenças são as seguintes:

• No teste que utiliza o operador &, ambos operandos serão avaliados, ou seja, vai
ser testado se a é diferente de null e se a String b é igual à String Testando, o
que resultará em um resultado falso.
• No teste que utiliza o operador &&, apenas o primeiro operando será avaliado.
Testa-se a é diferente de null. Como essa condição é falsa, não há mais
necessidade de se avaliar os demais operandos, já que na operação lógica AND,
se qualquer uma das condições for falsa, a expressão toda será falsa.

Listagem 17 - Exemplo Operando de Curto-Circuito: | e ||


String a = null; //Declara uma String nula de nome a
String b = new String(“Testando”); //Declara e inicializa uma String de nome b

if (a == null | b.equals(“Testando”) ){ //Teste com operador |


System.out.println(“Olá Pessoal”);
}

if (a == null || b.equals(“Testando”) ){ //Teste com operador ||


System.out.println(“Olá Pessoal”);
}

Nos testes da Listagem 17, o resultado também será o mesmo, ou seja, será
escrito na tela a String Olá Pessoal. Considere o primeiro operando como sendo a ==
null e o segundo operando como sendo b.equals(“Testando”). As diferenças são as
seguintes:

• No teste que utiliza o operador |, ambos operandos serão avaliados, ou seja, vai
ser testado se a é igual a null e se a String b é igual à String Testando, o que
resultará em um resultado verdadeiro.
• No teste que utiliza o operador ||, apenas o primeiro operando será avaliado.
Testa-se a é igual à null. Como essa condição é verdadeira, não há mais
necessidade de se avaliar os demais operandos, já que na operação lógica OR, se
qualquer uma das condições for verdadeira, a expressão toda será verdadeira.

23
9.7 Operador de Atribuição

Os operadores de atribuição, como o próprio nome já diz, atribuem um valor a


uma variável. Eles estão apresentados na Tabela 12.

Tabela 12 - Operadores de Atribuição


Operador Descrição
= Atribui um valor qualquer a uma variável.
+= Atribui a uma variável o resultado da soma de seu valor atual a um valor
qualquer.
-= Atribui a uma variável o resultado da subtração de seu valor atual de um
valor qualquer.
*= Atribui a uma variável o resultado da multiplicação entre seu valor atual
e um valor qualquer.
/= Atribui a uma variável o resultado da divisão de seu valor atual por um
valor qualquer.
%= Atribui a uma variável a o resto da divisão de seu valor atual por um
valor qualquer.

Exemplos de uso podem ser vistos na Listagem 18.

Listagem 18 - Exemplo Operandos de Atribuição


int a = 2;
a += 3; //a assume o valor 5 (2 + 3)

int b = 4; //b assume o valor 8 (4 * 2)


b *=2;

int c = 10; //c assume o valor 1 (Resto da divisão 10/3)


c %= 3;

10. Estruturas de Controle e Fluxo

Uma estrutura de controle é aquela que executa blocos de código delimitados por
chaves “{...}”. Todas as variáveis criadas dentro de um bloco de estrutura de controle
são vistas apenas dentro desse bloco.

10.1 Estrutura de Seleção if

Na estrutura de seleção IF, apenas uma condição é avaliada. Caso o resultado da


avaliação da condição seja verdadeiro (true), um bloco de instruções relacionado à

24
estrutura if é executado. Caso seja falso (false), esse bloco de código não será
executado e o fluxo de controle segue adiante. A sintaxe básica dessa estrutura está
apresentada na Listagem 19.

Listagem 19 - Sintaxe da Estrutura IF


if (condicao){
//Conjunto de comandos
comando_1;
comando_2;
}

Para exemplificar observe o código presente na Listagem 20. Neste exemplo, a


primeira condição testa se x > 9.5. Como anteriormente x foi declarado e inicializado
com valor 10.54, a condição é verdadeira e o comando para escrever x é maior que
9.5 na tela é executado. A segunda condição verifica se a String nome possui conteúdo
igual a David, porém nome tem o conteúdo Raphaela, dessa forma, o comando que
escreveria O nome é David não será executado.

Listagem 20 – Exemplo de uso da Estrutura IF


double x = 10.54;
String nome = “Raphaela”;
//Primeira condição
if (x > 9.5){
System.out.println(“x é maior que 9.5”);
}
//Segunda condição
If (nome.equals(“David”)){
System.out.println(“O nome é David”);
}

10.2 Estrutura de Seleção if/else

No caso da estrutura de seleção IF/ELSE, se uma condição for avaliada como


verdadeira, um conjunto de instruções será executado, caso contrário, um outro
conjunto de instruções será executado. A Listagem 21 apresenta a sintaxe básica da
estrutura if/else.

25
Listagem 21 - Sintaxe da Estrutura IF/ELSE
if (condicao){
//Conjunto de instruções para condição VERDADEIRA
instrucao_1;
instrucao_2;
}else{
//Conjunto de instruções para condição FALSA
instrucao_3;
instrucao_4;
instrucao_5;
}

O exemplo apresentado na Listagem 22 ilustra a estrutura de seleção if/else.


Neste caso, a condição é se x % 2 == 0, ou seja, se o resto da divisão do valor da
variável x por 2 for igual a zero, então escreva na tela x é par, se não escreva x é
ímpar.

Listagem 22 – Exemplo de uso da Estrutura IF/ELSE


if (x % 2 == 0){
System.out.println(“x é par”);
}else{
System.out.println(“x é ímpar”);
}

A estrutura if/else também pode ser utilizada de forma aninhada, testando


vários casos, colocando estruturas if/else dentro de outras estruturas if/else. Veja o
exemplo da Listagem 23.

Listagem 23 – Exemplo de uso da Estrutura IF/ELSE Aninhada


if (notaAluno >= 9){
System.out.println(“A”);
}else if (notaAluno >= 8){
System.out.println(“B”);
}else if (notaAluno >= 7){
System.out.println(“C”);
}else if (notaAluno >= 6){
System.out.println(“D”);

26
}else{
System.out.println(“E”);
}

10.3 Estrutura de repetição while

Uma estrutura de repetição especifica que uma ação será repetida enquanto
alguma condição permanecer verdadeira. No caso da estrutura de repetição while, a
sintaxe básica é mostrada na Listagem 24. As instruções dentro das {} da estrutura
while ficam sendo executadas até que a condição se torne falsa. A Listagem 25
apresenta um exemplo de uso. No exemplo, a variável contador terá seu valor
incrementado de um até que assuma o valor 29.

Listagem 24 - Sintaxe da Estrutura WHILE


while (condicao){
//Conjunto de instruções para condição VERDADEIRA
instrucao_1;
instrucao_2;
}

Listagem 25 - Exemplo de uso da Estrutura WHILE


int contador = 1;
while (contador < 30){
System.out.println(“Contador no número: ” + contador );
contador = contador + 1;
}

10.4 Estrutura de repetição do/while

A estrutura de repetição do/while é exatamente igual à estrutura while. A


diferença é que a condição é verificada ao final do bloco, de forma que ele é executado
pelo menos uma vez. A sintaxe básica e um exemplo de uso encontram-se,
respectivamente, na Listagem 26 e na Listagem 27.

27
Listagem 26 - Sintaxe da Estrutura DO/WHILE
do{
/* Conjunto de instruções para condição VERDADEIRA. Executado pelo
menos uma vez.*/
instrucao_1;
instrucao_2;
}while (condicao)

Listagem 27 - Exemplo de uso da Estrutura DO/WHILE


int valor = 1;
do {
total += valor;
valor++;
} while (valor <= 100)
System.out.println(“Soma é “ + total);

10.5 Estrutura de repetição for

A estrutura de repetição for implementa repetições baseada em índices. A


sintaxe básica pode ser encontrada na Listagem 28. No exemplo da Listagem 29, as
instruções contidas no bloco do for serão executadas até que a variável contador
assuma valor 10. Essa variável é inicializada com o valor 2 e é incrementada de uma
unidade a cada laço.

Listagem 28 - Sintaxe da Estrutura FOR


for ( [iniciações]; [condição];[incremento]){
instrucao_1;
instrucao_2;
}

Listagem 29 - Exemplo de uso da estrutura FOR


int x = 0;
for (int contador = 2; contador <= 10; contador++){
x += contador;
System.out.println(“Valor de x = ” + x);
System.out.println(“Valor do contador = ” + contador);
}

28
10.6 Estrutura de seleção múltipla switch

A estrutura de seleção múltipla switch é usada quando o algoritmo tem uma


série de decisões a tomar em que uma variável ou condição é testada separadamente
para cada um dos vários valores integrais constantes que ela pode assumir e ações
diferentes são tomadas. A sintaxe básica pode ser vista na Listagem 30. Exemplo de uso
na Listagem 31.

Listagem 30 - Sintaxe da Estrutura SWITCH


switch (expressão)
{
case cte1:
[ comandos1;
break; ]
case cte2:
[ comandos2;
break; ]
...
default:
comandos;
}

Listagem 31 - Exemplo de uso da estrutura SWITCH


int x = 0;
switch(x){
case 1:
System.out.println(“x = 1”);
break;
case 2:
System.out.println(“x = 2”);
break;
case 3:
System.out.println(“x = 3”);
break;
default:
System.out.println(“x não é igual a 1 nem a 2 nem a 3”);
}

29
10.7 Interrompendo os Fluxos de Controle

A interrupção das estruturas de controle pode ser feita usando:

• return – interrompe a execução do método em questão, retornando o controle


para o método chamador.
• break – interrompe a execução do fluxo atual saindo para a linha de comando
imediatamente posterior ao final do bloco de execução.
• continue - desvia a execução do programa para o início do bloco de execução,
evitando a execução dos trechos posteriores a chamada do continue.

11. Vetores/Arrays

Um vetor ou array é um conjunto de posições seqüenciais na memória que


possuem o mesmo nome e o mesmo tipo. Pode ser formado por tipos primitivos ou
objetos (referências). As suas principais características são:
Tamanho fixo. É preciso criar um novo vetor e copiar o conteúdo do antigo para o novo.
Vetores não podem ser redimensionados depois de criados;

• Quantidade máxima de elementos obtida através da propriedade length


(comprimento do vetor);
• Verificados em tempo de execução. Tentativa de acessar índice inexistente
provoca um erro na execução;
• Tipo definido. Pode-se restringir o tipo dos elementos que podem ser
armazenados.

Para declarar um vetor em Java, utilizada-se os colchetes ([]), como visto na


Listagem 32. Observa-se que basta adicionar [] na declaração da variável, podendo ser
antes ou depois do nome da variável. Neste exemplo, um vetor de inteiros e um vetor de
valores double são declarados.

Listagem 32 – Declaração de um Vetor


int[] vetorInt_1;
double vetorInt_2[];

Os vetores ocupam espaço em memória. Além de definir o tipo do vetor, é


necessário definir o número de elementos que ele vai suportar. Isso é feito a partir do
operador new, durante a inicialização do vetor. No exemplo da Listagem 33, são

30
alocados 10 elementos para o vetor de inteiros vetorInt. Quando o tamanho do vetor é
definido, automaticamente, as variáveis numéricas do tipo de dados primitivo são
inicializadas com zero; as variáveis booleanas são inicializadas com false; e as
referências (variáveis de tipos não-primitivo) são inicializadas com null.

Listagem 33 – Inicialização de um Vetor 1


int[] vetorInt; //Declara o vetor
vetorInt = new int[10]; //Inicializa o vetor, alocando 10 posições em memória

O tamanho do vetor pode ser alocado informando o número de dados entre


colchetes ou informando os elementos do vetor entre chaves, separados por vírgula,
como apresentado na Listagem 34. Neste último caso, a quantidade de elementos do
vetor é definida pela quantidade de elementos definida entre chaves. Além disso, a
inicialização de um vetor usando os valores entre chaves só pode ser feita na linha de
declaração do vetor.

Listagem 34 – Inicialização de um Vetor 2


int[] vetorInt = new int[10]; //Declara e inicializa vetor de 10 posições
String[] vetorString = {“Sol”, “Mar”, “Terra”, “Lua”};

Os elementos de um vetor são acessados fornecendo o nome do vetor seguido


pelo número da posição do elemento particular entre colchetes([]). O primeiro elemento
do vetor encontra-se na posição zero. No exemplo da Listagem 35, uma variável vetorD
é declarada como um vetor de double de 3 elementos. Durante a declaração e
inicialização do vetor, todos os elementos recebem o valor zero. Em seguida, cada
elemento do vetor recebe um novo valor. Por fim, a frase “O terceiro elemento do
vetor é o 3.6” é exibida na tela. O valor 3.6 é exibido a partir do acesso ao terceiro
elemento do vetor, de índice igual a 2.

Listagem 35 – Acessando Elementos de um Vetor


double vetorD = new double[3]; //Declaração e inicialização do vetor.
//Todos os elementos recebem o valor 0.0
vetorD[0] = 2.4; //Primeira posição do vetor
vetorD[1] = 2.0; //Segunda posição do vetor
vetorD[2] = 3.6; //Terceira posição do vetor

System.out.println(“O terceiro elemento do vetor é o = ” + vetorD[2] );

31
A Listagem 36 apresenta um exemplo de uso com vetor. No método principal,
inicialmente, um vetor de nome n é declarado e inicializado com 10 posições. Em
seguida, utilizando a estrutura de um loop for, percorre-se todos os elementos do vetor
e atribui a eles o valor da variável de controle do for (no caso, o valor da variável i)
multiplicada por 2. Observa-se que na condição da estrutura do for, o valor de i deve
ser menor que n.length. A expressão n.length determina o comprimento do vetor.
Neste caso, como o vetor tem tamanho igual a 10, o laço do for será executado até que
o valor da variável dontrole i seja menor que 10.

Listagem 36 – Exemplo que Utiliza Vetor – Elementos: Inteiros


public class Vetor{
public static void main( String args[] ){

int n[]; //Declara referência para um vetor


n = new int[10]; //Aloca dinamicamente o vetor

//Atribuição de valores ao vetor


for (int i = 0; i < n.length; i++){
n[i] = i * 2;
}
}
}

A Listagem 37 apresenta um exemplo com vetor, onde esse vetor é formado por
elementos que são objetos do tipo Casa.

Listagem 37 – Exemplo que Utiliza Vetor – Elementos: Objetos


public class VetorObjeto{
public static void main( String args[] ){

Casa c[]; //Declara o vetor c como sendo do tipo Casa


c= new Casa[3]; //Aloca dinamicamente o vetor. Elementos
//inicializados com null

Casa c1 = new Casa();

//Atribuição de valores ao vetor


c[0] = new Casa();

32
c[1] = new Casa();
c[2] =c1;

//Acesso aos elementos do vetor


c1.setNumPortas(4); //Atribui o valor 4 ao nº de portas do 3º
//elemento do vetor
int numJan = c[2].getNumJanelas(); //Atribui à variável numJan o
//nº de janelas do 2º elemento
//do vetor
}
}

11.1 Vetores/Arrays Multidimensionais

Os vetores que possuem dois subscritos são utilizados na representação de


tabelas de valores organizadas em linhas e colunas. A identificação de um elemento da
tabela é feita através da especificação dos dois subscritos. Convencionalmente, o
primeiro subscrito indica a linha do elemento e o segundo, a sua coluna.
Os vetores que possuem apenas dois subscritos são chamados de vetores
bidimensionais. Já os vetores multidimensionais são aqueles que possuem mais de um
subscritos, ou seja, podem ter também dois ou mais de dois. A linguagem Java não
suporta diretamente vetores multidimensionais, porém um vetor de um único subscrito
pode ser declarado e possuir elementos que são outros vetores de também apenas um
subscrito, portanto, atingindo o mesmo efeito.
Um exemplo de vetor bidimensional pode ser visto na Listagem 38.

Listagem 38 – Exemplo que utiliza vetor bidimensional


public class VetorBidimensional{
public static void main( String args[] ){

int vb[][]; //Declara referência para um vetor bidimensional


vb = new int[2][3]; //Aloca dinamicamente o vetor com
//duas linhas e três colunas

//Atribuição de valores ao vetor bidimensional vb


vb[0][0] = 1; //Elemento da 1ª linha e da 1ª coluna
vb[0][1] = 2; //Elemento da 1ª linha e da 2ª coluna

33
vb[0][2] = 15; //Elemento da 1ª linha e da 3ª coluna
vb[1][0] = 13; //Elemento da 2ª linha e da 1ª coluna
vb[1][1] = 12; //Elemento da 2ª linha e da 2ª coluna
vb[1][2] = 5; //Elemento da 2ª linha e da 3ª coluna

/* Vetor bidimensional declarado e inicializado com


valores entre chaves */
int vb_2[][] = { { 10, 11, 12},
{ 20, 21, 22}
};
//Escrita na tela dos elementos de vb e vb_2
for ( int i = 0; i < 2; i++ ){
for ( int j = 0; j < 3; j++ ){
System.out.println(“vb[” + i + “][” + j “] = ” vb[i][j]);
System.out.println(“vb_2[” + i + “][” + j “] = ” vb_2[i][j]);
}
}
}
}

11.2 Estrutura for Aprimorada

Antes da versão do Java 5.0, os elementos de um vetor somente eram


percorridos através de uma instrução for controlada por contador, como mostrado no
exemplo da Listagem 39.

Listagem 39 – Exemplo que percorre vetor com for controlado por contador
public class Vetor{
public static void main( String args[] ){

int vetor[] = {43, 75, 867, 89, 20};


int soma = 0;

//Soma valores ao vetor


for (int i = 0; i < vetor.length; i++){
soma += vetor[i];
}

34
System.out.println(“Soma = ” + soma);
}
}

Com a estrutura do for aprimorada introduzida no Java 5.0, é possível percorrer


os elementos de um vetor sem utilizar um contador. A sua sintaxe está apresentada na
Listagem 40. O parâmetro é formado por um tipo e um identificador, por exemplo: int i
e nomeDoVetor é o vetor que se deseja percorrer. O tipo do parâmetro corresponde ao
tipo dos elementos do vetor.

Listagem 40 – Sintaxe da Estrutura for Aprimorada


for (parâmetro:nomeDoVetor)
instrução

A Listagem 41 apresenta a versão do código presente na Listagem 40 utilizando a


estrutura aprimorada do for. Observa-se que essa estrutura simplifica o código para
percorrer um vetor. Essa estrutura somente pode ser utilizada para acessar elementos
do vetor. Se houver a necessidade de modificar elementos do vetor, deve-se utilizar a
estrutura do for tradicional controlada por contador.

Listagem 41 – Exemplo que percorre vetor com estrutura for aprimorada


public class Vetor{
public static void main( String args[] ){

int vetor[] = {43, 75, 867, 89, 20};


int soma = 0;

//Soma valores ao vetor


for (int i : vetor){
soma += i;
}
System.out.println(“Soma = ” + soma);
}
}

35
12. Programação Orientada a Objetos

A programação orientada a objetos tem como objetivo principal atingir um


desenvolvimento interativo e incremental, criação rápida de protótipos, códigos
reutilizáveis e extensíveis através do encapsulamento dos dados e uma modelagem do
problema a ser solucionado. Esta seção destina-se a descrever conceitos relacionados à
orientação a objetos.

12.1 Abstração

“Uma abstração denota as características essenciais de um objeto que o distingue


de todas as outras espécies de objetos e assim provê limites conceituais bem definidos,
sempre relativos à perspectiva de um observador.”
A Figura 3 apresenta um exemplo de abstração. Neste exemplo, a figura do gato
é vista por dois observadores diferentes: a vovó e a veterinária. Ambos observadores
sabem que o que estão vendo é um gato, porém cada um deles vê o gato com um
propósito diferente, ou seja, a vovó vê o gato como um animal para brincar e fazer
carinho, já a veterinário o vê como um objeto de estudo. Resumindo, a figura do gato é
identificada e distinguida das outras espécies de objetos existentes, porém apresenta
significado diferente dependendo do observador (vovó ou veterinária).

Figura 3 - Abstração

36
12.2 Objetos

Os objetos são abstrações de dados do mundo real, com nomes de operações e


um estado local. Em outras palavras, um objeto é qualquer coisa, real ou abstrata, sobre
a qual armazenamos dados e operações que manipulam os dados.
O estado interno de um objeto é descrito por seus atributos que somente são
acessados ou modificados a partir de operações definidas pelo criador do objeto.

12.3 Classes

Uma classe é um modelo para um objeto. Um conjunto de objetos que possuem o


mesmo tipo pode ser agrupado em uma classe. Uma classe define o comportamento dos
objetos, através de métodos, e quais estados ele é capaz de manter, através de
atributos. A Listagem 42 apresenta a sintaxe básica de uma classe. A definição da classe
é feita a partir da palavra chave class seguida do nome da classe (definida pelo
programador).

Listagem 42 - Sintaxe Geral de uma Classe


class nomeClasse{
//Atributos
//Métodos
}

Observa-se que uma classe é formada por um conjunto de atributos e métodos.


Os atributos podem ser definidos como características que mudam de indivíduo para
indivíduo. Já os métodos representam o comportamento de um objeto. Na estrutura de
um método encontra-se a codificação que utiliza os atributos do objeto para realizar seus
objetivos, também podendo receber informações externas (parâmetros) quando os
atributos da classe não são suficientes para a realização de suas atividades.
Cada classe deve ser definida dentro de um arquivo com extensão .java que
possui o mesmo nome da classe. Um arquivo fonte só pode ser composto de uma única
classe.
A Listagem 43 exibe um exemplo de uma classe de nome Retangulo. Essa classe
deve pertencer ao arquivo Retângulo.java. Ela é formada por um conjunto de atributos
do tipo float e um único método sem retorno chamado translacao. Esse método recebe
informação externa, ou seja, recebe como argumento os parâmetros do tipo float x e y.

37
Listagem 43 – Exemplo de uma Classe
class Retangulo{
//Atributos da Classe
float orig_x, orig_y;
float altura, largura;

//Método da classe
public void translacao (float x, float y) {
orig_x = x;
orig_y = y;
}
}

Já a Listagem 44 apresenta exemplos de instanciação de objetos da classe


Retangulo. Neste exemplo, dois objetos do tipo Retangulo são criados, recebem
valores para alguns de seus atributos e possuem seu método translacao invocado.
Observa-se que os objetos são do mesmo tipo, porém apresentam atributos
comportamentos de translação diferentes.
Listagem 44 – Exemplo de Objetos

//Declara e inicializa um objeto do tipo Retangulo


Retangulo ret_1;
ret_1 = new Retangulo();

//Cria um outro objeto do tipo Retangulo


Retangulo ret_2 = new Retangulo();

//Atribui valores a atributos do objeto ret_1


ret_1.orig_x = 10;
ret_1.orig_y = 100;
//Invoca método do objeto ret_1, passando como argumento os valores 0,0
ret_1.translacao (0, 0);

//Atribui valores a atributos do objeto ret_2


ret_2.orig_x = 20;
ret_2.orig_y = 87;
//Invoca método do objeto ret_2, passando como argumento os valores 5,5
ret_2.translacao (5, 5);

38
12.4 Passagem de Argumentos para Métodos

Existem duas maneiras de se passar argumentos/parâmetros para métodos:


passagem por valor ou passagem por referência.
Na passagem do argumento por valor, uma cópia do valor do argumento é feita e
passada para o método chamador. Neste caso, alterações na cópia do método chamado
não afetam o valor da variável original no método de chamada.
Já na passagem por referência, o método chamado é capaz de acessar
diretamente os dados do método chamador e alterar esses dados se o método chamado
assim o escolher.
Em Java, não é permitido ao programador escolher se deseja passar o argumento
por valor ou por referência. Os argumentos são sempre passados por valor. No caso dos
tipos primitivos, uma cópia do valor da variável é passada. Dessa forma, as modificações
na cópia desse valor feitas dentro do método não influenciam o valor da variável
original. Já no caso de variáveis do tipo referências a objetos, uma cópia da referência
original é passada ao método. Essa cópia da referência aponta para o mesmo objeto que
o apontado pela referência original. Referências geralmente são implementadas como
endereços de objetos, quando uma referência é passada como parâmetro, uma cópia
deste endereço é criada e apesar de ser uma cópia este endereço aponta para o mesmo
objeto, podendo assim modificá-lo.
Um exemplo de passagem por valor pode ser visto na Listagem 45. Neste
exemplo, a classe Retangulo possui o método translacao que recebe dois argumentos
do tipo float. Ambos os argumentos são passados por valor, já que são de tipo de
dados primitivo. No método principal, cria-se o objeto ret do tipo Retângulo. Em
seguida, declara-se duas variáveis do tipo float (x1 e y1) que serão passadas como
argumentos para o método translacao do objeto ret. No final do método translacao,
as variáveis passadas como argumentos recebem o valor zero. Essa alteração é vista
apenas dentro do método chamado e não também pelo método chamador. Isso pode ser
comprovado, ao ver o resultado das duas linhas finais do método principal, que irão
escrever na tela:

O valor da variável x1 é 15.5


O valor da variável y1 é 10.0

Ou seja, a alteração de atribuir zero aos argumentos recebidos pelo método


translacao não é vista pelo método chamador.

39
Listagem 45 – Exemplo de Passagem por Valor
class Retangulo{
//Atributos da Classe
float orig_x, orig_y;
float altura, largura;

//Método da classe
public void translacao (float x, float y) {
//Realiza translação
orig_x = x;
orig_y = y;

//Altera valores dos argumentos


x = 0.0;
y = 0.0;
}

public static void main( String args[] ){


Retangulo ret = new Retangulo();
float x1 = 15.5;
float y1 = 10.5;
ret.translacao(x1, y1);

System.out.println(“O valor de x1 é ” + x1);


System.out.println(“O valor de y1 é ” + y1);
}
}

Para exemplificar a passagem da cópia da referência, considere os vetores que


são tratados em Java como objetos. Neste caso, quando passados aos métodos são
passadas cópias de suas referências. Assim, um método chamado pode acessar os
elementos dos vetores originais do chamador.
Na Listagem 46, pode-se exemplificar a passagem da cópia de uma variável
primitiva e a passagem de uma referência utilizando um vetor. A classe Vetor é formada
por uma variável global mensagem do tipo String, um método sem retorno chamado
inicializa que não recebe nenhum argumento, um método sem retorno chamado
modificaVetor que recebe como argumento um vetor (passado por referência), um

40
método sem retorno chamado modificaElemento que recebe como argumento um
elemento do tipo inteiro (passado por valor) e um método chamado getMensagem que
retorna um objeto do tipo String.
No método inicializa, um vetor v é declarado com 5 elementos (1, 2, 3, 4, 5).
Em seguida, a variável mensagem recebe o valor:

Os valores originais do vetor são: 1 2 3 4 5

O próximo passo é a invocação do método modificaVetor passando v como


argumento. Esse método faz com que todos os elementos do vetor sejam multiplicados
por 3 e essa modificação vai ser vista pelo método chamador, já que o vetor foi passada
uma cópia da referência. A seguir, a variável mensagem passa a ser:

Os valores originais do vetor são: 1 2 3 4 5


Os valores do vetor após modificação são: 3 6 9 12 15

Por fim, o segundo elemento do vetor v[2] é passado para o método


modificaElemento. Dentro deste método, o argumento passado é multiplicado por 3. O
que se poderia esperar era que v[2] passasse do valor 9 para o valor 27, mas não é o
que acontece, já que uma cópia de v[2] é passada ao método, portanto não altera em
nada no valor do elemento de v. Dessa forma, a variável mensagem passa a ser:

Os valores originais do vetor são: 1 2 3 4 5


Os valores do vetor após modificação são: 3 6 9 12 15
O valor de v[2] é 9
O método principal da classe cria um objeto do tipo vetor, invoca o método
inicializa e escreve na tela o conteúdo final da variável mensagem, que é o valor
exibido anteriormente.

Listagem 46 – Exemplo de Passagem por Referência


class Vetor{
//Variável global da classe
String mensagem;

//Método de inicialização do vetor


public void inicializa(){
//Declaração e inicialização do vetor
int v[] = {1, 2, 3, 4, 5};

41
//Mensagem a ser escrita na tela
mensagem = “Os valores originais do vetor são: ”;

for (int i = 0; i < v.length; i++ ){


mensagem += “ ” + v[i];
}

/* Chamada ao método modificaVetor, passando v1 como argumento.


Passagem por referência */
modificaVetor( v );

mensagem += “\nOs valores do vetor após modificação são: “;


for (int i = 0; i < v.length; i++ ){
mensagem += “ ” + v[i];
}

modificaElemento( v[2] );
mensagem += “\n O valor de v[2] é =” + v[2];
}
//Modifica todos os elementos do vetor. Multiplica todos por 3
public void modificaVetor( int v1[] ){
for (int j = 0; j < v1.length; j++ ){
v1[j] *= 3;
}
}
//Modifica um elemento, multiplicando-o por 3
public void modificaElemento( int elem ){
elem *= 3;
}
//Retorna a mensagem
public String getMensagem(){
return mensagem;
}

public static void main( String args[] ){


Vetor v = new Vetor();
v.inicializa();
System.out.println( v.getMensagem() );

42
}
}

12.5 Lista de Argumentos de Comprimento Variável (Varargs)

Lista de argumentos de comprimento variável (Varargs) é um recurso


disponibilizado pela versão do Java 5.0. Com esse recurso, é possível criar um método
com um número não especificado de argumentos. Para isso, basta declarar como
parâmetro um tipo de argumento seguido por reticências (...), indicando que o método
recebe um número variável de argumentos desse tipo específico. As reticências só
podem aparecer uma única vez na lista de argumentos do método e juntamente com seu
tipo devem ser colocados no final da lista de argumentos.
A Listagem 47 mostra a método media que recebe uma lista de argumentos de
comprimento variável do tipo double. Esse método é invocado três vezes no método
principal da classe TesteVarargs e com lista de argumentos de tamanhos diferentes.

Listagem 47 – Exemplo de Uso de Varargs


public classe TesteVarargs(){
public static double media (double... numeros){
double total = 0.0;
for (int i = 0; i < numeros.length; i++){
total += números[i];
}
return total/numeros.length;
}

public static void main (String args[] ){


double d1 = 10.0;
double d2 = 20.0;
double d3 = 30.0;
double d4 = 40.0;

double media1 = + media(d1, d2);


System.out.print(“Média entre ” + d1 + “ e ” + d2 + “=” + media1);

double media2 = + media(d1, d2, d3);

43
System.out.print(“Média entre ” + d1 + “, ” + d2 + “ e ” + d3 “=”
+ media2);

double media3 = + media(d1, d2, d3, d4);


System.out.print(“Média entre ” + d1 + “, ” + d2 + “, ” + d3 + “ e ” + d4
+ “ =” + media3);
}
}

12.6 Retorno de Métodos

Os métodos que possuem a necessidade de retornar valores devem ser


declarados com tipo de retorno. Além disso, o valor retornado deve ser do tipo
declarado.
A Listagem 48 apresenta um exemplo de método sem retorno. Este tipo de
método é identificado pela palavra chave void. Já a Listagem 49 mostra um exemplo de
um método que retorna um valor do tipo int. Observe que o método deve ser declarado
com o tipo de retorno int e o retorno é feito através da palavra chave return.

Listagem 48 – Exemplo de Método sem Retorno


void metodoSemRetorno(){
//Método sem retorno
}

Listagem 49 – Exemplo de Método com Retorno


int metodoComRetorno(){
//Método com retorno
return 10;
}

Um método pode declarar apenas um tipo de retorno. Se for desejado que ele
retorne mais de um valor, é possível declarar o tipo de retorno como sendo um vetor.
Por exemplo, no método da Listagem 50 é necessário retornar dos valores do tipo int.
Para isso, um vetor de inteiros com duas posições é declarado e possuem os valores que
devem ser retornados.

44
Listagem 50 – Exemplo de Método com Mais de um Valor como Retorno
int[] getValores(){
int retornos[] = new int[2];
retornos[0] = 9;
retornos[1] = 23;
return retornos;
}

Em Java não é necessário utilizar o valor de retorno de um método, por exemplo,


a Listagem 51 e a Listagem 52 ilustram o uso do valor de retorno e o não uso ao se
invocado o método presente na Listagem 50, respectivamente.

Listagem 51 – Exemplo de Uso de Valor de Retorno


int[] a = getValores();

Listagem 52 – Exemplo de Não Uso de Valor de Retorno


getValores();

12.7 Construtores

Um método construtor é aquele que é chamado quando um objeto de uma classe


é instanciado. É utilizado para fazer inicializações necessárias das variáveis de instância
da classe, ou seja, inicializar o estado de um objeto da classe.
Um construtor possui o mesmo nome da classe e não possui tipo de retorno, já
que quando invocado a partir do operador new o retorno será a instância. Se nenhum
construtor for definido pela classe, o compilador Java cria automaticamente um
construtor vazio sem argumentos. Na Listagem 53, a classe ExemploConstrutor possui
três tipos de construtores diferentes. Cada uma com suas respectivas listas de
parâmetros e inicializações distintas.
Uma classe que apresenta mais de um construtor, ou seja, possui construtores
com listas de argumentos distintas, possui construtores sobrecarregados.

Listagem 53 – Exemplo de Classe com Construtores Definidos


public class ExemploConstrutor{

private int var_1;


private double var_2;
private String var_3;

45
public ExemploConstrutor(){
var_1 = 10;
var_3 = “Exemplo Construtor sem Argumentos”;
}
public ExemploConstrutor(int valor){
var_1 = valor;
var_2 = 15.0;
}
public ExemploConstrutor(int valor1, String valor2){
var_1 = valor1;
var_3 = valor2;
}
}

12.8 Sobrecarga de Métodos

A sobrecarga de um método consiste em utilizar um mesmo nome para vários


métodos, porém cada método possui uma lista de parâmetros distinta. Vários métodos
podem ser sobrecarregados em uma única classe. A Listagem 54 exibe alguns métodos
sobrecarregados. Observa-se que todos os métodos possuem o mesmo nome e tipo de
retorno, porém as listas de parâmetros deles são diferentes entre si.
É importante saber que ao realizar sobrecarga de métodos apenas é permitido
alterar o seu tipo de retorno, se e somente se, os métodos com mesmos nomes tiverem
listas de parâmetros distintas.

Listagem 54 – Exemplo de Sobrecarga de Métodos


public class ExemploSobrecarga(){

public void metodoSobrecarregado(int param1){}


public void metodoSobrecarregado(String param1){}
public void metodoSobrecarregado(int param1, String param2){}
public void metodoSobrecarregado(String param1, int param2){}
public void metodoSobrecarregado(double param1, String param2, int param3){}
}

46
13. Igualdade entre Objetos

A comparação entre dois tipos primitivos em Java se faz a partir do operador ==,
já em relação a objetos a utilização desse operador vai fazer com que os valores de suas
referências à memória sejam comparados e não o seu conteúdo semântico. Assim,
utilizar o operador == para comparar objetos pode gerar desigualdades para objetos
idênticos. No caso das Strings, para resolver este problema basta utilizar o método
equals(), como mostrado anteriormente.
Para compreender o porquê de dois objetos iguais serem diferentes quando
comparados com o operador == é necessária a introdução de dois conceitos: Pilha ou
Memória Stack (Pilha) e Heap de Objetos.
Assim como em outras linguagens de programação, em Java, as estruturas de
dados usadas durante a execução do programa na memória são mapeadas em duas
partes: a memória stack e a memória heap. A memória stack (também conhecida como
pilha) possui uma estrutura organizada de maneira seqüencial, em que os dados são
colocados uns sobre os outros. Os dados primitivos e as referências a objetos são
armazenados na pilha.
A memória heap consiste em uma estrutura de armazenamento não ordenada,
mas ampla, que pode ser acessada diretamente. Ela contém os objetos na linguagem
Java. O acesso direto a esses objetos somente é feito porque se sabe onde o dado
desejado está localizado a partir de sua referência armazenada na memória stack.
Para melhor entender esses conceitos, observe os exemplos presentes na
Listagem 55 e Listagem 56. A Listagem 55 apresenta uma classe Teste com uma única
variável e dois métodos. Já a Listagem 56 possui a TesteStackHeap com um método
principal onde algumas variáveis são instanciadas. No método principal, uma variável
double é criada e recebe o valor 12.5, uma variável do tipo int é instanciada e recebe o
valor 23, dois objetos do tipo Teste são instanciados e atribuídos as variáveis teste1 e
teste2, que guardarão suas referências e uma outra variável do tipo Teste é declarada
e recebe o conteúdo da variável teste2.

Listagem 55 – Exemplificando memória stack e heap de objetos


public class Teste(){
private int var1;
public void setVar1(int valor){
var1 = valor;
}
public int getVar1(){

47
return var1;
}
}

Listagem 56 – Exemplificando memória stack e heap de objetos


public class Teste (){
public static void main(String args[]){
Teste teste1 = new Teste();
Teste teste2 = teste1;
int b = 23;
double a = 12.5;
Teste teste3 = new Teste();
}
}

A Figura 4 mostra como ficaria a configuração do stack e do o heap após a


execução do método principal da Listagem 56. Observa-se que no stack as variáveis
primitivas e as referências aos objetos são armazenadas. Já o heap possui os dois
objetos instanciados. Como as variáveis teste1 e teste2 possuem a mesma referência,
elas apontam para o mesmo objeto no heap (Objeto 1).

Figura 4 - Ilustração de Stack e Heap

Após a apresentação desses conceitos, pode-se concluir que um objeto pode ser
referenciado por várias variáveis, ou seja, podem existir várias posições na memória
stack com o mesmo conteúdo (mesma referência). Dessa forma, comparar objetos a
partir do operador == implicará em comparar os valores de suas referências na
memória stack, mas na verdade o que se deseja é comparar se dois objetos são iguais
no que diz respeito a possuírem os mesmos valores para os seus dados internos.

48
Em Java, a comparação do conteúdo interno dos objetos é feita através do
método equals() que retorna verdadeiro se eles forem iguais e falso, caso contrário.
Para algumas classes (String, ArrayList), a API Java já disponibiliza esse método
implementado. Para as classes criadas pelo programador, esse método deve ser
implementado e seu código conterá a comparação entre os dados relevantes dos objetos
a serem comparados.

13.1 Método equals()

Todas as classes escritas em Java são filhas da classe Object, ou seja, todas as
classes herdam os seus métodos. Na classe Object é definido que a comparação entre
dois objetos é feita a partir do método equals(), também herdado. Esse método possui
a seguinte assinatura:

public boolean equals(Object obj)

Para comparar dois objetos de uma mesma classe, deve-se sobrescrever o


método equals() de maneira que ele possua a funcionalidade de comparar se o objeto
recebido é igual ao atual.
Para exemplificar o uso do método equals() considere a classe Aluno presente
na Listagem 57. Essa classe é formada por duas variáveis (matricula e nome) e quatro
métodos de acesso a elas.

Listagem 57 – Exemplificando o método equals: Classe Aluno


public class Aluno(){
private int matricula;
private String nome;

public int getMatricula(){


return matricula;
}
public void setMatricula(int matricula){
this.matricula = matricula;
}

public String getNome(){


return nome;
}

49
public void setNome(String nome){
this.nome = nome;
}
}

A Listagem 58 apresenta uma aplicação que compara objetos da classe Aluno.


Inicialmente, duas variáveis (aluno1 e aluno2) do tipo Aluno são criadas e recebem
valores para seus atributos. Em seguida aluno1 é comparada a aluno2 e como o
resultado é falso é escrito na tela Aluno 1 != Aluno 2. Verifica-se que eles são
realmente diferentes, pois não possuem o mesmo conteúdo.
Após a comparação entre essas duas variáveis, duas outras são instanciadas
(aluno3 e aluno4) e recebem também valores para seus atributos. Dessa vez ambas
variáveis possuem mesmo conteúdo, ou seja, valores iguais para seus atributos. Logo
em seguida, a variável aluno3 é comparada a aluno4. E o resultado é Aluno 3 !=
Aluno 4, implicando em novamente um resultado falso e em as variáveis serem
verdadeiras. Dessa forma, verifica-se que a comparação não funcionou como o desejado,
afinal se dois objetos possuem o mesmo conteúdo, então, a comparação entre eles é
para retornar que são iguais.
O resultado de que aluno3 é diferente de aluno4 se deve ao fato de o método
equals() que os compara ser herdado da classe Object e apenas realizar uma
comparação entre as referências dos objetos e não a comparação de atributo a atributo.
Portanto, a solução é sobrescrever o método equals(), implementando-o de acordo com
as necessidades que indicam se dois objetos da classe Aluno são iguais ou não. Neste
caso, dois objetos Aluno são iguais se possuírem todos os seus atributos iguais. Assim,
a Listagem 59 apresenta uma nova implementação da classe Aluno, incluindo a
implementação do método equals(), que fará com que a comparação entre aluno3 e
aluno4 retorne verdadeira.

Listagem 58 – Exemplificando o método equals


public class ComparaAlunos(){
public static void main(String args[]){
Aluno aluno1 = new Aluno();
Aluno aluno2 = new Aluno();

aluno1.setMatricula(12345);
aluno1.setNome(“Carlos”);
aluno2.setMatricula(54321);
aluno2.setNome(“Janaina”);

50
//Comparando aluno1 com aluno2
if (aluno1.equals(aluno2)){
System.out.println(“Aluno 1 = Aluno 2”);
}else{
System.out.println(“Aluno 1 != Aluno 2”);
}

Aluno aluno3 = new Aluno();


Aluno aluno4 = new Aluno();

aluno3.setMatricula(12345);
aluno3.setNome(“Carlos”);
aluno4.setMatricula(12345);
aluno4.setNome(“Carlos”);

//Comparando aluno3 com aluno4


if (aluno3.equals(aluno4)){
System.out.println(“Aluno 3 = Aluno 4”);
} else{
System.out.println(“Aluno 3 != Aluno 4”);
}
}
}

Listagem 59 – Exemplificando o método equals: Classe Aluno com equals()


public class Aluno(){
private int matricula;
private String nome;

public int getMatricula(){


return matricula;
}
public void setMatricula(int matricula){
this.matricula = matricula;
}

public String getNome(){

51
return nome;
}
public void setNome(String nome){
this.nome = nome;
}
public boolean equals(Object obj){
Aluno alun = (Aluno)obj;
if (matricula == alun.getMatricula()){
if (nome.equals(alun.getNome)){
return true;
}
}
return false;
}
}

14. Encapsulamento

O encapsulamento determina que os atributos de uma classe devem ser


privativos dela, de forma a garantir a confiabilidade das informações registradas em uma
instância de uma classe. Em outras palavras, é o processo de ocultar todos os detalhes
de um objeto que não contribuem para suas características essenciais.
A Figura 5 ilustra o conceito de encapsulamento, ou seja, existem algumas coisas
no gato que não necessitam ser conhecidas de fora, somente por ele mesmo. Portanto,
essas coisas são encapsuladas pela carcaça do gato.

Figura 5 - Encapsulamento

52
Na Listagem 60, há uma classe ContaCorrente que possui um atributo (saldo) e
alguns métodos que não foram exibidos. No método principal, uma instância dessa
classe é criada e através do acesso direto ao conteúdo da variável saldo é possível
saber se a conta tem ou não saldo. Na verdade, para algumas aplicações que acessam a
classe ContaCorrente não interessa saber o valor do saldo da conta, mas sim, apenas,
se a conta tem ou não saldo. Dessa forma, outras classes podem ter acesso ao
conteúdo da variável, o que não deveria ocorrer.

Listagem 60 – Exemplo de Não Encapsulamento


class ContaCorrente {
public double saldo;

// outros métodos...

public static void main(String args[]){

ContaCorrente c = new ContaCorrente();


if (c.saldo < 0) {
//Sem saldo
}
}
}

Uma forma de resolver o problema descrito anteriormente seria definir a variável


saldo como privada (private) e criar um método que informe se a conta possui ou não
saldo. Portanto, esse método deve ser público para ser acessado fora da classe a que
pertence.

Listagem 61 – Exemplo de Encapsulamento


class ContaCorrente {
private double saldo;
public boolean isSaldoNegativo(){
if (saldo > 0)
return false;
else
return true;
}
public static void main(String args[]){

53
ContaCorrente c = new ContaCorrente();
if (c.isSaldoNegativo()) {
//Sem saldo
}
}
}

15. Herança

A herança é um mecanismo que permite que uma classe herde características de


outra classe, absorvendo seus atributos e comportamentos. Além disso, a nova classe é
aprimorada com novas capacidades. Dessa forma, a herança permite a reutilização de
software, permitindo a economia de tempo no desenvolvimento de programas.
Uma classe que herda as características de outra classe é conhecida como
subclasse. Já a classe que tem suas características herdadas é chamada de superclasse.
Criada uma subclasse, ela também poderá ser uma futura superclasse.
Existem dois tipos de herança:

• Herança Simples: uma classe só pode herdar os atributos e métodos de uma


única superclasse.
• Herança Múltipla: uma classe pode herdar atributos e métodos de mais de uma
superclasse. Java não suporta herança múltipla.

Geralmente, uma subclasse adiciona novos atributos e novos métodos, tornando-


se maior que sua superclasse. Além disso, uma subclasse é mais específica que sua
superclasse e representa um grupo menor de objetos.
Cada objeto da subclasse é também um objeto de sua superclasse. Porém, o
contrário não é verdadeiro, ou seja, os objetos de uma superclasse não são objetos de
suas subclasses.
A Figura 6 apresenta um exemplo de herança. A classe Forma é herdada pelas
classes FormaBidirecional e FormaTridimensional, ou seja, Forma é a superclasse
de FormaBidirecional e FormaTridimensional e essas duas são suas subclasses. Um
objeto do tipo FormaBidirecional ou do tipo FormaTridimensional também é um
objeto do tipo Forma, o contrário não é verdadeiro.

54
Figura 6 – Herança

Observa-se que as classes FormaBidirecional e FormaTridimensional além


de subclasses da classe Forma também são superclasses. A primeira possui as
subclasses Circulo, Quadrado e Retângulo. Enquanto a segunda, possui as subclasses
Esfera, Tetraedro e Cubo.
A Figura 7 mostra com detalhes como seria a herança da superclasse
FormaBidimensional pela subclasse Quadrado. Além dos atributos e métodos
existentes na classe FormaBidimensional, a classe Quadrado adiciona novos atributos
e métodos que são particulares a ela.

Figura 7 - Herança: Exemplo

55
15.1 Como Definir o Relacionamento de Herança

Para demonstrar como definir o relacionamento de herança, considera-se o


contexto de ter que desenvolver um programa que simule o comportamento de vários
tipos de animais em determinado ambiente. Inicialmente, apenas um grupo de animais
estará presente neste ambiente e cada animal deve ser representado por um objeto.
Além disso, os animais movem-se no ambiente ao seu modo e podem fazer um conjunto
de coisas. O programa deverá prever que novos tipos de animais poderão ser incluídos
no ambiente.
O primeiro passo é observar cada animal e definir o que cada objeto do tipo
animal tem em comum no que diz respeito aos atributos e comportamentos (métodos).
Deve-se definir também como os tipos de animais se relacionam. Inicialmente, o
programa deve simular o comportamento dos animais ilustrados na Figura 8, ou seja, de
um leão (lion), de um lobo (wolf), de um gato (cat), de um hipopótamo (hippo), de um
tigre (tiger) e de um cachorro (dog).

Figura 8 - Definição de Relacionamento de Herança: 1º Passo

O segundo passo consiste em projetar a superclasse, ou seja, a classe que


representa o estado e o comportamento em comum a todos os animais. Para este
exemplo, como todos os objetos são animais, a superclasse foi denominada como
Animal e as variáveis e métodos em comuns a todos os animais foram atribuídos a ela,
como ilustrado na Figura 9. Neste caso, cinco variáveis foram definidas: picture (a
figura que representa o animal), food (o tipo de comida que o animal come), hunger (o
nível de fome do animal), boundaries (representação da altura e largura do espaço que
o animal vagará ao seu redor) e location (as coordenadas X e Y do animal no espaço).
Além disso, quatro métodos foram definidos para definir o comportamento dos animais:
makeNoise() (comportamento do animal ao fazer algum ruído), eat() (comportamento

56
do animal ao comer), sleep() (comportamento do animal dormindo) e roam()
(comportamento do animal quando não está nem dormindo nem comendo,
provavelmente vagueando no ambiente).

Figura 9 - Definição de Relacionamento de Herança: 2º Passo

O terceiro passo é o de decidir se alguma subclasse precisa de comportamentos


(métodos) específicos ao seu tipo de subclasse. Analisando a classe Animal (Ver Figura
10), pode-se extrair que os métodos eat() e makeNoise() devem ter implementações
diferentes em cada classe de animais, afinal, cada tipo de animal tem comportamento
distinto ao comer e fazer ruídos. Assim, esses métodos da superclasse devem ser
sobrescritos nas subclasses, ou seja, redefinidos. Os métodos sleep() e roam() não
foram escolhidos para serem redefinidos por achar que esses comportamentos podem
ser generalizados a todos os tipos de animais.

57
Figura 10 - Definição de Relacionamento de Herança: 3º Passo

A Figura 11 apresenta a hierarquia dos animais definida até o momento. Observa-


se que cada tipo de animal consiste em uma classe que herda da superclasse Animal.
Todos os atributos da superclasse são herdados pelas subclasses, assim como as
implementações dos métodos sleep() e roam(). Foi definido que os demais métodos,
makeNoise() e eat() deveriam ser redefinidos nas subclasses, já que para cada tipo de
animal esses métodos possuem comportamentos distintos. Dessa forma, analisando a
hierarquia definida na Figura 11, deve-se executar o quarto passo, ou seja, procurar
mais oportunidades de se utilizar abstração, ou seja, tentar encontrar duas ou mais
classes que possuam comportamentos comuns. A partir da hierarquia definida até o
momento, observa-se as classes que definem o lobo (Wolf) e o cachorro (Dog) podem
ter um mesmo comportamento em comum, assim como as classes Lion, Tiger e Cat.

58
Figura 11 - Definição de Relacionamento de Herança: 4º Passo

O último passo é finalizar a hierarquia de herança. Para este exemplo, a


hierarquia final está demonstrada na Figura 12. Dessa forma, a classe Animal possui
três subclasses: Feline, Hippo e Canine. Na subclasse Canine o método roam() foi
redefinido, pois os caninos tendem a se movimentar em grupos. Já na subclasse Feline
foi redefinido, pois os felinos tendem a evitar outros do mesmo tipo. A classe Hippo
continuou com o método roam() herdado da superclasse Animal. A classe Feline
também passou a ser superclasse das subclasses Lion, Tiger e Cat e a classe Canine
superclasse das subclasses Wolf e Dog.

59
Figura 12 - Definição de Relacionamento de Herança: 5º Passo

Para exemplificar que métodos são chamados quando em uma hierarquia de


herança há métodos sobrescritos, considere o exemplo da Figura 13. Esta figura
representa a hierarquia até se chegar à classe Wolf. Nela está ilustrado para cada
método chamado em relação a um objeto Wolf de que classe na hierarquia será
executado.

60
Figura 13 - Hierarquia do Animal tipo Wolf

15.2 Exemplo de Implementação de Herança em Java

Herança em Java é implementada através da palavra chave extends. Para


exemplificar, observe as classes PessoaFisica e PessoaJuridica presentes na Listagem
62 e na Listagem 63, respectivamente. Observa-se que tanto a classe PessoaFisica
quanto a classe PessoaJuridica possuem atributos e métodos em alguns. Os atributos
são: nome, endereço e telefone. Os métodos são os que atribuem valores a esses
atributos em comuns.

Listagem 62 – Classe PessoaFisica


class PessoaFisica{
String nome;
String endereco;
String telefone;

61
long cpf;
int estadoCivil;

public void setNome(String nome){


this.nome = nome;
}
public void setEndereco(String endereco){
this.endereco = endereco;
}
public void setTelefone (String telefone){
this.telefone = telefone;
}
public void setCpf( long cpf ){
this.cpf = cpf;
}
public void setEstadoCivil(int estadoCivil){
this.estadoCivil = estadoCivil;
}
}

Listagem 63 – Classe PessoaJuridica


class PessoaJuridica{
String nome;
String endereco;
String telefone;
long cnpj;
String razaoSocial;
String nomeRepresentante;

public void setNome(String nome){


this.nome = nome;
}
public void setEndereco(String endereco){
this.endereco = endereco;
}
public void setTelefone (String telefone){
this.telefone = telefone;
}

62
public void setCnpj( long cnpj ){
this.cnpj = cnpj;
}
public void setRazaoSocial (String razaoSocial){
this.razaoSocial = razaoSocial;
}
public void setNomeRepresentante (String nomeRepresentante){
this.nomeRepresentante = nomeRepresentante;
}
}

Caso uma aplicação necessite utilizar ambas as classes acima, estaria tendo
replicação de dados e métodos nelas. Uma solução seria a utilização da herança. Um
alternativa seria criar uma classe Pessoa (Listagem 64) com as informações comuns as
duas classes e as classes PessoaFisica e PessoaJuridica herdaria seus atributos e
métodos e implementariam apenas as suas particularidades, como visto na Listagem 65
e na Listagem 66. Como já citado, a herança é realizada a partir da palavra chave
extends.

Listagem 64 – Herança: Classe Pessoa


class Pessoa{
String nome;
String endereco;
String telefone;

public void setNome(String nome){


this.nome = nome;
}
public void setEndereco(String endereco){
this.endereco = endereco;
}
public void setTelefone (String telefone){
this.telefone = telefone;
}

63
Listagem 65 – Herança: Classe PessoaFisica
class PessoaFisica extends Pessoa{
long cpf;
int estadoCivil;

public void setCpf( long cpf ){


this.cpf = cpf;
}
public void setEstadoCivil(int estadoCivil){
this.estadoCivil = estadoCivil;
}
}

Listagem 66 – Herança: Classe PessoaJuridica


class PessoaJuridica extends Pessoa{
long cnpj;
String razaoSocial;
String nomeRepresentante;

public void setCnpj( long cnpj ){


this.cnpj = cnpj;
}
public void setRazaoSocial (String razaoSocial){
this.razaoSocial = razaoSocial;
}
public void setNomeRepresentante (String nomeRepresentante){
this.nomeRepresentante = nomeRepresentante;
}
}

Percebe-se que nos exemplos dessa seção uma nova palavra chave é introduzida,
this. Ela é utilizada para que um objeto tenha uma referência a ele próprio, a referência
this. Essa referência é utilizada para referenciar variáveis de instância e métodos de um
objeto. Nos exemplos apresentados, os métodos recebiam como argumentos variáveis
com os mesmos nomes de suas variáveis globais. A referência this foi utilizada para
dizer que quem estava recebendo o novo valor era a variável do objeto instanciado e
não a própria variável passada como argumento.

64
A referência this pode ser utilizada para: parâmetros com o mesmo nome de
campos; passar o objeto corrente como parâmetro; retornar o objeto corrente.
Existe um modificador de acesso que não permite que uma classe seja herdada, é
o modificador final. Por exemplo, se a classe for definida como na Listagem 67. O
modificador final também pode ser aplicado a variáveis e a métodos. No caso de uma
variável, ele indica que o seu valor não pode ser modificado. No caso de um método,
implica que ele não pode ser redefinido em subclasses.

Listagem 67 – Classe com Modificador de Acesso final


/*
* Definição de uma classe que não pode ser herdada
*/
public final class Imutável(){
//Implementação da Classe
}

16. Polimorfismo

O polimorfismo em Java permite que objetos de classes de uma hierarquia de


herança sejam tratados genericamente. Por exemplo, considera-se que a classe Dog
herda da classe Animal, então, com o polimorfismo, a criação de um objeto do tipo Dog
poderia ser feita de duas formas, como mostrado na Listagem 68.

Listagem 68 – Exemplo Polimorfismo


Dog d = new Dog();
Animal d = new Dog();

No primeiro caso, tanto o tipo da variável como o tipo do objeto são do tipo Dog,
como mostrado na Figura 14. Usando o polimorfismo, a referência (tipo da variável) e o
objeto podem ser de tipos diferentes, porém o tipo da referência deve ser do tipo da
superclasse do objeto atual, como ilustrado na Figura 15.

Figura 14 – Sem Polimorfismo

65
Figura 15 - Com Polimorfismo

A Listagem 69 apresenta um exemplo de polimorfismo usando vetores. Neste


exemplo, um vetor com cinco posições é criado e definido como sendo da superclasse
Animal. Em seguida, em cada posição do vetor é criado um objeto de uma das
subclasses da classe Animal. Isso é permitido porque cada objeto da subclasse é um
objeto da superclasse. O uso do polimorfismo é feito na estrutura de controle for que
invoca os métodos eat() e roam() para cada elemento do vetor. Assim, os métodos
que serão executados dependerão do tipo da subclasse que foi instanciada, ou seja, não
necessariamente as implementações desses métodos pertencentes à classe Animal
serão executadas. As implementações executadas dependerão da hierarquia definida
para as classes (Rever Figura 12).

Listagem 69 – Exemplo Polimorfismo com Vetores


//Declaração de vetor do tipo Animal. Vetor pode possuir qualquer tipo de animal
Animal[] animais[] = new Animal[5];

//Cada elemento do vetor possui um tipo de animal diferente.


//Tipos das subclasses da superclasse Animal
animais[0] = new Dog();
animais[1] = new Cat();
animais[2] = new Wolf();
animais[3] = new Hippo();
animais[4] = new Lion();

//Uso do polimorfismo. Para cada elemento do vetor, os métodos chamados


//executarão comportamentos de acordo com as instâncias das classes
for (int i = 0; i < animais.length(); i++){
animais[i].eat();
animais[i].roam();
}

66
Também é permitido usar polimorfismo em tipos de argumentos dos métodos e
tipos de retornos. Por exemplo, a classe da Listagem 70 possui um método que recebe
como argumento um objeto do tipo da superclasse Animal. Na classe da Listagem 71,
um método cria e inicializa dois objetos diferentes das subclasses Dog e Hippo que são
passados como argumentos ao método metodoPolimorfico da classe
ArgumentoPolimorfismo. Dentro deste método, o método makeNoise será invocado
de acordo com a instância da classe Animal passada.

Listagem 70 – Exemplo de Polimorfismo em Tipos Argumento de Métodos 1


class ArgumentoPolimorfismo{
public void metodoPolimorfico(Animal a){
a.makeNoise();
}
}

Listagem 71 – Exemplo de Polimorfismo em Tipos Argumento de Métodos 2


class TestePolimorfismo{
public void start(){
ArgumentoPolimorfismo ap = new ArgumentoPolimorfismo();
Dog d = new Dog();
Hippo h = new Hippo();

ap.metodoPolimorfico(d);
ap.metodoPolimorfico(h);
}
}

O polimorfismo também define o princípio pelo qual duas ou mais classes


derivadas de uma mesma superclasse podem invocar métodos que têm a mesma
identificação (assinatura), mas comportamentos distintos, especializados para cada
classe derivada, usando para tanto uma referência a um objeto do tipo da superclasse.
Por exemplo, que o método setEndereco da classe PessoaFisica tenha implementação
diferente do método setEndereco da classe PessoaJuridica.
Esse mecanismo é fundamental na programação orientada a objetos, permitindo
definir funcionalidades que operem genericamente com objetos, abstraindo-se de seus
detalhes particulares quando esses não forem necessários.
Para que o polimorfismo possa ser utilizado, é necessário que os métodos que
estejam sendo definidos nas classes derivadas tenham exatamente a mesma assinatura

67
do método definido na superclasse; nesse caso, está sendo utilizado o mecanismo de
redefinição de métodos (overriding).

16.1 Redefinição de Métodos

Quando uma classe é herdada por outra, todos os seus métodos não privados são
herdados. Em algumas situações, deseja-se alterar o comportamento de algum desses
métodos herdados. Isso é feito redefinindo o método da superclasse, a partir de sua
reescrita, definindo-o com o mesmo nome e mesma lista de parâmetros. Dessa maneira,
a chamada a um método sobrescrito a partir de um objeto instância da subclasse
executará o comportamento redefinido pela subclasse e não mais o definido pela
superclasse. Se o objeto for instância da superclasse, o método executará o
comportamento definido na própria superclasse.
Para exemplificar a redefinição de métodos, observa-se o exemplo da Figura 16.
Neste exemplo, a classe Ponto é herdada pelas classes Circulo e Retangulo. Dessa
maneira, os métodos desenhar() e getCor() estão disponíveis para as duas
subclasses. O primeiro método desenha o objeto na tela, já o segundo retorna sua cor.
Considerando um ponto, um círculo e um retângulo, verifica-se que eles devem ser
desenhados de maneiras distintas, pois apresentam conceitualmente formas diferentes.
Assim, o método desenhar() das subclasses devem ser redefinidos.

Figura 16- Exemplo de Redefinição de Métodos

Na Listagem 72, no método principal um vetor de três posições é instanciado com


objetos do tipo Ponto. O primeiro elemento é uma instância da superclasse e os outros
dois são instancias das subclasses. Dentro do laço for os métodos getCor() e
desenhar() são invocados por três vezes. Como os métodos estão associados aos
objetos referenciados no vetor, o método desenhar() das classes Ponto, Circulo e

68
Retângulo são chamados, respectivamente, e o método getCor() da classe Ponto é
chamado três vezes.

Listagem 72 – Exemplo de Redefinição de Métodos


public class ExemploRedefinicaoMetodos(){
public static void main (String args[]){
Ponto pontos[] = new Ponto[3];
pontos[0] = new Ponto();
pontos[1] = new Circulo();
pontos[2] = new Retangulo();

for (int k = 0; k < pontos.lenght; k++ ){


pontos[k].desenhar();
System.out.println(“Cor = ” + pontos[k].getCor() );
}
}
}

17. Classes e Métodos Abstratos

Quando uma classe é criada, o primeiro pensamento pode ser que ela seja criada
para funcionar como um tipo e que objetos desse tipo serão instanciados, ou seja, crie-
se uma classe concreta. Por outro lado, podem existir casos em que o programador
defina uma classe e nunca pretenda instanciá-la. Essas classes são conhecidas como
classes abstratas e são usadas como modelo ou base para o comportamento de outras.
O diagrama de classes da Figura 17 apresenta um exemplo de herança. No caso,
a classe Animal é herdada pelas classes Cachorro, Canguru e Coelho. Observa-se que
há coerência na existência de objetos dos tipos Cachorro, Canguru e Coelho, porém
não faz sentido existir um objeto do tipo Animal. Como já citado, classes que não
devem ser instanciadas são denominadas de classes abstratas. Dessa maneira, a
superclasse Animal deve ser implementada como uma classe abstrata (ou seja, nunca
será instanciada), possui três atributos e dois métodos um concreto e um abstrato. Um
método concreto é aquele que possui sua implementação em seu corpo. Já um método
abstrato é apenas declarada e sua implementação deve ser feita pelas suas subclasses,
ou seja, ele não possui “corpo”.
Para implementar uma classe ou método abstrato em Java, deve-se utilizar a
palavra chave abstract, como pode ser visto na implementação da classe Animal
presente na Listagem 73.

69
A classe Animal é uma classe abstrata e serve de modelo para suas subclasses.
Ela também possui um método abstrato, andar. Observa-se que este método não possui
implementação, é apenas declarado para ser possivelmente implementado pelas
subclasses. É importante saber que nem toda classe abstrata precisa possuir métodos
abstratos, porém todo método abstrato herdado de uma superclasse deve ser
implementado a menos que a subclasse também seja uma classe abstrata. De acordo
com o diagrama de classe da Figura 17, as classes Cachorro e Canguru implementam
o método abstrato andar. Já a classe Coelho não o implementa, portanto sendo
também de uma classe abstrata.

Figura 17 – Classe e Método Abstratos: Exemplo

Listagem 73 – Classe Abstrata: Animal


/*Implementação de uma classe abstrata*/
abstract class Animal {
String descricao;
int tipo;
double vidaMedia;

//Método concreto
public void comer (){
//Este método deve ser codificado
}
//Declaração de método abstrato
public abstract andar();
}

70
O exemplo a seguir ilustrará os conceitos de classes abstratas e polimorfismo. A
Figura 8 apresenta a classe abstrata Animal sendo herdada pelas subclasses concretas
Dog e Cat. O problema consiste em implementar uma lista para armazenar objetos do
tipo Dog e outra para armazenar objetos do tipo Cat.

Figura 18 - Pequena parte da hierarquia Animal

A Listagem 74 e a Listagem 75 apresenta as implementações de uma lista de


objetos Dog e de uma lista e objetos Cat, respectivamente. Em cada implementação,
existem dois atributos privados: um vetor de 5 posições declarados com tipos dos
objetos Dog ou Cat e uma variável inteira que armazena a posição atual do vetor, ou
seja, onde será armazenado o próximo elemento. Também há um método que adiciona
um objeto do respectivo tipo da lista. Cada lista somente aceita objetos dos tipo para
qual foi declarada.

Listagem 74 – Lista de Dog


public class MyDogList{
private Dog[] dogs = new Dog[5];
private int nextIndex = 0;

public void add(Dog d){


if (nextIndex < dogs.length){
dogs[nextIndex] = d;
nextIndex++;
}
}
}

71
Listagem 75 – Lista de Cat
public class MyCatList{
private Cat[] cats = new Cat[5];
private int nextIndex = 0;

public void add(Cat c){


if (nextIndex < cats.length){
cats[nextIndex] = c;
nextIndex++;
}
}
}

Considera-se agora, que houve uma mudança nos requisitos. Deseja-se a


implementação de uma lista que possa armazenar qualquer tipo de animal. A satisfação
desse requisito é feita utilizando o polimorfismo e classes abstratas. A Listagem 82
mostra a implementação de uma lista genérica para qualquer tipo de animal. O
polimorfismo foi usado na declaração de um vetor do tipo Animal, ou seja, esse vetor
poderá armazenar qualquer objeto de subclasses concretas da superclasse Animal,
porém esse vetor não pode armazenar instâncias da classe Animal, já que ela foi
definida com abstrata. No método add(Animal a) também há o conceito do
polimorfismo, pois qualquer objeto de classes que herdem da classe animal podem ser
passados como argumentos e assim incluído no vetor de animais.

Listagem 76 – Lista de Animal


public class MyAnimalList{
private Animal[] animais = new Animal[5];
private int nextIndex = 0;

public void add(Animal a){


if (nextIndex < animais.length){
animais[nextIndex] = a;
nextIndex++;
}
}
}

72
Na Listagem 83 são criados dos objetos, um do tipo Dog e outro do tipo Cat e
ambos podem ser inseridos na lista genérica de animais do tipo MyAnimalList.

Listagem 77 – Exemplo com Lista de Animal


public class TesteListaAnimal{
public static void main(String[] args){
MyAnimalList lista = new MyAnimalList();
Dog d = new Dog();
Cat c = new Cat();
lista.add(d);
lista.add(c);
}
}

18. Interfaces

Para a explanação do conceito de interfaces, considera-se a hierarquia de classes


apresentada na Figura 19. Inicialmente esse modelo de hierarquia foi definido para a
implementação de um programa que simulasse vários tipos de animais em determinado
ambiente, cada animal com seus estados e comportamentos.
Considera-se, então, um novo requisito: a utilização de simulação de objetos Dog
em um programa de ciências. Analisando a hierarquia, isso é perfeitamente possível.
Basta que o programador utilize a classe Dog. Lá estarão todos os atributos e métodos
necessários.

73
Figura 19 - Hierarquia de Animais

Imagina-se, em seguida, a implementação de um programa que simule um pet


shop, ou seja, uma lojinha de animais de estimação. Para isso, os animais da hierarquia
devem ter comportamentos de animais de estimação, por exemplo, ser amigáveis,
brincar, etc. Verificando como foram definidos os métodos para as classes da hierarquia,
em seção anterior desse material, observa-se que as subclasses da classe Animal não
possuem comportamentos (métodos) que satisfaçam os requisitos de um animal de
estimação. Dessa maneira, o problema consiste em como acrescentar esses novos
comportamentos na classe da hierarquia de animais.

1ª Opção: implementar métodos que simulem os comportamentos de um animal de


estimação na classe Animal.
• Prós: implicará em todas as classes herdarem o comportamento; possibilidade de
se utilizar as vantagens do polimorfismo.
• Contras: existem algumas subclasses (Hippo, Lion, Tiger e Wolf) que não
precisam desse comportamento, afinal dos animais presentes na hierarquia
apenas o cachorro e o gato têm o perfil de animais de estimação.

2ª Opção: implementar métodos abstratos que simulem os comportamentos de um


animal de estimação na classe Animal.

74
• Prós: implicará em todas as classes herdarem o comportamento; possibilidade de
se utilizar as vantagens do polimorfismo; cada subclasse poderia implementar
seu próprio comportamento de animal de estimação.
• Contras: como seriam métodos abstratos, toda classe concreta obrigatoriamente
deve implementá-lo e como citado anteriormente, existem algumas subclasses
(Hippo, Lion, Tiger e Wolf) que não precisam desse comportamento, afinal dos
animais presentes na hierarquia apenas o cachorro e o gato têm o perfil de
animais de estimação.

3ª Opção: implementar métodos que simulem os comportamentos de um animal de


estimação apenas nas classes Dog e Cat.
• Prós: comportamentos de animal de estimação somente onde necessário; cada
subclasse implementa o comportamento a sua maneira.
• Contras: pode ser que não haja uma padronização nos métodos que
implementem esses comportamentos, por exemplo, se as classes forem
implementadas por programadores diferentes; não possibilitaria o polimorfismo.

4ª Opção: implementar nova classe com comportamentos (métodos) de animais de


estimação, por exemplo, a classe Pet mostrada na Figura 20.
• Prós: garantia de que todos os animais de estimação terão as mesmas definições
de métodos; polimorfismo.
• Contras: existência de duas superclasses e Java não suporta herança múltipla.

Solução: utilizar interfaces.

75
Figura 20 - Nova Classe na Hierarquia: Pet

Uma interface possui sintaxe similar a uma classe, mas possui apenas as
especificações de suas funcionalidades e não como essas funcionalidades são
implementadas. Em Java, uma interface é uma classe abstrata em que todos os seus
métodos são abstratos e tem acesso público, já os seus atributos possuem níveis de
acessibilidade public, final e static (Os modificadores serão detalhados em uma seção
futura deste material. Brevemente, o modificador static aplicado a uma variável faz com
que ela seja associada à classe e não a instâncias da classe, ou seja, é uma variável
vista por todas as instâncias da classe onde foi definida).
Uma classe abstrata é herdada por subclasses a partir da palavra chave extends,
já uma interface é implementada por outras classes através da palavra chave
implements.
Utilizando-se do conceito de interfaces, o problema descrito anteriormente
poderia ser solucionado como mostrado pela Figura 21. Na Figura, existe uma interface
Pet que pode ser implementada por qualquer classe que necessite dos comportamentos
para animais de estimação. Por exemplo, as classes RoboDog, Cat e Dog implementam

76
esses comportamentos e também são subclasses das classes Robot, Feline e Canine,
respectivamente.

Figura 21 - Hierarquia com Interface

A definição da interface Pet encontra-se na Listagem 78. Dois métodos abstratos


que representam os comportamentos de um animal de estimação são definidos. Já a
Listagem 79 apresenta a implementação da interface Pet pela classe Dog. Observa-se
que os métodos da subclasse Dog devem ser implementados, assim como os métodos
definidos na interface Pet.

Listagem 78 – Interface: Pet


public interface Pet {
public abstract void beFriendly();
public abstract void play();
}

Listagem 79 – Implementação da Interface Pet


public class Dog extends Canine implements Pet {
public void beFriendly(){ ... }
public void play(){ ... }

77
public void roam(){ ... }
public void eat(){ ... }
}

As Listagem 80, Listagem 81 e Listagem 82 exibem um outro exemplo usando


interface. A interface ICMS é definida com uma variável alíquota que não pode ter seu
valor alterado e é associada à interface. Possui também um método abstrato meuICMS
que é implementado pelas classes que a implementem (classes Carro e Consultoria).

Listagem 80 – Interface: ICMS


public interface ICMS {
static final double aliquota = 0.9;
public abstract double meuICMS();
}

Listagem 81 – Implementação de Interface: classe Carro


class Carro implements ICMS{
String fabricante, modelo;
int ano, vel_max, peso;
float preco;
int num_rodas = 4;
int num_portas;

public double meuICMS(){


return 0.17 * preco;
}
}

Listagem 82 – Implementação de Interface: classe Consultoria


class Consultoria implements ICMS{
double valor;

public double meuICMS(){


return 0.1 * valor;
}
}

78
Resumindo, quando uma classe implementa uma interface, garante-se que todas
as funcionalidades especificadas pela interface serão oferecidas pela classe. Ou seja,
uma interface representa um comportamento que pode ser compartilhado por várias
classes que devem implementar seus métodos. Uma classe pode implementar várias
interfaces, podendo ser uma alternativa para herança múltipla.

18.1 Breve Comparação entre Classe Abstrata e Interface

A Tabela 13 mostra uma breve comparação entre classe abstrata e interface.

Tabela 13 - Comparação entre Classe Abstrata e Interface


Recurso Classe Abstrata Interface
Permite definir métodos abstratos (sem SIM SIM
implementação)
Permite mesclar métodos abstratos e comuns SIM NÃO
Permite herança múltipla NÃO SIM

19. Modificadores

Esta seção destina-se a explicar alguns modificadores da linguagem Java. Alguns


já foram brevemente explanados em seções anteriores.
Os modificadores de acesso são utilizados para especificar o nível de visibilidade
de uma variável, de um método ou de uma classe. Eles podem ser de quatro tipos:
private, public, protected e package.
Java possui outros tipos de modificadores, dentre eles: static, final, abstract,
transient e synchronized.

19.1 Private

Dentro todos os modificadores de acesso é o mais restritivo. Indica que uma


variável ou método não estão visíveis para nenhuma classe, apenas para aquela onde
aparecem. O modificador private pode ser aplicado a variáveis e métodos. Pode ser
utilizado também em classes internas (Classes declaradas dentro de outra classe.
Assunto abordado posteriormente).

79
19.2 Public

O modificador public é o mais abrangente de todos. Permite que a variável,


método ou classe seja visualidade de qualquer classe Java.

19.3 Protected

O modificador protected pode ser aplicado a variáveis e métodos. Faz com que
eles apenas possam ser acessados por classes pertencentes ao mesmo pacote ou por
subclasses da classe onde o elemento foi definido.

19.4 Package

O modificador package a variáveis, métodos ou classes. Não existe uma palavra


chave que determine que o elemento possuirá esse modificador, ou seja, o que o
identifica é a ausência dos modificadores private, public e protected. O acesso
package a algum elemento implica em ele ser visível somente por classes do mesmo
pacote da classe onde foi definido.

19.5 Static

Os elementos que possuem o modificador static são associados com a classe e


não com instâncias da classe. Esse modificador pode ser aplicado a variáveis, métodos
ou a uma porção de código que não está dentro de nenhum método (geralmente
referenciado como bloco estático).
As variáveis e métodos estáticos são compartilhados por todas as instâncias da
classe onde foi definida. Para existirem, não há necessidade de se instanciar nenhum
objeto da classe, apenas é necessário que a classe seja carregada pela Máquina Virtual
Java. O método principal de uma classe (main()) é um método estático e invocado sem
a existência de nenhuma instância.
Além de serem referenciados pelo nome do objeto, as variáveis e métodos
estáticos podem ser referenciados através do nome da classe. Essa última opção é a
mais indicada, permitindo a compreensão imediata de que a variável ou o método ou é
estático.
A Listagem 83 e a Listagem 84 exemplificam o uso do modificador static. A
Listagem 83 declara a classe ModificadorStatic contendo uma variável estática, um
método estático e um método comum. No método principal da classe UsoStatic
presente na Listagem 84, três objetos da primeira classe são criados. O método
incrementaVariavel é chamado para cada um desses objetos. Esse método faz com
que a variável estática variavel seja incrementada de uma unidade. No momento do

80
carregamento da classe ModificadorStatic, essa variável é criada e inicializada com o
valor zero e como tem acesso estático é uma variável da classe. Dessa forma, a última
linha do método principal imprime na tela o valor 3. Observa-se que para imprimir esse
valor, invoca-se o método getVariavel() da classe ModificadorStatic, que por ser um
método estático, pode ser invocado através do nome da classe, ou seja,
ModificadorStatic.getVariavel().

Listagem 83 – Exemplo com o modificador static


public class ModificadorStatic(){

//Declaração de variável estática


private static int variável;

public static int getVariavel(){


return variável;
}
public void incrementaVariavel(){
variável++;
}
}

Listagem 84 – Exemplo com o modificador static


public class UsoStatic(){

public static void main(String args[]){

ModificadorStatic obj1 = new ModificadorStatic();


ModificadorStatic obj2 = new ModificadorStatic();
ModificadorStatic obj3 = new ModificadorStatic();

obj1.incrementaVariavel();
obj2.incrementaVariavel();
obj3.incrementaVariavel();

System.out.println( ModificadorStatic.getVariavel() );
}
}

81
19.6 Final

Aplicado a variáveis, métodos e classes, o modificador final indica que o


elemento é imutável, ou seja, que não pode ser alterado.
Quando aplicado a uma variável, não permite que ela tenha o seu valor alterado,
ou seja, torna-a uma constante. Já os métodos final não podem ser redefinidos, ou seja,
um método de uma superclasse com esse modificador não pode ser redefinido nas suas
subclasses.
No caso de uma classe, faz com que ela não possa ser herdada. Dois dos motivos
existentes para que uma classe seja definida como imutável são: segurança (não
permitindo que exista uma subclasse com códigos indesejáveis que substitua a sua
classe no sistema) e design.

19.7 Abstract

O modificador abstract pode ser aplicado a classes e métodos. Uma classe


abstrata permite que alguns métodos só sejam implementados em suas subclasses, não
pode ser instanciada nem pode ter seus construtores invocados. Um método é definido
como abstrato quando ainda não se sabe como será sua implementação, mas é sabido
que ele será necessário.

19.8 Transient

O modificador transient é aplicado apenas para variáveis quando ela não é


armazenada como parte do estado de persistência do objeto.

19.9 Synchronized

O modificador syncronized é usado para sincronizar o acesso a códigos em


ambientes paralelos.

20. Redefinição de Métodos e Modificadores

Após a inclusão dos conceitos sobre modificadores, é importante ressaltar alguns


aspectos relacionados à redefinição de métodos.
O primeiro detalhe é que métodos estáticos não podem ser redefinidos. Afinal,
são métodos que pertencem à classe e não a instâncias de objetos, portanto serão
objetos da classe filha também.

82
O segundo detalhe é que o método sobrescrito deve não pode ter um nível de
acessibilidade menor do que o método original. Afinal, as subclasses são extensões da
superclasse e se tiver nível de acesso a métodos reduzido, seria uma redução e não uma
redução. Resumindo, as regras de utilização de modificadores de acessibilidade na
sobrescrita de métodos são:
• Um método private não pode ser sobrescrito;
• Um método default pode ser sobrescrito para default, protected e public;
• Um método protected pode ser sobrescrito para protected e public;
• Um método public pode ser sobrescrito somente para e public.

21. Wrappers de Tipos

Em Java, números podem ser representados por tipos primitivos ou por objetos.
Para representar uma variável de um tipo primitivo como um objeto, basta utilizar uma
instância de alguma das classes de Java chamadas de wrappers de tipos. Um objeto de
uma classe wrapper contém um único valor de um tipo primitivo da linguagem,
permitindo assim estabelecer uma ponte entre valores literais e objetos. A Tabela 14
apresenta os tipos primitivos com suas respectivas classes wrappers.

Tabela 14 – Classes Wrappers


Tipo Primitivo Wrapper
boolean Boolean
char Character
double Double
float Float
int Integer
long Long
byte Byte
short Short

As classes wrappers têm o papel de encapsular os tipos primitivos para a


possibilidade de operações como: conversões, mudança de bases decimais, e algumas
operações que somente a objetos é permitido, como por exemplo, trabalhar com
conjuntos. Além dos métodos para obter o valor associado ao objeto de cada uma dessas
classes, métodos auxiliares como a conversão de e para strings são suportados.
A Listagem 85 apresenta exemplos de criação de objetos do tipo wrapper.
Observa-se que os construtores dessas classes são sobrecarregados, aceitando o valor

83
do tipo primitivo ou Strings.

Listagem 85 – Exemplo de Criação de Variável do tipo Wrapper


Integer i1 = new Integer(25);
Integer i2 = new Integer(“25”);
Float f1 = new Float("10.0f");
Float f2 = new Float(10.0f);
Boolean b1 = new Boolean("TRUE");
Boolean b2 = new Boolean(true);
Boolean b2 = new Boolean(false);

Dentre os métodos pertencentes às classes wrappers, existem aqueles que


realizam conversões do objeto wrapper para o seu respectivo tipo primitivo.

Tabela 15 – Métodos de Conversão em Tipos Primitivos das Classes Wrappers


Wrapper Método
Boolean public boolean booleanValue();
Character public char charValue();
Double public double doubleValue();
Float public float floatValue();
Integer public int intValue();
Long public long longValue();
Byte public byte byteValue();
Short public short shortValue();

A Listagem 86 apresenta exemplos de conversões de objetos wrappers em tipos


primitivos.

Listagem 86 – Exemplo de Conversão de Wrappers em Tipos Primitivos


Integer i1 = new Integer(25);
int i = i1.intValue();

Float f1 = new Float("10.0f");


float f = f1.floatValue();

Boolean b1 = new Boolean(false);


boolean b = b1.booleanValue();

84
Long l1 = new Long(1234567);
long l = l1.longValue();

21.1 Autoboxing e Auto-Unboxing

Antes da versão do Java 5.0, por exemplo, se fosse desejado inserir um valor
primitivo em uma estrutura de dados que armazenasse somente Objects, deveria-se
criar um novo objeto da classe wrapper de tipo correspondente e depois inserir o objeto
na coleção. Da mesma forma, se um objeto da classe wrapper necessitasse ser
recuperado da coleção para que tivesse seu valor primitivo manipulado, o método que o
converte em tipo primitivo deveria ser invocado. Para exemplificar, considera-se o
exemplo presente na Listagem 87. Neste exemplo, um vetor do tipo Integer é criado
com três posições. Em seguida, atribui-se aos elementos do vetor objetos do tipo
Integer com valor 10. Por fim, recupera-se os valores primitivos do vetor utilizando o
método de conversão intValue().

Listagem 87 – Exemplo de Uso de Wrappers em Coleções


Integer[] vetorInt = new Integer[3];

vetorInt[0] = new Integer(1);


vetorInt[1] = new Integer(2);
vetorInt[2] = new Integer(3);

int valor1 = vetorInt[0].intValue();


int valor2 = vetorInt[1].intValue();
int valor3 = vetorInt[2].intValue();

Com o exemplo anterior, percebe-se que valores primitivos são utilizados para
iniciar objetos Integer. Dessa forma, consegue-se o pretendido, porém código extra é
digitado. Além disso, é necessário utilizar o método intValue() para manipular os
valores do vetor como valores de tipos primitivos.
A partir da versão do Java 5.0, essa conversão entre valores de tipos primitivos e
objetos wrappers é simplificada. Não há mais necessidade de nenhum código de
conversão entre eles. Surgiram, então, dois tipos de conversões: autoboxing e auto-
unboxing. Uma conversão boxing converte um valor de um tipo primitivo em um objeto
da classe wrapper do tipo correspondente. Já a conversão auto-unboxing converte um
objeto de uma classe wrapper em um valor do tipo primitivo correspondente. Essas duas
conversões são realizadas automaticamente no Java 5.0. Por exemplo, o código presente

85
na Listagem 88.

Listagem 88 – Exemplo de Uso de Conversões Boxing e Auto-Unboxing


Integer[] vetorInt = new Integer[3];

vetorInt[0] = 1;
vetorInt[1] = 2;
vetorInt[2] = 3;

int valor1 = vetorInt[0];


int valor2 = vetorInt[1];
int valor3 = vetorInt[2];

22. Constantes em Java

Em Java, as constantes são definidas a partir da utilização da palavra chave


final. Por Exemplo, a Listagem 89 apresenta a classe EstacaoAno de constantes, onde
quatro constantes (ESTACAO_VERAO, ESTACAO_OUTONO, ESTACAO_INVERNO e
ESTACAO_PRIMAVERA) são definidas. Cada constante dessa possui um valor e se
houver tentativa de se modificar esse valor, o código fonte não será compilado.

Listagem 89 – Exemplo de Classe de Constantes com palavra chave final


public class EstacaoAno{
public static final int ESTACAO_VERAO = 1;
public static final int ESTACAO_OUTONO = 2;
public static final int ESTACAO_INVERNO = 3;
public static final int ESTACAO_PRIMAVERA = 4;
}

Algumas desvantagens de se utilizar essa padronização para definir constantes


em Java são:
• Declarar constantes dessa maneira pode não ser seguro. Por exemplo, para a
classe de constantes presente na Listagem 89, uma estação do ano é apenas um
inteiro. Com essa abordagem, uma variável que deveria ser uma estação de ano
poderia receber um outro valor inteiro que não estivesse na classe de constantes,
ou até mesmo duas estações poderiam ser somadas (já que são do tipo int), o
que não faz sentido. Com isso, verificações desses erros não poderiam ser feitos
em tempo de compilação, mas somente em tempo de execução.

86
• Pode ser que haja necessidade de se concatenar Strings (ESTACAO_) para
definir os nomes das constantes de forma que não haja colisão com nomes de
constantes de outras classes.
• Se o valor de uma das constantes for mudado, todas as classes que utilizam
alguma das constantes devem ser recompiladas juntamente com a classe que
define as constantes. Isto se deve ao fato das constantes serem estáticas e terem
seus valores definidos no momento da criação da classe.
• A impressão do valor de uma constante pode não dizer nada se ela for definida
como um inteiro, pois apenas um valor do tipo int será impresso.

Esse problemas podem ser resolvidos através da utilização do padrão TypeSafe Enum
definido por Joshua Bloch, ex-funcionária do SUN. Não caberá a esse material explicar
esse padrão, mas adianta-se que ele trás outros problemas (Mais sobre esse padrão em:
http://java.sun.com/docs/books/effective/).
Com o Java 5.0, a linguagem Java passou a dar suporte a um tipo de enumerações,
assim como em C, C++. Surge, então, um tipo enum que define um conjunto de
constantes representadas como identificadores únicos. Como nas classes, os tipos enum
são tipos por referência, significando que um objeto do tipo enum pode ser
referenciado.
Um tipo enum é declarado com uma declaração enum, uma lista separada por
vírgula de constantes, A declaração também pode incluir outros componentes das
classes tradicionais como construtores, atributos e métodos. A Listagem 90 apresenta a
declaração de uma enumeração para as estações do ano.

Listagem 90 – Exemplo de Classe de Constantes com Tipo enum


public enum EstacaoAno{
PRIMAVERA,
VERAO,
OUTONO,
INVERNO
}

Declarar uma classe do tipo enum significa ter as seguintes restrições:

• Tipos enum são implicitamente final, pois declaram constantes que não podem
ser modificadas;
• Constantes enum são implicitamente static;
• Tentar criar um objeto do tipo enum usando o operador new implicará em erro

87
de compilação.

As constantes enum pode ser utilizadas em qualquer lugar em que constantes


podem ser utilizadas, como nos rótulos case das instruções switch, para controlar
instruções for aprimoradas, etc.
A Listagem 91 apresenta a declaração da classe Cor como do tipo enum. Em sua
declaração há duas partes: declaração das constantes e declaração de outros membros
(variáveis e métodos). Neste exemplo a classe enum possui cinco constantes, dois
atributos (nome e codigo), um construtor e dois métodos de acesso. O construtor
recebe dois argumentos, o nome da cor e o seu código.

Listagem 91 – Exemplo de Classe com Tipo enum


public enum Cor{
//Declaração de constantes enum
RED (“Vermelho”, “#FF0000”),
BLUE (“Azul”, “#0000FF”),
GREEN (“Verde”, “#00FF00”),
WHITE (“Branco”, “#000000”),
BLACK (“Preto”, “#FFFFFF”);

//Variáveis
private final String nome;
private final String codigo;

//Construtor
Cor( String nome, String codigo ){
this.nome = nome;
this.codigo = codigo;
}
//Retorna nome da cor
public String getNome(){
return nome;
}
//Retorna código da cor
public String getCodigo(){
return codigo;
}
}

88
A Listagem 92 apresenta um exemplo de uso da classe enum Cor. Para todo
enum, o compilador gera um método estático chamado values que retorna um vetor
das constantes na ordem em que foram declaradas. Neste exemplo, todos as constantes
são percorridas através do uso do método values e têm o identificador da constante, o
nome da cor e o código da cor escritos na tela através da chamada a cor,
cor.getNome(), cor.getCodigo(), respectivamente.
O segundo for exibe os dados das constantes de determinado intervalo através
do método estático range da classe EnumSet (java.util.EnumSet). Este método recebe
dois parâmetros: a primeira constante enum no intervalo e a última constante enum no
intervalo. A ordenação das constantes obedece à ordem em que foram declaradas na
enum. A Listagem 93 apresenta o resultado da execução do código presente na
Listagem 92.

Listagem 92 – Exemplo de Uso da Classe com Tipo enum


import java.util.EnumSet;
public class TesteEnumCor {
public static void main(String[] args) {

//Percorre todos as constantes


for (Cor cor: Cor.values() ){
System.out.printf("%s => %s - %s \n",
cor, cor.getNome(), cor.getCodigo());
}
System.out.println("");
//Percorre intervalo
for (Cor cor : EnumSet.range(Cor.RED, Cor.GREEN)){
System.out.printf("%s => %s - %s \n",
cor, cor.getNome(), cor.getCodigo());
}
}
}

Listagem 93 – Resultado da Execução do Código presente na Listagem 92


RED => Vermelho - #FF0000
BLUE => Azul - #0000FF
GREEN => Verde - #00FF00
WHITE => Branco - #000000
BLACK => Preto - #FFFFFF

89
RED => Vermelho - #FF0000
BLUE => Azul - #0000FF
GREEN => Verde - #00FF00

90
23. Referências Bibliográficas

[1] Head First Java. Kathy Sierra e Bert Bates.


[2] Gleydson de Azevedo Ferreira Lima. Material Didático. 2005.
[3] API Java disponível em http://java.sun.com
[4] Tutoriais disponíveis em http://www.j2eebrasil.com.br
[5] Java Como Programar. H. M. Deitel e P. J. Deitel
[6] Apostila “Aprendendo Java”. Hamilton Lima
[7] Programação Orientada a Objetos: Uma Abordagem com Java. Ivan Luiz Marques
Ricarte

91

Vous aimerez peut-être aussi