Java 2 Módulo Básico

Transcrição

Java 2 Módulo Básico
Módulo Básico
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
ÍNDICE JAVA MÓDULO BÁSICO
01.
JAVA UMA VISÃO GERAL ........................................................................................................................................ 3
02.
JAVA E A ORIENTAÇÃO A OBJETOS .................................................................................................................... 3
03.
ABSTRAÇÃO................................................................................................................................................................. 3
04.
OS TRÊS PRINCÍPIOS DA ORIENTAÇÃO A OBJETOS ...................................................................................... 5
05.
ENCAPSULAMENTO .................................................................................................................................................. 5
06.
HERANÇA...................................................................................................................................................................... 7
07.
POLIMORFISMO ......................................................................................................................................................... 8
08.
POLIMORFISMO, ENCAPSULAMENTO E HERANÇA JUNTOS...................................................................... 9
09.
EXEMPLO POLIMORFISMO/ENCAPSULAMENTO/HERANÇA JUNTOS .................................................... 10
10. ANALISANDO NOSSOEXEMPLO.JAVA ................................................................................................................ 11
11.
O COMANDO IF ......................................................................................................................................................... 13
12.
O LOOP FOR............................................................................................................................................................... 15
13.
SEPARADORES .......................................................................................................................................................... 17
14.
PALAVRAS-CHAVE .................................................................................................................................................. 17
15.
OS TIPOS SIMPLES ................................................................................................................................................... 18
16.
CONVERSÕES E USO DE CAST EM JAVA .......................................................................................................... 19
17.
USANDO CAST ENTRE TIPOS INCOMPATÍVEIS .............................................................................................. 20
18.
ARRAYS ....................................................................................................................................................................... 22
19.
ARRAYS MULTIDIMENSIONAIS........................................................................................................................... 27
20.
INTRODUÇÃO A STRINGS...................................................................................................................................... 34
21.
OS OPERADORES ARITMÉTICOS ........................................................................................................................ 35
22.
OPERADORES RELACIONAIS ............................................................................................................................... 43
23.
OPERADORES LÓGICOS BOOLEANOS .............................................................................................................. 44
24.
O OPERADOR ?.......................................................................................................................................................... 46
25.
O COMANDO SWITCH ............................................................................................................................................. 47
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
1
26.
O COMANDO WHILE ............................................................................................................................................... 49
27.
O COMANDO DO-WHILE ........................................................................................................................................ 51
28.
CLASSES EM JAVA ................................................................................................................................................... 56
29.
VARIÁVEIS DE INSTÂNCIA.................................................................................................................................... 57
30.
DECLARANDO OBJETOS........................................................................................................................................ 59
31.
DETALHES SOBRE NEW ......................................................................................................................................... 60
32.
ENTENDENDO AS REFERÊNCIAS ........................................................................................................................ 62
33.
ENTENDENDO OS MÉTODOS ................................................................................................................................ 62
34.
ACRESCENTANDO UM MÉTODO À CLASSE CAIXA....................................................................................... 63
35.
MÉTODOS COM PARÂMETROS ........................................................................................................................... 67
36.
CONSTRUTORES....................................................................................................................................................... 71
37.
CONSTRUTORES PARAMETRIZADOS................................................................................................................ 74
38.
A PALAVRA-CHAVE THIS ...................................................................................................................................... 76
39.
A CLASSE PILHA....................................................................................................................................................... 79
40.
SOBRECARREGANDO MÉTODOS ........................................................................................................................ 86
41.
RECURSÃO ................................................................................................................................................................. 92
42.
PUBLIC E PRIVATE .................................................................................................................................................. 97
43.
A PALAVRA-CHAVE STATIC ............................................................................................................................... 100
44.
FINAL CONSTANTES EM JAVA .......................................................................................................................... 103
45.
A CLASSE OBJECT ................................................................................................................................................. 104
46.
APRESENTANDO OS PACKAGES ....................................................................................................................... 105
47.
APRESENTANDO CLASSPATH ............................................................................................................................ 107
48.
MAIS SOBRE CONTROLE DE ACESSO.............................................................................................................. 109
49.
A PALAVRA-CHAVE IMPORT ............................................................................................................................. 110
50.
INTERFACES ............................................................................................................................................................ 112
51.
ACESSO À BANCO DE DABOS USANDO JDBC ............................................................................................... 117
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
2
01. Java uma visão geral
Uma das dificuldades no aprendizado de uma nova linguagem de
programação está no fato de que os elementos da linguagem não fazem sentido
isoladamente. Eles funcionam em conjunto formando a linguagem como um todo.
Isso acontece também com a linguagem Java. Essa interdependência pode
fazer com que seja difícil descrever um aspecto de Java sem falar de vários
outros. Muitas vezes a discussão de uma característica pressupõe o conhecimento
prévio de outra. Por este motivo, apresentaremos inicialmente uma rápida visão
geral de diversos aspectos básicos de Java. Esta visão proporcionará uma base
que permitirá que você escreva e entenda programas simples. Se você tiver
dificuldade em entender algum aspecto, não se apavore. Todos os tópicos aqui
discutidos serão examinados com maiores detalhes em outras lições.
02. Java e a orientação a objetos
Se você é programador como eu, deve estar com os dedos coçando para
começar a escrever código em Java. Porém antes disso, precisamos fazer uma
breve introdução (ou revisão) de um conceito que é fundamental para a
programação em Java: A orientação a objetos. Todos os programas Java são
orientados a objetos.
Em algumas linguagens, como C++, o programador pode optar por
trabalhar ou não com orientação a objetos.
Em Java, não existe essa opção. Na verdade, a orientação a objetos é tão
importante em Java que é preciso entender seus princípios básicos, para poder
escrever qualquer programa em Java, mesmo o mais simples. Por isso, logo no
início queremos discutir alguns aspectos teóricos da orientação a objetos.
03. Abstração
Um elemento essencial da programação orientada a objetos é a abstração.
Nós seres humanos utilizamos a abstração como uma forma de lidar com a
complexidade. Por exemplo, ninguém pensa em um carro como um conjunto de
dezenas de milhares de peças individuais.
Pensamos em um carro como um objeto bem definido e que tem um
determinado comportamento.
Essa abstração permite que as pessoas utilizem um carro para ir de um
lugar para outro, mesmo sem entender nada de mecânica de automóveis.
Podemos ignorar os detalhes de como o motor, a transmissão e o sistema de
freios funcionam. Ao invés disso, utilizamos o objeto automóvel como um todo.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
3
Uma maneira poderosa de lidar com a abstração é usando classificações
hierárquicas. Isso permite pensar em sistemas complexos como sendo formados
por diversas camadas. Tais camadas representam porções mais compreensíveis.
Visto de fora, um carro é um objeto único. Porém, uma vez dentro dele, o carro
aparece para nós como consistindo de diversos subsistemas: direção, freios,
sistema de som, ar condicionado, e assim por diante. O usuário pode utilizar os
vários sistemas do automóvel, mesmo sem entender como eles funcionam. Basta
saber que girando o botão ele aumenta o volume do rádio. Ou pisando no pedal
de freio, o veículo pára. Ou seja, para usar o sistema basta conhecer a interface.
Por sua vez, o técnico que conserta o ar condicionado do carro pode fazer
seu trabalho com muita competência, mesmo que não saiba nada sobre como
funciona o sistema de freios.
Cada um dos subsistemas é formado por unidades mais especializadas. Por
exemplo, o sistema de som consiste de rádio, toca-CDs, toca-fitas, amplificador e
alto-falantes.
A idéia é que lidamos com a complexidade do carro, ou de qualquer outro
sistema complexo, utilizando abstrações hierárquicas.
Abstrações hierárquicas de sistemas complexos também podem ser
aplicadas a programas de computador. Podemos usar a abstração para criar
objetos a partir dos dados de um programa tradicional orientado para o processo.
Uma seqüência de passos de processo pode ser tornar uma coleção de
mensagens entre esses objetos. Assim, cada um desses objetos descreve seu
próprio comportamento característico.
Podemos então tratar esses objetos como entidades concretas que respondem a
mensagens que mandam que eles façam alguma coisa. Esta é a essência da
programação orientada a objetos.
Os conceitos da orientação a objetos formam a base de Java. Na verdade, a
orientação a objetos é intuitiva para nós humanos, porque nosso mundo é
orientado a objetos. É importante compreender como esses conceitos se
traduzem em programas. Como veremos, a programação orientada a objetos é
um paradigma poderoso e natural. Os programas desenvolvidos desta forma
podem ser modificados posteriormente com facilidade, para acompanhar
mudanças nas necessidades do usuário ou da empresa.
Uma vez que temos objetos definidos de forma clara, com interfaces limpas
e confiáveis, podemos elegantemente modificar ou substituir partes de um
sistema já existente sem medo de introduzir erros. Por exemplo, se o fabricante
substituir o sistema de direção mecânica de um modelo de automóvel por um
sistema de direção hidráulica, o motorista continuará fazendo curvas à esquerda e
à direita da mesma forma que antes: girando o volante no sentido desejado. Ou
seja, mudou a implementação, mas a interface continuou sendo a mesma.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
4
04. Os três princípios da orientação a objetos
Linguagens de programação orientadas a objetos são aquelas que
proporcionam mecanismos que ajudam a implementar o modelo orientado a
objetos. Esses mecanismos são:
·
·
·
Encapsulamento
Herança
Polimorfismo
A seguir, examinaremos cada um desses conceitos.
05. Encapsulamento
O encapsulamento é o mecanismo que permite agrupar em uma única
entidade o código e os dados que esse código manipula. Tais elementos ficam
assim protegidos contra interferências externas e utilização inadequada.
Uma forma de pensar no encapsulamento é como um envoltório protetor
que impede que o código e os dados sejam acessados arbitrariamente por outro
trecho de código que esteja definido fora do envoltório. O acesso ao código e aos
dados que ficam dentro do envoltório é rigidamente controlado por meio de uma
interface bem definida.
Para dar um exemplo do mundo real, consideremos a caixa de marchas de
um automóvel. Ela encapsula centenas de porções de informações como:
·
·
·
·
Potência do motor
Tipo de uso do automóvel
Peso máximo do automóvel
Número de marchas
O motorista tem apenas um método de afetar esse complexo
encapsulamento: mudar a posição da alavanca. Não é possível afetar a
transmissão mexendo na alavanca do pisca-pisca nem acionando o limpador de
pára-brisa. Assim, a alavanca de mudança é uma interface bem definida, e na
verdade única, para a transmissão.
Por outro lado, o que acontece dentro da transmissão não afeta os objetos
que estão fora dela. Por exemplo, uma mudança de marcha não acende os faróis.
Pelo fato da caixa de marchas ser encapsulada, cada fabricante pode implementar
internamente sua transmissão do jeito que achar melhor. Mas do ponto de vista
do motorista, todas elas funcionam da mesma forma.
Essa mesma idéia pode ser aplicada à programação. O poder do código
encapsulado está no fato de que todo mundo sabe como acessá-lo e por isso pode
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
5
usá-lo, independentemente dos detalhes de programação – e sem medo de
efeitos colaterais indesejáveis.
Em Java, a base do encapsulamento é a classe. Embora o conceito de classe
vá ser examinado detalhadamente em outra lição, apresentamos a seguir uma
breve discussão que será suficiente por enquanto.
Uma classe define a estrutura e o comportamento (dados e código) que
serão comuns a um conjunto de objetos. Cada objeto de uma dada classe contém
a estrutura e o comportamento definidos pela classe, como se a classe fosse um
molde usado para estampar o formato de cada um deles. Por esse motivo, os
objetos são muitas vezes chamados de instâncias de uma classe. Assim, uma
classe é uma construção lógica; um objeto é uma realidade física.
Voltando ao exemplo do automóvel, o projeto e as especificações do
Volkswagen Gol 1000 seriam a classe. Existe apenas uma classe Gol 1000. Cada
automóvel Gol 1000 é um objeto, ou instância, dessa classe, e existem milhares
desses objetos.
Quando criamos uma classe, especificamos o código e os dados que a
constituem. Coletivamente, esses elementos são chamados membros da classe.
Especificamente, os dados definidos pela classe são chamados variáveis
membros, ou variáveis de instância. O código que opera sobre os dados forma os
chamados métodos membros, ou simplesmente métodos. Para o programadores
C/C++, podemos dizer que o que um programador Java chama de método, é
aquilo que em C/C++ é chamado de função.
Em um programa Java escrito corretamente, os métodos definem como as
variáveis membros, podem ser usadas. Isso significa que o comportamento e a
interface de uma classe são definidos pelos métodos que operam sobre seus
dados de instância.
Como o propósito de uma classe é encapsular a complexidade, ela tem
mecanismos para ocultar a complexidade da implementação que está dentro da
classe. Cada método ou variável de uma classe pode ser marcado como sendo
privativo (private) ou público (public). A interface pública de uma classe
representa tudo que os usuários externos da classe podem saber, ou precisam
saber. Os métodos e dados privativos somente podem ser acessados por código
que seja membro da classe. Portanto, qualquer outro código que não seja
membro da classe não pode acessar um método privativo ou uma variável
privativa.
Como os membros privativos de uma classe somente podem ser acessados
por outras partes do programa através dos métodos públicos da classe, isso
assegura que nenhuma ação inadequada acontecerá. É claro que isso significa
que a interface pública precisa ser cuidadosamente projetada para não expôr
excessivamente o funcionamento interno da classe.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
6
06. Herança
A herança é um processo pelo qual um objeto adquire as propriedades de
outro objeto.
A herança é o que viabiliza o conceito de uma classificação hierárquica. Veja
como:
Um Pastor Alemão faz parte da classificação Cachorro...
que faz parte da classificação Mamífero...
que faz parte da classificação Animal.
Observe que herança se refere aqui a características e comportamento, e
não a bens materiais. No exemplo do Pastor Alemão, estamos falando da herança
de características genéticas.
Conforme mencionado anteriormente, a maioria dos tipos de conhecimento
pode ser gerenciada por classificações hierárquicas de cima para baixo. Sem o
uso de hierarquias, cada objeto precisaria definir todas as suas características
explicitamente. Contudo, com o uso da herança, um objeto somente precisa
definir aquelas qualidades que o tornam único dentro de sua classe. Ele pode
herdar seus atributos gerais de sua classe-mãe. Assim, a herança é o mecanismo
que torna possível que um objeto seja uma instância específica de um caso mais
geral. Vamos dar uma olhada nesse processo mais de perto.
As pessoas naturalmente vêem o mundo como sendo formado de objetos
que são relacionados entre si de forma hierárquica, como animais, mamíferos e
cachorros. Se quiséssemos descrever os animais de uma forma abstrata, diríamos
que eles têm alguns atributos como tamanho, inteligência e tipo de esqueleto. Os
animais têm também alguns aspectos de comportamento. Eles comem, respiram
e dormem. Essa descrição de atributos e comportamento representa a definição
de classe para os animais.
Se quisermos descrever uma classe mais específica de animais, como os
mamíferos, eles terão atributos mais específicos como pelos, tipo de dentes e
glândulas mamárias. Os mamíferos são então conhecidos como uma subclasse de
animais, e os animais são chamados de uma superclasse de mamíferos.
Como os mamíferos são simplesmente animais especificados com mais
precisão, eles herdam todos os atributos de animais. Uma subclasse que fica mais
abaixo na hierarquia de heranças herda todos os atributos de cada um de seus
ancestrais na chamada hierarquia de classes.
A herança interage também com o encapsulamento. Se uma dada classe
encapsula alguns atributos, então uma subclasse dela terá os mesmos atributos,
e mais alguns, que são acrescentados como parte de sua especialização. Esse é o
conceito fundamental que permite que os programas orientados a objetos
cresçam em complexidade linearmente, e não geometricamente. Uma nova
subclasse herda todos os atributos de todos os seus ancestrais. Ela não tem
interações imprevisíveis com o restante do código do sistema.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
7
07. Polimorfismo
Polimorfismo significa muitas formas.
Trata-se de uma característica que permite que uma interface seja usada
para uma classe genérica de ações, sendo que a ação específica em cada caso é
determinada pela exata natureza da situação. Não entendeu? Vamos explicar
melhor.
Consideremos uma pilha, que é uma lista do tipo último-que-entra,
primeiro-que-sai. Podemos ter um programa que requer três tipos de pilhas. Uma
pilha é usada para valores inteiros, outra pilha para valores com ponto flutuante,
e outra para caracteres. O algoritmo que implementa cada pilha é o mesmo,
muito embora os dados sendo armazenados sejam diferentes.
Em uma linguagem não orientada a objetos, seria necessário criar três
conjuntos diferentes de rotinas de pilha, cada conjunto com um nome diferente.
Mas em Java, por causa do polimorfismo, Java podemos especificar um conjunto
geral de rotinas de pilha; todas as rotinas compartilhando os mesmos nomes.
Mais genericamente, o conceito de polimorfismo é muitas vezes expresso
pela frase "uma interface, múltiplos métodos". Isso significa que é possível
projetar uma interface genérica que pode ser usada para especificar uma classe
geral de ações. Cabe ao compilador selecionar a ação específica (isto é, o
método) que se aplica a cada situação. O programador não precisa fazer essa
seleção manualmente. Ele apenas precisa lembrar da interface geral.
Ampliando a analogia do cachorro, o sentido do olfato do cachorro é
polimórfico:
·
Se o cachorro sente o cheiro do dono, ele abana a cauda e corre ao
seu encontro.
·
Se o cachorro sente o cheiro de um gato, ele late e corre atrás dele.
·
Se o cachorro sente o cheiro de sua ração, ele começa a salivar e
corre para seu prato.
O mesmo sentido do olfato funciona de forma diferente nas várias
situações. A diferença é o cheiro que está sendo sentido, isto é, o tipo de dados
sobre os quais o nariz do cachorro está operando. Esse mesmo conceito geral
pode ser implementado em Java, sendo aplicado aos métodos dentro de um
programa Java.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
8
08. Polimorfismo, encapsulamento e herança juntos
O polimorfismo, o encapsulamento e a herança combinam se para produzir
um ambiente de programação que dá suporte ao desenvolvimento de programas
muito mais robustos e escaláveis do que o modelo orientado a processo.
Uma hierarquia de classes bem projetada é a base para a reutilização do
código que representa um valioso patrimônio. Afinal de contas, o
desenvolvimento de código requer um grande investimento em tempo e esforço.
O encapsulamento permite melhorar as implementações com o passar do
tempo. Desde que a interface pública das classes seja respeitada, elas poderão
continuar sendo utilizadas sem rupturas.
O polimorfismo permite criar código limpo, legível, robusto e lógico.
Entre os dois exemplos do mundo real que mencionamos, o automóvel
ilustra de forma mais completa o poder da orientação a objetos. Pode ser mais
divertido pensar em cães do ponto de vista da herança, mas os automóveis são
mais parecidos com programas de computador. Todos os motoristas utilizam a
herança para dirigir diferentes tipos (subclasses) de veículos. Quer o veículo seja
um ônibus escolar, esportivo Porsche ou a perua da família, os motoristas mais ou
menos são capazes de encontrar e operar a direção, os freios e o acelerador
(geralmente).
Depois de arranhar um pouco as marchas, a maioria das pessoas é capaz
até mesmo de dominar a diferença entre a mudança automática e a alavanca de
mudanças tradicional. Isso porque os motoristas têm o conceito geral da
superclasse da caixa de marchas tradicional e da automática, que é a
transmissão.
Por fim, o polimorfismo, reflete-se na possibilidade que os fabricantes de
carros têm de oferecer uma ampla variedade de opções no mesmo veículo básico.
Por exemplo, podemos ter sistema de freios anti-travamento ou freios
tradicionais, direção hidráulica ou mecânica, motores de 4, 6 ou 8 cilindros. De
qualquer forma, ainda pressionamos um pedal para parar, giramos o volante de
direção para fazer uma curva e pressionamos o acelerador quando queremos que
o carro avance. A mesma interface pode ser usada para controlar uma série de
implementações diferentes.
Como podemos ver, é através da aplicação do encapsulamento, da herança
e do polimorfismo que as partes individuais se transformam em um objeto
conhecido como automóvel.
O mesmo vale para os programas de computador. Aplicando os princípios
da orientação a objetos, as várias partes de um programa complexo podem ser
montadas formando um todo robusto, coeso e de fácil manutenção.
Conforme mencionamos anteriormente, todo programa em Java é orientado
a objetos. Ou, dizendo de forma mais precisa, todo programa em Java envolve
encapsulamento, herança e polimorfismo.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
9
Embora os pequenos programas didáticos que apresentaremos neste início
de curso possam não exibir claramente todas essas características, elas estão
presentes mesmo assim. Como veremos, muitos dos recursos fornecidos por Java
fazem parte de bibliotecas de classes que acompanham a linguagem. Tais
bibliotecas fazem uso intensivo do encapsulamento, da herança e do
polimorfismo.
09. Exemplo polimorfismo/encapsulamento/herança juntos
Classe Pai:
1 public class Pai {
2
public void i() {
3
System.out.println("Pai");
4
}
5}
Classe Filho:
1 public class Filho extends Pai {
2
public void i() {
3
System.out.println("Filho");
4
}
5}
Classe NossoExemplo:
1 // NossoExemplo
2 public class NossoExemplo {
3
// Todo aplicativo Java tem um, e somente
4
// um método main().
5
// A execução do programa sempre
6
// começa no método main().
7
public static void main(String args[]) {
8
Pai p = new Filho();
9
p.i();
10
} // Fim de main()
11 } // Fim da classe NossoExemplo
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
10
10. Analisando NossoExemplo.java
Embora o programa NossoExemplo.java seja bastante simples, ele inclui
diversas características importantes que encontraremos em todos os programas
Java. Vamos examinar de perto cada parte do programa.
Ele começa da seguinte forma (linha 1):
// NossoExemplo.java
Isto é um comentário. Como acontece com todas as linguagens de
programação, Java permite digitar comentários no código fonte dos programas. O
conteúdo de um comentário é ignorado pelo compilador. Um comentário descreve
ou explica a operação de um programa para as pessoas que estejam lendo o
código fonte.
Neste caso, o comentário descreve o nome do programa. Em aplicativos de
verdade, os comentários geralmente explicam como alguma parte do programa
funciona, ou o que faz um determinado recurso.
Java suporta três estilos de comentários. Um comentário de linha única,
como o mostrado acima, começa com o par de caracteres // e estende-se até o
fim da linha.
Outro tipo de comentário é o de múltiplas linhas. Ele começa com o par de
caracteres /* e estende-se até o par de caracteres de fechamento */.
Geralmente, esses comentários de múltiplas linhas são usados para explicações
mais longas, e os comentários de linha única para explicações curtas, linha por
linha.
A linha seguinte de NossoExemplo.java é a seguinte (linha 2):
class NossoExemplo
Esta linha utiliza a palavra-chave class para indicar que uma nova classe
está sendo definida. A palavra NossoExemplo é o identificador usado como nome
da classe. Toda a definição da classe, incluindo seus membros, aparecerá entre a
chave de abertura { e a chave de fechamento }. O uso de chaves em Java é
idêntico aquele que é feito em C e C++.
Por enquanto, não se preocupe muito com os detalhes de uma classe,
exceto pelo fato de que em Java, toda atividade do programa ocorre dentro das
classes. Isso é conseqüência do fato de que todos os programas Java são
orientados a objetos.
Após algumas linhas de comentários, a próxima linha executável do
programa é a seguinte (linha 7):
public static void main(String args[])
Essa linha dá início ao método main(). (Lembre-se que em Java, um método
é o equivalente a uma função em linguagem C). O método main() marca o local
onde começa a execução de todos os programas Java. Todos os aplicativos Java
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
11
começam a execução chamando o método main(), da mesma forma que em
C/C++ os programas começam na função main().
Não vamos explicar agora o significado exato de cada parte dessa linha, já
que isso exigiria um detalhado entendimento do enfoque de Java para o
encapsulamento. Contudo, como muitos exemplos desta primeira parte do curso
utilizarão esta linha de código, vamos dar uma rápida olhada em cada parte.
A palavra-chave public é um especificador de acesso, que permite que o
programador controle a visibilidade dos membros de uma classe. Quando um
membro de uma classe é precedido pela palavra public esse membro pode ser
acessado por código de fora da classe na qual é declarado. O contrário de public é
private, que impede que um membro seja usado por código definido fora de sua
classe. Neste caso, main() deve ser declarado como public, já que ele deve ser
chamado por código de fora da classe quando o programa é iniciado.
A palavra-chave static permite que main() seja chamado sem necessidade
de instanciar uma determinada classe. Isso é necessário, já que main() é
chamado pelo interpretador Java antes que qualquer objeto seja criado.
A palavra-chave void simplesmente avisa ao compilador que main() não
retorna nenhum valor. Como veremos, os métodos podem retornar valores. Se
tudo isso parece um tanto confuso, não entre em pânico. Procure entender o que
está sendo apresentado aqui sobre esses conceitos. Todos eles serão discutidos
com detalhes nas lições subseqüentes.
Como dissemos, main() é o método que é chamado quando um aplicativo
Java é iniciado. Tenha em mente que Java faz diferença entre letras maiúsculas e
minúsculas. Portanto, Main()
é diferente de main().
É importante compreender que o compilador Java é capaz de compilar
classes que não contenham um método main(). Na verdade, isso é feito com
bastante freqüência. Mas o interpretador Java não tem como rodar essas classes
diretamente. Elas precisam ser criadas por outras classes.
Por isso, se você digitar Main() ao invés de main(), o compilador vai
compilar o programa. Contudo, o interpretador Java vai reportar um erro, porque
não conseguirá encontrar o método main().
Qualquer informação que se precise passar para um método é recebida
pelas variáveis especificadas dentro do par de parênteses que se segue ao nome
do método. Essas variáveis são chamadas parâmetros. Se nenhum parâmetro for
exigido para um determinado método, ainda assim é necessário incluir um par de
parênteses vazio. Ao longo de todo este curso, sempre que uma palavra aparecer
seguida de parênteses, assim:
exemploDeMetodo()
Isso indicará que estamos falando de um método.
Em main(), há somente um parâmetro, ainda que um tanto complicado.
String args[ ] declara um parâmetro chamado args, que é um array de instâncias
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
12
da classe String. Arrays são coleções de objetos do mesmo tipo. Os objetos do
tipo String armazenam strings de caracteres. Neste caso, args recebe quaisquer
argumentos da linha de comando que estejam presentes quando o programa for
executado. Este programa não faz nenhum uso dessas informações, mas outros
programas mostrados mais adiante farão.
A linha seguinte (linha 7) contém uma chave de abertura {. Ela indica o
início do corpo de main(). Todo o código que forma um método deverá aparecer
entre o par de chaves { e } (linha 10) que delimita o método.
Outro ponto importante: main() é simplesmente o ponto de partida para o
interpretador começar a execução. Um programa complexo pode ter dúzias de
classes, sendo que apenas uma delas pode ter um método main() para dar
partida no programa. Quando começarmos a trabalhar com applets, que são
programas Java criados para rodar em um Web browser, veremos que
normalmente um applet não tem método main(), já que o Web browser usa uma
maneira diferente para dar início à execução do applet.
Observe que as linhas que contém comandos terminam com um ponto-evírgula (;). Todos os comandos em Java são terminados com ponto-e-vírgula. O
motivo pelo qual as outras linhas do programa não terminam em ponto-e-vírgula
é porque, tecnicamente, elas não são comandos.
Conforme indicam os comentários, o próximo caractere } que aparece na
linha 10 encerra o método main(). O segundo caractere } que aparece na linha 11
encerra a definição da classe NossoExemplo.
11. O comando if
Em outro ponto deste curso, faremos um exame completo dos comandos de
controle. Por enquanto dois deles serão apresentados brevemente para que
possam ser usados nos próximos exemplos de programas. Eles ajudam também a
ilustrar um importante conceito de Java: os blocos de código.
O comando if funciona em Java de forma muito parecida com os de outras
linguagens. Na verdade, ele é sintaticamente idêntico ao if de C e C++. Sua
forma mais simples é a seguinte:
if(condição)
comando;
Neste exemplo, condição é uma expressão booleana. Se condição for
verdadeira (true), então comando é executado. Se condição for falsa (false),
comando é ignorado. Eis um exemplo:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
13
if(var < 10)
println("var é menor que 10");
Neste caso, se var contiver um valor inferior a 10, a expressão condicional
será verdadeira, e a linha contendo println() será executada. Se var contiver um
valor maior ou igual a 10, a linha contendo println() será ignorada.
Como veremos em outro ponto deste curso, Java define uma completa
coleção de operadores relacionais que podem ser usados em expressões
condicionais. Eis alguns deles:
Operador
<
>
==
Significado
Menor do que
Maior do que
Igual a
Observe que o teste de igualdade é um duplo sinal de igualdade ==.
Eis um programa que ilustra o comando if:
Listagem: MostraIf.java
1 // MostraIf.java
2 class MostraIf
3{
4
public static void main(String args[])
5
{
6
int i, j;
7
i = 50;
8
j = 100;
9
10
if(i < j)
11
System.out.println("i e' menor que j.");
12
13
i = i * 2;
14
if(i == j)
15
System.out.println("Agora i e' igual a j.");
16
17
i = i * 2;
18
if(i > j)
19
System.out.println("Agora i e' maior que j.");
20
21
// Esta linha não será exibida.
22
if(i == j)
23
System.out.println("Isso não será exibido.");
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
14
24
} // Fim de main()
25 } // Fim da classe MostraIf.
A saída exibida por esse programa é a seguinte:
i e' menor que j.
Agora i e' igual a j.
Agora i e' maior que j.
Outro aspecto a observar neste programa é que a linha 6
int i, j;
Declara duas variáveis, i e j, usando uma lista separada por vírgula. Essa
técnica é idêntica à usada em C/C++.
12. O loop for
Os comandos de loop são uma parte importante de todas as linguagens de
programação. Java não é exceção. Veremos em outro ponto deste curso que Java
oferece uma poderosa coleção de construções de loops. O mais versátil deles é o
loop for, que apresentaremos resumidamente aqui.
Se você conhece C ou C++, verá que o loop for em Java funciona da
mesma forma que nessas linguagens. Se você não conhece C/C++, verá que o
loop for não apresenta nenhuma dificuldade. A forma mais simples do loop for é a
seguinte:
for(inicialização; condição; iteração)
comando;
Geralmente a porção de inicialização ajusta a variável de controle do loop
para um valor inicial. A condição é uma expressão booleana que testa a variável
de controle do loop. Se o resultado do teste for verdadeiro (true), o loop for
continua a ser executado. Se for falso (false), o loop é encerrado. A expressão de
iteração determina como a variável de controle do loop é alterada cada vez que o
loop é executado. Eis um pequeno programa que ilustra o loop for.
1 // TesteFor
2 class TesteFor
3 {
4
public static void main(String args[])
5
{
6
int i;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
15
7
for(i = 0; i < 10; i = i + 1)
8
System.out.println("Valor de i: " + i);
9
} // Fim de main()
10 } // Fim da classe TesteFor.
Eis a saída deste programa:
Valor de i: 0
Valor de i: 1
Valor de i: 2
Valor de i: 3
Valor de i: 4
Valor de i: 5
Valor de i: 6
Valor de i: 7
Valor de i: 8
Valor de i: 9
Neste exemplo, i é a variável de controle do loop. Ela é inicializada com o
valor zero na porção de inicialização de for. No início de cada iteração (inclusive a
primeira), o teste condicional i < 10 é executado. Se o resultado do teste for
verdadeiro, o comando println() é executado, e depois a porção de iteração do
loop é executada. Esse processo continua até que o teste condicional resulte em
false.
Uma observação: programas Java de nível profissional, raramente terão a
porção de iteração do loop escrita da forma mostrada no programa acima. Ou
seja, raramente teremos uma linha como esta:
i = i + 1;
O fato é que Java inclui um operador especial de incremento que executa
essa operação de forma mais eficiente. O operador de incremento é representado
por ++. Isto é, dois sinais de adição, lado a lado. O operador de incremento
aumenta o valor do operando em 1. Usando o operador de incremento, o
comando acima pode ser escrito da seguinte forma:
x++;
Assim, o for do programa acima geralmente será escrito da seguinte forma:
for(i = 0; i < 10; i++)
Experimente fazer essa modificação. Você verá que o loop continuará
funcionando da mesma forma.
Java oferece também um operador de decremento, representado por --.
Esse operador diminui 1 do valor do operando.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
16
13. Separadores
Java define alguns caracteres como separadores. O mais comum deles é o
caractere de ponto-e-vírgula (;). Como já vimos, ele é usado para encerrar os
comandos (statements). Outros separadores são comentados a seguir:
()
Parênteses Usados para conter listas de parâmetros nas definições e
invocações de métodos. Também são usados para definir precedência em
expressões, conter expressões em comandos de controle e envolver tipos em
conversões (casts).
{ } Chaves
Usados para conter arrays inicializados automaticamente.
Também são usados para definir blocos de código, em classes, métodos e escopos
locais.
[]
Colchetes Usados para declarar tipos de arrays. Usados também
para de-referenciar valores de arrays.
;
Ponto-e-vírgula Encerra os comandos.
,
Vírgula
Separa identificadores consecutivos em declarações de
variáveis. Usada também para encadear as declarações dentro de um comando
for.
.
Ponto Usado para separar nomes de packages de subpackages e
classes. Usado também para separar uma variável ou método de uma referência
a variável.
14. Palavras-chave
Java tem 47 palavras-chave reservadas e atualmente definidas. Essas
palavras-chave, combinadas com a sintaxe de operadores e separadores, formam
a definição da linguagem. As palavras-chave não podem ser usadas como nomes
de variáveis, classes ou métodos.
abstract
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
extends
final
finally
float
for
goto
if
implements
import
instance
int
interface
long
native
new
package
private
of
public
return
short
static
super
switch
synchronized
protected
throw
throws
transient
try
void
volatile
while
this
As palavras-chave const e goto são reservadas, mas não utilizadas. Nos
primeiros dias de Java, diversas outras palavras-chave foram reservadas para
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
17
possível utilização futura. Contudo, a atual especificação de Java somente define
como palavras-chaves aquelas mostradas acima.
Além das palavras-chave, Java reserva as seguintes:
true false null
Elas representam valores definidos por Java e não podem ser usadas como
nomes de variáveis, classes ou métodos.
15. Os tipos simples
Como acontece com todas as linguagens de programação modernas, Java
suporta diversos tipos de dados. Podemos usar esses tipos para declarar variáveis
e criar arrays.
Desde já, é importante ter em mente que Java é uma linguagem com
tipagem forte. Na verdade, parte da robustez e da segurança de Java é
conseqüência desse fato.
Mas o que significa isso?
Primeiro, toda variável em Java tem um tipo. O mesmo vale para as expressões.
Esses tipos são rigorosamente definidos.
Segundo, todas as atribuições, quer sejam explícitas ou via passagem de
parâmetros nas chamadas a métodos, são checadas quanto à compatibilidade de
tipos. Não existem conversões automáticas, nem conversões de tipos conflitantes,
como acontece em algumas linguagens. O compilador Java checa todas as
expressões e parâmetros para assegurar que os tipos sejam compatíveis.
Qualquer disparidade entre tipos é um erro e deve ser corrigida para que o
compilador possa concluir a compilação da classe.
Quem tem uma vivência em C ou C++ perceberá que a tipagem em Java é
mais rigorosa do que nessas duas linguagens. Por exemplo, em C/C++ é possível
atribuir um valor de ponto flutuante a um inteiro. Em Java, isso não é permitido.
Também em C, não existe necessariamente um checagem forte de tipos entre um
parâmetro e um argumento. Em Java, isso sempre acontece. A forte checagem de
tipos de Java pode parecer inicialmente um pouco aborrecida. Mas é preciso ter
em mente que a longo prazo isso ajudará a reduzir a possibilidade de erros no
código.
Java define oito tipos simples (ou elementares) de dados. Eles podem ser
divididos em quatro grupos:
Os tipos elementares de Java (separados por grupo)
Números inteiros Ponto flutuante Caracteres Booleano
Números inteiros, todos com sinal Números fracionários Letras, símbolos e
algarismos Valor falso ou verdadeiro
byte
short
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
18
int
long
float
double
char
boolean
Esses tipos podem ser usados no estado original, ou para construir arrays
ou criar novos tipos de classes. Assim, eles formam a base de todos os outros
tipos de dados que podem ser criados.
Os tipos elementares representam valores individuais, e não objetos
complexos. Embora sob outros aspectos Java seja uma linguagem totalmente
orientada a objetos, os tipos elementares não são objetos. Eles são análogos aos
tipos simples encontrados na maioria das outras linguagens não-orientadas a
objetos. A razão para isto é a eficiência. Se os tipos simples fossem objetos, a
performance seria muito prejudicada.
Os tipos simples são definidos explicitamente para ter uma determinada
faixa de valores e um comportamento matemático. Linguagens como C e C++
permitem que o tamanho de um inteiro varie conforme o ambiente de execução.
Em Java isso não acontece. Por causa dos requisitos de portabilidade de Java,
todos os tipos de dados têm uma faixa de valores estritamente definida. Por
exemplo, um int é sempre de 32 bits, independente da plataforma de execução.
Isso garante que os programas rodarão em qualquer arquitetura de máquina,
sem necessidade de serem portados.
Embora uma rigorosa especificação do tamanho de um inteiro possa causar
uma pequena perda de performance em alguns ambientes, isso é necessário para
garantir a portabilidade.
16. Conversões e uso de cast em Java
Para resolver certos problemas em programação, é relativamente comum
atribuir um valor de um tipo a uma variável de outro tipo. Se os dois tipos forem
compatíveis, Java fará a conversão automaticamente. Um exemplo: sempre é
possível atribuir um valor int a uma variável long.
Porém nem todos os tipos são compatíveis, de modo que nem todas as
conversões são implicitamente permitidas. Por exemplo, não existe uma
conversão definida de double para byte.
Para conseguir uma conversão entre tipos incompatíveis, usamos o recurso
chamado cast. Ele executa uma conversão explícita entre tipos incompatíveis.
Quando um dado de um tipo é atribuído a uma variável de outro tipo,
ocorre uma conversão automática de tipo se as duas condições abaixo forem
satisfeitas:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
19
·
Os dois tipos forem compatíveis
·
tipo de destino tiver largura maior que o tipo original
Quando essas duas condições são satisfeitas, ocorre uma conversão por
alargamento.
Por exemplo, o tipo int sempre é grande o suficiente para conter todos os
valores do tipo byte, de modo que não há necessidade de uso de um cast
explícito.
Nas conversões por alargamento, os tipos numéricos, inclusive os tipos
inteiros e de ponto flutuante, são compatíveis entre si. Contudo, os tipos
numéricos não são compatíveis com char ou boolean. E também, char e boolean
não são compatíveis entre si.
Conforme mencionado anteriormente, Java também executa uma conversão
automática de tipo ao armazenar uma constante literal inteira em uma variável do
tipo byte, short ou long.
17. Usando cast entre tipos incompatíveis
As conversões automáticas ajudam muito, mas não atendem a todas as
necessidades de programação.
Por exemplo, o que acontece se precisarmos atribuir um valor int a uma
variável byte? Essa conversão não é feita automaticamente, porque um byte é
menor que um int. Esse tipo de conversão é conhecido como conversão por
estreitamento, já que estamos explicitamente tornando o valor mais estreito, para
que ele caiba no tipo alvo.
Para criar uma conversão entre dois tipos incompatíveis, é preciso usar um
cast. Um cast é simplesmente uma conversão explícita de tipo. Em nosso exemplo
acima, de int para byte, o cast teria a seguinte forma:
int intVar;
byte byteVar;
// . . .
byteVar = (byte) intVar;
Aqui, byteVar representa uma variável tipo byte, e intVar representa uma
variável tipo int.
Se o valor int for maior que o valor máximo possível para um byte, ele será
reduzido usando a conversão módulo, que consiste no resto de uma divisão
inteira pelo alcance de byte.
A conversão que ocorre quando um valor de ponto flutuante é atribuído a
um tipo inteiro é diferente. Consiste de um truncamento. Como sabemos, valores
inteiros não têm o componente fracionário. Assim, quando um valor de ponto
flutuante é atribuído a um tipo inteiro, o componente fracionário é perdido.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
20
Por exemplo, se o valor 3.1416 for atribuído a um inteiro, o valor resultante
será simplesmente 3. A parte fracionária .1416 será truncada.
Importante: se o tamanho da parte inteira do número for grande demais
para caber no tipo inteiro alvo, então o valor será reduzido usando a operação
módulo pelo alcance do tipo alvo.
O programa abaixo demonstra algumas conversões de tipos que requerem
casts:
Listagem: Casts.java
1 // Casts.java
2 // Ilustra o uso de
3 // casts.
4
5 class Casts
6{
7
public static void main(String args[])
8
{
9
byte byteVar;
10
int intVar = 257;
11
double doubleVar = 317.999;
12
13
System.out.println("\nConversao de int para byte.");
14
byteVar = (byte) intVar;
15
System.out.println("intVar = " + intVar);
16
System.out.println("byteVar = " + byteVar);
17
18
System.out.println("\nConversao de double para int.");
19
intVar = (int) doubleVar;
20
System.out.println("doubleVar = " + doubleVar);
21
System.out.println("intVar = " + intVar);
22
23
System.out.println("\nConversao de double para byte.");
24
byteVar = (byte) doubleVar;
25
System.out.println("doubleVar = " + doubleVar);
26
System.out.println("byteVar = " + byteVar);
27 } // Fim de main()
28 } // Fim de classe Casts.
Este programa gera a seguinte saída:
Conversao de int para byte.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
21
intVar = 257
byteVar = 1
Conversao de double para int.
doubleVar = 317.999
intVar = 317
Conversao de double para byte.
doubleVar = 317.999
byteVar = 61
Vamos examinar cada uma das conversões.
Na linha 14, quando o valor 257 é convertido por meio de um cast em uma
variável byte, o resultado é o resto da divisão de 257 por 256, o valor máximo de
um byte. No caso, esse resto é igual a 1.
Na linha 19, quando doubleVar é convertida em int, o componente fracional
é perdido.
Na linha 24, quando doubleVar é convertida em byte, o componente
fracional é perdido e o valor é reduzido com a operação módulo 256. O resultado
é 61.
18. Arrays
Um array é um grupo de variáveis do mesmo tipo que são referenciadas por
um nome comum.
Podemos criar arrays de qualquer tipo, e eles podem ter uma ou mais
dimensões. Um elemento específico de um array é acessado por seu índice. Os
arrays representam uma forma prática de agrupar elementos correlacionados do
mesmo tipo.
Um array unidimensional é, essencialmente, uma lista de variáveis do
mesmo tipo. Para criar um array, primeiro criamos uma variável array do tipo
desejado. Por exemplo, a linha abaixo:
int num_dias_mes[];
Cria uma variável array do tipo int chamada num_dias_mes.
Aqui, o tipo int declara o tipo básico do array. O tipo básico determina o tipo
de dados de cada elemento que forma o array. Ou seja, o tipo básico do array
determina o tipo dos dados que o array conterá. No caso, a linha acima cria uma
variável array de ints.
Embora esta declaração estabeleça o fato de que num_dias_mes é uma
variável array, ainda não existe um array de fato neste ponto. Na verdade, o
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
22
valor de num_dias_mes é automáticamente ajustado para null, que representa
um array sem nenhum valor.
Para conectar num_dias_mes a um array físico e real de inteiros, é preciso
alocá-lo, usando new e atribui-lo a num_dias_mes. new é um operador especial
que aloca memória.
Examinaremos new com mais detalhes mais adiante neste curso, mas
precisamos usá-lo agora para alocar memória para arrays. Eis a forma como new
é usado para alocar um array de 12 ints:
num_dias_mes = new int[12];
Observe que nesta mesma linha, o array de ints recém-alocado está sendo
atribuído à variável num_dias_mes.
Aqui, int especifica o tipo dos dados que estão sendo alocados. O número
12 entre colchetes [ ] especifica o número de elementos contidos no array. E
num_dias_mes é a variável array à qual o array recém alocado é conectado.
Resumindo: quando usamos new para alocar um array, precisamos
especificar o tipo e o número de elementos a alocar. Os elementos do array
alocado por new serão automaticamente inicializados com o valor zero.
Portanto, depois que a linha acima for executada, num_dias_mes se referirá
a um array de 12 inteiros. Além disso, todos os elementos do array terão sido
inicializados com o valor zero.
Mais uma vez: a obtenção de um array é um processo em duas etapas.
Primeiro, é preciso declarar uma variável do tipo de array desejado. Segundo, é
preciso alocar a memória que conterá o array, usando new, e atribuí-la à variável
do array. Assim, em Java, todos os arrays são alocados dinamicamente. Mais
adiante neste curso trataremos com mais detalhes do conceito de alocação
dinâmica.
Uma vez que você tenha alocado um array, é possível acessar um elemento
específico do array especificando seu índice dentro dos colchetes. Todos os índices
de arrays começam em zero. Por exemplo, a linha abaixo atribui o valor 28 ao
segundo elemento de num_dias_mes:
num_dias_mes[1] = 28;
A linha abaixo exibe o valor armazenado no índice 3:
System.out.println(num_dias_mes[3]);
Para ilustrar todos esses conceitos, eis um programa que cria um array com
o número de dias de cada mês:
Listagem: ArrayUni.Java
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
23
1 // ArrayUni.java
2 // Demonstra um array unidimensional.
3 class ArrayUni
4{
5 public static void main(String args[])
6 {
7
int num_dias_mes[];
8
num_dias_mes = new int[12];
9
// Janeiro.
10
num_dias_mes[0] = 31;
11
12
// Fevereiro.
13
num_dias_mes[1] = 28;
14
15
// Março, e assim
16
// por diante.
17
num_dias_mes[2] = 31;
18
19
num_dias_mes[3] = 30;
20
num_dias_mes[4] = 31;
21
num_dias_mes[5] = 30;
22
num_dias_mes[6] = 31;
23
num_dias_mes[7] = 31;
24
num_dias_mes[8] = 30;
25
num_dias_mes[9] = 31;
26
num_dias_mes[10] = 30;
27
28
// Dezembro.
29
num_dias_mes[11] = 31;
30
System.out.println("Abril tem "
31
+ num_dias_mes[3] +
32
" dias.");
33 } // Fim de main()
34 } // Fim da classe ArrayUni.
Ao ser executado, este programa exibe a seguinte saída:
Abril tem 30 dias.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
24
Observe na linha 10 que, conforme mencionamos anteriormente, os índices
dos arrays em Java começam em zero. É por isso que o número de dias de abril
está em num_dias_mes[3], conforme mostrado na linha 19.
Podemos combinar em uma única linha a declaração da variável array (linha
7) com a alocação do próprio array (linha 8), conforme mostrado abaixo:
int num_dias_mes[] = new int[12];
Esta é a forma mais usada em programas Java profissionais.
Os arrays podem ser inicializados no momento da declaração.
O processo é muito parecido com aquele usado para inicializar tipos
simples. Um inicializador de array é uma lista de expressões separadas por
vírgulas contidas entre chaves { }. As vírgulas separam os valores dos elementos
do array. O array será automaticamente criado com tamanho certo para conter o
número de elementos especificados no inicializador do array. Não há necessidade
de usar new.
O exemplo abaixo é uma nova versão do programa ArrayUni.java:
Listagem: ArrayUni2.java
1 // ArrayUni2.java
2 // Uma segunda versão
3 // ilustrando o uso
4 // de um array unidimensional.
5 class ArrayUni2
6{
7 public static void main(String args[])
8 {
9
// Declara e inicializa
10
// automaticamente o array.
11
int num_dias_mes[] = {31, 28, 31, 30, 31, 30,
12
31, 31, 30, 31, 30, 31};
13
System.out.println("Abril tem " +
14
num_dias_mes[3] +
15
" dias.");
16 } // Fim de main()
17 } // Fim da classe ArrayUni2.
Ao ser executado, este programa gera a mesma saída da versão anterior.
Abril tem 30 dias.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
25
Observe nas linhas 11 e 12 que, já ao ser criado, o array de inteiros é
inicializado com o número de dias de cada mês.
Java faz uma rigorosa checagem para assegurar que o programador não
tente armazenar ou referenciar valores fora do alcance do array. O sistema
runtime de Java faz essa checagem para ter certeza de que todos os índices do
array estejam dentro da faixa correta. Neste aspecto, Java é fundamentalmente
diferente de C/C++, que não faz nenhuma checagem de limites em tempo de
execução. Por exemplo, o sistema runtime faz a checagem do valor de cada índice
do array num_dias_mes para assegurar que todos estejam dentro da faixa de 0 a
11, inclusive. Se tentarmos acessar um elemento fora do alcance do array,
usando como índice um número negativo ou um número maior que o
comprimento do array, isso causará um erro de tempo de execução.
O exemplo abaixo utiliza um array unidimensional para encontrar a média
de um conjunto de números.
Listagem: MediaArray.Java
1 // MediaArray.java
2 // Utiliza um array
3 // para encontra a média
4 // entre vários números.
5
6 class MediaArray
7{
8 public static void main(String args[])
9 {
10
double numeros[] = {3.14, 7.2, 10.17, 14.31, 15.49};
11
double resultado = 0;
12
int contador;
13
14
for(contador = 0; contador < 5; contador++)
15
resultado = resultado + numeros[contador];
16
17
System.out.println("Valor medio = " +
18
resultado / 5);
19 } // Fim de main().
20 } // Fim da classe MediaArray.
Ao ser executado, MediaArray.java produz a seguinte saída:
Valor medio = 10.062000000000001
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
26
19. Arrays multidimensionais
Os arrays multidimensionais são implementados em Java na forma de
arrays de arrays. Tais arrays de arrays parecem e funcionam exatamente como
arrays multidimensionais. Porém existem algumas pequenas diferenças.
Para declarar uma variável array multidimensional, especificamos cada
índice adicional usando mais um par de colchetes. Por exemplo, a seguinte linha
declara uma variável array bidimensional chamada array2d.
int array2d[][] = new int[4][5];
Essa declaração aloca um array de ints de 4 por 5 e o atribui a array2d.
Internamente, essa matriz é implementada como um array de arrays de int,
representado pelo seguinte layout:
[0][0]
[1][0]
[2][0]
[3][0]
[0][1]
[1][1]
[2][1]
[3][1]
[0][2]
[1][2]
[2][2]
[3][2]
[0][3]
[1][3]
[2][3]
[3][3]
[0][4]
[1][4]
[2][4]
[3][4]
Observe que o primeiro índice representa a linha e o segundo índice
representa a coluna.
O programa abaixo exemplifica um array bidimensional:
Listagem: Array2D.java
1 // Array2D.java
2 // Ilustra o uso
3 // de um array
4 // bidimensional.
5
6 class Array2D
7{
8 public static void main(String args[])
9 {
10
int array2d[][] = new int[4][5];
11
int i, j, k = 0;
12
13
// Preenche o array.
14
for(i = 0; i < 4; i++)
15
for(j = 0; j < 5; j++)
16
{
17
array2d[i][j] = k;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
27
18
k++;
19
} // Fim de for(j = 0...
20
21
// Exibe o conteúdo do
22
// array na tela.
23
for(i = 0; i < 4; i++)
24
{
25
for(j = 0; j < 5; j++)
26
System.out.print(array2d[i][j] + "\t");
27
System.out.println();
28
} // Fim de for(i = 0...
29 } // Fim de main()
30 } // Fim da classe Array2d.
Eis a saída de Array2D.java:
0
5
10
15
1
6
11
16
2
7
12
17
3
8
13
18
4
9
14
19
Este programa preenche cada elemento do array da esquerda para a direita,
de cima para baixo com os números de 0 a 19 (linhas 14 a 19). Depois exibe o
array preenchido na tela (linhas 23 a 28), com os elementos separados por um
caractere de tabulação (\t).
Quando alocamos memória para um array multidimensional, somente
precisamos especificar a memória para a primeira dimensão, a da esquerda. As
dimensões restantes podem ser alocadas separadamente. Por exemplo, o código
abaixo aloca memória para a primeira dimensão de array2d quando ele é
declarado. A segunda dimensão é alocada manualmente para cada uma das
linhas:
int array2d[][] = new int[4][];
array2d[0] = new int[5];
array2d[1] = new int[5];
array2d[2] = new int[5];
array2d[3] = new int[5];
Embora neste exemplo não haja nenhuma vantagem em alocar
individualmente cada linha, há ocasiões em que isso pode ser muito útil.
Por exemplo, quando alocamos as dimensões manualmente, não é
necessário alocar o mesmo número de elementos para cada dimensão. Como
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
28
afirmamos anteriormente, os arrays multidimensionais são na verdade arrays de
arrays. Portanto, o comprimento de cada array pode ser escolhido livremente.
O programa abaixo cria um array bidimensional no qual os tamanhos da
segunda dimensão são desiguais:
Listagem: ArrayDesigual.Java
1 // ArrayDesigual.java
2 // Ilustra a alocação
3 // manual de tamanhos
4 // desiguais para
5 // a segunda dimensão de
6 // um array.
7
8 class ArrayDesigual
9{
10 public static void main(String args[])
11 {
12
// A declaração
13
// especifica somente a
14
// primeira dimensão.
15
int array2d[][] = new int[4][];
16
17
// Aloca manualmente
18
// a dimensão de cada
19
// linha do array.
20
array2d[0] = new int[1];
21
array2d[1] = new int[2];
22
array2d[2] = new int[3];
23
array2d[3] = new int[4];
24
25
// Preenche o array.
26
int i, j, k = 0;
27
for(i = 0; i < 4; i++)
28
for(j = 0; j < i + 1; j++)
29
{
30
array2d[i][j] = k;
31
k++;
32
} // Fim de for(j = 0...
33
34
// Exibe na tela
35
// o array preenchido.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
29
36
for(i = 0; i < 4; i++)
37
{
38
for(j = 0; j < i + 1; j++)
39
System.out.print(array2d[i][j] + "\t");
40
System.out.println();
41
} // Fim de for(i = 0...
42 } // Fim de main()
43 } // Fim da classe ArrayDesigual.
Este programa gera a seguinte saída:
0
1
3
6
2
4
7
5
8
9
Na linha 15, o programa ArrayDesigual.java cria um array bidimensional
especificando somente a primeira dimensão. Depois, nas linhas 20 a 23, a
segunda dimensão é especificada, com valores diferentes para cada uma das
linhas. Nas linhas 27 a 32, o array é preenchido com os números de 0 a 9.
Finalmente, nas linhas 36 a 41, o conteúdo do array é exibido na tela, com os
elementos separados por um caractere de tabulação (\t).
Eis o layout do array criado por ArrayDesigual.java:
[0][0]
[1][0] [1][1]
[2][0] [2][1] [2][2]
[3][0] [3][1] [3][2] [3][3]
Na maioria das aplicações, o uso de arrays multidimensionais desiguais ou
irregulares não é recomendável. Isto porque eles vão contra a forma intuitiva
como as pessoas visualizam um array multidimensional.
Porém há situações em que eles são muito eficientes. Por exemplo, no caso
de um array bidimensional muito grande e que seja esparsamente preenchido.
Esparsamente preenchido é usado aqui no sentido de que muitos dos elementos
do array não são utilizados. Neste caso, um array irregular pode ser uma boa
solução.
Arrays multidimensionais também podem ser inicializados. Para isso, basta
envolver o inicializador de cada dimensão em um par de chaves { }.
O programa abaixo ilustra a criação de uma array inicializado:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
30
Listagem: ArrayInic.Java
1 // ArrayInic.java
2 // Ilustra inicialização
3 // de um array
4 // bidimensional.
5
6 class ArrayInic
7{
8 public static void main(String args[])
9 {
10
// Declara e inicializa
11
// o array.
12
double arrayInic[][] =
13
{
14
{0 * 0, 1 * 0, 2 * 0, 3 * 0},
15
{0 * 1, 1 * 1, 2 * 1, 3 * 1},
16
{0 * 2, 1 * 2, 2 * 2, 3 * 2},
17
{0 * 3, 1 * 3, 2 * 3, 3 * 3}
18
}; // Fim do inicializador.
19
20
// Exibe na tela o conteúdo
21
// do array inicializado.
22
int i, j;
23
for(i = 0; i < 4; i++)
24
{
25
for(j = 0; j < 4; j++)
26
System.out.print(arrayInic[i][j] + " ");
27
System.out.println();
28
} // Fim de for(i = 0...
29 } // Fim de main()
30 } // Fim da classe ArrayInic.
Nas linhas 12 a 18, ArrayInic.java cria um array e o inicializa de forma que
cada elemento contenha o produto do índice da coluna multiplicado pelo índice da
linha. Isso serve para mostrar também que é possível usar expressões, além de
valores literais dentro dos inicializadores de arrays.
Nas linhas 22 a 28, o conteúdo do array preenchido é exibido na tela.
Eis a saída gerada por ArrayInic.java:
0.0 0.0 0.0 0.0
0.0 1.0 2.0 3.0
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
31
0.0 2.0 4.0 6.0
0.0 3.0 6.0 9.0
O programa abaixo ilustra o uso de um array tridimensional:
Listagem: Array3D.Java
1 // Array3D.java
2 // Ilustra o uso de
3 // um array tridimensional.
4
5 class Array3D
6{
7 public static void main(String args[])
8 {
9
int array3d[][][] = new int[3][4][5];
10
11
// Preenche o array.
12
int i, j, k;
13
for(i = 0; i < 3; i++)
14
for(j = 0; j < 4; j++)
15
for(k = 0; k < 5; k++)
16
array3d[i][j][k] = i * j * k;
17
18
// Exibe na tela o
19
// conteúdo do array.
20
for(i = 0; i < 3; i++)
21
{
22
for(j = 0; j < 4; j++)
23
{
24
for(k = 0; k < 5; k++)
25
System.out.print(array3d[i][j][k] +
26
"\t");
27
System.out.println();
28
} // Fim de for(j = 0...
29
System.out.println();
30
} // Fim de for(i = 0...
31 } // Fim de main()
32 } // Fim da classe Array3D.
A linha 9 declara e aloca um array tridimensional, 3 por 4 por 5. Nas linhas
13 a 16, cada elemento é preenchido com o produto de seus índices. Finalmente,
o conteúdo do array preenchido é exibido na tela (linhas 20 a 30).
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
32
Array3D.java gera a seguinte saída:
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
2
3
0
2
4
6
0
3
6
9
0
4
8
12
0
0
0
0
0
2
4
6
0
4
8
12
0
6
12
18
0
8
16
24
Java permite ainda uma outra maneira de declarar um array. Por exemplo,
compare a declaração:
int num_dias_mes[];
com a forma:
int[] num_dias_mes;
Nesta segunda forma, os colchetes [ ] vêm logo depois do especificador de
tipo, e não depois do nome da variável de array. Similarmente, as duas
declarações abaixo são equivalentes:
int num_dias_mes[] = new int[12];
int[] num_dias_mes = new int[12];
Este tipo de declaração aplica-se também a arrays multidimensionais,
conforme ilustrado abaixo:
int array2d[][] = new int[4][5];
int[][] array2d = new int[4][5];
Tecnicamente, não há nenhuma diferença entre esses dois estilos de
declaração de arrays. A escolha de uma das duas formas depende totalmente do
gosto do programador.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
33
20. Introdução a strings
Falamos sobre os vários tipos de dados básicos de Java, mas não
mencionamos strings de texto. Também na discussão sobre arrays, as strings não
foram mencionadas. O fato é que o tipo string de Java, chamado String, não é um
tipo simples. Também não é apenas um array de caracteres como acontece em
C/C++.
Java define uma classe chamada String para representar as strings de texto
como objetos. Por isso, para entender por completo as strings em Java é
necessário ter um entendimento de diversos aspectos relacionados com objetos.
Assim, o tipo String será discutido em outro ponto deste curso, depois da
descrição dos objetos.
Contudo, para que possamos usar strings nos próximos exemplos,
apresentamos aqui uma breve introdução.
O tipo String é usado para declarar variáveis de strings de texto. Também é
possível declarar arrays de strings. Uma string de texto entre aspas " " pode ser
atribuída a uma variável String, conforme mostrado no exemplo abaixo. Uma
variável do tipo String também pode ser atribuída a outra variável do tipo String.
Um objeto do tipo String pode ser usado como argumento para println().
Por exemplo, considere o seguinte fragmento:
String str1 = "Exemplo de texto";
System.out.println(str1);
Neste exemplo, str1 é um objeto do tipo String. A string "Exemplo de texto"
é atribuída ao objeto str1. Depois, essa string é exibida com um comando que usa
o método println().
Como veremos adiante, os objetos String têm recursos e atributos especiais
que os tornam bastante poderosos e fáceis de usar. Contudo por enquanto, nos
exemplos das próximas lições, usaremos apenas os recursos mais simples dos
objetos String.
Outro aspecto importante a ressaltar é que, no trabalho com strings e
arrays em Java, o uso de ponteiros não tem nenhuma importância. Isso é
totalmente diferente do que acontece em C/C++.
Na verdade, Java não suporta nem permite o uso de ponteiros. Ou melhor
dizendo, Java não permite que os ponteiros possam ser acessados ou modificados
pelo programador.
Isso é compatível com a filosofia de segurança de Java. A utilização de
ponteiros da forma como eles aparecem em C/C++ possibilitaria aos applets Java
romper a barreira de proteção entre o ambiente de execução Java e o
computador hospedeiro.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
34
Isto porque um ponteiro pode receber qualquer endereço da memória mesmo endereços que poderiam estar fora do sistema runtime de Java. Como
C/C++ utiliza ponteiros extensivamente, você poderia pensar que essa perda
representa uma desvantagem significativa para Java. Porém isso não é verdade.
Java é projetado de tal maneira que, desde que você fique dentro dos limites do
ambiente de execução, nunca haverá necessidade de usar ponteiros.
21. Os operadores aritméticos
Os operadores de Java podem ser divididos em quatro grupos principais:
·
·
·
·
aritméticos
bit-a-bit
relacionais
lógicos
Java define também alguns operadores adicionais que tratam de algumas
situações especiais.
Nesta lição, descreveremos os operadores aritméticos de Java. Nas
próximas lições, discutiremos os demais tipos de operadores.
Programadores C/C++ perceberão que a maioria dos operadores de Java
funciona da mesma forma que em C/C++. Porém, atenção: existem algumas
diferenças sutis, de modo que recomenda-se um estudo cuidadoso.
Os operadores aritméticos são usados em expressões matemáticas da
mesma forma que são usados em álgebra. A tabela abaixo lista os operadores
aritméticos:
Operador Resultado
+
Adição
Subtração (também menos unário)
*
Multiplicação
/
Divisão
%
Módulo (resto da divisão)
++ Incremento
+= Adição e atribuição
-=
Subtração e atribuição
*=
Multiplicação e atribuição
/=
Divisão e atribuição
%= Módulo e atribuição
-Decremento
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
35
Os operandos dos operadores aritméticos devem ser de um tipo numérico.
Não é possível usá-los com o tipo boolean, mas eles podem ser usados com o tipo
char, já que o tipo char em Java é um subconjunto de int.
As operações aritméticas fundamentais são
·
·
·
·
adição
subtração
multiplicação
divisão
Em Java, elas se comportam como seria de se esperar em se tratando de
tipos numéricos. O operador menos ( - ) tem também uma forma unária, que
torna negativo um operando isolado.
Lembre-se que quando o operador de divisão é aplicado a um tipo inteiro,
não há componente fracional acoplado ao resultado.
O exemplo abaixo demonstra os operadores aritméticos e a diferença entre
a divisão de ponto flutuante e a divisão inteira:
Listagem: AritmBasica.java
1 //\ AritmBasica.java
2 // Ilustra os operadores
3 // aritméticos básicos.
4 class AritmBasica
5{
6 public static void main(String args[])
7 {
8
// Aritmética com
9
// inteiros.
10
System.out.println("Aritmetica com inteiros:");
11
int a = 2 + 2;
12
int b = a * 3;
13
int c = b / 4;
14
int d = c - a;
15
int e = -d;
16
System.out.println("a = 2 + 2 = " + a);
17
System.out.println("b = a * 3 = " + b);
18
System.out.println("c = b / 4 = " + c);
19
System.out.println("d = c - a = " + d);
20
System.out.println("e = -d = " + e);
21
22
// Aritmética com
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
36
23
// doubles.
24
System.out.println("\nAritmetica c/ ponto flutuante:");
25
double ad = 2 + 2;
26
double bd = ad * 3;
27
double cd = bd / 4;
28
double dd = cd - ad;
29
double ed = -dd;
30
System.out.println("ad = 2 + 2 = " + ad);
31
System.out.println("bd = ad * 3 = " + bd);
32
System.out.println("cd = bd / 4 = " + cd);
33
System.out.println("dd = cd - ad = " + dd);
34
System.out.println("ed = -dd = " + ed);
35 } // Fim de main()
36 } // Fim da classe AritmBasica.
AritmBasica.java gera a seguinte saída:
Aritmetica com inteiros:
a=2+2=4
b = a * 3 = 12
c=b/4=3
d = c - a = -1
e = -d = 1
Aritmetica c/ ponto flutuante:
ad = 2 + 2 = 4.0
bd = ad * 3 = 12.0
cd = bd / 4 = 3.0
dd = cd - ad = -1.0
ed = -dd = 1.0
As linhas 11 a 15 executam várias operações aritméticas fundamentais com
inteiros. Os resultados dessas operações são então mostrados na tela (linhas 16 a
20).
Depois, nas linhas 25 a 29, são executadas várias operações aritméticas
fundamentais com ponto flutuante, usando o tipo double. Os resultados dessas
operações são então mostrados na tela (linhas 30 a 34).
O operador módulo, representado pelo caractere de porcentagem %,
retorna o resto de uma operação de divisão. Em Java o operador % pode também
ser aplicado a tipos de ponto flutuante, além de tipos inteiros. Eis um exemplo
que demonstra o uso do operador %:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
37
Listagem: ModuloDemo.java
1 // ModuloDemo.java
2 // Ilustra o uso do
3 // operador módulo.
4
5 class ModuloDemo
6{
7 public static void main(String args[])
8 {
9
int intVar = 37;
10
double doubleVar = 37.54;
11
12
System.out.println("intVar modulo 10 = " +
13
intVar % 10);
14
System.out.println("doubleVar modulo 10 = " +
15
doubleVar % 10);
16 } // Fim de main()
17 } // Fim da classe ModuloDemo.
Ao ser executado, ModuloDemo.java produz a seguinte saída:
intVar modulo 10 = 7
doubleVar modulo 10 = 7.539999999999999
Nas linhas 9 e 10, ModuloDemo.java define uma variável int e outra double.
Depois, nas linhas 12 a 15, aplica o operador módulo % a essas variáveis e exibe
o resultado na tela.
Java oferece operadores especiais que podem ser usados para combinar
uma operação aritmética com uma atribuição.
Por exemplo, operações do tipo:
x = x + 15;
são bastante comuns em programação.
Com o operador de adição e atribuição de Java, essa linha pode ser
reescrita da seguinte forma:
x += 15;
As duas versões acima executam a mesma ação: acrescentam 15 ao valor
de x.
Eis um outro exemplo:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
38
intVar = intVar % 10;
que pode ser reescrito assim:
intVar %= 10;
Neste caso, o operador %= obtém o resto da divisão intVar / 10 e coloca o
resultado de volta na variável intVar. Existem operadores de atribuição para todos
os operadores aritméticos binários.
Os operadores de atribuição proporcionam dois benefícios:
·
Reduzem o trabalho de digitação, porque funcionam como uma versão
mais curta das operações equivalentes
·
São implementados mais eficientemente pelo sistema runtime de Java
Por isso, os operadores de atribuição são usados com freqüência em
programas Java profissionais.
Eis um exemplo que demonstra o uso de vários operadores de atribuição:
Listagem: OperAtrib.java
1 // OperAtrib.java
2 // Ilustra o uso
3 // dos operadores de
4 // atribuição.
5
6 class OperAtrib
7{
8 public static void main(String args[])
9 {
10
int x = 5;
11
int y = 6;
12
int z = 7;
13
14
System.out.println("Valores iniciais: ");
15
System.out.println("x = " + x +
16
", y = " + y +
17
", z = " + z +
18
"\n");
19
x += 3;
20
System.out.println("Depois de x += 3, x = "
21
+ x + "\n");
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
39
22
23
y *= 4;
24
System.out.println("Depois de y *= 4, y = "
25
+ y + "\n");
26
27
z += x * y;
28
System.out.println("Depois de z += x * y, z = "
29
+ z + "\n");
30
31
z %= 6;
32
System.out.println("Depois de z %= 6, z = "
33
+ z + "\n");
34 } // Fim de main()
35} // Fim da classe OperAtrib.java.
A saída deste programa é mostrada abaixo:
Valores iniciais:
x = 5, y = 6, z = 7
Depois de x += 3, x = 8
Depois de y *= 4, y = 24
Depois de z += x * y, z = 199
Depois de z %= 6, z = 1
Nas linhas 10, 11 e 12, OperAtrib.java define três variáveis inteiras, cujos
valores iniciais são mostrados nas linhas 15 a 18. Depois, nas linhas 19 a 33, são
executadas várias operações utilizando os operadores de atribuição. A cada
operação, os novos valores das variáveis são exibidos na tela.
Os caracteres ++ e -- representam os operadores de incremento e
decremento de Java.
Esses operadores têm algumas propriedades especiais que os tornam
bastante interessantes. Vamos começar revendo exatamente o que fazem os
operadores de incremento e decremento.
O operador de incremento acrescenta 1 ao valor de seu operando. O
operador de decremento diminui 1 do valor de seu operando. Por exemplo, o
comando
i = i + 1;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
40
pode ser reescrito da seguinte forma, usando o operador de incremento:
i++;
Da mesma forma, o comando
i = i - 1;
é equivalente a
i--;
Esses operadores podem aparecer de duas formas:
·
·
como posfixo (depois do operando) Exemplo: i++, i-como prefixo (antes do operando) Exemplo: ++i, --i
Nos exemplos anteriores, não faz nenhuma diferença se usarmos as formas
de prefixo e de posfixo. Contudo, quando os operadores de incremento e
decremento fazem parte de uma expressão mais longa, ocorre uma diferença
sutil, mas poderosa. Na forma de prefixo, o operando é incrementado ou
decrementado antes que o valor seja obtido para uso na expressão. Na forma de
posfixo, o valor anterior é obtido para uso na expressão e depois o operando é
modificado. Por exemplo:
i = 25;
j = ++i;
Neste caso, j recebe o valor 26, porque o incremento ocorre antes do valor
de i ser atribuído a j. Portanto, a linha
j = ++i;
é equivalente às duas linhas abaixo:
i = i + 1;
j = i;
Contudo, quando o trecho é reescrito como:
i = 25;
j = i++;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
41
o valor de i é obtido antes que o operador de incremento seja executado, de
modo que o valor atribuído a j é 25. Observe que em ambos os casos, o valor
final de i passa a ser 26. Neste último caso, a linha j = i++; é equivalente ao
seguinte trecho:
j = i;
i = i + 1;
O seguinte programa demonstra o operador de incremento:
Listagem: IncrDemo.java
1 // IncrDemo.java
2 // Ilustra o uso do
3 // operador ++
4
5 class IncrDemo
6{
7 public static void main(String args[])
8 {
9
int a = 1;
10
int b = 2;
11
int c;
12
int d;
13
14
System.out.println("Valores iniciais: ");
15
System.out.println("a = " + a +
16
", b = " + b);
17
18
c = ++b;
19
System.out.println("c = ++b = " + c);
20
21
d = a++;
22
System.out.println("d = a++ = " + d);
23
24
c++;
25
System.out.println("c depois de c++ = " + c);
26 } // Fim de main()
27 } // Fim da classe IncrDemo.
A saída deste programa é a seguinte:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
42
Valores iniciais:
a = 1, b = 2
c = ++b = 3
d = a++ = 1
c depois de c++ = 4
Nas linhas 9 a 12, IncrDemo.java declara algumas variáveis inteiras. Nas
linhas 15 e 16, os valores iniciais das variáveis a e b.
Nas linhas 18 a 25, o operador de incremento ++ é usado várias vezes,
com os resultados sendo exibidos na tela.
22. Operadores relacionais
Conforme sugere o nome, os operadores relacionais determinam
relacionamentos entre os operandos. Ou seja, eles determinam igualdade e
ordenação.
Os operadores relacionais de Java são os seguintes:
Operador Resultado
== Igual a
!=
Diferente de
>
Maior do que
<
Menor do que
>= Maior do que ou igual a
<= Menor do que ou igual a
O resultado de uma operação relacional é um valor boolean. Os operadores
relacionais são muitas vezes usados em expressões que controlam o comando if e
os vários comandos de loops.
Qualquer tipo em Java, inclusive inteiros, números de ponto flutuante,
caracteres e booleanos, pode ser comparado usando o teste de igualdade == ou
de desigualdade !=. Observe que em Java, a igualdade é denotada com dois
sinais de igualdade ==, e não com apenas um. Um único sinal de igualdade = na
verdade representa o operador de atribuição.
Já os operadores de ordenamento somente podem ser usados para
comparar tipos numéricos. Isto é, somente operandos inteiros, de ponto flutuante
e caracteres podem ser comparados em termos de maior do que ou menor do
que.
Como já dissemos, o resultado produzido por um operador relacional é um
valor booleano. Por exemplo, o seguinte trecho de código é perfeitamente válido:
int x = 4;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
43
int y = 1;
boolean b = x < y;
No caso, o resultado de x < y, ou seja, que é falso, é armazenado na
variável booleana b.
Em Java, true e false são valores não-numéricos, que não têm relação
direta com zero ou não-zero. Portanto, para testar zero ou não-zero, é preciso
usar operadores relacionais explicitamente.
23. Operadores lógicos booleanos
Os operadores lógicos booleanos mostrados nesta lição operam somente
sobre operandos booleanos. Todos os operadores lógicos booleanos operam sobre
valores booleanos e dão como resultado um valor booleano.
Operador Resultado
&
AND lógico
|
OR lógico
^
XOR lógico (OR exclusivo)
||
OR de curto circuito
&&
AND de curto circuito
!
Não unário lógico
&= AND de atribuição
|=
OR de atribuição
^= XOR de atribuição
== Igual a
!=
Diferente de
?:
if...else ternário
Os operadores lógicos booleanos &, | e ^ operam sobre valores booleanos
da mesma maneira que operam sobre os bits de um inteiro. O operador lógico !
inverte o estado booleano:
!true == false
!false == true
A tabela abaixo mostra o efeito de cada operação lógica:
A
False
True
False
B
False
False
True
A|B
False
True
True
A & B A ^ B!A
False False True
False True False
False True True
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
44
True True True True False False
Listagem: BoolDemo.java
1 // BoolDemo.java
2 // Ilustra o uso dos
3 // operadores lógicos
4 // booleanos.
5
6 class BoolDemo
7{
8 public static void main(String args[])
9 {
10
boolean a = true;
11
boolean b = false;
12
boolean c = a | b;
13
boolean d = a & b;
14
boolean e = a ^ b;
15
boolean f = (!a & b) | (a & !b);
16
boolean g = !a;
17
18
System.out.println("a = " + a);
19
System.out.println("b = " + b);
20
System.out.println("c = a | b = " + c);
21
System.out.println("d = a & b = " + d);
22
System.out.println("e = a ^ b = " + e);
23
System.out.println("f = (!a&b) | (a&!b) = "
24
+ f);
25
System.out.println("g = !a = " + g);
26 } // Fim de main()
27 } // Fim da classe BoolDemo.
Nas linhas 10 e 11, duas variáveis booleanas, a e
inicializadas com os valores true e false.
Depois, nas linhas 12 a 16, outras variáveis são criadas
operações lógicas booleanas executadas sobre as variáveis a e
Por fim, nas linhas 18 a 25 os valores de a e b e
operações lógicas booleanas são mostrados na tela.
BoolDemo.java gera a seguinte saída:
b, são criadas e
e inicializadas com
b.
os resultados das
a = true
b = false
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
45
c = a | b = true
d = a & b = false
e = a ^ b = true
f = (!a&b) | (a&!b) = true
g = !a = false
Esse programa mostra que as regras lógicas aplicadas aos valores
booleanos são em tudo similares àquelas aplicadas aos bits. A saída acima mostra
também que a representação em string de um valor booleano Java é um dos
valores literais true ou false.
24. O operador ?
Java dispõe de um operador ternário especial. Ele pode ser usado para
substituir comandos if...else em alguma situações.
Esse operador é representado por um caractere ?, e embora inicialmente
possa parecer um tanto confuso, com alguma prática ele se torna muito útil.
Eis sua forma geral:
expressao1 ? expressao2 : expressao3
expressao1 podem ser qualquer expressão cuja avaliação resulte em um
valor booleano. Se expressao1 for true, então expressao2 é avaliada; caso
contrário, quem é avaliada é expressao3. O resultado da operação ? é aquele da
expressão avaliada.
Ambas as expressões expressao2 e expressao3 devem retornar o mesmo
tipo de valor, que não pode ser void.
Eis um exemplo de situação em que esse operador pode ser útil:
div = denom == 0 ? 0 : num / denom;
Quando Java avalia essa expressão de atribuição, primeiro a expressão à
esquerda do sinal ? é examinada. Se denom for igual a zero, então e expressão
que vem antes dos dois pontos (no caso, 0
) é avaliada e usada como o valor final da expressão, que é atribuído à
variável div.
Se denom não for igual a zero, então a expressão que vem depois dos dois
pontos (no caso, num / denom) é avaliada e usada como o valor final da
expressão. Em qualquer dos casos, o resultado produzido pelo operador ? é então
atribuído à variável div.
O programa abaixo demonstra o uso do operador ?.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
46
Listagem: TernDemo.java
1 // TernDemo.java
2 // Demonstrate ?.
3 class TernDemo
4{
5 public static void main(String args[])
6 {
7
int i, j;
8
9
i = 25;
10
System.out.print("Valor absoluto de ");
11
System.out.println(i + ": "
12
+ (j = i < 0 ? -i : i));
13
14
i = -25;
15
System.out.print("Valor absoluto de ");
16
System.out.println(i + ": "
17
+ (j = i < 0 ? -i : i));
18 } // Fim de main()
19 } // Fim de classe TernDemo.
TernDemo.java utiliza o operador ternário ? nas linhas 12 e 17 para obter o
valor absoluto de uma variável.
Eis a saída gerada por este programa:
Valor absoluto de 25: 25
Valor absoluto de -25: 25
25. O comando switch
O comando switch de Java permite que a execução de um programa escolha
uma entre múltiplas ramificações. Ele representa uma forma fácil de dirigir a
execução para diferentes partes do código com base no valor de uma expressão.
Por isso, muitas vezes é melhor utilizar um switch do que uma longa série
de comandos if-else-if. Eis a forma geral de um comando switch:
switch(expressao)
{
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
47
case valor1:
// Seqüência
break;
case valor2:
// Seqüência
break;
.
.
.
case valorN:
// Seqüência
break;
default:
// Seqüência
} // Fim do switch
de comandos 1.
de comandos 2.
de comandos N.
de comandos default.
expressao pode retornar qualquer tipo simples; cada um dos valores
especificados nos comandos case deve ser compatível com o tipo de expressao. O
valor de cada case deve ser uma valor literal único (isto é, deve ser uma
constante, e não uma variável). Valores case duplicados não são permitidos.
O comando switch funciona da seguinte maneira: o valor de expressao é
comparado com os valores literais de cada um dos comandos case. Se os valores
forem iguais, a sequência de código que se segue ao comando case
correspondente é executada. Se nenhuma das constantes for igual ao valor de
expressao, o comando default é executado. Contudo, o comando default é
opcional. Se nenhum case for igual ao valor da expressão e não houver um
comando default, nenhuma ação será executada.
O comando break é usado dentro de um switch para terminar uma
seqüência de comandos. Quando um comando break é encontrado, a execução
salta para a primeira linha de código que vem depois do comando switch. Ou
seja, break tem o efeito de provocar um salto para fora do comando switch.
Eis um exemplo de uso do comando switch:
Listagem: SwitchDemo.Java
1
2
3
4
5
6
7
// SwitchDemo.java
// Ilustra o uso de
// switch.
class SwitchDemo
{
public static void main(String args[])
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
48
8 {
9
for(int i = 0; i < 6; i++)
10
switch(i)
11
{
12
case 0:
13
System.out.println("i
14
break;
15
case 1:
16
System.out.println("i
17
break;
18
case 2:
19
System.out.println("i
20
break;
21
case 3:
22
System.out.println("i
23
break;
24
default:
25
System.out.println("i
26
} // Fim do switch.
27 } // Fim de main()
28 } // Fim da classe SwitchDemo.
= 0");
= 1");
= 2");
= 3");
> 3");
A saída produzida por este programa é mostrada abaixo:
i=0
i=1
i=2
i=3
i>3
i>3
Cada vez que atravessamos o loop for (linha 9), os comandos associados
com o case cuja constante é igual ao valor de i são executados (linhas 12 a 23).
Todos os outros cases são ignorados. Depois que i assume um valor maior que 3,
nenhum case corresponde ao valor de i, de modo que os comandos executados
são os que correspondem a default (linha 25).
O comando break é opcional. Se não usarmos a palavra break, a execução
continuará para o case seguinte. Isso pode ser exatamente o que se deseja em
algumas situações.
26. O comando while
O comando while é um dos comandos de iteração de Java. Os outros são:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
49
·
·
for
do-while
Esses comandos criam aquilo que no jargão da programação é conhecido
como loop. Um loop executa repetidas vezes um determinado conjunto de
instruções até que ocorra uma condição previamente estabelecida para o término.
Esses três tipos de loops de Java são capazes de atender a todas as
necessidades de programação.
Examinaremos inicialmente o comando while. Trata-se do tipo de loop mais
fundamental de Java. Ele repete um comando ou um bloco de comandos
enquanto a expressão de controle for verdadeira. Eis sua forma geral:
while (condição)
{
// Corpo do loop.
} // Fim do loop while.
condição pode ser qualquer expressão booleana. O corpo do loop será
executado enquanto a expressão condicional representada por condição for
satisfeita. Quando condição se tornar falsa, o controle passa para a próxima linha
de código imediatamente após o loop. As chaves { } são desnecessárias se o que
estiver sendo repetido for apenas um único comando.
Eis um exemplo simples de loop while:
Listagem: WhileDemo.Java
1 // WhileDemo.java
2 // Ilustra um loop
3 // while básico.
4
5 class WhileDemo
6{
7 public static void main(String args[])
8 {
9
int contador = 10;
10
11
while(contador > 0)
12
{
13
System.out.println(contador + "!");
14
contador--;
15
} // Fim de while.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
50
16
System.out.println("Fogo!!!");
17 } // Fim de main()
18 } // Fim de class WhileDemo.
O loop while (linhas 11 a 15) conta de 10 até 0. A cada passagem pelo loop,
um comando usando o método println() exibe na tela o valor da variável contador
(linha 13). A variável é então decrementada (linha 14), o que garante que o loop
chegará a um final, quando a condição contador > 0 (linha 11) deixar de ser
verdadeira.
Encerrado o loop, a execução prossegue na linha seguinte ao final do loop
(linha 16), fazendo com que seja exibida na tela a string "Fogo!!!".
Ao ser executado, este programa exibe a seguinte saída:
10!
9!
8!
7!
6!
5!
4!
3!
2!
1!
Fogo!!!
Como o loop while avalia sua expressão condicional no início do loop, o
corpo do loop não será executado nenhuma vez se a condição for inicialmente
falsa. Por exemplo, no fragmento abaixo, a chamada a println() nunca é
executada:
int x = 15, y = 16;
while(x > y)
System.out.println("Mensagem nao exibida");
27. O comando do-while
Vimos na lição 26 que, se a expressão condicional que controla um loop
while for inicialmente falsa, então o corpo do loop nunca será executado.
Porém em muitas situações é desejável que o corpo do loop seja executado
pelo menos uma vez, mesmo que a condição de controle seja inicialmente falsa.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
51
Em outras palavras, há ocasiões em que a condição de terminação deve ser
testada no final do loop, e não no início.
Para esses casos, Java oferece um loop que faz exatamente isso: o dowhile. O loop do-while sempre executa seu corpo pelo menos uma vez, porque a
expressão condicional está no final do loop. Sua forma geral é:
do
{
// Corpo do loop.
} while(condição);
Cada iteração do loop do-while executa primeiro o corpo do loop e depois
avalia a expressão condicional representada por condição. Se a expressão for
verdadeira, o loop será repetido. Caso contrário, o loop termina. Como acontece
com todos os loops de Java, a condição deve ser uma expressão booleana.
Eis uma versão revisada do programa WhileDemo.java, apresentado na
lição 26. Esta versão demonstra o loop do-while. Ela gera a mesma saída que a
versão anterior.
Listagem: DoWhileDemo.java
1 // DoWhileDemo.java
2 // Ilustra o uso do
3 // loop do-while.
4
5 class DoWhileDemo
6{
7 public static void main(String args[])
8 {
9
int contador = 10;
10
11
do
12
{
13
System.out.println(contador + " !");
14
contador--;
15
} while(contador > 0);
16
System.out.println("Fogo!!!");
17 } // Fim de main()
18 } // Fim de class DoWhileDemo.
Eis a saída deste programa:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
52
10 !
9!
8!
7!
6!
5!
4!
3!
2!
1!
Fogo!!!
A saída acima mostra que, neste caso, o loop do-while (linhas 11
a 15) tem exatamente o mesmo efeito que o loop while de
WhileDemo.java.
Embora esteja correto, o loop do-while de DoWhileDemo.java poderia ser
reescrito de forma mais eficiente. Eis como isso poderia ser feito:
do
{
System.out.println(contador + " !");
} while(--contador > 0);
Aqui, a expressão (--contador > 0) combina o decremento de contador e o
teste do loop em uma única expressão. Primeiro, o comando --contador é
executado, decrementando contador e retornando seu novo valor. Esse novo
valor é então comparado com zero. Se for maior que zero, o loop continua; caso
contrário, ele é encerrado.
O loop do-while é especialmente útil no processamento de seleções de
menus, porque geralmente é desejável que o corpo do loop seja executado pelo
menos uma vez. Eis um exemplo de uso dessa característica:
Listagem: DoWhileMenu.Java
1
2
3
4
5
6
//
//
//
//
DoWhileMenu.java
Utiliza do-while
para implementar um
menu.
class DoWhileMenu
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
53
7{
8 public static void main(String args[])
9
throws java.io.IOException
10 {
11
char item;
12
13
do
14
{
15
System.out.println("Ajuda sobre:");
16
System.out.println(" 1. if");
17
System.out.println(" 2. switch");
18
System.out.println(" 3. while");
19
System.out.println(" 4. do-while");
20
System.out.println(" 5. for\n");
21
System.out.println(
22
"Digite um numero + <Enter>:");
23
item = (char)System.in.read();
24
} while(item < '1' || item > '5');
25
26
System.out.println("\n");
27
28
switch(item)
29
{
30
case '1':
31
System.out.println("Comando if:\n");
32
System.out.println("if(condicao)");
33
System.out.println(" comando1;");
34
System.out.println("else");
35
System.out.println(" comando2;");
36
break;
37
case '2':
38
System.out.println("Comando switch:\n");
39
System.out.println("switch(expressao) {");
40
System.out.println(" case constante1:");
41
System.out.println("
sequencia1");
42
System.out.println("
break;");
43
System.out.println(" case constante2:");
44
System.out.println("
sequencia2");
45
System.out.println("
break;");
46
System.out.println(" // . . .");
47
System.out.println("}");
48
break;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
54
49
case '3':
50
System.out.println("Comando while:\n");
51
System.out.println("while(condicao)");
52
System.out.println(" comando;");
53
break;
54
case '4':
55
System.out.println("Comando do-while:\n");
56
System.out.println("do {");
57
System.out.println(" comando;");
58
System.out.println("} while (condicao);");
59
break;
60
case '5':
61
System.out.println("Comando for:\n");
62
System.out.println(
63
"for(inic; condicao; iteracao)");
64
System.out.println(" comando;");
65
break;
66
} // Fim de switch.
67 } // Fim de main()
68 } // Fim de class DoWhileMenu.
O uso do loop do-while (linhas 13 a 24) assegura que o menu seja exibido
até que o usuário escolha um número correto, entre 1 e 5 (linha 24). Se for feita
uma escolha inválida, o menu é reexibido.
O número escolhido é então usado em um comando switch (linhas 28 a 66)
para exibir na tela um exemplo de uso correspondente à palavra-chave de Java
escolhida.
Como o menu deve ser exibido no mínimo uma vez, o do-while é o loop
perfeito para essa tarefa.
Observe na linha 23 que os caracteres são lidos do teclado com uma
chamada a System.in.read(). Este é um dos métodos de entrada por console de
Java. Ele lê caracteres da entrada padrão. Esses caracteres são retornados como
ints. Por isso o valor retornado é convertido para char com um cast na linha 23.
Por default, a entrada padrão utiliza um buffer, de modo que é preciso
pressionar Enter para que os caracteres digitados sejam enviados para o
programa.
A entrada por console de Java é bastante limitada, e é complicado trabalhar
com ela. Além disso, a maioria dos programas e applets Java do mundo real serão
gráficos e usarão uma janela. Por esse motivo, não dedicamos muito espaço à
entrada por console. Contudo, ela é útil neste contexto.
Observe ainda que, como estamos usando System.in.read(), o programa
precisa especificar a cláusula throws java.io.IOException (linha 9). Essa linha é
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
55
necessária para manipular erros de entrada. Isso faz parte dos recurso de Java
para manuseio de exceções.
Eis um exemplo de execução de DoWhileMenu.java:
Ajuda sobre:
1. if
2. switch
3. while
4. do-while
5. for
Digite um numero + <Enter>:
4
28. Classes em Java
O conceito de classe é um dos alicerces da linguagem Java. Isto é
conseqüência do fato de que Java é totalmente orientado a objetos. Uma classe
define a forma e a natureza de um objeto. Por isso, o conceito de classe forma a
base da programação orientada a objetos em Java.
Qualquer conceito que se queira implementar em um programa Java deve
ser encapsulado dentro de uma classe. Pelo fato das classes serem tão
fundamentais em Java, esta lição e as próximas tratarão deste assunto com
profundidade.
Apresentaremos os elementos básicos de uma classe e mostraremos como
uma classe pode ser usada para criar objetos. Trataremos também de assuntos
relacionados com classes, como métodos, construtores e a palavra-chave this.
Na verdade, no momento em que você escreveu seu primeiro programa em
Java, você já usou classes. Porém, até agora, as classes somente foram usadas
em sua forma mais elementar.
As classes criadas nas lições anteriores basicamente existiam simplesmente
para encapsular o método main(), que tem sido usado para demonstrar o básico
da sintaxe de Java.
Veremos agora que as classes são muito mais poderosas do que aquilo que
foi demonstrado anteriormente. Talvez a coisa mais importante a entender sobre
as classes é que elas definem um novo tipo de dado. Por exemplo, se criarmos
uma classe chamada NovoTipo, podemos a partir daí criar objetos do tipo
NovoTipo.
Portanto, uma classe é o carimbo de um objeto, e um objeto é uma
instância de uma classe. Como um objeto é uma instância de uma classe, muitas
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
56
vezes veremos essas duas palavras, objeto e instância, sendo usados de forma
intercambiável.
Quando definimos uma classe, precisamos declarar com exatidão sua forma
e sua natureza. Isso é feito especificando-se os dados que ela contém e o código
que opera sobre esses dados. Embora classes muito simples possam conter
apenas código ou apenas dados, no mundo real, a maior parte das classes
contém ambos. Como veremos, o código de uma classe define a interface para
acessar seus dados.
Uma classe é declarada com o uso da palavra-chave class. As classes que
usamos até este ponto são na verdade exemplos muito limitados desta forma
completa. Como veremos, as classes podem ser muito mais complexas, e
geralmente são.
29. Variáveis de instância
Cada objeto tem suas próprias cópias das variáveis de instância. Isso
significa que se tivermos dois objetos Caixa, cada um deles tem sua própria cópia
das variáveis largura, altura e profundidade.
Alterações nas variáveis de instância de um objeto não têm efeito sobre as
variáveis de instância de outro objeto.
O programa abaixo ilustra esse fato. Ele declara dois objetos da classe
Caixa.
Listagem: CaixaDemo.java
1 // CaixaDemo.java
2 // Ilustra a separação
3 // entre variáveis
4 // de instância.
5
6 class Caixa
7{
8
double largura;
9
double altura;
10 double profundidade;
11 } // Fim de class Caixa.
12
13 class CaixaDemo
14 {
15 public static void main(String args[])
16 {
17
// Declara dois objetos
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
57
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// da classe Caixa,
// chamados caixa1 e
// caixa2.
Caixa caixa1 = new Caixa();
Caixa caixa2 = new Caixa();
double volume1, volume2;
// Atribui valores
// às variáveis de
// instância do
// objeto caixa1.
caixa1.largura = 5;
caixa1.altura = 10;
caixa1.profundidade = 20;
// Atribui valores
// às variáveis de
// instância do
// objeto caixa2.
caixa2.largura = 10;
caixa2.altura = 15;
caixa2.profundidade = 25;
// Calcula o volume
// de caixa1.
volume1 = caixa1.largura *
caixa1.altura *
caixa1.profundidade;
// Calcula o volume
// de caixa2.
volume2 = caixa2.largura *
caixa2.altura *
caixa2.profundidade;
// Exibe o volume
// de caixa1.
System.out.println(
"Volume de caixa1 = " + volume1);
// Exibe o volume
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
58
60
// de caixa2.
61
System.out.println(
62
"Volume de caixa2 = " + volume2);
63 } // Fim de main().
64 } // Fim da classe CaixaDemo.
A saída de CaixaDemo.java é a seguinte:
Volume de caixa1 = 1000.0
Volume de caixa2 = 3750.0
Vemos que os dados definidos para caixa1 (linhas 30 a 32) são
completamente independentes dos dados definidos para caixa2 (linhas 38 a 40).
30. Declarando objetos
Na lição 28, vimos que quando criamos uma classe, estamos criando um
novo tipo de dado. Portanto, podemos declarar e criar objetos desse novo tipo.
Em Java, a criação de objetos de uma classe é um processo feito em duas
etapas:
·
Primeiro, declaramos uma variável do tipo da classe.
·
Segundo, criamos uma cópia física do objeto propriamente dito, e a
atribuimos à variável criada no passo anterior.
A variável criada no primeiro passo acima não define um objeto. Trata-se
simplesmente de uma variável que pode referir-se a um objeto.
No segundo passo é que o objeto é realmente criado. Isso é feito usando-se
o operador new. O operador new aloca dinamicamente a memória para um
objeto, e retorna uma referência ao objeto. Dinamicamente aqui significa em
tempo de execução.
Podemos pensar na referência ao objeto como sendo o endereço de
memória do objeto alocado por new. Essa referência é então armazenada na
variável criada no primeiro passo.
Assim, em Java, todos os objetos de classes devem ser alocados
dinamicamente. Vamos examinar esse procedimento com mais detalhes.
No programa CaixaDemo.java da lição 29, a seguinte linha é usada para
declarar um objeto do tipo Caixa:
Caixa caixa1 = new Caixa();
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
59
Esse comando combina os dois passos descritos acima. Ele pode ser
reescrito da forma abaixo, que mostra cada um dos passos mais claramente:
Caixa caixa1;
// Declara referência
// ao objeto da classe
// Caixa.
caixa1 = new Caixa(); // Aloca o objeto
// propriamente dito
// da classe Caixa.
Acima, a primeira linha declara caixa1 como sendo uma referência a um
objeto do tipo Caixa. Depois que essa linha é executada, caixa1 contém o valor
null, que indica que essa referência ainda não aponta para nenhum objeto real.
Qualquer tentativa de usar caixa1 neste ponto resultará em um erro de
compilação.
A linha seguinte aloca um objeto real e atribui a referência a esse objeto a
caixa1. Depois que a segunda linha é executada, podemos usar caixa1 como se
ele fosse um objeto da classe Caixa. Mas na realidade, caixa1 simplesmente
contém o endereço de memória do objeto Caixa real.
Uma referência a um objeto é similar a um ponteiro para a memória. A
principal diferença, que é uma das bases da segurança em Java, é que o
programador não pode manipular referências da forma como é possível fazer com
ponteiros. Assim, não é possível fazer com que uma referência a um objeto
aponte para um local arbitrário na memória, nem manipulá-la como se fosse um
inteiro.
31. Detalhes sobre new
Dissemos que o operador new aloca memória dinamicamente para um
objeto. Ele tem a seguinte forma geral:
classeVar = new NomeDaClasse();
Onde classeVar é uma variável do tipo do objeto de classe que está sendo
criado. NomeDaClasse é o nome da classe que está sendo instanciada.
O nome da classe seguido de parênteses ( ) especifica o método construtor
da classe. Um método construtor define o que ocorre quando um objeto de uma
classe é criado. Os construtores são uma parte importante de todas as classes e
têm muitos atributos significativos.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
60
No mundo real, a maior parte das classes define explicitamente seus
construtores dentro da definição da classe. Contudo, se um construtor não for
especificado explicitamente, Java fornecerá automaticamente um construtor
default. É isso que acontece com a classe Caixa (lição 30).
Por enquanto, usaremos o construtor default. Em breve, veremos como
definir nossos próprios construtores.
Uma pergunta que pode surgir é: por que não usamos new para criar coisas
como inteiros ou caracteres. A resposta é que em Java, os tipos simples não são
implementados como objetos. Ao invés disso, são implementados como variáveis
"normais". Isso é feito para melhorar a eficiência.
Isto porque, como veremos, os objetos têm muitas características e
atributos que exigem que Java os trate diferentemente dos tipos simples. Ao
evitar de aplicar aos tipos simples a mesma sobrecarga de trabalho que aplica aos
objetos, Java consegue implementá-los de forma mais eficiente.
Porém, veremos que existem versões de objetos dos tipos simples, que
podem ser usadas naquelas situações em que existe necessidade de objetos
completos, no lugar desses tipos.
É importante entender que new aloca memória para um objeto em tempo
de execução. A vantagem desse enfoque é que durante a execução o programa
pode criar exatamente a quantidade de objetos necessários, nem mais nem
menos.
Contudo, como a memória é finita, pode acontecer de new não conseguir
alocar memória para um objeto, por falta de memória suficiente. Se isso
acontecer, uma exceção de tempo de execução é gerada.
Observe que, ao contrário de Java, em muitas linguagens new retorna null
em caso de falha.
Nos exemplos deste curso, que são programas muito curtos, não
precisamos nos preocupar muito com a possibilidade de ficar sem memória.
Porém essa é uma possibilidade real que deve ser levada em conta nos
programas que escrevemos para o mundo real.
Agora, à luz de todas essas informações, podemos rever mais uma vez a
diferença entre uma classe e um objeto.
Uma classe cria um novo tipo de dado, que pode ser usado para criar
objetos. Isto é, uma classe cria uma estrutura lógica que define o relacionamento
entre seus membros.
Quando declaramos um objeto de uma classe, estamos criando uma
instância dessa classe. Assim, uma classe é uma construção lógica. Um objeto é
uma realidade física. Ou seja, um objeto ocupa espaço na memória. É importante
manter essa distinção clara na mente.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
61
32. Entendendo as referências
Um aspecto de Java que precisa ser entendido com muita clareza é a forma
como funcionam as referências. Observe o seguinte exemplo:
Caixa caixa1 = new Caixa();
Caixa caixa2 = caixa1;
À primeira vista, pode parecer que caixa2 está recebendo uma referência a
uma cópia do objeto referenciado por caixa1. Isto é, você poderia pensar que
caixa1 e caixa2 referem-se a objetos separados e distintos. Porém não é isso que
acontece.
Na verdade, depois que esse fragmento é executado, ambas as variáveis,
caixa1 e caixa2 referem-se ao mesmo objeto. A atribuição de caixa1 a caixa2 não
alocou nenhuma memória, nem copiou nenhuma parte do objeto original. Ela
simplesmente fez com que caixa2 refira-se ao mesmo objeto que caixa1.
Assim, quaisquer mudanças feitas no objeto através de caixa2 afetarão o
objeto ao qual caixa1 se refere, já que se trata do mesmo objeto.
Embora caixa1 e caixa2 referenciem o mesmo objeto, essas variáveis não
estão ligadas de qualquer outra forma. Por exemplo, uma atribuição subseqüente
a caixa1 simplesmente desconectará caixa1 do objeto original, sem afetar o
objeto, nem caixa2.
Observe este outro exemplo:
Caixa caixa1 = new Caixa();
Caixa caixa2 = caixa1;
// . . .
caixa1 = null;
Aqui, embora caixa1 tenha sido igualado a null, caixa2 ainda aponta para o
objeto original.
Resumindo: quando você atribui uma variável de referência a objeto a outra
variável de referência a objeto, você não está criando uma cópia do objeto; está
apenas fazendo uma cópia da referência.
33. Entendendo os métodos
Dissemos na lição 29 que as classes geralmente consistem de duas coisas:
variáveis de instância e métodos. O assunto métodos é muito extenso, porque é
deles que Java obtém muito de seu poder e flexibilidade. Por isso, em diferentes
lições deste curso falaremos sobre diversos aspectos dos métodos.
Neste momento, você precisa entender alguns fundamentos dos métodos,
para que possa começar a acrescentar métodos a suas classes.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
62
Esta é a forma geral de um método:
tipo nomeDoMetodo(lista-de-parâmetros)
{
// Corpo do método.
}
tipo especifica o tipo de dados retornado pelo método. Pode ser qualquer
tipo válido, inclusive os tipos de classes criadas pelo programador.
Se um método não retorna nenhum valor, seu valor retornado deve ser
declarado como sendo void.
O nome do método é especificado por nomeDoMetodo. nomeDoMetodo pode
ser qualquer identificador legal, a não ser aqueles já usados por outros itens
dentro do escopo atual.
A lista-de-parâmetros é uma seqüência de pares compostos de um tipo e
um identificador, separados por vírgulas. Os parâmetros são essencialmente
variáveis que recebem o valor dos argumentos passados para o método quando
ele é chamado. Se o método não tem parâmetros, os parênteses deverão estar
vazios.
Métodos que retornam um tipo que não seja void retornam um valor para a
rotina que os chama, usando a seguinte forma do comando return:
return valor;
valor é o valor retornado.
Nas próximas lições, veremos como criar vários tipos de métodos, inclusive
aqueles que recebem parâmetros e aqueles que retornam valores.
34. Acrescentando um método à classe Caixa
Raramente criamos uma classe como Caixa (lição 29) que contém somente
dados, embora isso seja perfeitamente legal em Java.
Na prática, geralmente usamos métodos para acessar as variáveis de
instância definidas pela classe.
São os métodos que definem a interface da maioria das classes. Isso
permite que o implementador da classe oculte o layout interno das estruturas de
dados da classe, por trás das abstrações de métodos. Isso é muito mais seguro e
elegante.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
63
Além de definir métodos que proporcionam acesso aos dados, também
podemos definir métodos que são usados internamente pela própria classe.
Vamos começar acrescentando um método à classe Caixa.
Observe novamente o programa CaixaDemo.java (lição 29). Dentro da
filosofia da orientação a objetos, é muito mais lógico que o cálculo do volume da
caixa seja feito pela própria classe Caixa, e não pela classe CaixaDemo. Como o
volume de uma caixa depende de suas dimensões, faz sentido deixar que a classe
Caixa o calcule.
Vamos fazer isso, acrescentando um método à classe Caixa, conforme
mostra o exemplo abaixo:
Listagem: CaixaDemo2.Java
1 // CaixaDemo2.java
2 // Ilustra o uso de
3 // um método simples.
4
5 class Caixa2
6{
7 double largura;
8 double altura;
9 double profundidade;
10
11 // Calcula e exibe
12 // o volume da caixa.
13 void volume()
14 {
15
System.out.println("Volume = "
16
+ largura * altura * profundidade);
17 } // Fim do método volume()
18 } // Fim de class Caixa2.
19
20 class CaixaDemo2
21 {
22 public static void main(String args[])
23 {
24
// Declara dois objetos
25
// da classe Caixa,
26
// chamados caixa1 e
27
// caixa2.
28
Caixa2 caixa1 = new Caixa2();
29
Caixa2 caixa2 = new Caixa2();
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
64
30
31
// Atribui valores
32
// às variáveis do
33
// objeto caixa1.
34
caixa1.largura = 5;
35
caixa1.altura = 10;
36
caixa1.profundidade = 20;
37
38
// Atribui outros valores
39
// às variáveis do
40
// objeto caixa2.
41
caixa2.largura = 8;
42
caixa2.altura = 12;
43
caixa2.profundidade = 16;
44
45
// Calcula e exibe
46
// o volume
47
// de caixa1, usando
48
// o método volume().
49
caixa1.volume();
50
51
// Calcula e exibe
52
// o volume
53
// de caixa2, usando
54
// o método volume().
55
caixa2.volume();
56
57 } // Fim de main().
58 } // Fim da classe CaixaDemo2.
Este programa gera a seguinte saída:
Volume = 1000.0
Volume = 1536.0
Observe as linhas 49 e 55:
caixa1.volume();
caixa2.volume();
A linha
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
65
caixa1.volume();
invoca o método volume() para o objeto caixa1. Isto é, ela chama volume()
em relação ao objeto caixa1, usando o nome do objeto seguido pelo operador
ponto. Portanto, a chamada caixa1.volume() exibe o volume da caixa definida por
caixa1.
Da mesma forma, a chamada
caixa2.volume();
exibe o volume da caixa definida por caixa2.
Cada vez que volume() é invocado, ele exibe o volume da caixa
especificada.
Vamos estudar com mais detalhes o que acontece quando um método é
chamado.
Quando caixa1.volume() (linha 49) é executado, o sistema runtime de Java
transfere o controle para o código definido dentro de volume() (linha 15).
Depois que os comandos que estão dentro de volume() são executados,
(linhas 15 e 16) o controle volta para a rotina chamadora, e a execução é
retomada na linha de código que se segue à chamada (linha 50).
Podemos dizer que o método é a forma usada por Java para implementar
sub-rotinas.
Há uma coisa muito importante a observar dentro do método volume(): as
variáveis de instância largura, altura e profundidade são referenciadas
diretamente na linha 16, sem necessidade de precedê-las com o nome do objeto
nem o operador ponto. Quando um método utiliza uma variável de instância que é
definida por sua classe, ele faz isso diretamente, sem referência explícita a um
objeto e sem usar o operador ponto.
Não é difícil entender esse fato. Um método é sempre invocado em relação
a um objeto de uma determinada classe. Uma vez que essa invocação acontece, o
objeto é conhecido. Assim, dentro de um método, não há necessidade de
especificar o objeto uma segunda vez. Isso significa que as variáveis largura,
altura e profundidade que aparecem dentro de volume() referem-se
implicitamente às cópias dessas variáveis encontradas dentro do objeto que
invoca volume().
Resumindo:
·
Quando uma variável de instância é acessada por código que não faz
parte da classe na qual essa variável de instância é definida, isso precisa ser feito
através de um objeto, usando o operador ponto.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
66
·
Quando uma variável de instância é acessada por código que faz parte
da mesma classe que a variável de instância, essa variável pode ser referenciada
diretamente.
O mesmo se aplica aos métodos.
35. Métodos com parâmetros
Muitos métodos utilizam parâmetros para realizar suas tarefas. Os
parâmetros permitem que um método seja mais genérico. Ou seja, um método
com parâmetros pode operar sobre diferentes valores de dados e ser usado em
diversas situações.
Por exemplo, observe o método abaixo, que retorna o quadrado de 2:
int quadrado()
{
return 2 * 2;
} // Fim do método quadrado()
Embora esse método retorne de fato o valor de 2 ao quadrado, seu uso é
muito limitado. Contudo, se modificarmos o método de maneira que ele receba
um parâmetro, conforme mostrado a seguir, é possível fazer com que quadrado()
seja muito mais útil.
int quadrado(int i)
{
return i * i;
} // Fim de método quadrado()
Agora, quadrado() retorna o quadrado de qualquer valor para o qual ele
seja chamado. Isto é, quadrado() é agora um método de uso geral, capaz de
computar o quadrado de qualquer valor inteiro, e não apenas de 2, conforme
mostram as linhas abaixo.
int x, y;
x = quadrado(7); // x é igual a 49.
x = quadrado(9); // x é igual a 81.
y = 10;
x = quadrado(y); // x é igual a 100.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
67
Na primeira chamada a quadrado(), o valor 7 é passado para o parâmetro i.
Na segunda chamada, i recebe o valor 9. A terceira chamada passa o valor de y,
que no caso é 10.
Como mostram esses exemplos, quadrado() é capaz de retornar o quadrado
de qualquer valor que lhe é passado.
É importante esclarecer o significado dos termos parâmetro e argumento.
Um parâmetro é uma variável definida por um método, e que recebe um valor
quando o método é chamado. Por exemplo, em quadrado(), i é um parâmetro.
Um argumento é um valor que é passado para um método quando ele é
invocado. Por exemplo, quadrado(10) passa 10 como argumento. Dentro de
quadrado(), o parâmetro i recebe esse valor.
Vamos aplicar esse novo conceito de métodos parametrizados à classe
Caixa. Nos exemplos anteriores, as dimensões de cada caixa precisavam ser
definidas separadamente com o uso de uma seqüência de comandos:
caixa1.largura = 5;
caixa1.altura = 10;
caixa1.profundidade = 20;
Embora esse código funcione, ele é trabalhoso de escrever e sujeito a erros.
Por exemplo, o programador pode facilmente esquecer de definir uma dimensão.
Além disso, em programas Java bem projetados, as variáveis de instância
devem ser acessadas somente através de métodos definidos em sua classe.
Assim, digamos em uma nova versão do programa, podemos alterar o
comportamento de um método. Porém não poderíamos alterar o comportamento
de uma variável de instância exposta, sob pena de comprometer o funcionamento
de todo o programa.
Portanto, um enfoque melhor para definir as dimensões de uma caixa é
criar um método que recebe a dimensão da caixa em seus parâmetros e ajusta
cada variável de instância apropriadamente. Esse conceito é implementado no
seguinte programa:
Listagem: CaixaDemo3.Java
1
2
3
4
5
6
7
8
//
//
//
//
CaixaDemo3.java
Ilustra o uso
de um método
parametrizado.
class Caixa3
{
double largura;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
68
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
double altura;
double profundidade;
// Calcula e retorna
// o volume.
double volume()
{
return largura *
altura *
profundidade;
} // Fim do método volume()
// Define as dimensões
// da caixa.
void defineDim(double larg,
double alt,
double prof)
{
largura = larg;
altura = alt;
profundidade = prof;
} // Fim de defineDim()
} // Fim de class Caixa3.
class CaixaDemo3
{
public static void main(String args[])
{
// Declara dois objetos
// da classe Caixa3,
// chamados caixa1 e
// caixa2.
Caixa3 caixa1 = new Caixa3();
Caixa3 caixa2 = new Caixa3();
// Uma variável para
// conter o valor do volume.
double volumeVar;
// Inicializa cada
// uma das caixas.
caixa1.defineDim(5, 10, 20);
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
69
51
caixa2.defineDim(8, 12, 16);
52
53
// Calcula e exibe
54
// o volume
55
// de caixa1, usando
56
// o método volume().
57
volumeVar = caixa1.volume();
58
System.out.println(
59
"Volume de caixa1 = "
60
+ volumeVar);
61
62
// Calcula e exibe
63
// o volume
64
// de caixa2, usando
65
// o método volume().
66
volumeVar = caixa2.volume();
67
System.out.println(
68
"Volume de caixa2 = "
69
+ volumeVar);
70 } // Fim de main().
71 } // Fim da classe CaixaDemo3.
Vemos nas linhas 50 e 51, o método defineDim() sendo usado para definir
as dimensões de cada caixa. Assim, quando a linha 50
caixa1.defineDim(5, 10, 20);
é executada:
·
·
·
o valor 5 é copiado para o parâmetro larg
o valor 10 é copiado para o parâmetro alt
o valor 20 é copiado para o parâmetro prof
Dentro de defineDim() (linhas 27 a 29), os valores de larg, alt e prof são
atribuídos a largura, altura e profundidade, respectivamente.
Os conceitos apresentados nesta lição e nas anteriores, como chamadas a
métodos, argumentos e parâmetros são muito importantes para a programação
em Java. Por isso, procure entendê-los por completo antes de prosseguir.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
70
36. Construtores
Não é prático inicializar todas as variáveis de uma classe cada vez que uma
instância é criada, mesmo que seja por meio de métodos como defineDim(), no
exemplo da lição anterior.
Seria mais simples e prático se toda a inicialização fosse feita
automaticamente no momento da criação do objeto. Como a necessidade de
inicialização é muito comum, Java permite que os objetos inicializem a si mesmos
quando são criados. Essa inicialização automática é executada através do uso de
um método construtor.
Um construtor é um método que inicializa um objeto imediatamente, no
momento de sua criação. Ele tem o mesmo nome da classe na qual reside, e é
sintaticamente similar a qualquer outro método. Uma vez definido, o construtor é
automaticamente chamado imediatamente após a criação do objeto, antes que o
operador new complete seu trabalho.
Um aspecto diferente do construtor é que ele não têm tipo retornado, nem
mesmo void. Isto acontece porque o tipo retornado pelo construtor de uma classe
é o tipo da própria classe.
A missão do construtor é inicializar o estado interno de um objeto, de modo
que após sua criação, esse objeto esteja totalmente inicializado e pronto para
uso.
O exemplo da classe Caixa da lição anterior pode ser reescrito, de modo que
as dimensões da caixa sejam automaticamente inicializadas quando o objeto for
construído.
Isso é feito com o uso de um construtor no lugar do método defineDim().
Nesta primeira versão, o construtor simplesmente ajusta todas as dimensões da
caixa para um mesmo valor.
Esta versão é mostrada abaixo:
Listagem: CaixaDemo4.Java
1 // CaixaDemo4.java
2 // Ilustra o uso
3 // de um construtor
4 // simples.
5
6 class Caixa4
7{
8 double largura;
9 double altura;
10 double profundidade;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
71
11
12 // Construtor.
13 Caixa4()
14 {
15
System.out.println(
16
"Construindo objeto caixa...");
17
largura = 20;
18
altura = 20;
19
profundidade = 20;
20 } // Fim do construtor Caixa().
21
22 // Calcula e retorna
23 // o volume.
24 double volume()
25 {
26
return largura *
27
altura *
28
profundidade;
29 } // Fim do método volume()
30 } // Fim de class Caixa4.
31
32 class CaixaDemo4
33 {
34 public static void main(String args[])
35 {
36
// Declara dois objetos
37
// da classe Caixa,
38
// chamados caixa1 e
39
// caixa2.
40
Caixa4 caixa1 = new Caixa4();
41
Caixa4 caixa2 = new Caixa4();
42
43
// Uma variável para
44
// conter o valor do volume.
45
double volumeVar;
46
47
// Calcula e exibe
48
// o volume
49
// de caixa1, usando
50
// o método volume().
51
volumeVar = caixa1.volume();
52
System.out.println(
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
72
53
"Volume de caixa1 = "
54
+ volumeVar);
55
56
// Calcula e exibe
57
// o volume
58
// de caixa2, usando
59
// o método volume().
60
volumeVar = caixa2.volume();
61
System.out.println(
62
"Volume de caixa2 = "
63
+ volumeVar);
64 } // Fim de main().
65 } // Fim da classe CaixaDemo4.
Ao ser executado, este programa gera o resultado abaixo:
Construindo objeto caixa...
Construindo objeto caixa...
Volume de caixa1 = 8000.0
Volume de caixa2 = 8000.0
Nas linhas 40 e 41, caixa1 e caixa2 são inicializados pelo construtor Caixa(),
no momento em que são criados. Nas linhas 17 a 19, vemos que o construtor
atribui a todas as caixas as mesmas dimensões, 20 por 20 por 20. Por isso, tanto
caixa1 quanto caixa2 terão o mesmo volume.
O comando println() contido na linha 15, dentro do construtor Caixa4(),
serve apenas para ilustrar o momento em que o construtor é executado.
Normalmente, o construtor apenas inicializa o objeto, sem exibir nenhuma
mensagem.
À luz de nosso novo conhecimento sobre os construtores, vamos fazer uma
rápida revisão do operador new. Como sabemos, quando alocamos um objeto,
devemos usar a seguinte forma geral:
classeVar = new nomeDaClasse();
Agora os parênteses ( ) que aparecem após o nome da classe passam a
fazer sentido. Na verdade, o que está acontecendo é que o construtor da classe
está sendo chamado. Assim, na linha
Caixa4 caixa1 = new Caixa4();
new Caixa4() representa uma chamada ao construtor Caixa4().
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
73
Se o programador não definir explicitamente um construtor para uma
determinada classe, Java cria um construtor default. É por isso que a linha de
código acima funcionava nas versões anteriores de Caixa4, quando ainda não
tínhamos definido o construtor.
O construtor default definido por Java automaticamente inicializa todas as
variáveis de instância para zero. Muitas vezes, esse construtor default é suficiente
para classes simples. As classes mais sofisticadas geralmente precisam de
construtores definidos explicitamente.
Se um construtor for definido pelo programador, o construtor default não é
mais usado.
37. Construtores parametrizados
Obviamente, o construtor Caixa5() da lição anterior é muito limitado. Com
ele, todas as caixas terão sempre as mesmas dimensões.
Na prática, é interessante poder construir caixas com dimensões variadas.
Uma solução fácil é acrescentar parâmetros ao construtor. Como você já deve ter
adivinhado, isso os torna muito mais úteis.
No exemplo abaixo, a classe Caixa define um construtor parametrizado, que
define as dimensões da caixa segundo os valores passados como parâmetros.
Observe a forma como os objetos Caixa são criados agora:
Listagem: CaixaDemo5.Java
1 // CaixaDemo5.java
2 // Ilustra o uso
3 // de um construtor
4 // parametrizado.
5
6 class Caixa5
7{
8 double largura;
9 double altura;
10 double profundidade;
11
12 // Construtor.
13 Caixa5(double larg, double alt, double prof)
14 {
15
System.out.println(
16
"Construindo objeto caixa...");
17
largura = larg;
18
altura = alt;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
74
19
profundidade = prof;
20 } // Fim do construtor Caixa5().
21
22 // Calcula e retorna
23 // o volume.
24 double volume()
25 {
26
return largura *
27
altura *
28
profundidade;
29 } // Fim do método volume()
30 } // Fim de class Caixa5.
31
32 class CaixaDemo5
33 {
34 public static void main(String args[])
35 {
36
// Declara dois objetos
37
// da classe Caixa5,
38
// chamados caixa1 e
39
// caixa2.
40
Caixa5 caixa1 = new Caixa5(5, 10, 15);
41
Caixa5 caixa2 = new Caixa5(6, 12, 20);
42
43
// Uma variável para
44
// conter o valor do volume.
45
double volumeVar;
46
47
// Calcula e exibe
48
// o volume
49
// de caixa1, usando
50
// o método volume().
51
volumeVar = caixa1.volume();
52
System.out.println(
53
"Volume de caixa1 = "
54
+ volumeVar);
55
56
// Calcula e exibe
57
// o volume
58
// de caixa2, usando
59
// o método volume().
60
volumeVar = caixa2.volume();
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
75
61
System.out.println(
62
"Volume de caixa2 = "
63
+ volumeVar);
64 } // Fim de main().
65 } // Fim da classe CaixaDemo5.
A saída deste programa é mostrada abaixo:
Construindo objeto caixa...
Construindo objeto caixa...
Volume de caixa1 = 750.0
Volume de caixa2 = 1440.0
Vemos pela saída acima que cada um dos objetos Caixa5 é inicializado
conforme os parâmetros passados para seu construtor nas linhas 40 e 41 .
A linha 40
Caixa5 caixa1 = new Caixa5(5, 10, 15);
indica que os valores 5, 10 e 15 são passados para o construtor Caixa5()
quando new cria o objeto caixa1. Isso faz com que as cópias de altura, largura e
profundidade pertencentes a caixa1 contenham os valores 5, 10 e 15,
respectivamente. Isso está de acordo com o que está contido na definição do
construtor (linhas 17, 18 e 19).
38. A palavra-chave this
Pelo fato de Java ser uma linguagem totalmente orientada a objetos, muitas
vezes pode surgir a necessidade de um método precisa fazer uma referência à
própria instância do objeto que o contém. Java viabiliza isso com o uso da
palavra-chave this.
this pode ser usada dentro de qualquer método para referir-se ao objeto
atual. Ou seja, this é sempre uma referência ao objeto para o qual o método foi
invocado. this pode ser usado em qualquer lugar em que uma referência a um
objeto do tipo da classe atual é permitida.
Para melhor entender o uso de this, vamos rever o construtor da classe
Caixa da lição 37:
// Construtor.
Caixa4()
{
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
76
System.out.println(
"Construindo objeto caixa...");
largura = 20;
altura = 20;
profundidade = 20;
} // Fim do construtor Caixa4().
Esse construtor poderia ser reescrito da seguinte maneira:
// Construtor.
Caixa4()
{
System.out.println(
"Construindo objeto caixa...");
this.largura = 20;
this.altura = 20;
this.profundidade = 20;
} // Fim do construtor Caixa4().
Observe que o uso de this neste caso é desnecessário, embora legal. Esta
versão do construtor Caixa4() funciona exatamente da mesma forma que a
versão anterior.
Embor o uso de this neste caso seja redundante, ele serve para demonstrar
a forma como this sempre se refere ao objeto para o qual é invocado.
Naturalmente, em outras situações o uso de this é muito útil e
indispensável. Estaremos trabalhando com situações desse tipo e ilustrando o uso
de this na prática em outro ponto deste curso.
Em Java é ilegal declarar duas variáveis locais com o mesmo nome dentro
do mesmo escopo, ou dentro de escopos aninhados. Entretanto é possível ter
variáveis locais, inclusive parâmetros formais para métodos, que se superpõem
aos nomes das variáveis de instância da classe.
Quando uma variável local tem o mesmo nome que uma variável de
instância, a variável local oculta a variável de instância. Por exemplo, na lição 37,
altura, largura e profundidade não foram usados como nomes dos parâmetros do
construtor Caixa4() dentro da classe Caixa4.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
77
// Construtor.
Caixa4(double larg, double alt, double prof)
{
System.out.println(
"Construindo objeto caixa...");
largura = larg;
altura = alt;
profundidade = prof;
} // Fim do construtor Caixa4().
Se tivéssemos feito isso, largura se referiria ao parâmetro formal do
construtor Caixa4(), efetivamente ocultando a variável de instância largura.
Em geral, o mais fácil é simplesmente usar nomes diferentes, como fizemos
no exemplo acima. Porém com o uso de this, se quisermos podemos usar os
mesmos nomes para os parâmetros do construtor e as variáveis de instância.
Vimos que this permite referir-se diretamente ao objeto. Por isso this pode
ser usado para resolver quaisquer colisões dentro do espaço de nomes que
possam vir a ocorrer entre variáveis de instância e variáveis locais.
Vamos reescrever o construtor Caixa4() usando esta técnica:
// Construtor.
// Esta versão usa
// this para resolver
// os conflitos de
// nomes.
Caixa4(double largura,
double altura,
double profundidade)
{
System.out.println(
"Construindo objeto caixa...");
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
78
this.largura = largura;
this.altura = altura;
this.profundidade = profundidade;
} // Fim do construtor Caixa4().
Você pode achar que o uso dos mesmos nomes em variáveis locais e
parâmetros formais é confuso e complicado. Por outro lado, outros
programadores acham exatamente o contrário: que é uma boa idéia usar os
mesmos nomes por uma questão de clareza, usando this para contornar o
aspecto da ocultação de variáveis. É uma questão de estilo de programação, que
varia de pessoa para pessoa. O importante é entender que Java oferece a solução
do uso de this, optar por um estilo de programação e ser coerente com ele em
seu trabalho.
Observe que a utilidade de this não se restringe a este caso. Em outros
pontos deste curso, veremos usos mais poderosos para a palavra-chave this.
39. A classe Pilha
Para pôr em prática os conceitos que aprendemos sobre classes até agora,
desenvolveremos um exemplo de classe Pilha.
Uma pilha é uma estrutura clássica de programação, e serve muito bem
para ilustrar as propriedades das classes.
Uma pilha armazena dados usando ordenação primeiro que entra, último
que sai. Ou seja, uma pilha é como uma pilha de pratos sobre uma mesa - o
primeiro prato colocado na mesa é o último prato a ser usado.
As pilhas são controladas por meio de duas operações, tradicionalmente
chamadas de push e pop. Para colocar um item no topo da pilha, utilizamos push.
Para retirar um item da pilha, usamos pop. Veremos, a seguir como encapsular o
mecanismo da pilha em uma classe.
Vimos na lição 5 que um dos benefícios mais importantes da orientação a
objetos é o encapsulamento dos dados e do código que manipula esses dados.
Vimos também na lição 28 que a classe é o mecanismo que permite fazer esse
encapsulamento em Java.
Recapitulando, quando criamos uma classe, estamos criando um novo tipo
de dado que define tanto a natureza dos dados que estão sendo manipulados
quanto as rotinas usadas para manipulá-los.
Ademais, os métodos definem uma interface consistente e controlada para
os dados da classe. Portanto, é possível usar a classe através de seus métodos
sem precisar se preocupar com os detalhes de sua implementação, nem saber
como os dados são realmente gerenciados dentro da classe.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
79
De certa forma, uma classe é como um “mecanismo de dados”. Nenhum
conhecimento do que acontece dentro do mecanismo é necessário para usar o
mecanismo através de seus controles. Na verdade, como os detalhes ficam
ocultos, o funcionamento interno pode ser alterado conforme a necessidade.
Desde que o código utilize a classe através de seus métodos, os detalhes internos
podem mudar sem causar efeitos colaterais fora da classe.
Por uma questão de simplicidade, nossa classe Pilha, abaixo, implementa
uma pilha de números inteiros. Observe que neste exemplo, ao contrário do que
fizemos até agora, a classe Pilha está é um arquivo separado da classe TestePilha,
que contém o método main(). Esse é um procedimento comum em programas
Java mais complexos.
Listagem: Pilha.Java
1 // Esta classe Pilha
2 // pode conter
3 // 12 números inteiros.
4 class Pilha
5{
6 int aPilha[] = new int[12];
7 int topoDaPilha;
8
9
// O construtor
10 // inicializa o
11 // topo da pilha.
12 Pilha()
13 {
14
topoDaPilha = -1;
15 } // Fim do construtor Pilha()
16
17 // O método push()
18 // coloca um item
19 // no topo da pilha.
20 void push(int item)
21 {
22
// Verifica se há
23
// espaço na pilha.
24
if(topoDaPilha == 11)
25
System.out.println(
26
"A pilha esta' cheia!");
27
else
28
aPilha[++topoDaPilha] = item;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
80
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 }
} // Fim de push()
// O método pop()
// extrai um item
// do topo da pilha.
int pop()
{
// Checa se a pilha
// está vazia.
if(topoDaPilha < 0)
{
System.out.println(
"Pilha vazia!");
return 0;
} // Fim de if.
else
return aPilha[topoDaPilha--];
} // Fim de pop()
// Fim da classe Pilha.
A classe Pilha define dois membros de dados (linhas 6 e 7).
Pilha define também três métodos (linhas 12, 20 e 34).
A pilha de inteiros propriamente dita é representada por um array, chamado
aPilha (linha 6). Esse array é indexado pela variável topoDaPilha (linha 7), que
sempre contém o índice do topo da pilha.
O construtor Pilha() (linha 12) inicializa topoDaPilha com o valor -1 (linha
14). Esta é forma de indicar que a pilha está vazia.
O método push() (linha 20) coloca um item na pilha.
Para extrair um item, chamamos pop() (linha 34).
Como o acesso à pilha é feito através dos métodos push() e pop(), o fato de
que os dados da pilha são mantidos em um array na verdade é irrelevante para
quem usa a pilha. Por exemplo, os dados da pilha poderiam ser mantidos em uma
estrutura de dados mais complicada, como uma lista encadeada, e no entanto, a
interface definida por push() e pop() continuaria sendo a mesma.
A classe TestePilha, mostrada abaixo, representa um programa para testar
a classe Pilha.
Listagem: TestePilha.Java
1 // TestePilha.java
2 // Testa a classe
3 // Pilha.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
81
4 class TestePilha
5{
6 public static void main(String args[])
7 {
8
Pilha pilha1 = new Pilha();
9
Pilha pilha2 = new Pilha();
10
11
// Preenche as pilhas.
12
for(int i = 0; i < 12; i++)
13
pilha1.push(i);
14
for(int i = 12; i < 24; i++)
15
pilha2.push(i);
16
17
// Extrai os números
18
// de pilha1 e
19
// exibe-os.
20
System.out.println(
21
"Conteudo de pilha1:");
22
for(int i = 0; i < 12; i++)
23
System.out.println(pilha1.pop());
24
25
// Extrai os números
26
// de pilha2 e
27
// exibe-os.
28
System.out.println(
29
"Conteudo de pilha2:");
30
for(int i = 0; i < 12; i++)
31
System.out.println(pilha2.pop());
32 } // Fim de main().
33 } // Fim de classe TestePilha.
Nas linhas 8 e 9, TestePilha.java cria duas pilhas de inteiros. Nas linhas 11 a
15, dois loops for são usados para preencher as pilhas.
Nas linhas 20 a 31, os valores são extraídos das pilhas e exibidos na tela.
Eis a saída de TestePilha.java:
Conteudo de pilha1:
11
10
9
8
7
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
82
6
5
4
3
2
1
0
Conteudo de pilha2:
23
22
21
20
19
18
17
16
15
14
13
12
Esta implementação de Pilha é interessante, mas está longe de ser perfeita.
Do ponto de vista de orientação a objetos, o principal problema é o fato de que o
array aPilha[] pode ser acessado e modificado por elementos externos à classe.
Com isso, a classe Pilha fica aberto a mau uso ou erros de programação. Veremos
como solucionar esse problema em outros pontos deste curso.
Exemplos adicionais:
Interface:
public
public
public
public
}
interface IPilha {
void insira(String s);
String retire();
boolean estaVazio();
Arrays:
public class PilhaArray implements IPilha {
private String pilha[];
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
83
private int topo;
public void insira(String s){
topo++;
pilha[topo] = s;
}
public String retire(){
topo--;
return pilha[topo + 1];
}
public boolean estaVazio(){
return topo == -1;
}
public PilhaArray(int n){
pilha = new String [n];
topo = -1;
}
}
Stack:
import java.util.Stack;
public class PilhaStack implements IPilha {
private Stack pilha;
public void insira(String s){
pilha.push(s);
}
public String retire(){
return (String) pilha.pop();
}
public boolean estaVazio(){
return pilha.empty();
}
public PilhaStack(){
pilha = new Stack();
}
}
Vector:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
84
import java.util.Vector;
public class PilhaVector implements IPilha {
private Vector pilha;
public void insira(String s){
pilha.addElement(s);
}
public String retire(){
return (String) pilha.remove( pilha.size() -1);
}
public boolean estaVazio(){
return pilha.size() == 0;
}
public PilhaVector(int n){
pilha = new Vector(n);
}
}
Programa:
import java.util.Vector;
public class TestePilha2 {
public static void main(String args[]){
PilhaVector pilhaVector = new PilhaVector(args.length);
PilhaArray pilhaArray = new PilhaArray(args.length);
PilhaStack pilhaStack = new PilhaStack();
for(int i = 0; i < args.length ; i++){
pilhaArray.insira( args[i] );
pilhaVector.insira( args[i] );
pilhaStack.insira( args[i] );
}
imprime(pilhaArray);
System.out.println();
imprime(pilhaVector);
System.out.println();
imprime(pilhaStack);
}
public static void imprime(IPilha p){
System.out.println(p.getClass().getName());
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
85
while(! p.estaVazio()){
System.out.println( p.retire() );
}
}
}
40. Sobrecarregando métodos
Java permite definir dois ou mais métodos dentro da mesma classe
utilizando o mesmo nome, desde que suas declarações de parâmetros sejam
diferentes. Esse procedimento é conhecido como sobrecarga de métodos (method
overload). Dizemos que os métodos estão sendo sobrecarregados (overloaded).
A sobrecarga de métodos é uma das formas de implementar o polimorfismo
mencionado na lição 7. Inicialmente, o conceito da sobrecarga pode parecer
estranho. Mas veremos que a sobrecarga de métodos é um dos recursos mais
interessantes e poderosos de Java.
Quando um método sobrecarregado é chamado, Java utiliza o tipo e o
número dos argumentos como um guia para determinar qual versão do método
sobrecarregado deve ser de fato executada. Por isso, métodos sobrecarregados
devem diferir no tipo ou no número de seus parâmetros (ou em ambos, tipo e
número).
Embora métodos sobrecarregados possam retornar tipos diferentes,
somente o tipo retornado não é suficiente para fazer a distinção entre duas
versões de um método. Quando Java encontra uma chamada a um método
sobrecarregado, o ambiente executa a versão do método cujos parâmetros casam
com os argumentos passados na chamada.
O exemplo abaixo ilustra a sobrecarga de métodos:
Listagem: Sobrecarga.java
1 // Sobrecarga.java
2 // Ilustra o uso
3 // da sobrecarga de
4 // métodos.
5
6 class SobrecargaDemo
7{
8 void metodoSobre()
9 {
10
System.out.println(
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
86
11
"Versao sem parametros.");
12 } // Fim de metodoSobre()
13
14 // Versão com um
15 // parâmetro int.
16 void metodoSobre(int iParam)
17 {
18
System.out.println(
19
"Um param. int");
20
System.out.println(
21
"Valor: "
22
+ iParam);
23 } // Fim de metodoSobre(int)
24
25 // Versão com dois
26 // parâmetros int.
27 void metodoSobre(int iPar1, int iPar2)
28 {
29
System.out.println(
30
"Dois params. int");
31
System.out.println(
32
"Valores: "
33
+ iPar1
34
+"e"
35
+ iPar2);
36 } // Fim de metodoSobre(int, int)
37
38 // Versão com um
39 // parâmetro double.
40 double metodoSobre(double dParam)
41 {
42
System.out.println(
43
"Um param. double");
44
System.out.println(
45
"Valor: "
46
+ dParam);
47
return dParam * 10;
48 } // Fim de metodoSobre(double)
49 } // Fim de classe SobrecargaDemo.
50
51 class Sobrecarga
52 {
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
87
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 }
public static void main(String args[])
{
SobrecargaDemo objeto1 =
new SobrecargaDemo();
double valorRet;
// Chama as várias
// versões de
// metodoSobre().
objeto1.metodoSobre();
objeto1.metodoSobre(5);
objeto1.metodoSobre(10, 15);
valorRet = objeto1.metodoSobre(3.1416);
// Exibe o valor
// retornado pela
// versão
// double metodoSobre(double).
System.out.println(
"Valor retornado = "
+ valorRet);
} // Fim de main()
// Fim da classe Sobrecarga.
Este programa gera a seguinte saída:
Versao sem parametros.
Um param. int
Valor: 5
Dois params. int
Valores: 10 e 15
Um param. double
Valor: 3.1416
Valor retornado = 31.416
Na classe SobrecargaDemo, o método metodoSobre() é sobrecarregado
quatro vezes:
linha 8: Versão sem parâmetros
linha 16: Um parâmetro int
linha 27: Dois parâmetros int
linha 40: Um parâmetro double, retornando double
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
88
O fato de que esta última versão de metodoSobre() também retorna um
valor (linhas 40 e 47) é irrelevante para a sobrecarga, já que o tipo retornado não
desempenha nenhum papel na resolução da sobrecarga.
Quando um método sobrecarregado é chamado, Java procura casar os
argumentos usados na chamada ao método com os parâmetros do método.
Observe, porém, que este casamento nem sempre precisa ser exato. Isto
porque as conversões automáticas de tipos de Java podem entrar em ação e ter
um papel importante na resolução da sobrecarga.
O exemplo abaixo ilustra esse fato:
Listagem: Sobrecarga2.Java
1 // Sobrecarga2.java
2 // Ilustra chamada
3 // a métodos
4 // sobrecarregados.
5
6 class SobrecargaDemo2
7{
8 void metodoSobre()
9 {
10
System.out.println(
11
"Versao sem parametros.");
12 } // Fim de metodoSobre()
13
14 // Versão com dois
15 // parâmetros int.
16 void metodoSobre(int iPar1, int iPar2)
17 {
18
System.out.println(
19
"Dois params. int");
20
System.out.println(
21
"Valores: "
22
+ iPar1
23
+"e"
24
+ iPar2);
25 } // Fim de metodoSobre(int, int)
26
27 // Versão com um
28 // parâmetro double.
29 void metodoSobre(double dParam)
30 {
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
89
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
System.out.println(
"Um param. double");
System.out.println(
"Valor: "
+ dParam);
} // Fim de metodoSobre(double)
} // Fim de classe SobrecargaDemo2.
class Sobrecarga2
{
public static void main(String args[])
{
SobrecargaDemo2 objeto2 =
new SobrecargaDemo2();
int iVar = 25;
// Chama as várias
// versões de
// metodoSobre().
objeto2.metodoSobre();
objeto2.metodoSobre(10, 15);
// Chama
// metodoSobre()
// passando um int
// como argumento.
// A versão executada
// será
// metodoSobre(double).
objeto2.metodoSobre(iVar);
// Aqui também
// a versão executada
// será
// metodoSobre(double).
objeto2.metodoSobre(3.1416);
} // Fim de main()
} // Fim da classe Sobrecarga2.
Este programa gera a seguinte saída:
Versao sem parametros.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
90
Dois params. int
Valores: 10 e 15
Um param. double
Valor: 25.0
Um param. double
Valor: 3.1416
Vemos que a classe SobrecargaDemo2 (linha 6) não define
metodoSobre(int). Portanto, quando metodoSobre() é chamado com um
argumento int (linha 60), nenhum método que case é encontrado. Porém Java é
capaz de converter automaticamente um int em um double, e essa conversão é
usada para resolver a chamada. Assim, já que metodoSobre(int) não é
encontrado, Java promove i para double, e depois chama metodoSobre(double).
Esse fato é evidenciado pela saída na tela.
É claro que se existisse metodoSobre(int), essa versão seria chamada, e
não metodoSobre(double). Java utiliza a conversão automática de tipos somente
se não encontrar uma versão que case exatamente com a chamada.
A sobrecarga de métodos representa uma forma de polimorfismo porque é
uma das formas como Java implementa o paradigma de "uma interface, múltiplos
métodos". Para entender isso, considere o seguinte. Em linguagens que não
suportam sobrecarga de métodos, cada método deve receber um nome único.
Contudo, muitas vezes queremos implementar o que é essencialmente o mesmo
método, só que para diferentes tipos de dados.
Vejamos por exemplo a função valor absoluto. Em linguagens que não
suportam a sobrecarga, geralmente é preciso ter três ou mais versões dessa
função, cada uma com um nome ligeiramente diferente.
Por exemplo, em C, a função abs() retorna o valor absoluto de um int,
labs() retorna o valor absoluto de um long e fabs() retorna o valor absoluto de
float. Como C não suporta sobrecarga, cada função precisa ter um nome único,
muito embora as três funções façam essencialmente a mesma coisa. Isso cria
uma complexidade desnecessária.
Embora o conceito que está por trás de cada uma dessas funções seja o
mesmo, o programador precisa lembrar de três nomes diferentes. Isso não
acontece em Java, porque todos os métodos que retornam o valor absoluto
podem usar o mesmo nome. Na verdade, a biblioteca padrão de Java inclui um
método que retorna o valor absoluto, chamado abs(). Esse método é
sobrecarregado na classe Math de Java para lidar com todos os tipos numéricos.
Java determina qual a versão de abs() a ser chamada com base no tipo de
argumento.
O valor prático da sobrecarga é que ela permite que métodos interrelacionados possam ser acessados com o uso de um único nome. Assim, o nome
abs() representa a ação genérica que está sendo executada. Cabe ao compilador
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
91
escolher a versão específica correta para uma dada situação. O programador
precisa lembrar somente da operação genérica que está sendo executada. Com o
uso do polimorfismo, diversos nomes ficam reduzidos a um só. Embora o exemplo
acima seja bastante simples, se expandirmos o conceito veremos como a
sobrecarga pode ajudar a reduzir a complexidade da programação.
Observe porém que quando sobrecarregamos um método, cada versão
desse método pode executar qualquer atividade que desejemos. Não há uma
regra que diga que os métodos sobrecarregados devem ter funções parecidas.
É claro que, do ponto de vista de estilo, a sobrecarga de métodos implica
em um relacionamento. Assim, embora seja possível usar o mesmo nome em
métodos que não têm nenhuma relação, isso deve ser evitado. Por exemplo, um
programador pode usar o nome quadrado() para criar um método que retorne o
quadrado de um número inteiro e a raíz quadrada de um número de ponto
flutuante. Mas essas duas operações são fundamentalmente diferentes. Usar a
sobrecarga de métodos desta forma é ignorar a intenção original dos criadores da
linguagem. Na prática, somente devemos usar a sobrecarga quando se tratar de
métodos que de fato desempenhem funções muitos similares.
41. Recursão
Em ciência de computação, recursão (do Inglês recursion) refere-se ao
processo de definir algo em termos de si mesmo. No que diz respeito à
programação em Java, a recursão é o atributo que permite que um método
chame a si mesmo. Um método que faz isso é chamado de recursivo.
O exemplo clássico de recursividade é o cálculo do fatorial de um número. O
fatorial de um número N, representado em matemática pela notação N! é o
produto de todos os números inteiros que existem entre 1 e N. Por exemplo:
Fatorial de 3:
Fatorial de 5:
3! = 3 x 2 x 1 = 6
5! = 5 x 4 x 3 x 2 x 1 = 120
O programa abaixo ilustra o cálculo do fatorial usando um método
recursivo:
Listagem: TesteRecurs.Java
1
2
3
4
5
//
//
//
//
TesteRecurs.java
Ilustra o uso
de um método
recursivo.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
92
6 class Fatorial
7{
8 // O método calculaFat()
9 // é um exemplo de
10 // uso de
11 // recursão.
12 int calculaFat(int num)
13 {
14
int resultado;
15
16
if(num == 1)
17
return 1;
18
resultado = calculaFat(num - 1) * num;
19
return resultado;
20 } // Fim de calculaFat()
21 } // Fim da classe Fatorial.
22
23 class TesteRecurs
24 {
25 public static void main(String args[])
26 {
27
Fatorial fat = new Fatorial();
28
29
System.out.println("Fatorial de 4 = "
30
+ fat.calculaFat(4));
31
System.out.println("Fatorial de 5 = "
32
+ fat.calculaFat(5));
33
System.out.println("Fatorial de 6 = "
34
+ fat.calculaFat(6));
35 } // Fim de main()
36 } // Fim de class TesteRecurs.
A saída deste programa é mostrada aqui:
Fatorial de 4 = 24
Fatorial de 5 = 120
Fatorial de 6 = 720
Inicialmente, o conceito de recursão pode parecer um pouco confuso. Para
entendê-lo melhor, vamos analisar o método calculaFat() (linhas 12 a 20).
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
93
Na linha 17, vemos que quando calculaFat() é chamado com o argumento
1, ele retorna 1. Para qualquer outro valor, calculaFat() retorna o produto da linha
18
calculaFat(num - 1) * num
Na execução da linha 18 , calculaFat() é chamado com o argumento (num 1). Esse é o processo recursivo; ele se repete até que num seja igual a 1. Nesse
momento, as várias chamadas ao método calculaFat() começam a retornar.
Por exemplo, vejamos como calculaFat() funciona quando recebe como
argumento o valor 3. Quando calculamos o fatorial de 3, a primeira chamada a
calculaFat() faz com que a segunda chamada seja feita com o argumento 2 (linha
18).
Essa invocação faz com que calculaFat() seja chamado uma terceira vez
com o argumento 1. Acontece que quando o argumento é 1, calculaFat() retorna
o valor 1 (linhas 16 e 17). Esse valor 1 é então multiplicado por 2 (o valor de num
na segunda invocação). Esse resultado, que é 2, é então retornado para a
invocação original de calculaFat(), sendo multiplicado por 3, o valor original de
num.
Isso gera a resposta, que é 6.
Experimente inserir a seguinte linha no programa acima, entre as linhas 18
e 19:
System.out.println("resultado = "
+ resultado);
Isso mostrará em qual nível está cada chamada e também os valores
intermediários. A saída do programa ficará assim:
resultado = 2
resultado = 6
resultado = 24
Fatorial de 4 = 24
resultado = 2
resultado = 6
resultado = 24
resultado = 120
Fatorial de 5 = 120
resultado = 2
resultado = 6
resultado = 24
resultado = 120
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
94
resultado = 720
Fatorial de 6 = 720
O uso da recursão requer cuidados especiais, pois implica em alguns riscos.
Para entendê-los, considere a explicação abaixo:
Quando um método chama a si mesmo, as variáveis locais e parâmetros
criados por cada chamada têm seu armazenamento alocado na pilha. O código do
método é então executado com essas novas variáveis desde o começo.
A chamada a um método recursivo não cria novas cópias do método.
Somente os argumentos são novos. À medida que cada chamada recursiva
retorna, as velhas variáveis locais e parâmetros são removidos da pilha, e a
execução é retomada no ponto da chamada dentro do método.
As versões recursivas de muitas rotinas podem ser executadas um pouco
mais lentamente do que suas versões iterativas equivalentes, por causa da
sobrecarga adicional das chamadas a funções. Um número excessivo de
chamadas recursivas a um método pode causar um estouro de pilha. Como o
armazenamento dos parâmetros e variáveis locais é feito na pilha e cada nova
chamada cria novas cópias dessas variáveis, é possível que a pilha se esgote.
No caso da linguagem Java, se isso ocorrer, o sistema runtime de Java
causa uma exceção. Contudo, provavelmente não será preciso preocupar-se com
isso a não ser que uma rotina recursiva saia fora de controle.
A principal vantagem dos métodos recursivos é que eles podem ser usados
para criar versões mais elegantes de algoritmos, que se fossem implementados
iterativamente seriam extremamente desajeitados.
Ao escrever métodos recursivos, é preciso ter uma condição if em algum
lugar para forçar o método a retornar sem que a chamada recursiva seja
executada. Se essa condição não estiver presente, uma vez que o método seja
chamado, ele nunca retornará. Esse é um erro muito comum no trabalho com
recursão.
Uma boa maneira de acompanhar um algoritmo recursivo durante a
implementação é usar o método println() para mostrar o que está acontecendo
em cada passo.
O programa abaixo é mais um exemplo de recursão:
Listagem: TesteRecurs2.Java
1
2
3
4
5
6
// TesteRecurs2.java
// Ilustra o uso
// de recursão.
class RecArray
{
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
95
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int umArray[];
// Construtor.
RecArray(int i)
{
umArray = new int[i];
} // Fim do construtor RecArray()
// O método mostraArray()
// usa recursão
// para exibir na tela
// o conteúdo de umArray[].
void mostraArray(int i)
{
if(i == 0)
return;
else
mostraArray(i - 1);
System.out.println("umArray["
+ (i - 1)
+ "] = "
+ umArray[i - 1]);
} // Fim de mostraArray()
} // Fim de class RecArray.
class TesteRecurs2
{
public static void main(String args[])
{
RecArray objArray = new RecArray(10);
int i;
// Preenche o array.
for(i = 0; i < 10; i++)
objArray.umArray[i] = i * 10;
// Exibe o array.
objArray.mostraArray(10);
} // Fim de main()
} // Fim de class TesteRecurs2.
TesteRecurs2.java gera a seguinte saída:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
96
umArray[0]
umArray[1]
umArray[2]
umArray[3]
umArray[4]
umArray[5]
umArray[6]
umArray[7]
umArray[8]
umArray[9]
=
=
=
=
=
=
=
=
=
=
0
10
20
30
40
50
60
70
80
90
O método mostraArray () (linhas 19 a 29), exibe os primeiros i elementos
do array umArray[]. Ele é recursivo, pois chama a sim mesmo, na linha 24.
42. public e private
Java oferece três especificadores de acesso:
·
·
·
public
private
protected
Além disso, há também um nível de acesso default.
·
O especificador de acesso protected aplica-se somente quando há
envolvimento de herança. Por isso, ele será tratado em outro ponto deste curso.
Nesta lição, descreveremos o uso de public e private.
Quando um membro de uma classe é modificado pelo especificador public,
esse membro pode ser acessado por qualquer parte do código do programa.
Quando um membro de uma classe é especificado como private, esse
membro somente pode ser acessado por outros membros de sua classe.
Esse é o motivo porque main() é sempre precedido pelo especificador de
acesso public. Ele é chamado por código que está fora do programa, qual seja, o
sistema runtime de Java.
Quando nenhum especificador de acesso é usado, então por default o
membro de uma classe é public dentro de seu próprio package, mas não pode ser
acessado fora de seu package. O conceito de package será discutido em outro
ponto deste curso.
Nas classes desenvolvidas até agora, todos os membros de uma classe
usaram o modo de acesso default, que por enquanto, podemos considerar
equivalente a public.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
97
Porém para ser fiel aos princípios de orientação a objetos, normalmente não
é isso que se deseja. Geralmente, é desejável restringir o acesso aos membros de
dados de uma classe. Idealmente, esse acesso somente deve ser possível através
de métodos. Também há ocasiões em que é interessante definir métodos que são
private, sendo acessíveis somente para a própria classe.
Um especificador de acesso aparece no início da linha, antes do tipo de um
membro.
Eis alguns exemplos:
public int intVar;
private double dVar;
private int umMetodo(int i, char ch)
{ // . . .
O programa abaixo ilustra os efeitos do acesso public e private:
Listagem: DemoAcesso.Java
1 // DemoAcesso.java
2 // Ilustra o efeito
3 // de public e private.
4
5 class UmaClasse
6{
7 // Uma variável
8 // com acesso
9 // default.
10 int defVar;
11
12 // Uma variável com
13 // acesso public.
14 public int pubVar;
15
16 // Uma variável com
17 // acesso private.
18 private int priVar;
19
20 // Métodos para acessar
21 // a variável private
22 // priVar;
23 void set_priVar(int valor)
24 // Define o valor de priVar.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
98
25 {
26
priVar = valor;
27 } // Fim de set_priVar()
28
29 int get_priVar()
30 // Obtém o valor de priVar.
31 {
32
return priVar;
33 } // Fim de get_priVar()
34 } // Fim de class UmaClasse.
35
36 class DemoAcesso
37 {
38 public static void main(String args[])
39 {
40
UmaClasse obj = new UmaClasse();
41
42
// Acessa diretamente
43
// os valores de pubVar e
44
// defVar.
45
obj.defVar = 20;
46
obj.pubVar = 40;
47
48
// Esta linha geraria
49
// um erro. priVar
50
// não pode ser
51
// acessada diretamente.
52
// obj.priVar = 80; // ERRO!!!
53
54
// priVar somente pode
55
// ser acessada através
56
// dos métodos apropriados.
57
// Define o valor de
58
// priVar como sendo 80.
59
obj.set_priVar(80);
60
61
// Obtém e exibe o valor
62
// de priVar, usando println() e
63
// o método get_priVar().
64
System.out.println("Valores: ");
65
System.out.println("defVar = " + obj.defVar
66
+ ", pubVar = " + obj.pubVar
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
99
67
+ ", priVar = " + obj.get_priVar());
68 } // Fim de main()
69 } // Fim da classe DemoAcesso.
Este programa gera a seguinte saída:
Valores:
defVar = 20, pubVar = 40, priVar = 80
Observe as variáveis definidas na classe UmaClasse. defVar (linha 10)
utiliza o acesso default, que no caso deste programa, equivale ao acesso public.
pubVar (linha 14) utiliza o acesso public. E priVar é definida como sendo private
(linha 18).
Por ser private, priVar não pode ser acessada por código de fora da classe
UmaClasse. Você pode comprovar isso retirando o sinal de comentário // da linha
52.
Por isso, a classe UmaClasse define os métodos set_priVar() (linhas 23 a
27) e get_priVar() (linhas 29 a 33).
set_priVar() é usado para definir o valor de priVar como sendo 80 (linha
59).
Na linha 67, get_priVar() obtém o valor de priVar, para ser exibido na tela
com a ajuda de println().
43. A palavra-chave static
Em geral, um membro de uma classe somente pode ser acessado por meio
de uma instância de um objeto dessa classe. Porém há ocasiões em que
precisamos definir um membro de uma classe que possa ser usado
independentemente de qualquer objeto dessa classe. Esse tipo de membro de
classe pode ser utilizado por si mesmo, sem precisar ser acessado a partir de um
objeto específico.
Para criar um membro assim, devemos preceder sua declaração com a
palavra-chave static. Quando um membro é declarado static, ele pode ser
acessado antes que qualquer objeto de sua classe seja criado, e sem referência a
nenhum objeto.
Tanto métodos quanto variáveis podem ser declarados como static.
O exemplo mais comum de um membro static é o método main(). main() é
declarado como static porque ele deve ser chamado antes que qualquer objeto
exista.
As variáveis declaradas como static são, essencialmente, variáveis globais.
Quando objetos de sua classe são declarados, nenhuma cópia da variável static é
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
100
criada. Ao invés disso, todas as instâncias da classe compartilham a mesma
variável static.
Os métodos declarados como static têm várias restrições:
Somente podem chamar outros métodos static
Somente podem acessar dados static
Não podem usar nenhuma referência a this ou a super
A palavra-chave super será descrita em outro ponto deste curso.
Como conseqüência das restrições acima, observe que um método static
não pode fazer referências a variáveis de instância.
Se houver necessidade de executar alguma computação para inicializar
variáveis static, é possível declarar um bloco static, que é executado somente
uma vez, quando a classe é inicialmente carregada.
O exemplo abaixo ilustra o uso de um método static, variáveis static e um
bloco de inicialização static:
Listagem: DemoStatic.Java
1 // DemoStatic.java
2 // Ilustra o uso de
3 // variáveis, métodos e
4 // blocos static.
5
6 class DemoStatic
7{
8 static int stVar1 = 5;
9 static int stVar2;
10
11 static void stMetodo(int valor)
12 {
13
System.out.println("valor = "
14
+ valor);
15
System.out.println("stVar1 = "
16
+ stVar1);
17
System.out.println("stVar2 = "
18
+ stVar2);
19 } // Fim de stMetodo()
20
21 // Bloco static.
22 static
23 {
24
System.out.println(
25
"Executando bloco static...");
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
101
26
27
28
29
30
31
32
33 }
stVar2 = stVar1 * 10;
} // Fim do bloco static.
public static void main(String args[])
{
stMetodo(25);
} // Fim de main()
// Fim de class DemoStatic.
As linhas iniciadas com static são executadas logo que a classe DemoStatic
é carregada.
Primeiro, a linha 8 é executada, inicializando stVar1 com o valor 5.
Depois o bloco static (linhas 22 a 27) é executado, exibindo sua mensagem
(linha 25) e inicializando stVar2 com o valor stVar1 * 10 (linha 26).
Quando main() é executado, o método stMetodo() é chamado com o valor
25.
Os três comandos println() de stMetodo() (linhas 13 a 18) são então
executados. Observe que em stMetodo() há referências às variáveis static stVar1
(linha 16), stVar2 (linha 18) e à variável local valor (linha 14).
Eis a saída deste programa:
Executando bloco static...
valor = 25
stVar1 = 5
stVar2 = 50
Fora da classe na qual são definidos, os métodos e variáveis static podem
ser usados independentemente de qualquer objeto. Basta especificar o nome da
classe, seguido do operador ponto (.) e do membro static que se deseja acessar.
Por exemplo:
NomeDaClasse.metodoStatic()
chama um método static chamado metodoStatic() definido na classe
NomeDaClasse.
Esse formato é idêntico ao usado para chamar um método não-static
através de um objeto.
O acesso a uma variável static é feito da mesma forma.
Os membros static são o equivalente no mundo orientado a objetos de Java
às funções globais e variáveis globais.
Eis um outro exemplo de uso de membros static:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
102
Listagem: DemoStatic2.Java
1 // DemoStatic2.java
2 // Um outro exemplo
3 // de uso de membros
4 // static.
5
6 class UmaClasse2
7{
8 static int stVar1 = 25;
9 static int stVar2 = 45;
10
11 static void stMetodo()
12 {
13
System.out.println("stVar1 = "
14
+ stVar1);
15 } // Fim de stMetodo()
16 } // Fim de class UmaClasse2.
17
18 class DemoStatic2
19 {
20 public static void main(String args[])
21 {
22
UmaClasse2.stMetodo();
23
System.out.println("stVar2 = "
24
+ UmaClasse2.stVar2);
25 } // Fim de main()
26 } // Fim de class DemoStatic2.
Na linha 22, o método static stMetodo() é chamado, com o uso do nome da
classe UmaClasse mais o operador ponto (.)
Na linha 24, a variável static stVar2 é chamada da mesma forma.
Eis a saída de DemoStatic2.java:
stVar1 = 25
stVar2 = 45
44. final constantes em Java
Java permite que o programador impeça que o valor de uma variável seja
modificado. Isso é feito com o uso da palavra-chave final.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
103
Naturalmente, uma variável final deve ser inicializada no momento de sua
declaração.
Eis alguns exemplos:
final
final
final
final
final
int
int
int
int
int
ARQUIVO_NOVO = 1;
ARQUIVO_ABRIR = 2;
ARQUIVO_SALVAR = 3;
ARQUIVO_SALVAR_COMO = 4;
ARQUIVO_SAIR = 5;
Após essas declarações, as variáveis ARQUIVO_NOVO, ARQUIVO_ABRIR,
etc. podem ser usadas como valores constantes em todo o restante do programa.
Observe que a convenção comum em C++ de usar MAIÚSCULAS para
nomes de constantes também é habitualmente adotada em Java, embora não
seja obrigatória.
Como se tratam de constantes, as variáveis final não são recriadas para
cada nova instância da classe em que são declaradas. Portanto, somente ocupam
espaço de memória uma única vez.
É importante saber que a palavra-chave final também pode ser aplicada a
métodos. Neste caso, seu significado é bastante diferente.
45. A classe Object
Entre as classes definidas na biblioteca Java, uma tem um significado
especial: a classe Object. Isto porque todas as outras classes são derivadas de
Object.
Por esse motivo, e conforme já dissemos, uma variável de referência ao tipo
Object pode referenciar um objeto de qualquer outra classe. Isso vale inclusive
para os arrays, são implementados como objetos em Java.
Por enquanto, eis alguns dos métodos definidos na classe Object, e que
portanto estão disponíveis em todos os outros objetos em Java:
Método
Object clone()
Finalidade
Cria um novo objeto, idêntico ao objeto
atual
boolean equals (Object object)
Verifica se o objeto atual é igual ao
argumento object; compara o conteúdo
dos dois objetos, retornando true, se eles
forem iguais; caso contrário, retorna
false
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
104
void finalize()
Chamado antes que um objeto que não
está mais em uso seja retomado pela
coleta de lixo
Class getClass()
Obtém a classe de um objeto em tempo
de execução; é um método final
int hashCode()
Retorna um valor hash associado com o
objeto atual
void notify()
Retoma a execução de uma thread que
está esperando; é um método final
void notifyAll()
Retoma a execução de todas as threads
que estão esperando; é um método final
String toString()
Retorna uma string descritiva do objeto
atual; é chamado automaticamente
quando o objeto é exibido com println();
pode ser superposto para fornecer uma
descrição mais específica de um dado
objeto
void wait()
Espera a execução de outra thread; é um
método final
void wait(long milliseconds)
46. Apresentando os packages
Os packages representam o recurso usado por Java para organizar o espaço
de nomeação de classes.
Por exemplo, usando packages o programador pode criar uma classe que
tenha o mesmo nome de outra classe já existente, sem que isso venha a criar um
conflito entre os nomes.
Em todos os exemplos até agora, o nome das classes estava contido dentro
do mesmo espaço de nomeação. Por isso, cada classe tinha de ter um nome
único, para evitar colisões entre os nomes.
Ao trabalhar profissionalmente com Java, é preciso ter uma forma de
gerenciar o espaço de nomes, caso contrário pode começar a ficar difícil criar
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
105
nomes únicos para as classes. Além disso, há o risco de colisões com os nomes de
classes criadas por diferentes programadores.
Para se definir um package, o procedimento é bastante simples. Basta
incluir um comando package como primeira linha do arquivo fonte Java. A partir
daí, quaisquer classes declaradas dentro desse arquivo pertencerão ao package
especificado. O comando package define um espaço de nomes no qual as classes
ficam armazenadas.
Se o comando package for omitido, os nomes das classes são colocados em
um package default, sem nome. Foi isso que fizemos até agora nos exemplos de
programas deste curso.
O uso do package default é aceitável em pequenos programas, mas não
quando lidamos com programas do mundo real. Em geral, cada programador
deve definir um package para o seu código.
Eis a forma geral de uso do comando package:
package nome_do_package;
Por exemplo, o comando abaixo cria um package chamado Curso:
package Curso;
Java armazena cada package em um diretório ou pasta do sistema de
arquivos do computador. Assim, no exemplo acima, os arquivos .class para as
classes que são declaradas como parte do package Curso devem ser armazenadas
em um diretório chamado Curso.
Ao usar nomes de packages, é importante lembrar que Java faz diferença
entre maiúsculas e minúsculas.
Observe que as classes de um determinado package podem estar em
diferentes arquivos. O comando package apenas especifica a qual package
pertencem as classes definidas em um determinado arquivo. Isso não impede que
outras classes de outros arquivos façam parte do mesmo package. Na verdade, é
muito comum no mundo real que um package esteja espalhado entre vários
arquivos.
Por exemplo, eis o nome de um package da biblioteca original de Java:
package java.awt.image;
A divisão das pastas ou diretórios no sistema de arquivos do computador
deve refletir a organização dos packages. Por exemplo, em uma máquina
Windows, o package
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
106
package java.awt.image;
deve ficar armazenado no diretório
java\awt\image
Observe o uso do ponto (.) para separar os nomes dos packages.
Os nomes dos packages devem ser escolhidos criteriosamente pelo
programador. Caso haja necessidade de mudar o nome do package, é preciso
renomear também os diretórios que contêm as classes correspondentes.
Evidentemente, essa não é uma forma prática de trabalhar com os
packages.
47. Apresentando CLASSPATH
Para solucionar o problema mencionado na lição anterior sobre o acesso a
uma classe contida dentro de um package, precisamos definir uma variável de
ambiente chamada CLASSPATH.
CLASSPATH informa à máquina virtual Java e aos outros aplicativos onde
procurar as classes Java.
Normalmente, se não definirmos CLASSPATH, as ferramentas Java vão
procurar essas classes no seguinte caminho:
.;[bin]\..\classes;[bin]\..\lib\classes.zip
Onde [bin] representa o caminho absoluto para o diretório j2sdk1.4.1\bin.
Isto é válido para uma instalação que respeite a estrutura padrão de diretórios do
JDK.
Portanto, se mantivermos os diretórios lib e bin como subdiretórios do
mesmo nível, ou seja, se os dois estiverem dentro do mesmo diretório, os
executáveis encontrarão as classes, sem necessidade de definirmos a variável
CLASSPATH.
Porém mesmo que a variável CLASSPATH seja definida, ainda assim o
caminho
.;[bin]\..\classes;[bin]\..\lib\classes.zip
será acrescentado àquele definido por CLASSPATH.
Portanto, a variável CLASSPATH somente tem influência para as classes
definidas pelo usuário, ou outras classes Java que não façam parte do JDK.
No caso das classes definidas pelo usuário, por default o run-time Java as
procura no diretório atual, ou em um caminho relativo a ele. Por isso, enquanto
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
107
trabalhamos com o package default, não havia necessidade de definir a variável
CLASSPATH.
A variável de ambiente CLASSPATH deve ser definida quando houver
necessidade de informar ao run-time um outro local onde procurar as classes
definidas pelo usuário, como aconteceu na lição anterior.
Observe que interpretador sempre acrescenta a localização de suas classes
de sistema ao caminho especificado pela variável de ambiente CLASSPATH.
Os valores de caminhos (paths) contidos na especificação do valor de
CLASSPATH indicando onde estão as classes devem se referir a:
-
diretórios contendo classes
arquivos ZIP contendo classes
No ambiente Windows, esses itens devem estar separados pelo caractere de
ponto e vírgula (;). Eis um exemplo de valor para CLASSPATH em uma máquina
Windows:
set CLASSPATH=.;c:\CursoJava\Projetos;c:\local\javatools\classes.zip
Esta linha informa ao run-time Java para procurar as classes no diretório
atual, indicado pelo ponto (.), no diretório c:\CursoJava\Projetos e no arquivo
c:\local\javatools\classes.zip.
A linha que especifica CLASSPATH pode ser passada para o run-time Java
de várias maneiras. Se for uma especificação permanente, ela pode ser inserida
no arquivo AUTOEXEC.BAT, no caso do ambiente Windows. Pode também ser
digitada diretamente na linha de comandos.
No caso da lição anterior, se, estando dentro do diretório Curso digitarmos:
set CLASSPATH=C:\CursoJava\Projetos
Ou, no caso do ambiente Windows, podemos inserir essa linha no arquivo
AUTOEXEC.BAT, para resolver o problema permanentemente. Neste caso, é
conveniente incluir também o diretório atual (indicado por um ponto . ) em
CLASSPATH. A linha a ser digitada em AUTOEXEC.BAT seria portanto:
set CLASSPATH=.;C:\CursoJava\Projetos
O interpretador Java aceita também um argumento de linha de comando
chamado -classpath. Neste caso, o valor indicado pelo argumento -classpath tem
predominância sobre aquele indicado pela variável de ambiente CLASSPATH.
Também, se o argumento -classpath for usado, o interpretador não
acrescenta automaticamente a localização das classes de sistema, como acontece
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
108
quando usamos a variável de ambiente CLASSPATH. Isso significa que, ao usar o
argumento -classpath, cabe a você indicar também a localização das classes de
sistema de Java.
Observe que o valor da variável de ambiente CLASSPATH é reconhecido e
respeitado também pelo compilador javac. Além disso, o compilador javac aceita
também um argumento de linha de comando -classpath, com a mesma finalidade.
48. Mais sobre controle de acesso
Na lição 42 apresentamos as palavras-chave public e private, e explicamos
como podem ser usadas para controlar o acesso a variáveis e métodos em Java.
Apenas para relembrar, um membro private de uma classe somente pode
ser acessado por outros membros dessa classe.
Os packages, vistos na lição 46, também têm influência sobre o controle de
acesso. Em conjunto, os vários recursos de Java para essa finalidade
proporcionam ao programador um fino controle sobre o acesso e a visibilidade de
variáveis e métodos dentro de classes, sub-classes e packages.
Tanto as classes quanto os packages funcionam como formas de encapsular
e conter o espaço de nomes e o escopo de variáveis e métodos. Os packages
funcionam como repositórios de classes e também de outros packages. As classes
funcionam como repositórios de dados e código.
A classe é a menor unidade de abstração de Java. Levando em conta as
classes e os packages, podemos definir quatro níveis de visibilidade para os
membros das classes:
·
·
·
·
Subclasses do mesmo package
Não-subclasses do mesmo package
Subclasses em packages diferentes
Classes que não são subclasses nem estão no mesmo package
Os especificadores de acesso private, public e protected proporcionam as
formas necessárias para a criação dos vários níveis de acesso exigidos por essas
categorias. A tabela abaixo resume todas as possibilidades:
Private
Mesma classe
SIM
Subclasse do mesmo package
NÃO
Não-subclasse do mesmo package NÃO
Sem
modificador
SIM
SIM
SIM
Protected
public
SIM
SIM
SIM
SIM
SIM
SIM
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
109
Subclasse de outro package
Não-subclasse de outro package
NÃO
NÃO
NÃO
NÃO
SIM
NÃO
SIM
SIM
Embora esse mecanismo de controle de acesso possa parecer complicado,
podemos deduzir algumas regras simples da tabela acima:
·
aquilo que é declarado public pode ser acessado em qualquer lugar
(última coluna da tabela)
·
aquilo que é declarado private não pode ser visto fora de sua classe
(primeira coluna da tabela)
·
Quando não há um especificador de acesso, o membro é visível para
as subclasses e para as outras classes do mesmo package (segunda coluna); este
é o chamado acesso default
·
protected permite que um membro seja visível para as subclasses,
dentro ou fora do mesmo package (terceira coluna)
A tabela e as regras acima aplicam-se aos membros das classes.
As classes propriamente ditas têm somente dois níveis de acesso:
·
·
default: acessível somente dentro de seu package
public: acessível por qualquer outro código
49. A palavra-chave import
O ambiente de desenvolvimento Java traz diversas classes já prontas para
uso. Coerentemente, essas classes originais de Java são elas próprias organizadas
em packages.
Conforme vimos na lição 46, quando nos referimos a uma classe contida em
um package, precisamos usar seu nome completo na forma:
NomeDoPackage.NomeDaClasse
Isso tornaria tedioso o processo de utilizar as classes originais de Java, com
a necessidade de digitar longos nomes separados por pontos. Para evitar isso,
Java oferece a palavra-chave import.
Usando import, podemos importar classes ou packages inteiros. A partir daí,
podemos nos referir às classes importadas apenas por seu nome simples, sem
necessidade de incluir o nome do package.
Observe que o uso de import é apenas uma conveniência; se preferir, o
programador pode não utilizar essa palavra-chave, e sempre referir-se às classes
por seus nomes completos.
Porém, como na prática a maioria dos programas utiliza dúzias de classes, o
uso de import pode poupar muito trabalho de digitação e evitar erros.
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
110
No arquivo de código fonte, os comandos import devem aparecer logo após
o comando package, se houver.
Existem duas formas básicas para o uso de import. A forma
import java.util.Date;
é usada para importar uma classe específica, no caso acima, a classe Date
do package java.util.
A outra forma,
import java.io.*;
é usada para importar todas as classes de um determinado package. No
exemplo acima, estamos importando todas as classes do package java.io.
Esta última forma de import, usando o *, pode aumentar o tempo de
compilação, se forem importados muitos packages grandes. Por esse motivo, se
você quiser otimizar o tempo de compilação, o melhor é importar somente as
classes realmente necessárias, usando a primeira forma de import mostrada
acima.
Em qualquer dos casos, o uso de import não tem influência sobre a
performance ou o tamanho do programa depois de compilado.
Todas as classes da biblioteca padrão Java ficam contidas dentro de um
package chamado java. Esse package é dividido em sub-packages, como
java.lang, java.util e java.text.
Normalmente, precisamos importar os packages ou classes que queremos
usar em um dado programa. Porém há uma exceção: o package java.lang. Como
esse package contém a funcionalidade básica da linguagem Java, ele é
implicitamente importado para todos os programas. Ou seja, é como se todos os
programas Java contivessem a linha:
import java.lang.*;
Se dois packages diferentes importados contiverem classes com nomes
repetidos, o compilador só reclamará no momento em que o programador tentar
usar uma dessas classes. Nessa ocasião, o compilador emitirá um erro de
compilação. Para corrigi-lo, será preciso fornecer o nome completo da classe
desejada.
O nome completo de uma classe pode ser usado em qualquer ponto do
programa em que se utiliza o nome de uma classe. Por exemplo, os dois trechos
de código abaixo têm exatamente o mesmo efeito:
import java.util.*;
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
111
class UmaData extends Date
{
}
ou
class UmaData extends java.util.Date
{
}
Finalmente, um lembrete: as classes importadas estão obviamente sujeitas
às regras de acesso e visibilidade descritas na lição 48.
50. Interfaces
Java oferece um mecanismo que permite abstrair por completo a interface
de uma classe de sua implementação. Ou seja, usando a palavra-chave interface
é possível criar uma entidade que especifica o que uma classe deve fazer, sem
definir como ela deve fazer.
Uma interface é similar a uma classe, porém com duas diferenças
fundamentais:
·
·
uma interface não tem variáveis de instância
os métodos de uma interface são apenas declarados, não tendo corpo
Isso significa que é possível definir uma interface sem assumir qualquer
pressuposto sobre a forma como ela será implementada.
Depois de definida, a interface pode ser implementada por qualquer classe.
Ademais, uma única classe pode implementar diversas interfaces.
Quando uma classe implementa uma interface, normalmente ela deve
implementar todos os métodos definidos nessa interface. Caso contrário, a classe
deve obrigatoriamente ser declarada como abstract. Coerentemente, qualquer
classe derivada de uma classe abstract deve implementar todos os métodos, ou
ser ela própria declarada abstract.
Em todos os casos, a classe tem liberdade para determinar os detalhes de
sua implementação.
Do ponto de vista de orientação a objetos, a interface é um dos mecanismos
de Java para implementação do polimorfismo do tipo "uma interface, múltiplos
métodos", mencionado na lição 7.
As interfaces de Java suportam a resolução dinâmica de métodos em tempo
de execução. Normalmente, quando um método de uma classe é chamado de
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
112
dentro de outra classe, ambas as classes precisam estar presentes no momento
da compilação para que o compilador possa fazer a checagem da compatibilidade
das assinaturas dos métodos. Com isso, o ambiente de classes torna-se estático e
não-extensível.
O resultado disso é que a funcionalidade tende a se concentrar nas camadas
superiores da hierarquia de classes, para permitir que as rotinas estejam
disponíveis para o maior número possível de subclasses.
Com as interfaces, esse problema é evitado. O conceito de interface torna a
definição de um método independente da hierarquia de herança. Ou seja, as
interfaces obedecem a uma hierarquia diferente da hierarquia das classes. Isso
resulta no grande poder das interfaces: classes que não têm relacionamento
hierárquico podem implementar a mesma interface.
O conceito de interface representa efetivamente um substituto para a
herança múltipla no estilo de C++, que não está disponível em Java.
A definição de uma interface é feita de forma muito semelhante à definição
de uma classe. Eis um exemplo:
public interface InterfExemplo
{
void metodo1(int param);
double metodo2(double param1,
double param2);
// Etc.
} // Fim de InterfExemplo
Observe o uso da palavra-chave public. Tal como acontece com as classes
(lição 48) as interfaces somente podem usar este especificador de acesso. public
indica que a interface é publicamente acessível, podendo ser usada por qualquer
código Java. Além disso, todos os métodos e variáveis de uma interface public são
implicitamente public.
Se nenhum especificador de acesso for usado, a interface terá acesso
default, significando que é acessível somente dentro do package no qual é
declarada.
Conforme dissemos, os métodos da interface são declarados sem corpo. Um
caractere de ponto-e-vírgula (;) encerra cada declaração de método.
Isso significa que os métodos das interfaces são abstratos. Nenhum método
definido dentro de uma interface pode ter uma implementação default. Cabe a
cada classe que implementa a interface definir todos os métodos dessa interface.
Dissemos que as interfaces não podem conter variáveis de instância. Ou
seja, variáveis definidas dentro de uma interface são implicitamente consideradas
final ou static, significando que:
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
113
·
Os valores das variáveis não podem ser modificados pela classe que
implementa a interface
·
As variáveis devem ser inicializadas com um valor constante
Uma classe indica que implementa uma determinada interface usando a
palavra-chave implements. Caso uma classe implemente mais de uma interface,
os nomes das interfaces devem ser separados por vírgulas.
Se uma classe implementa duas interfaces que declaram o mesmo método,
apenas uma implementação do método deve ser feita.
Os métodos de uma interface devem ser declarados public dentro da classe
que os implementa.
Naturalmente, a assinatura do método de uma interface implementado
dentro de uma classe deve coincidir exatamente com a assinatura da definição
desse método contida na interface.
O exemplo abaixo ilustra a definição e implementação de uma interface
simples.
Listagem: InterfDemo.Java
1 // InterfDemo.java
2 // Ilustra o uso de uma interface.
3
4 interface InterfExemplo
5{
6 void metodo1(int param);
7 } // Fim de InterfExemplo
8
9 class InterfClass implements InterfExemplo
10 {
11 // Implementação do método
12 // declarado na interface.
13 public void metodo1(int param)
14 {
15
System.out.println(
16
"Valor de param = " + param);
17 } // Fim de metodo1()
18
19 // Um método que
20 // não é da interface.
21 void metodo2()
22 {
23
System.out.println(
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
114
24
25
26
27
28
29
30
31
32
33
34
35
36
"Metodo extra-interface");
} // Fim de metodo2()
} // Fim de InterfClass.
class InterfDemo
{
public static void main(String args[])
{
InterfClass IC = new InterfClass();
IC.metodo1(10);
IC.metodo2();
} // Fim de main()
} // Fim de class InterfDemo.
A interface InterfExemplo é definida nas linhas 4 a 7. A classe InterfClass
implementa a interface InterfExemplo (linha 9).
Como dissemos, o método metodo1() da interface InterfExemplo
é declarado dentro da classe InterfClass com o especificador de acesso
public.
Observe que uma classe que implementa uma interface pode também
implementar outros métodos extra-interface. É isso que acontece nas linhas 21 a
25.
A classe de teste InterfDemo cria um objeto da classe InterfClass (linha 32)
e em seguida chama seus dois métodos (linhas 33 e 34).
Eis a saída de InterfDemo.java:
Valor de param = 10
Metodo extra-interface
Mais Exemplos de Interfaces:
Interface IImposto:
public interface IImposto {
public double calcula();
}
Classe CalculoIcms:
public class CalculoIcms implements IImposto {
private double base, aliquota;
public double calcula(){
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
115
return ( ( base * aliquota ) / 100 );
}
public CalculoIcms( double base, double aliquota ){
this.base = base;
this.aliquota = aliquota;
}
}
Classe CalculoIpi:
public class CalculoIpi implements IImposto {
private double base, aliquota;
public double calcula(){
return ( ( base * aliquota ) / 100 );
}
public CalculoIpi( double base, double aliquota ){
this.base = base;
this.aliquota = aliquota;
}
}
Classe TesteImposto:
public class TesteImposto {
public static void main(String [] args){
Object calculo;
if (args[0].equals("icms")){
calculo = new CalculoIcms( Double.parseDouble(args[1]),
Double.parseDouble(args[2] )
);
}else {
calculo = new CalculoIpi( Double.parseDouble(args[1] ),
Double.parseDouble(args[2] )
);
}
imprime( (IImposto) calculo);
}
public static void imprime(IImposto p){
System.out.println(p.getClass().getName());
System.out.println( p.calcula() );
}
}
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
116
51. Acesso à banco de dabos usando JDBC
A tecnologia JDBC foi criada para facilitar o acesso de programas Java a qualquer
banco de dados relacional. A idéia era criar uma maneira simples de prover
acesso à sintaxe SQL, propiciando ao programador o uso de SELECT´s,
UPDATE´s e outros comandos relevantes na sintaxe.
JDBC é um conjunto de classes e interfaces (API),foi inspirado em ODBC da
Microsoft que é baseado no Open Group (X/Open) SQL CLI (Call Level Interface).
Simplificando, JDBC realiza as seguintes tarefas:
1. Carregar o driver de acesso ao banco de dados
2. Estabelece uma conexão com um banco de dados
3. Executa comandos SQL
4. Recebe um conjunto de resultados
5. Executa stored procedures
6. Obtém informações sobre o banco de dados (metadados)
7. Executa transações
1. Carregando o driver
Ex.: usando driver jdbc-odbc: Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”)
Ou, usando driver jdbc MySql: Class.forName(“org.gjt.mm.mysql.Driver”)
2. Estabelecendo um conexão
Connection con = DriverManager.getConnection(url, login, Password);
Exemplo de url:
“jdbc:odbc:BD” para jdbc-odbc ou usar jdbc driver (consultar documentação do
driver para ver o sub-prlo)
"jdbc:mysql://53.248.185.200:3306/curso"
onde:
- jdbc:mysql indica o nome do driver;
- 53.248.185.200 indica a máquina host com a qual se realizará a conexão;
- 3306 é a porta escolhida no host;
- curso: é o nome do BD.
3. Criando um comando JDBC
// cria um statement na conexão aberta
Statement stmt = con.createStatement();
stmt.executeUpdate(“CREATE TABLE Empregado” +
“(matricula int, nome varchar(20),” +
“endereco varchar(32), salario float)”);
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
117
4. Inserindo dados
stmt.executeUpdate(“INSERT INTO Empregado values”+
“(1000, ‘Biliu’, ‘Rua das Cruzetas, Sem Futuro, Brasil’, 30.000,00)”);
5. Selecionando valores
JDBC retorna resultados num objeto da classe ResultSet portanto devemos criar
uma instância desta classe para armazenar os resultados.
ResulSet rs = stmt.executeQuery(“Select * from empregado”);
6. Usando o método next
A variável rs é um cursor para o conjunto resultado obtido da consulta. Portanto,
usamos o método next() da classe ResultSet para avançarmos o cursor para a
próxima linha do resultado.
Como vimos antes, este cursor é posicionado inicialmente para uma linha acima
do resultado. Portanto, acessamos à primeira linha usando rs.next().
Então para varremos todo o resultado precisamos de um laço do tipo:
while(rs.next()) {
…
}
7. Usando os métodos getXXX
Para cada tipo de dado em SQL usamos o acessor equivalente em JDBC.
Por exemplo: varchar é lido com getString(), int com getInt(), double com
getDouble(), etc..
Então o laço anterior ficaria da seguinte forma:
while (rs.next()) {
int mat = rs.getInt(“Matricula”);
String nome = rs.getString(“Nome”);
String endereco = rs.getString(“Endereco”);
float salario = rs.getFloat(“Salario”);
}
Existe uma outra maneira de identificar as colunas na tabela: usando a posição do
atributo na tabela. Por exemplo:
while rs.next()) {
int mat = rs.getInt(1);
String nome = rs.getString(2);
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
118
}
String endereco = rs.getString(3);
float salario = rs.getFloat(4);
8. Atualizando dados
String updateString = “Update Empregado set salario = salario * 1.1 where
salario < 1000.00”;
Stmt.executeUpdate(updateString);
9. Usando prepared statements
Usado principalmente quando queremos passar parâmetros pelo SQL.
Muitas vezes o comando já vai para o SGBD compilado, o que reduz seu tempo de
processamento.
Ex.: PreparedStatment updateSalario = con.prepareStatement(“UPDATE
Empregado set salario = ? where matricula = ?”);
Precisamos então suprir os valores que estão com ?.
Isto é feito usando métodos setXXX definidos na interface PreparedStatement
updateSalario.setFloat(1, 10000);
updateSalario.setInt(2, 1000);
updateSalario.executeUpdate();
onde o primeiro argumento refere-se a qual ? e o segundo contém o valor.
Então o prepadedStatement seria equivalente a:
stmt.executeUpdate(“UPDATE Empregado set salario = 10000 where matricula =
1000”);
10. Liberando os recursos
Após o uso precisamos fechar o statement e a conexão usando os métodos:
stmt.close();
con.close();
-
Connection: representa a conexão com o SGBD especificado. Dentro do
contexto da conexão, comandos SQL são executados e os resultados
devolvidos
DatabaseMetaData: usado para obter informações sobre as bases, tabelas,
atributos, índices, tipos de atributos
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
119
-
Statement: usado ara executar comandos SQL estáticos e obter
PreparedStatement: usados para executar comandos SQL que se repetem
várias vezes e executar consultas parametrizadas.
CallableStatement: usado para executar Stored Procedures
ResultSet: usado para acessar os dados retornados por um comando SQL
ResultSetMetaData: usado para obter informações sobre os atributos que
compõem o resultado de uma consulta.
- Exceções
Precisamos colocar os comandos SQL dentro de um try … catch, para que
possamos manipular as exceções que venham a acontecer.
A API JDBC permite que se veja as exceptions geradas pelo SGBD e pela Java
Virtual Machine.
Para tanto, devemos imprimi-las para vermos o que aconteceu.
Ex.: Forma simples de manipular exceções
…
try {
// Comandos
} catch (SQLException e) {
System.out.println(“SQLException:”+e.getMessage());
}
...
Exemplo de menssagem impressa:
SQLException: There is already an object called ‘Empregado’int the database.
Severity 16, State 1, Line 1
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
120
Resumo de um cenário de uma aplicação JDBC
Cliente DriverManager Connection Statement
ResultSet
1
2
3
4
5
6
Onde:
1. getConnection
2. createStatement
3. executeQuery
4. Laço: next e get
5. close
6. close
Mapeamento entre tipos SQL e tipos Java
SQL type
CHAR
VARCHAR
LONGVARCHAR
NUMERIC
DECIMAL
BIT
Java Type
String
String
String
Java.lang.Bignum
Java.lang.Bignum
boolean
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
121
TINYINT
SMALLINT
INTEGER
BIGINT
REAL
FLOAT
BINARY
DATE
TIME
TIMESTAMP
byte
short
int
long
float
double
byte[]
java.sql.Date
java.sql.Time
java.sql.Timestamp
Listagem: AcessoBanco.java
1 import java.sql.*;
2
3 public class AcessoBanco {
4
5
public static void main(String[] args) {
6
String driver = "org.gjt.mm.mysql.Driver";
7
String url = "jdbc:mysql://53.248.185.200:3306/curso";
8
String user = "root";
9
String password = "";
10
String strQuery = "";
11
ResultSet rs = null;
12
13
try {
Class.forName(driver);
14
15
Connection conectar =
DriverManager.getConnection(url,user,password);
16
17
strQuery = "select * from usuarios";
PreparedStatement pstmt = conectar.prepareStatement(
18
19
strQuery );
20
rs = pstmt.executeQuery();
21
22
while(rs.next()){
23
System.out.print("Usuario = " + rs.getString("usuario") +
"\t");
24
25
System.out.println("Senha = " + rs.getString("senha"));
26
}
27
28
} catch(ClassNotFoundException cnfex){
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
122
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
}
46 }
System.err.println("Erro ao conectar na base...");
cnfex.printStackTrace();
System.exit(1);
} catch(SQLException sqlex){
System.err.println("Sem conexão...");
sqlex.printStackTrace();
} catch(Exception ex){
ex.printStackTrace();
}
Rua Valorbe, 123 – Lauzane Paulista – Cep 02442-140 – São Paulo – SP
[email protected]
123