Declarando
Campos
Um campo é uma variável declarada no corpo de uma classe e sua
declaração tem a sintaxe abaixo:
[ ( 'public' | 'private' | 'protected' ) ]
[ ( 'final' | 'volatile' ) ]
[ 'static' ] [ 'transient' ]
tipo_do_dado nome_do_campo [ '=' expressao ] ';'
A declaração de um campo especifica o nome do campo, o tipo do
dado, uma expressão opcional que inicializa o campo, especificadores de acesso
e modificadores:
class Pessoa
{
String nome; // Nome da pessoa
String endereco; // Endereço da pessoa
int idade; // Idade da pessoa
}
Declarando
Métodos
Para declarar um método no código fonte, use a seguinte sintaxe:
[ ( 'public' | 'private' | 'protected' ) ]
( [ 'abstract' ] | [ 'final' ] [ 'static' ] [ 'native' ] [ 'synchronized' ] )
tido_de_retorno nome_do_metodo '(' [ lista_de_parametros ] ')'
sequencia_de_instrucoes
Uma
declaração de método consistem de uma assinatura de método seguida por uma
sequência de instruções. A assinatura do
método especifica o nome do método, o tipo de dado de retorno, a lista de
parâmetros, especificadores de acesso, modificadores e os tipos de exceções que
o método pode lançar. A sequência de
instruções é um bloco (grupo) de instruções que executam quando o método é
chamado pelo código executado em um outro método. O código que chama um método
é conhecido como chamador do método.
Antecedendo cada método está um tipo de dado de retorno, que ou identifica o
tipo de dado dos valores que o método retorna ou indica que o método não
retorna nada. Além de void tipo_de_retorno pode ser:
- uma das palavras-chave de tipos de dados primitivos: boolean, byte, char, double, float, int, long
ou short;
- o identificador de um tipo de dado referência (referência para um objeto).
Um par de parênteses segue cada método. Você pode opcionalmente declarar uma
lista de variáveis separada por vírgulas entre os parênteses. Cada variável na
declaração é conhecida como um parâmetro.
Modificadores
de Membros
Existem
palavras reservadas que servem para modificar o comportamento ou a forma de
tratamento dos membros de uma classe (campos e métodos). Alguns servem para
diferenciar o nível de acesso ao um membro e outros servem para adicionar
características diferenciadas aos membros da classe. Vamos vê-los um a um:
Modificadores
de Acesso
Em Java, você pode usar
especificadores de acesso para proteger tanto as variáveis quanto os métodos de
uma classe quando você os declara. A linguagem Java suporta quatro níveis de
acesso distintos para variáveis e métodos membros: private, protected,
public e, se deixar sem especificação, package-access .
A tabela abaixo mostra o nível de acesso permitido por cada especificador:
Especificador
|
classe
|
sub-classe
|
package
|
exterior
|
private
|
X
|
|
|
|
protected
|
X
|
X*
|
X
|
|
public
|
X
|
X
|
X
|
X
|
package
|
X
|
|
X
|
|
A primeira coluna indica se a própria classe tem acesso ao membro definido pelo
especificador de acesso. Como você pode ver, uma classe sempre tem acesso aos
seus próprios membros. A segunda coluna indica se subclasses da classe
(independente em qual package elas estejam) têm acesso ao membro. A terceira
coluna indica se classes no mesmo package que a classe (independente de seus
parentescos) têm acesso ao membro. A quarta coluna indica se todas as classes
têm acesso ao membro.
A interseção protected/subclasse tem um '*' (este caso em particular será
discutido mais adiante).
Vejamos agora detalhadamente cada um dos níveis de acesso:
private:
O nível de acesso mais restritivo é o private. Um membro privado é acessível
apenas para a classe na qual ele é definido. Use este modificador para declarar
membros que devem ser usados apenas pela classe. Isto inclui variáveis que
contém informações que se acessadas por algum objeto estranho poderia colocar o
objeto em um estado inconsistente, ou métodos que se invocados por outros
objetos poderiam colocar em risco o estado do objeto ou do programa que o está
executando. Membros privados são como segredos que você nunca falaria a
ninguém.
Para declarar um membro privado, use a palavra-chave private na sua declaração.
A classe a seguir contém uma variável membro privada e um método privado.
class Alpha {
private int iamprivate;
private void privateMethod() {
System.out.println("Método privado");
}
}
Objetos do tipo Alpha podem examinar ou modificar a variável
iamprivate e podem invocar privateMethod(), mas objetos de outros tipos não
podem. Por exemplo, a classe Beta definida aqui:
class Beta {
void accessMethod() {
Alpha a = new Alpha();
a.iamprivate = 10; // ilegal
a.privateMethod(); // ilegal
}
}
não pode acessar a variável iamprivate ou invocar privateMethod()
sobre um objeto do tipo Alpha porque Beta não é do tipo Alpha.
Quando uma de suas classes está tentando acessar uma variável membro para a
qual ela não tem acesso, o compilador imprimirá uma mensagem de erro similar à
seguinte e não compilará o programa:
Beta.java:12: No method matching privateMethod() found in class Alpha.
a.privateMethod(); // illegal
1 error
Novos programadores Java poderiam se perguntar se um objeto Alpha
pode acessar os membros privados de um outro objeto Alpha. Isto é ilustrado no
exemplo a seguir. Suponha que a classe Alpha contivesse um método de instância
que comparasse o objeto Alpha atual (this) a outro objeto colocado na sua
variável iamprivate:
class Alpha {
private int iamprivate;
boolean isEqualTo(Alpha anotherAlpha) {
if (this.iamprivate == anotherAlpha.iamprivate)
return true;
else
return false;
}
}
Isto é perfeitamente legal. Objetos do mesmo tipo têm acesso aos
membros privados uns dos outros. Isto porque as restrições de acesso aplicam-se
no nível da classe ou do tipo (todas as instâncias de uma classe) em vez de ser
aplicada apenas sobre o objeto (a instância particular de uma classe).
protected:
O próximo especificador de nível de acesso é protected que permite que a
própria classe, subclasses, e todas as classes no mesmo package acessem os membros.
Use o nível de acesso protected quando as subclasses de uma classe tem que
acessar os seus membros, mas não classes não relacionadas. Membros protegidos
são como segredos de família (você não se importa se toda a família sabe e
mesmo se alguns poucos amigos sabem, mas você não gostaria que algum estranho
soubesse).
Para declarar um membro protegido use a palavra-chave protected. Primeiro vamos
ver como o especificador protected afeta o acesso para as classes no mesmo
package.
Considere esta versão da classe Alpha que é agora declarada para estar dentro
de um package chamado Greek e que tem uma variável membro e um método protegido
declarados nela:
package Greek;
class Alpha {
protected int iamprotected;
protected void protectedMethod() {
System.out.println("protectedMethod");
}
}
Agora, suponha que a classe, Gamma, também foi declarada para ser
um membro do package Greek (e ela não é uma subclasse de Alpha). A classe Gamma
pode acessar legalmente a variável membro iamprotected de um objeto Alpha e
pode legalmente invocar seu protectedMethod():
package Greek;
class Gamma {
void accessMethod() {
Alpha a = new Alpha();
a.iamprotected = 10; // legal
a.protectedMethod(); // legal
}
}
É bem simples. Agora, vamos investigar como o especificador
protected afeta o acesso para subclasses de Alpha.
Vamos introduzir uma nova classe, Delta, que deriva de Alpha mas que fica em um
package diferente (Latin). A classe Delta pode acessar tanto iamprotected quanto
protectedMethod(), mas apenas sobre objetos do tipo Delta ou suas subclasses. A
classe Delta não pode acessar iamprotected ou protectedMethod() em objetos do
tipo Alpha. accessMethod() no código exemplo a seguir tenta acessar a variável
membro iamprotected em um objeto do tipo Alpha, o que é ilegal, e em um objeto
do tipo Delta, o que é legal. Da mesma forma, accessMethod() tenta invocar um
protectedMethod() de objetos Alpha o que também é ilegal:
import Greek.*;
package Latin;
class Delta extends Alpha {
void accessMethod(Alpha a, Delta d) {
a.iamprotected = 10; // ilegal
d.iamprotected = 10; // legal
a.protectedMethod(); // ilegal
d.protectedMethod(); // legal
}
}
Se uma classe é tanto uma subclasse
da classe com o membro protegido e está no mesmo pacote dela, então a classe
tem acesso ao membro protegido.
public:
A especificador de acesso mais fácil é o public. Qualquer classe, em qualquer
package, tem acesso aos membros públicos da classe. Declare membros públicos
apenas se tal acesso não puder produzir resultados indesejados se algum objeto
externo os utilizar. Não existem segredos pessoais ou de família aqui; isto é
para as coisas que você não se importa se outros ficarem sabendo.
Para declarer um membro público, use a palavra-chave public. Por exemplo,
package Greek;
class Alpha {
public int iampublic;
public void publicMethod() {
System.out.println("publicMethod");
}
}
Vamos re-escrever nossa classe Beta uma vez mais e colocá-la em um
package diferente de Alpha e garantir que ela esteja completamente
desrelacionada (não é uma subclasse) à Alpha:
package Roman;
import Greek.*;
class Beta {
void accessMethod() {
Alpha a = new Alpha();
a.iampublic = 10; // legal
a.publicMethod(); // legal
}
}
Como você pode ver do código acima, Beta pode inspecionar e
modificar a variável iampublic na classe Alpha e pode legalmente invocar
publicMethod().
package:
E finalmente, o último nível de acesso é aquele que você tem se não ajustar
explicitamente o nível de acesso do membro para um dos outros níveis. Este
nível de acesso permite que classes no mesmo package que a sua classe acessem
os membros. Este nível de acesso assume que classes no mesmo package são amigos
confiáveis. Este nível de confiança é como aquele que você extende para os seus
amigos mais próximos mas que não concederia até mesmo para sua família. Por
exemplo, esta versão da classe Alpha declara uma variável membro com nível de
acesso de package e um método simples com nível de package. Alpha fica no package Greek:
package Greek;
class Alpha {
int iampackage;
void packageMethod() {
System.out.println("packageMethod");
}
}
A classe Alpha acessa tanto iampackage quanto packageMethod().
Além disso, todas as classes declaradas dentro do mesmo package que Alpha
também têm acesso à iampackage e packageMethod(). Suponha que tanto Alpha e
Beta foram declaradas como parte do package Greek:
package Greek;
class Beta {
void accessMethod() {
Alpha a = new Alpha();
a.iampackage = 10; // legal
a.packageMethod(); // legal
}
}
Assim, Beta pode acessar
legalmente iampackage e packageMethod() como mostrado.
Outros
Modificadores
abstract:
Classes e métodos podem ser abstratos. Métodos abstratos não possuem corpo.
Dessa forma eles devem ser implementados por uma subclasse. Uma classe que
contém métodos abstratos é uma classe abstrata.
abstract class Item {
...
abstract void print();
...
abstract object nextElement();
...
abstract boolean hasMoreElements();
..
}
static:
Quando usado antes da declaração de um método, a palavra-chave static indica
que este método é um método de classe. Isto significa que existe apenas uma cópia
do método que está associada com a classe e não com alguma instância criada da
classe. Então um método de classe é invocado através da classe em vez de ser
através de uma instância da classe. O mesmo vale para declaração de variáveis.
De dentro de um método estático, não se pode acessar variáveis de instância ou
métodos de instância. Uma variável de classe é criada quando uma classe é
carregada e é destruída se e quando a sua classe é descarregada (pela JVM). Uma
vez que uma classe é carregada, a JVM aloca memória para cada variável de
classe e estabelece um valor default para ele.
class Estatico
{
// variável estática
public static int valor = 5;
// método estático
// só pode manipular valores estáticos da classe
public static int getValor()
{ return valor; }
}
public class Teste
{
public static void main( String[] args )
{
// Acessando o valor da variável estática
// da classe Estatico
System.out.println(Estatico.valor);
// Alterando o valor da variável estática
Estatico.valor++;
// Chamando um método estático
System.out.println(Estatico.getValor());
}
}
Veja o código a seguir:
class Employee
{
private static int numEmployees;
static void setEmployeeCount (int numEmployees)
{
this.numEmployees = numEmployees; // erro de compilação.
}
}
O erro ocorre porque a palavra-chave this representa à intância da
classe corrente, e o método de classe setEmployeeCount não está associado com
uma instância da classe.
volatile:
A palavra-chave volatile pode ser aplicada a variáveis em uma classe. Ela
especifica que a variável é usada por threads sincronizadas e que o compilador
não deve tentar efetuar otimizações com ela. Veja abaixo a utilização de
volatile:
private volatile int doNotOptimizeAccessToMe;
final:
A palavra-chave final pode ser usada com declarações de classes, métodos ou
variáveis. O significado nestes três usos é similar, mas não idêntico.
public final
class Leaf { // esta nunca poderá ter subclasses
// esta variável nunca poderá ter seu valor ajustado -> valor constante
public final int SIZE = 15;
// este método nunca poderá ser sobrescrito
public final void tryToOverride() {}
}
synchronized:
A palavra-chave synchronized é usada se tivermos múltiplas threads e quisermos
evitar que elas modifiquem objetos ou arrays simultaneamente de forma que possa
haver alguma inconsistência de valores. A instrução synchronized pode ser usada
de duas formas: antes de um bloco de código ou como um modificador de método.
Uso de synchronized antes de um bloco de código. Depois da palavra -chave
synchronized vem uma referência par aum objeto ou array. A código crítico não é
executado até que um bloqueio exclusivo para o objeto ou array possa ser
obtido.
public static void SortIntArray(int[] a) {
synchronized (a) {
..// aqui vem o código crítico
}
}
A palavra-chave synchronized é mais usada em Java como um
modificador de método ou classe. Desta forma ela indica que todo o método é uma
seção crítica de código que tem que ser executado sozinha.
public synchronized void SortIntArray(int[] a) {
..// critical code comes here
}
native:
A palavra-chave native pode ser usada com declarações de métodos. Ela indica
que o método é implementado em código nativo, dependente da plataforma (por
exemplo em C). Devido a perda de uma das vantagens, a independência de
plataforma, apenas utilize código nativo para aplicações críticas se nenhuma
outra otimização de código for suficiente.
public class NativeTest {
public native void printNative( String text );
}
transient:
Quando queremos transportar algum dado que está armazenado em um objeto Java
através da rede ou mesmo torná-lo persistente em algum dispositivo de
armazenagem devemos serializar o objeto, isto é, transformá-lo em uma
sequência de bytes que posteriormetne poderá ser completamente restaurada para
gerar novamente o objeto original (futuramente trateremos de serialização em um
artigo).
Quando você está controlando serialização, pode haver algum subobjeto
particular que você não queira que o mecanismo de serialização do Java salve e
restaure automaticamente. Este geralmente é o caso se tal subobjeto representa
alguma informação importante que você não queira serializar, tal como uma
senha. Mesmo que esta informação seja privada dentro do objeto, quando ela for
serializada, é possível que alguém acesse-a lendo o arquivo ou interceptando
uma transmissão na rede.
Ao trabalhar com serialização geralmente implementamos nossas classes a partir
da interface Serializable. Com isso a serialização de todos os dados da classe
será automática. Para controlar isto, você pode desligar a serialização sobre
um campo utilizando a palavra-chave transient, que diz "Não se
incomode de salvar ou restaurar ele, eu cuidarei disto."
Por exemplo, considere um objeto Login que guarda informações sobre uma sessão
logada. Suponha que, uma vez verificado o login, você queira armazenar os
dados, mas sem o password. A forma mais fácil de se fazer isto é implementando
Serializable e tornando o campo password um transient. Veja como ficaria:
import java.io.*;
import java.util.*;
public class Logon implements Serializable {
private Date date = new Date();
private String username;
private transient String password;
public Logon(String name, String pwd) {
username = name;
password = pwd;
}
public String toString() {
return "Informações: \n username: " + username +
"\n data: " + date + "\n password: " + password;
}
public static void main(String[] args) throws Exception {
Logon a = new Logon("HourGlass", "pentaBrasil");
System.out.println( "logon a = " + a);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Logon.out"));
o.writeObject(a);
o.close();
Thread.sleep(1000); // Espera por um segundo
// Restaurando os dados
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Logon.out"));
System.out.println("Recuperando objeto em "+new Date());
a = (Logon)in.readObject();
System.out.println("logon a = " + a);
}
}
Como você pode ver, a data e o nome de usuário são normais (não
transientes) e, assim, são automaticamente serializados. Já o password é
transiente, e por isso não é armazenado no disco; e o mecanismo de serialização
nem tenta recuperá-lo. You can
see that the date and username fields are ordinary (not transient), and thus
are automatically serialized. However, the password is transient, so it is not
stored to disk; also, the serialization mechanism makes no attempt to recover
it. A saída fica assim:
logon a = logon info:
username: Hulk
date: Mon Oct 21 12:10:13 MDT 2002
password: myLittlePony
Recovering object at Mon Oct 21 12:10:14 MDT 2002
logon a = logon info:
username: Hulk
date: Mon Oct 21 12:10:13 MDT 2002
password: null
Quando o objeto é recuperado, o campo de password é nulo.