static
)Os atributos que criámos até agora denominam-se "de instância" pois cada instância da classe tem esses atributos; os seus valores, em cada instante, definem o "estado" do objeto.
De igual forma, os métodos que criámos até agora denominam-se "de instância" pois:
pontuacaoObjetivo
sobre um jogo que tem 30 como objetivo e sobre um jogo que tem 100 como objetivo).Há casos em que precisamos de definir um valor que está relacionado com os objetos duma dada classe mas que é igual para todos. Também há casos em que precisamos de definir um método numa classe que não depende das características específicas de cada objeto. Para estes casos queremos criar atributos e métodos de classe.
Não queremos usar literais "espalhados" pelos métodos das nossas classes pois isso dificulta não só a compreensão (sobre o que representam) mas também uma futura alteração desses valores no texto dos programas que os usam.
No nosso caso, queremos evitar escrever diretamente os valores 5, 0 e 10 na classe Jogo
, tanto no corpo dos métodos como na documentação. Estes são os valores do número máximo de jogadores e dos valores mínimo e máximo de pontuação por jogada, respetivamente.
Vamos criar mais três atributos e atribuir-lhes estes valores no construtor? Não propriamente. Se não, vejamos:
Basta defini-los como atributos de classe
Um atributo de classe
- existe um só para a classe e não um por cada instância;
- declara-se usando a palavra
static
.
Além de querermos defini-los como atributos de classe (static
) também queremos que os seus valores se mantenham constantes e não possam ser alterados durante a vida de um objeto deste tipo.
Para indicar que são constantes usamos a palavra reservada final
e atribuímos-lhes um valor inicial (que não mudará).
Por convenção, os nomes das constantes escrevem-se todos em maiúsculas e com _
como separador de palavras.
public
Queremos ainda que as classes cliente de Jogo
possam conhecer estes valores.
Declaramo-los então como public
– como são constantes, não há problema em serem public
pois nenhuma classe cliente conseguirá alterar o seu valor.
Acrescentamos estes atributos de classe constantes à nossa classe e alteramos os pontos onde eram usados estes valores, tanto internamente como na documentação, para passarem a referir estas constantes:
xxxxxxxxxx
public class Jogo {
/**
* O numero maximo de jogadores por jogo
*/
public static final int MAX_JOGADORES = 5;
/**
* O minimo e maximo de pontos que um jogador pode ter
* numa jogada
*/
public static final int MIN_PONTUACAO_JOGADA = 0;
public static final int MAX_PONTUACAO_JOGADA = 10;
// atributos que definem este jogo:
// A coleccao dos jogadores
private Jogador [] jogadores;
// Pontuacao objetivo
private int objetivo;
// Numero de jogadores em jogo
private int quantosEmJogo;
/**
* Construir um jogo.
* @param objetivo A pontuacao com que se ganha este jogo
* @requires objetivo > 0
*/
public Jogo (int objetivo) {
this.objetivo = objetivo;
this.jogadores = new Jogador [MAX_JOGADORES];
this.quantosEmJogo = 0;
}
/**
* A pontuacao objetivo deste jogo.
*/
public int pontuacaoObjetivo () {
return this.objetivo;
}
/**
* O numero de jogadores que estao em jogo.
*/
public int quantosEmJogo () {
return this.quantosEmJogo;
}
/**
* Um dado jogador estah em jogo?
* @param nomeJog O nome do jogador.
* @return true se o jogador com este nome estah inscrito neste jogo
* false cc
*/
public boolean estahEmJogo (String nomeJog) {
return this.obterJogador(nomeJog) != null;
}
/**
* Juntar um jogador com um dado nome a este jogo.
* @param nomeJog O nome do jogador.
* @requires !this.estahEmJogo(nomeJog) &&
* this.quantosEmJogo() < MAX_JOGADORES
*/
public void juntarJogador (String nomeJog) {
this.jogadores[this.quantosEmJogo] = new Jogador (nomeJog);
this.quantosEmJogo++;
}
/**
* Juntar um jogador a este jogo.
* @param novo O novo jogador.
* @requires novo != null &&
* !this.estahEmJogo(novo.nome()) &&
* this.quantosEmJogo() < MAX_JOGADORES
*/
public void juntarJogador (Jogador novo) {
this.jogadores[this.quantosEmJogo] = novo.copia();
this.quantosEmJogo++;
}
/**
* Retorna o jogador correspondente a um nome, ou null se nao houver.
* Metodo privado
* @param nomeJog O nome do jogador.
* @returns O jogador com este nome se existir no jogo;
* null c.c.
*/
private Jogador obterJogador (String nomeJog) {
Jogador result = null;
for (int i = 0 ; result == null && i < this.quantosEmJogo ; i++){
if (nomeJog.equals(this.jogadores[i].nome())){
result = this.jogadores[i];
}
}
return result;
}
/**
* Registar a pontuacao na jogada atual de um dado jogador.
* @param nomeJog O nome do jogador.
* @param pontuacao A pontuacao obtida na jogada atual.
* @requires this.estahEmJogo (nomeJog) &&
* pontuacao >= MIN_PONTUACAO_JOGADA &&
* pontuacao <= MAX_PONTUACAO_JOGADA
*/
public void registarPontosJogada (String nomeJog, int pontuacao) {
this.obterJogador(nomeJog).registarPontos(pontuacao);
}
/**
* A pontuacao de um dado jogador deste jogo
* @param nomeJog O nome do jogador.
* @return A pontuacao do jogador com este nome
* @requires this.estahEmJogo (nomeJog)
*/
public int pontuacao (String nomeJog) {
return this.obterJogador(nomeJog).pontuacao();
}
/**
* A pontuacao maxima numa soh jogada de um dado jogador deste jogo
* @param nomeJog O nome do jogador.
* @return A pontuacao maxima numa soh jogada do jogador com este nome
* @requires this.estahEmJogo (nomeJog)
*/
public int maximoNumaJogada (String nomeJog) {
return this.obterJogador(nomeJog).maximoNumaJogada();
}
// Ainda nao estah completa
}
Segue-se um exemplo de utilização destas constantes. As seguintes instruções numa qualquer classe cliente de Jogo
,
xxxxxxxxxx
Scanner leitor = new Scanner(System.in);
Jogo teuJogo = new Jogo(65);
teuJogo.juntarJogador("John Snow");
System.out.print("Qual a pontuacao? ");
double pontos;
do{
System.out.println("insira valor entre " + Jogo.MIN_PONTUACAO_JOGADA +
" e " + Jogo.MAX_PONTUACAO_JOGADA);
pontos = leitor.nextDouble();
} while(pontos < Jogo.MIN_PONTUACAO_JOGADA ||
pontos > Jogo.MAX_PONTUACAO_JOGADA);
teuJogo.registarPontosJogada("John Snow",pontos);
pedem ao utilizador um valor entre Jogo.MIN_PONTUACAO_JOGADA
e Jogo.MAX_PONTUACAO_JOGADA
e invocam o método que permite registar nova pontuação para um jogador.
Esta classe respeita a pré-condição do método registarPontosJogada
, pois este só é invocado quando há a certeza de que o valor para o parâmetro pontuacao
está no intervalo exigido pela pré-condição.
Enquanto o utilizador introduzir um valor fora do intervalo [0,10], verá a mensagem "insira valor entre 0 e 10" no ecrã. Logo que introduza um valor no intervalo requerido, o ciclo termina e o método registarPontosJogada
é invocado.
Suponhamos que queremos ter um segundo construtor na classe Jogo que permita registar todos os jogadores no momento de criação do jogo, em vez de criar um jogo e ir adicionando jogador a jogador como temos feito.
Esse construtor poderia ter a seguinte forma:
x/**
* Construir um jogo registando logo os jogadores
* @param designacao
* @param objetivo
* @param participantes Nomes dos participantes
* @requires designacao != null && objetivo > 0 &&
* ???????
*/
public Jogo(String designacao, int objetivo,
String[] participantes) {
this.designacao = designacao;
this.objetivo = objetivo;
this.jogadores = new Jogador[participantes.length];
for (int i = 0 ; i < participantes.length ; i++) {
this.jogadores[i] = new Jogador(participantes[i]);
}
}
Na pré-condição gostaríamos de exprimir uma série de restrições acerca do array participantes
como, por exemplo, que tem que ter um ou mais elementos mas que não pode ter mais do que MAX_JOGADORES
elementos e que não pode conter nenhum elemento repetido.
O ideal seria ter um método na classe Jogo
que, dado um array de strings, permitisse verificar se esse array satifaz as restrições acima. Este método – chamemos-lhe dadosValidos
– podia então ser invocado em qualquer classe cliente de Jogo
para verificar se um dado array de strings pode ser usado para criar um jogo.
Se este método dadosValidos
fosse um método de instância, teríamos que o invocar sobre um objeto do tipo Jogo
. Mas que objeto seria este? Nós queremos saber se o array de strings é válido precisamente para o podermos usar para criar um jogo! Logo, não faz sentido ser invocado sobre uma instância de Jogo
.
Este método tem ser um método de classe, ou seja, ele deve ser invocado sobre a classe Jogo
e não sobre uma qualquer instância em particular, pois o resultado do método não depende de maneira nenhuma das características particulares de nenhum objeto.
Indicamos que é de classe usando a palavra static
.
xxxxxxxxxx
/**
* Uma dada lista de nomes eh valida para construir um
* jogo?
* @param nomes Nomes para os jogadores
* @return true se nomes != null e
* nomes.length in [1,MAX_JOGADORES] e
* nao tem nomes repetidos
*/
public static boolean dadosValidos(String[] nomes) {
boolean result = nomes != null &&
nomes.length > 0 &&
nomes.length <= MAX_JOGADORES;
// verificar se há nomes repetidos
for (int i = 0 ; result && i < nomes.length ; i++) {
String nome = nomes[i];
result = nome != null;
for (int j = 0 ; result && j < nomes.length ; j++) {
if(nome.equals(nomes[j]) && i != j) {
result = false;
}
}
}
return result;
}
Para invocar um método de classe, o alvo da invocação é a classe, como exemplificado nestas instruções numa qualquer classe cliente de Jogo
:
x...
String[] nomes = {"Maria","Pedro"};
if(!Jogo.dadosValidos(nomes)) {
System.out.println("Conjunto de nomes de participantes invalido");
}
...
Anterior: 11.4. Outros métodos relativos a um dado jogador do jogo
Seguinte: 11.6. O método toString na classe Jogo