UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 70
3.7 TRABALHANDO COM CLASSES E OBJETOS
Origem: Capítulo 3 – Introdução a classes e Objetos. Livro: Java – Como Programar, Deitel & Deitel, 6ª. Edição; capítulo 4
– Orientação a Objetos Básica, capítulo 6 – Modificadores de Acesso e Atributos de Classe. Apostila FJ-11 (Java e
Orientação a Objetos)
Escrever um programa utilizando uma linguagem de programação orientada a objeto significa criar
um programa que será composto por uma série de objetos (identificados a partir do mundo
real), com propriedades, características e interações.
Um conjunto de objetos com
características similares é representado através de uma classe, e assim, a programação orientada
a objeto foca sua atenção na identificação das classes que irão compor um problema.
Qualquer característica, seja informacional ou comportamental, na programação orientada
a objeto deverá ser definida através de uma classe. Mesmo que o programa seja muito
pequeno e requeira apenas o método main(), ainda sim este deverá ser declarado através
de uma classe, como já comentado anteriormente.
3.7.1 UMA CLASSE EM JAVA
Considere um programa para um banco. Uma entidade extremamente importante para o sistema é
a conta. A idéia aqui é generalizar alguma informação, juntamente com funcionalidades que toda
conta deve ter.
O que toda conta tem de importante?
•
Número da conta;
•
Nome do cliente;
•
Saldo;
•
Limite.
O que toda conta faz de importante?
•
Sacar uma quantidade x;
•
Depositar uma quantidade x;
•
Imprimir o nome do dono da conta;
•
Devolver o saldo atual;
•
Transferir uma quantidade x entre uma conta e outra;
•
Devolver o tipo de conta.
Com isso, tem-se o projeto de uma conta bancária. Podemos pegar esse projeto e acessar seu
saldo? Não. O que temos ainda é o projeto (a classe). Antes, precisamos construir (instanciar)
uma conta, para poder acessar o que ela tem, e pedir a ela que faça alguma coisa.
Declaração básica de uma classe em Java
Considerando as propriedades que uma Conta deve ter (e não o que ela faz), teria-se o seguinte
código Java:
/* Arquivo: Conta.java
Contém o tratamento de uma Conta Bancária */
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 71
public class Conta
{
int numero;
String nome;
double saldo;
double limite;
// ..
}
Estes são os atributos que toda conta, quando criada, vai ter. Repare que essas variáveis foram
declaradas fora de um bloco de método, sendo diferente de variáveis locais a métodos. Quando
uma variável é declarada diretamente dentro do escopo da classe, é chamada de variável de
objeto (ou de instância), ou atributo.
A declaração de toda classe deve conter a palavra-chave class seguida pelo nome da classe. A
palavra-chave public é um modificador de acesso, que declara que o acesso à classe é público.
O corpo de cada classe estará entre os delimitadores “{“ e “}”
A declaração de todo atributo segue a sintaxe de declaração de variáveis, visto os atributos serem
as variáveis de instância de um objeto.
3.7.2 CRIANDO E USANDO UM OBJETO
Criar uma classe é apenas declarar as definições de suas características. Para utilizar essas
características é preciso instanciar um objeto.
Uma vez que se criou uma classe, ela poderá ser utilizada em outras classes, através de sua
instanciação. Cada objeto criado terá seu estado e comportamento próprios, além da identidade
única, e essas serão justamente as características que poderão ser acessadas pelos objetosclientes (de acordo com a especificação de acesso da característica, claro).
Para criar (construir, instanciar) uma Conta, basta usar a palavra-chave new. Utiliza-se também
os parênteses, que serão explicados em item posterior:
/* Arquivo: Programa.java
Programa responsável por utilizar a classe Conta */
public class Programa
{
public static void main(String[] args) {
new Conta();
}
}
O código acima é conhecido como Expressão de criação de instância de classe (formado pela
palavra-chave new e o nome da classe seguido por parênteses – disparando um construtor que
será posteriormente detalhado).
O código acima cria um objeto do tipo Conta, mas como acessar esse objeto que foi criado? É
preciso ter alguma forma de nos referenciarmos a esse objeto, o que é feito através de uma
variável. O código acima deve ser substituído por:
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 72
/* Arquivo: Programa.java
Programa responsável por utilizar a classe Conta */
public class Programa
{
public static void main(String[] args)
{
Conta minhaConta;
minhaConta = new Conta();
}
}
Uma classe, após ser criada em Java, pode ser considerada como um tipo definido pelo usuário, e
portanto, podemos ter uma variável declarada como sendo desse tipo.
Pode parecer estranho escrevermos duas vezes Conta: uma vez na declaração da variável e outra
vez no uso do new. As duas instruções acima descritas (declaração de uma variável do tipo da
classe e instanciação dessa variável) podem ser disponibilizadas em uma única instrução, como
no exemplo abaixo:
Conta minhaConta =
new Conta();
//forma mais comum de declaração
Através da variável minhaConta, agora é possível acessar o objeto recém criado para alterar seu
nome, seu saldo, etc:
/* Arquivo: Programa.java
Programa responsável por utilizar a classe Conta */
public class Programa
{
public static void main(String[] args)
{
Conta minhaConta =
new Conta();
/* Alteração de valores de atributos do objeto minhaConta */
minhaConta.nome = "Duke";
minhaConta.saldo = 1000.0;
System.out.println("Saldo atual: " + minhaConta.saldo);
}
}
É importante destacar que o acesso aos atributos/métodos de um objeto ocorre através da
notação de ponto, com o nome do objeto à esquerda do ponto e o atributo/método à direita.
OBSERVAÇÃO:
(1) Apesar do acesso direto aos valores de um objeto ser possível, segundo os princípios da
orientação a objeto (encapsulamento) ele deve ser evitado. Um objeto-cliente não deve
acessar diretamente os atributos de um outro objeto. Para tal acesso / atualização devem
ser criados métodos getters e setters (e habitualmente tem-se um método get() e outro
set() para cada atributo da classe que precise ser manipulado).
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 73
(2) É comum criar uma classe separada para conter o método main() e fazer a chamada do(s)
método(s) que irão efetivamente realizar a execução do programa, similar ao que ocorre no
exemplo acima com as classes Conta e Programa.
3.7.3 MÉTODOS
Dentro da classe, também deve ser declarado o que cada conta faz e como isto é feito, ou seja, o
comportamento que cada classe tem. Por exemplo, de que maneira uma conta saca dinheiro?
Isso é especificado dentro da própria classe Conta, e não em um local desatrelado das
informações da própria conta. É por isso que essas “funções” são chamadas de métodos. Pois é
a maneira de fazer uma operação com um objeto.
Assim, qualquer tarefa que precise ser realizada em um programa Java deve ser declarada
através de um método, que irá conter os mecanismos necessários para a execução da tarefa.
Uma declaração de método é composta por:
•
Cabeçalho / Assinatura – formado por especificador de acesso (opcional) + tipo de
dados de retorno (void para nenhum retorno) + nome do método + parênteses (e
dentro deles a lista de parâmetros - a declaração das variáveis locais que são
parâmetros de entrada para o método em questão. Parênteses vazios indicam que não há
parâmetro de entrada);
•
Corpo – contém o código-fonte a ser executado, delimitado entre chaves. Em caso de
método com retorno indicado no cabeçalho em algum momento antes do delimitador final
deve estar presente a palavra-chave return, seguido do retorno esperado.
Por exemplo: Para criar um método que saca uma determinada quantidade e não devolve
nenhuma informação para quem acionar esse método tem-se o seguinte código.
/* Arquivo: Conta.java
Contém o tratamento de uma Conta Bancária */
public class Conta
{
//Declaração de variáveis de instância
int numero;
String nome;
double saldo;
double limite;
//Declaração de método
void sacar (double quantidade)
{
double novoSaldo = this.saldo - quantidade;
this.saldo = novoSaldo;
}
}
A palavra-chave void indica que não há nenhum retorno para o método, ou seja, quando você
pedir para a conta sacar uma quantia, nenhuma informação será enviada de volta a quem pediu.
Quando alguém pedir para sacar, ele também vai dizer quanto quer sacar. Por isso precisamos
declarar o método com tal informação de entrada - um argumento do método (ou parâmetro).
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 74
Essa variável é uma variável comum, chamada também de temporária ou local, pois, ao final da
execução desse método, ela deixa de existir.
Dentro do método, é declarada uma nova variável (novoSaldo). Essa variável, assim como o
argumento, vai morrer no fim do método, pois este é seu escopo. Para diferenciar o uso de
variáveis locais ao método e variáveis de instância da classe é utilizada a palavra-chave this,
para se referenciar a um valor do objeto (como é feito no exemplo acima para this.saldo).
Da mesma forma, temos o método para depositar alguma quantia:
class
... {
// ... outros atributos e métodos ...
void depositar (double quantidade)
{
this.saldo += quantidade;
}
}
Observe que, agora, não foi usada uma variável auxiliar e, além disso, usou-se o comando
reduzido += para deixar o método bem simples. O += soma quantidade ao valor antigo de
saldo e guarda no próprio saldo o valor resultante.
Para mandar uma mensagem ao objeto e solicitar que ele execute um método, também é usada a
notação de ponto. O termo usado para isso é invocação (chamada) de método.
O método main() é o único método executado automaticamente pela Java Virtual Machine.
Qualquer outro método para ser executado deverá ser invocado por algum outro método.
O código a seguir saca dinheiro e depois deposita outra quantia na conta exemplo que estamos
trabalhando.
/* Arquivo: SacaEDeposita.java
Classe responsável por realizar saques e depósitos em uma conta */
public class SacaEDeposita
{
public static void main(String[] args)
{
// criando a conta
Conta minhaConta = new Conta();
// alterando os valores de minhaConta
minhaConta.nome = "Duke";
minhaConta.saldo = 1000;
// saca 200 reais
minhaConta.sacar(200);
// deposita 500 reais
minhaConta.depositar(500);
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 75
// exibe saldo da conta
System.out.println(minhaConta.saldo);
}
}
Uma vez que seu saldo inicial é 1000 reais, se sacarmos 200 reais, depositarmos 500 reais e
imprimirmos o valor do saldo, o que será impresso?
OBSERVAÇÃO:
•
A chamada de um método deve estar sempre associada aos parênteses ao final do nome
do método, mesmo que o método em questão não contenha argumentos de entrada;
•
Observar no exemplo acima que a chamada dos métodos sacar() e depositar() são
realizadas com o informe de um argumento cada, que será o valor passado para o
método, já que ambos requerem parâmetros de entrada em suas execuções;
•
Um método pode especificar múltiplos parâmetros, separando cada um deles do próximo
com uma vírgula. O número de argumentos em uma chamada de método deve
corresponder ao número de parâmetros na lista de parâmetros da declaração do método
chamado. Além disso, os tipos de dados dos argumentos na chamada de método devem
ser consistentes com os tipos dos parâmetros correspondentes na declaração do método.
Não precisa haver correspondência de nome entre o argumento passado para o método e
o parâmetro no método declarado.
3.7.4 MÉTODOS COM RETORNO
Um método sempre tem que retornar alguma coisa, nem que essa coisa seja nada, como nos
exemplos anteriores onde é usada a palavra-chave void.
Um método pode retornar um valor para o cliente que o chamou. No caso do exemplo do método
sacar() pode-se devolver um valor booleano indicando se a operação foi bem sucedida.
/* Arquivo: Conta.java
Contém o tratamento de uma Conta Bancária – atualização */
public class Conta {
//Declaração de variáveis de instância
int numero;
String nome;
double saldo;
double limite;
//Método para realização da operação de saque, com retorno
boolean sacar (double valor) {
if (this.saldo < valor) {
return false;
}
else {
this.saldo = this.saldo - valor;
return true;
}
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 76
}
//Método para realização da operação de depósito, sem retorno
void depositar (double quantidade) {
this.saldo += quantidade;
}
}
Agora a declaração do método mudou. O método sacar() não tem void na frente, isto quer
dizer que, quando é acessado, ele devolve algum tipo de informação. No caso, um boolean. A
palavra-chave return indica que o método vai terminar ali, retornando tal informação.
Exemplo de uso:
minhaConta.saldo = 1000;
boolean consegui = minhaConta.sacar(2000);
if(consegui) {
System.out.println("Consegui sacar");
} else {
System.out.println("Não consegui sacar");
}
Ou então, posso eliminar a variável temporária, se desejado:
minhaConta.saldo = 1000;
System.out.println(minhaConta.sacar(2000));
Mais adiante, veremos que algumas vezes é mais interessante lançar uma exceção (exception)
nesses casos.
Trabalhando com mais de um objeto
O programa pode manter na memória não apenas uma conta, porém várias. Essa possibilidade é
demonstrada no exemplo abaixo.
/* Arquivo: TestaDuasContas.java
Classe que trabalha com dois objetos da classe conta */
public class TestaDuasContas {
public static void main(String[] args) {
Conta minhaConta =
new Conta();
minhaConta.saldo = 1000;
System.out.println(“Saldo da 1ª. Conta: ” + minhaConta.saldo);
Conta meuSonho = new Conta();
meuSonho.saldo = 1500000;
System.out.println(“Saldo da 2ª. Conta: ” + meuSonho.saldo);
}
}
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 77
3.7.5 VARIÁVEIS POR REFERÊNCIA
Quando declaramos uma variável para associar a um objeto, na verdade, essa variável não
guarda uma cópia do objeto (como ocorre com tipos de dados primitivos), e sim uma maneira de
acessá-lo, chamada de referência, que é uma referência a memória onde o objeto está
armazenado. É por esse motivo que é necessário o uso do comando new após a declaração da
variável.
Seja o exemplo parcial de código:
public static void main(String args[]) {
Conta c1 = new Conta();
Conta c2 = new Conta();
}
O correto aqui é dizer que c1 se refere a um objeto. Não é correto dizer que c1 é um objeto, pois
c1 é uma variável por referência, apesar de, depois de um tempo, os programadores Java falarem
“Tenho um objeto c1 do tipo Conta”, mas apenas para encurtar a frase “Tenho uma referência c1 a
um objeto do tipo Conta”.
Esse código nos deixa na seguinte situação:
Internamente, c1 e c2 vão guardar um número que identifica em que posição da memória as
contas instanciadas se encontram. Dessa maneira, ao utilizarmos o “.” para navegar, o Java vai
acessar a conta que se encontra naquela posição de memória, e não uma outra. Para quem
conhece, é parecido com um ponteiro, porém você não pode manipulá-lo e utilizá-lo para guardar
outras coisas.
Agora vamos a um outro exemplo.
/* Arquivo: TestaReferencias.java
Aplicativo responsável por testar declaração de variável por
referência */
public class TestaReferencias {
public static void main(String args[]) {
Conta c1 = new Conta();
c1.depositar(100);
Conta c2 = c1; // linha importante!
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 78
c2.depositar(200);
System.out.println(c1.saldo);
System.out.println(c2.saldo);
}
}
Qual é o resultado do código acima? O que aparece ao rodar?
O que acontece aqui? O operador de atribuição (“=”) copia o valor de uma variável. Mas qual é o
valor da variável c1? É o objeto? Não. Na verdade, o valor guardado é a referência (endereço) de
onde o objeto se encontra na memória principal.
Na memória, o que acontece nesse caso é descrito pela figura abaixo.
Quando fizemos c2 = c1, c2 passa a fazer referência para o mesmo objeto que c1 referencia
nesse instante.
Então, nesse código em específico, quando utilizamos c1 ou c2 estamos nos referindo
exatamente ao mesmo objeto! Elas são duas referências distintas, porém apontam para o mesmo
objeto! Compará-las com “==” irá nos retornar true, pois o valor que elas carregam é o mesmo!
Outra forma de perceber, é que executamos apenas um new, então só pode haver um objeto
Conta na memória.
Atenção: não estamos discutindo aqui a utilidade de fazer uma referência apontar pro mesmo
objeto que outra. Essa utilidade ficará mais clara quando passarmos variáveis do tipo referência
como argumento para métodos.
new
O que exatamente faz o new?
O new executa uma série de tarefas, que veremos mais adiante.
Mas, para melhor entender as referências no Java, saiba que o new, depois de alocar a memória
para esse objeto, devolve uma “flecha”, isto é, um valor de referência. Quando você atribui isso a
uma variável, essa variável passa a se referir para esse mesmo objeto.
Podemos então ver outra situação no código a seguir.
/* Arquivo: TestaReferencias2.java
Aplicativo responsável por testar declaração de variável por
referência */
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 79
public class TestaReferencias2 {
public static void main(String args[]) {
Conta c1 = new Conta();
c1.nome = "Duke";
c1.saldo = 227;
Conta c2 = new Conta();
c2.nome = "Duke";
c2.saldo = 227;
if (c1 == c2) {
System.out.println("Contas iguais");
} else {
System.out.println("Contas diferentes");
}
}
}
O operador == compara o conteúdo das variáveis, mas essas variáveis não guardam o objeto, e
sim o endereço em que ele se encontra. Como em cada uma dessas variáveis guardamos duas
contas criadas diferentemente, eles estão em espaços diferentes da memória, o que faz o teste no
if retornar false. As contas podem ser equivalentes no nosso critério de igualdade, porém elas
não são o mesmo objeto. Quando se trata de objetos, pode ficar mais fácil pensar que o ==
compara se os objetos (referências, na verdade) são o mesmo, e não se são iguais em termos de
estado.
Para saber se dois objetos têm o mesmo conteúdo, você precisa comparar atributo por atributo.
Veremos uma solução mais elegante para isso também.
Mais um exemplo:
E se quisermos ter um método que transfere dinheiro entre duas contas? Podemos ficar tentados
a criar um método que recebe dois parâmetros: conta1 e conta2 do tipo Conta. Mas cuidado:
assim estamos pensando de maneira procedural.
A idéia é que, quando chamarmos o método transferir(), já teremos um objeto do tipo Conta
(o this), portanto o método recebe apenas um parâmetro do tipo Conta, a conta destino (além
do valor). Seja o código abaixo:
/* Arquivo: Conta.java
Tratamento de uma conta – acréscimo de código */
public class Conta {
// atributos e métodos...
//Método que realiza transferência entre uma conta origem e
//outra destino
void transferir(Conta destino, double valor) {
this.saldo = this.saldo - valor;
destino.saldo = destino.saldo + valor;
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 80
}
}
Para deixar o código mais robusto, poderíamos verificar se a conta possui a quantidade a ser
transferida disponível. Para ficar ainda mais interessante, pode-se chamar os métodos
depositar() e sacar() já existentes para fazer essa tarefa. Um possível código está a seguir
transcrito.
/* Arquivo: Conta.java
Tratamento de uma conta – acréscimo de código */
public class Conta {
// atributos e métodos...
boolean transferir(Conta destino, double valor) {
boolean retirou = this.sacar(valor);
if (retirou == false) {
// não deu pra sacar!
return false;
}
else {
destino.depositar(valor);
return true;
}
}
}
Quando passamos uma conta como argumento, o que será que acontece na memória? Será que
o objeto é clonado?
No Java, a passagem de parâmetro funciona como uma simples atribuição, como no uso do “=”.
Então, esse parâmetro vai copiar o valor da variável do tipo Conta que for passado como
argumento. E qual é o valor de uma variável dessas? Seu valor é um endereço, uma referência,
nunca um objeto. Por isso não há cópia de objetos aqui.
Esse último código poderia ser escrito com uma sintaxe muito mais clara. Como? Perceber que o
nome deste método poderia ser transferirPara() ao invés de só transferir(). A chamada
do método fica muito mais natural, pois é possível ler a frase em português que ela tem um
sentido.
conta1.transferirPara(conta2, 50);
A leitura deste código seria “conta 1 transfere para conta 2 R$50”.
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 81
3.7.6 ATRIBUTOS
Os atributos ou variáveis de instância declaradas em uma classe têm valores exclusivos para cada
objeto da classe, e portanto, havendo mais de um objeto da mesma classe declarados em um
mesmo método, esses terão seus valores tratados completamente em separado.
Ao contrário da maior parte das classes e dos métodos, os atributos costumam ser declarados
com o modificador de acesso private, indicando que tais variáveis são acessíveis apenas dentro
da própria classe na qual estão declaradas. Essa especificação é seguida para preservar o
princípio de encapsulamento da orientação a objetos.
Normalmente as variáveis de instância são declaradas logo após a declaração do cabeçalho da
classe, como forma de ficarem mais legíveis e fáceis de serem localizadas para serem utilizadas
nos métodos das classes. No entanto suas declarações podem ficar em qualquer ponto de uma
classe, desde que estejam fora da declaração de um método. Lembrando que se estiverem
dentro da declaração de um método serão consideradas variáveis locais ao método, e não
variáveis de instância.
As variáveis do tipo atributo, diferentemente das variáveis temporárias (declaradas dentro de um
método), recebem um valor padrão. No caso numérico valem 0, no caso de boolean valem
false, no caso de classes valem null. Também é possível estabelecer valores default, como
segue no exemplo:
public class Conta {
//Declaração de atributos
private int numero = 1234;
private String nome = "Duke";
private String cpf = "123.456.789-10";
private double saldo = 1000;
private double limite = 1000;
/* Declaração de método set para manipulação do atributo numero
Recebe um Parâmetro de entrada cujo valor será repassado
para o atributo */
public void setNumero (int numero) {
this.numero = numero; //this.numero se refere à variável de
//instância. numero se refere ao
//parâmetro de entrada
}
/* Declaração de método get para manipulação do atributo numero
retorna o valor do atributo, para o método que o houver
chamado */
public int getNumero() {
return this.numero;
}
}
Nesse caso, quando você criar uma conta, seus atributos já estão “populados” com esses valores
colocados. Essa solução não é a mais comum para inicialização de atributos, normalmente
trabalha-se com construtores (que serão posteriormente detalhados).
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 82
Imagine que, agora, começamos a aumentar nossa classe Conta e queremos adicionar
sobrenome e cpf do cliente dono da conta. Começaríamos a ter muitos atributos relacionados ao
cliente e, se você pensar direito, uma conta não tem nome, nem sobrenome, nem cpf, quem tem
esses atributos é um cliente. Então podemos criar uma nova classe e fazer uma composição. Os
atributos também podem ser referências para outras classes. Suponha a seguinte classe
Cliente, e uma declaração alterada da classe Conta:
class Cliente {
String nome;
String sobrenome;
String cpf;
}
class Conta {
int numero;
double saldo;
double limite;
Cliente titular;
// ..
}
E dentro do main() da classe de teste, tem-se:
class Teste {
public static void main(String[] args) {
Conta minhaConta = new Conta();
Cliente c = new Cliente();
minhaConta.titular = c;
// ...
}
}
Aqui, simplesmente houve uma atribuição. O valor da variável c é copiado para o atributo titular do
objeto ao qual minhaConta se refere. Em outras palavras, minhaConta agora tem uma
referência ao mesmo cliente ao qual c se refere, e pode ser acessado através de
minhaConta.titular.
Você pode realmente navegar sobre toda essa estrutura de informação, sempre usando a notação
de ponto:
Cliente clienteDaMinhaConta = minhaConta.titular;
clienteDaMinhaConta.nome = "Duke";
Ou ainda, pode fazer isso de uma forma mais direta e até mais elegante:
minhaConta.titular.nome = "Duke";
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 83
Um sistema orientado a objetos é um grande conjunto de classes que vai se comunicar,
delegando responsabilidades para quem for mais apto a realizar determinada tarefa. A classe
Banco usa a classe Conta que usa a classe Cliente, que usa a classe Endereco. Dizemos que
esses objetos colaboram, trocando mensagens entre si. Por isso acabamos tendo muitas
classes em nosso sistema, e elas costumam ter um tamanho relativamente curto.
Mas, e se dentro do meu código eu não desse new em Cliente e tentasse acessá-lo
diretamente?
public class Teste {
public static void main(String[] args) {
Conta minhaConta = new Conta();
minhaConta.titular.nome = "paulo";
// ...
}
}
Quando damos new em um objeto, ele o inicializa com seus valores default, 0 para números,
false para boolean e null para referências. null é uma palavra-chave em Java, que indica
uma referência para nenhum objeto.
Se, em algum caso, você tentar acessar um atributo ou método de alguém que está se
referenciando para null, você receberá um erro durante a execução (NullPointerException,
que veremos mais à frente). Dá para perceber, então, que o new não traz um efeito cascata, a
menos que você dê um valor default (ou use construtores, que também veremos mais a frente).
Uma forma de resolver a questão comentada poderia ser como no código seguinte.
class Conta {
int numero;
double saldo;
double limite;
Cliente titular = new Cliente(); // quando chamarem new Conta,
//haverá um new Cliente para ele.
}
Com esse código, toda nova conta criada já terá um novo cliente associado, sem necessidade de
instanciá-lo logo em seguida da instanciação de uma conta. Qual alternativa você deve usar?
Depende do caso: para toda nova conta você precisa de um novo cliente? É essa pergunta que
deve ser respondida.
Nesse nosso caso a resposta é não, mas depende do nosso problema.
Atenção: para quem não está acostumado com referências, pode ser bastante confuso pensar
sempre em como os objetos estão na memória para poder tirar as conclusões do que ocorrerá ao
executar determinado código, por mais simples que ele seja. Com o tempo, você adquire a
habilidade de rapidamente saber o efeito de atrelar as referências, sem ter de gastar muito tempo
para isso. É importante, nesse começo, você estar sempre pensando no estado da memória. E
realmente lembrar que, no Java “uma variável nunca carrega um objeto, e sim uma referência para
ele” facilita muito.
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 84
3.7.7 UM NOVO EXEMPLO
Além do exemplo do Banco criado anteriormente, vamos ver como ficariam certas classes
relacionadas a uma fábrica de carros.
Se uma classe Carro, com certos atributos, que descrevem suas características, e com certos
métodos, que descrevem seu comportamento.
/* Arquivo: Carro.java
Classe com o tratamento de um carro */
//classe Motor – componente de um carro
class Motor {
int potencia;
String tipo;
//inicialização das características de um motor
Motor() {
this.potencia = 10;
this.tipo = “Flex”;
}
}
//Classe carro, com as características de um carro
public class Carro {
String cor;
String modelo;
double velocidadeAtual;
double velocidadeMaxima;
Motor motor;
//liga o carro
void ligar() {
System.out.println("O carro está ligado");
}
//acelera uma certa quantidade
void acelerar(double quantidade) {
double velocidadeNova = this.velocidadeAtual + quantidade;
this.velocidadeAtual = velocidadeNova;
}
//devolve a marcha do carro
int pegarMarcha() {
if (this.velocidadeAtual < 0) {
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 85
return -1;
}
if (this.velocidadeAtual >= 0 && this.velocidadeAtual < 40) {
return 1;
}
if (this.velocidadeAtual >= 40 && this.velocidadeAtual < 80){
return 2;
}
return 3;
}
}
Agora, vamos testar nosso Carro em um programa de testes.
/* Arquivo: TestaCarro.java
Classe com o tratamento para teste de um carro */
import java.util.Scanner;
public class TestaCarro {
public static void main(String[] args) {
Carro meuCarro = new Carro();
meuCarro.motor = new Motor();
Scanner input = new Scanner(System.in);
System.out.print(“Informe a cor do carro: ”);
meuCarro.cor = input.nextLine(); //método que lê uma linha de texto
System.out.print(“Informe o modelo do carro: ”);
meuCarro.modelo = input.nextLine();
System.out.print(“Informe a velocidade atual do carro: ”);
meuCarro.velocidadeAtual = input.nextDouble(); //método para
//leitura de
//valor double
System.out.print(“Informe a velocidade máximo do carro: ”);
meuCarro.velocidadeMaxima = input.nextDouble();
// liga o carro
meuCarro.ligar();
// acelera o carro
meuCarro.acelerar(20);
System.out.println(meuCarro.velocidadeAtual);
}
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 86
}
OBSERVAÇÃO:
(1) Da classe Scanner já foram estudados os seguintes métodos:
•
nextInt() – leitura de um valor numérico inteiro;
•
nextDouble() – leitura de um valor numérico em ponto flutuante;
•
nextLine() – leitura de uma linha de caracteres (do início da linha ao fim, quando se
encontra um caracter de nova linha, que não é incluído na string que recebe o valor lido) .
Um outro método é o seguinte:
•
next() – leitura de uma palavra até que se encontre um espaço em branco (espaço,
tabulação ou nova linha). Se naquela linha ainda sobrar outros textos esses poderão ser
acessados com a chamada de novos métodos.
Discussão sobre o comando nextLine() e problemas com sua saída -> O uso do
nextLine() lê o caracter de pula linha, enquanto os outros métodos de leitura de Scanner não.
Recomenda-se o uso de um nextLine() após cada next() lido só para garantir que o caracter
de pula linha será lido. O problema só ocorre se após algum next() tentar-se usar o
nextLine(), pois este irá reconhecer o caracter de pula linha e assumir que o necessário já foi
lido.
(2) Sobre a declaração de importação de classe
No exemplo acima há uma instrução de importação de classe (a Scanner, pertencente ao pacote
java.util). Essa declaração indica ao compilador que o programa utiliza a classe Scanner. A
dúvida é: Por que precisa dessa instrução para Scanner, mas não para System, String ou
Carro? A maioria das classes utilizadas em programas Java precisa ser importada. As classes
System e String pertencem ao pacote java.lang, que é implicitamente importado em todo
programa Java, daí não ser necessária uma importação explícita (como no caso de Scanner).
Há um relacionamento especial entre as classes que são compiladas no mesmo diretório no disco,
como as classes Carro e TestaCarro. Por padrão, considera-se que essas classes estão no
mesmo pacote (conhecido como pacote padrão). As classes no mesmo pacote também são
importadas implicitamente nos arquivos de código-fonte de outras classes no mesmo pacote, sem
requerer a importação explícita.
Uma outra forma de declaração de importação de classe pode ser utilizada. Ela é conhecida como
nome de classe completamente qualificado. Se o nome da classe for completamente utilizado,
a declaração de importação, através da palavra-chave import não é necessária. Por exemplo, a
declaração de importação da classe Scanner poderia ser feita da seguinte maneira, reunida com
a declaração de criação:
java.util.Scanner input = new java.util.Scanner(System.in);
3.7.8 O CONSTRUTOR DE UMA CLASSE
O que devemos fazer quando queremos inicializar as variáveis de instância de um objeto no
momento de sua instanciação? A resposta a essa pergunta está no Construtor da Classe. E o
Java requer a chamada de um construtor para cada objeto que for criado.
Quando usamos a palavra chave new, estamos construindo um objeto. Sempre quando o new é
chamado, ele executa o construtor da classe. O construtor da classe é um bloco declarado com
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 87
o mesmo nome da classe, seguido por parênteses (que pode, ou não, conter argumentos – como
ocorre com um método comum):
class Conta {
int numero;
Cliente titular;
double saldo;
double limite;
// construtor
Conta() {
System.out.println("Construindo uma conta.");
}
// ..
}
Então, quando fizermos:
Conta c = new Conta();
A mensagem “Construindo uma conta” aparecerá. É como uma rotina de inicialização que é
chamada sempre que um novo objeto é criado. Um construtor pode parecer, mas não é um
método.
O construtor default
Até agora, as nossas classes não possuíam nenhum construtor. Então como é que era possível
dar new, se todo new chama um construtor obrigatoriamente?
Quando você não declara nenhum construtor na sua classe, o Java cria um para você. Esse
construtor é o construtor default, ele não recebe nenhum argumento e o corpo dele é vazio.
A partir do momento que você declara um construtor, o construtor default não é mais fornecido.
O interessante é que um construtor pode receber um argumento, podendo assim inicializar algum
tipo de informação, e não da forma fixa que ocorreria com a inicialização da variável no momento
de sua declaração. Pode-se perceber tal situação pelo exemplo abaixo.
class Conta {
int numero;
Cliente titular;
double saldo, limite;
// construtor
Conta (Cliente titular){
this.titular = titular;
}
// ..
}
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 88
Esse construtor recebe o titular da conta. Assim, quando criarmos uma conta, ela já terá um
determinado titular. O trecho de código acima pode ser complementado por:
Cliente carlos = new Cliente();
carlos.setNome("Carlos");
Conta c = new Conta(carlos);
System.out.println(c.getTitular().setNome());
A necessidade de um construtor
Tudo estava funcionando até agora. Para que utilizamos um construtor?
A idéia é bem simples. Se toda conta precisa de um titular, como obrigar todos os objetos que
forem criados a ter um valor desse tipo? Basta criar um único construtor que recebe essa entrada!
O construtor se resume a isso! Dar possibilidades ou obrigar o usuário de uma classe a passar
argumentos para o objeto durante o processo de criação do mesmo.
Por exemplo, não podemos abrir um arquivo para leitura sem dizer qual é o nome do arquivo que
desejamos ler! Portanto, nada mais natural que passar uma String representando o nome de um
arquivo na hora de criar um objeto do tipo de leitura de arquivo, e que isso seja obrigatório.
Você pode ter mais de um construtor na sua classe e, no momento do new, o construtor
apropriado será escolhido. Os diferentes construtores são diferenciados pela sua assinatura.
Como nesse caso o nome é o mesmo, será a lista de parâmetros que irá indicar o construtor
adequado a ser disparado.
Construtor: um método especial?
Um construtor não é um método. Algumas pessoas o chamam de um método especial, mas
definitivamente não é, já que não possui retorno e só é chamado durante a construção do
objeto.
Chamando outro construtor
Um construtor só pode rodar durante a construção do objeto, isto é, você nunca conseguirá
chamar o construtor em um objeto já construído. Porém, durante a construção de um objeto, você
pode fazer com que um construtor chame outro, para não ter de ficar copiando e colando. Isso
pode ser observado no exemplo a seguir.
class Conta {
private int numero;
private Cliente titular;
private double saldo;
private double limite;
// construtores
public Conta (Cliente titular) {
// faz mais uma série de inicializações e configurações
this.titular = titular;
}
public Conta (int numero, Cliente titular) {
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 89
this(titular); // chama o construtor que foi declarado acima
//Precisa ser a 1ª. Instrução do código
this.numero = numero;
}
//..
}
Existe um outro motivo, o outro lado dos construtores: facilidade. Às vezes, criamos um construtor
que recebe diversos argumentos para não obrigar o usuário de uma classe a chamar diversos
métodos do tipo ’set’.
No nosso exemplo do Banco, podemos forçar que a classe Cliente receba no mínimo o CPF,
dessa maneira um cliente já será construído com um CPF válido.
OBSERVAÇÃO: Normalmente os construtores são declarados públicos, isto é, contém a palavrachave public em sua declaração.
Java Bean
Quando criamos uma classe com todos os atributos privados, seus getters e setters e um
construtor vazio (padrão), na verdade estamos criando um Java Bean (mas não confunda com
EJB, que é Enterprise Java Beans – componentes utilizados em servidores).
Um Java Bean é um componente de software reutilizável, seguindo algumas convenções de
nomeclatura de métodos, construtores e comportamento.
Para saber mais acesse: http://java.sun.com/products/javabeans/
3.7.9 LEITURA RECOMENDADA
•
Material indicado no início do tópico, como origem do texto disponibilizado;
•
Fóruns na Internet.
3.7.10
EXERCÍCIOS
1. O objetivo dos exercícios a seguir é fixar o conceito de classes e objetos, métodos e
atributos. Dada a estrutura de uma classe, basta traduzí-la para a linguagem Java e fazer
uso de um objeto da mesma em um programa simples.
a) Programa 1
Classe: Pessoa
Atributos: nome, idade.
Método: void fazerAniversario()
Crie uma pessoa, coloque seu nome e idade iniciais, faça alguns aniversários
(aumentando a idade) e imprima seu nome e sua idade.
b) Programa 2
Classe: Porta
Atributos: aberta, cor, dimensaoX, dimensaoY, dimensaoZ
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 90
Métodos: void abrir(), void fechar(),
void pintar(String s), boolean checarEstaAberta()
Crie uma porta, abra e feche a mesma, pinte-a de diversas cores, altere suas
dimensões e use o método ChecarEstaAberta() para verificar se ela está aberta.
3) Programa 3
Classe: Casa
Atributos: cor, porta1, porta2, porta3
Método: void pintar(String s), int verficarPortasEstaoAbertas()
Crie uma casa e pinte-a. Crie três portas e coloque-as na casa; abra e feche as
mesmas como desejar.
Utilize o método verificarPortasEstaoAbertas() para imprimir o número de portas
abertas.
2. Um método pode chamar ele mesmo. Chamamos isso de recursão. Você pode resolver a
série de fibonacci usando um método que chama ele mesmo. O objetivo é você criar uma
classe, que possa ser usada da seguinte maneira:
Fibonacci fibo = new Fibonacci();
int i = fibo.calcularFibonacci(5);
System.out.println(i);
Aqui imprimirá 8, já que este é o sexto número da série.
Este método calcularFibonacci() não pode ter nenhum laço, só pode chamar ele
mesmo como método. Pense nele como uma função, que usa a própria função para
calcular o resultado.
a) Por que o modo acima é extremamente mais lento para calcular a série do que o modo
iterativo (que se usa um laço)?
b) Escreva o método recursivo novamente, usando apenas uma linha. Para isso, pesquise
sobre o operador condicional ternário.
3. O modelo de funcionários a seguir será utilizado para os exercícios de alguns dos
capítulos posteriores. O objetivo aqui é criar um sistema para gerenciar os funcionários do
Banco.
a) Modele um funcionário. Ele deve ter o nome do funcionário, o departamento onde
trabalha, seu salário (double), a data de entrada no banco (String), seu RG (String) e
um valor booleano que indique se o funcionário está na empresa no momento ou se já foi
embora.
Você deve criar alguns métodos de acordo com sua necessidade. Além deles, crie um
método bonificar() que aumenta o salário do funcionário de acordo com o parâmetro
passado como argumento. Crie, também, um método demitir(), que não recebe
parâmetro algum, só modifica o valor booleano indicando que o funcionário não trabalha
mais aqui.
A idéia aqui é apenas modelar, isto é, só identifique que informações são importantes e o
que um funcionário faz. Desenhe no papel tudo o que um Funcionario tem e tudo que ele
faz.
b) Transforme o modelo acima em uma classe Java. Teste-a, usando uma outra classe
que tenha o main(). Você deve criar a classe do funcionário chamada Funcionario, e a
classe de teste você pode nomear como quiser. A de teste deve possuir o método main().
UFES/CEUNES
Disciplina: Programação III
Conteúdo: Programação Orientada a Objetos – Linguagem JAVA
Página: 91
Incremente essa classe. Faça outros testes, imprima outros atributos e invoque os
métodos que você criou a mais.
Lembre-se de seguir a convenção Java, isso é importantíssimo. Isto é, nomeDeAtributo,
nomeDeMetodo, nomeDeVariavel, NomeDeClasse, etc...
c) Crie um método mostrar(), que não recebe nem devolve parâmetro algum e
simplesmente imprime todos os atributos do nosso funcionário. Dessa maneira, você não
precisa ficar copiando e colando um monte de System.out.println() para cada
mudança e teste que fizer com cada um de seus funcionários, você simplesmente vai
fazer:
Funcionario f1 = new Funcionario();
//brincadeiras com f1....
f1.mostrar();
d) Construa dois funcionários com o new e compare-os com o ==. E se eles tiverem os
mesmos atributos?
e) Crie duas referências para o mesmo funcionário, compare-os com o ==. Tire suas
conclusões.
f) Em vez de utilizar uma String para representar a data, crie uma outra classe, chamada
Data. Ela possui 3 campos int, para dia, mês e ano. Faça com que seu funcionário
passe a usá-la. (é parecido com o último exemplo, em que a Conta passou a ter referência
para um Cliente).
Modifique sua classe TestaFuncionario para que você crie uma Data e atribua ela ao
Funcionário.
Modifique seu método mostrar() para que ele imprima o valor de dataDeEntrada
daquele Funcionário.
Agora, o que acontece se chamarmos o método mostrar() antes de atribuirmos uma
data para este Funcionario?
g) Garanta que qualquer atributo que precisar ser manipulado em suas classes estará
sendo feito através dos métodos get() / set() quando não forem atributos da própria
classe, mas uma variável.
Download

Conteúdo 3.Item 7.Texto de Apoio