Java Atributos
Os atributos são as variáveis que moram dentro de um objeto: o nome e o preço de uma fruta, o titular e o saldo de uma conta. Eles guardam o estado do objeto. Nesta aula você aprende a declará-los, a ler e mudar os seus valores e, principalmente, a entender que cada objeto tem os seus.
- Como declarar atributos numa classe, com ou sem valor inicial.
- Os valores padrão: por que um atributo sem valor vale
0,falseounull(e uma variável local não). - Como ler e mudar atributos com
objeto.campo, inclusive com+=e++. - Que cada objeto tem a sua própria cópia dos atributos (e a exceção:
static). - Atributos que são outros objetos, e os erros que aparecem pelo caminho.
A ideia antes do código: os campos da ficha
Volte à ficha de matrícula da aula O que é POO. Os campos impressos na ficha ("Nome: ____", "Turma: ____") são os atributos: todas as fichas têm os mesmos campos. O que cada aluno escreve neles é o valor do atributo, e muda de ficha para ficha. Apagar e reescrever a turma na ficha do Bruno não muda a ficha da Carla.
Em Java, os atributos também são chamados de campos (fields, no inglês da documentação) ou variáveis de instância. São três nomes para a mesma coisa.
Declarar atributos
class Fruta {
String nome;
double preco;
int estoque = 10;
}
- String nome;
- Um atributo: tipo + nome, como qualquer variável. Fica dentro da classe e fora de qualquer método.
- double preco;
- Outro atributo. Sem valor escrito: começa com o valor padrão do tipo (veja abaixo).
- int estoque = 10;
- Um atributo com valor inicial. Toda fruta criada começa com estoque 10.
- (sem static)
- Atributos de objeto não levam
static. Comstatic, a variável passa a ser da classe, uma só para todos (veja a exceção).
Para os nomes, valem as regras de Variáveis: começam com minúscula, camelCase (precoDeCusto, emPromocao), substantivos que dizem o que guardam. O tipo pode ser qualquer um: primitivos, String, arrays ou outras classes.
Valores padrão
Quando um objeto é criado com new, o Java preenche cada atributo sem valor inicial com um valor padrão, que depende do tipo:
class Produto {
String nome;
int estoque;
double preco;
boolean emPromocao;
char categoria;
}
public class Main {
public static void main(String[] args) {
Produto p = new Produto();
System.out.println("nome = " + p.nome);
System.out.println("estoque = " + p.estoque);
System.out.println("preco = " + p.preco);
System.out.println("emPromocao = " + p.emPromocao);
System.out.println("categoria = [" + p.categoria + "]");
System.out.println("código da categoria = " + (int) p.categoria);
}
}
$ javac Main.java && java Main nome = null estoque = 0 preco = 0.0 emPromocao = false categoria = [] « entre os colchetes há um caractere invisível, o de código 0 código da categoria = 0
| Tipo do atributo | Valor padrão |
|---|---|
byte, short, int, long | 0 |
float, double | 0.0 |
boolean | false |
char | o caractere de código 0 ('\u0000'), invisível |
Qualquer classe (String, Fruta…) e arrays | null |
Variáveis locais (declaradas dentro de um método) não recebem valor nenhum; usá-las antes de dar um valor é erro de compilação: variable … might not have been initialized (veja Escopo). Com atributos, o Java sempre preenche. Cuidado com o null das Strings: p.nome.length() num produto recém-criado dá NullPointerException.
Ler e mudar: objeto.campo
Do lado de fora da classe, você chega a um atributo pelo nome do objeto, um ponto e o nome do atributo. Ele funciona como uma variável comum: pode ser lido, recebe valores e entra em contas.
class Fruta {
String nome;
double preco;
int estoque = 10;
}
public class Main {
public static void main(String[] args) {
Fruta maca = new Fruta();
maca.nome = "maçã";
maca.preco = 7.9;
System.out.println(maca.nome + ": R$ " + maca.preco + ", estoque " + maca.estoque);
maca.estoque = maca.estoque - 3;
maca.preco *= 0.9;
maca.estoque++;
System.out.println(maca.nome + ": R$ " + maca.preco + ", estoque " + maca.estoque);
}
}
$ javac Main.java && java Main maçã: R$ 7.9, estoque 10 maçã: R$ 7.11, estoque 8
| Linha | O que faz | estoque | preco |
|---|---|---|---|
new Fruta() | Cria a fruta: estoque começa em 10 (valor inicial), preco em 0.0 (padrão) | 10 | 0.0 |
maca.preco = 7.9; | Atribui um valor | 10 | 7.9 |
maca.estoque = maca.estoque - 3; | Lê o valor atual, subtrai 3 e guarda de volta (vendeu 3) | 7 | 7.9 |
maca.preco *= 0.9; | 10% de desconto, com o operador composto (veja Operadores de Atribuição) | 7 | 7.11 |
maca.estoque++; | Mais uma unidade chegou | 8 | 7.11 |
Nos métodos da própria classe, os atributos são usados direto pelo nome: preco, e não maca.preco. Foi assim no media() do Aluno em O que é POO. Você verá isso em detalhes em Métodos de Objeto.
Cada objeto tem os seus
Esta é a ideia central da aula. A Ana e o Bruno abriram contas no banco. A classe Conta é a mesma; os saldos, não:
class Conta {
String titular;
double saldo;
}
public class Main {
public static void main(String[] args) {
Conta daAna = new Conta();
daAna.titular = "Ana";
Conta doBruno = new Conta();
doBruno.titular = "Bruno";
daAna.saldo = daAna.saldo + 500;
doBruno.saldo = doBruno.saldo + 80;
daAna.saldo = daAna.saldo - 120;
System.out.println(daAna.titular + ": " + daAna.saldo);
System.out.println(doBruno.titular + ": " + doBruno.saldo);
}
}
$ javac Main.java && java Main Ana: 380.0 « 0 + 500 - 120 Bruno: 80.0 « 0 + 80: o saque da Ana não mexeu aqui
Cada new Conta() reservou um espaço novo na memória com um titular e um saldo. São duas "caixinhas" saldo diferentes, uma em cada objeto. O daAna.saldo e o doBruno.saldo têm o mesmo nome de atributo, mas moram em objetos diferentes, como duas fichas com o campo "Turma".
Objeto daAna | Objeto doBruno | |
|---|---|---|
titular | "Ana" | "Bruno" |
saldo | 380.0 | 80.0 |
Missão: a Ana recebeu 100 de salário e depois pagou uma conta de 30. O Bruno sacou 60. Faça essas três operações mexendo só nos atributos saldo de cada conta, para a saída ser Ana: 450.0 e Bruno: 20.0.
class Conta {
String titular;
double saldo;
}
public class Main {
public static void main(String[] args) {
Conta daAna = new Conta();
daAna.titular = "Ana";
daAna.saldo = 380;
Conta doBruno = new Conta();
doBruno.titular = "Bruno";
doBruno.saldo = 80;
// faça as operações aqui
System.out.println(daAna.titular + ": " + daAna.saldo);
System.out.println(doBruno.titular + ": " + doBruno.saldo);
}
}
class Conta {
String titular;
double saldo;
}
public class Main {
public static void main(String[] args) {
Conta daAna = new Conta();
daAna.titular = "Ana";
daAna.saldo = 380;
Conta doBruno = new Conta();
doBruno.titular = "Bruno";
doBruno.saldo = 80;
daAna.saldo += 100;
daAna.saldo -= 30;
doBruno.saldo -= 60;
System.out.println(daAna.titular + ": " + daAna.saldo);
System.out.println(doBruno.titular + ": " + doBruno.saldo);
}
}
A exceção: atributos static
Um atributo declarado com static não pertence a cada objeto, e sim à classe: existe uma cópia só, compartilhada por todos. É usado com o nome da classe, e não de um objeto:
class Fruta {
static int criadas = 0;
String nome;
}
public class Main {
public static void main(String[] args) {
Fruta a = new Fruta();
a.nome = "maçã";
Fruta.criadas++;
Fruta b = new Fruta();
b.nome = "uva";
Fruta.criadas++;
System.out.println(a.nome + " e " + b.nome + ": cada uma tem o seu nome");
System.out.println("criadas = " + Fruta.criadas + " (um só contador, da classe)");
}
}
$ javac Main.java && java Main maçã e uva: cada uma tem o seu nome criadas = 2 (um só contador, da classe)
É o mural do corredor da aula Escopo. Quando usar e como, você verá na aula static. Por enquanto, a regra: atributo sem static = um por objeto.
Atributos que são outros objetos
Um atributo pode ter como tipo outra classe sua. A loja da Ana compra frutas de fornecedores, e cada fruta guarda quem a fornece:
class Fornecedor {
String nome;
String cidade;
}
class Fruta {
String nome;
double preco;
Fornecedor fornecedor;
}
public class Main {
public static void main(String[] args) {
Fornecedor sitio = new Fornecedor();
sitio.nome = "Sítio Boa Vista";
sitio.cidade = "Campinas";
Fruta maca = new Fruta();
maca.nome = "maçã";
maca.fornecedor = sitio;
Fruta pera = new Fruta();
pera.nome = "pera";
pera.fornecedor = sitio;
System.out.println(maca.nome + " vem de " + maca.fornecedor.cidade);
sitio.cidade = "Jundiaí";
System.out.println(pera.nome + " vem de " + pera.fornecedor.cidade);
System.out.println(maca.nome + " vem de " + maca.fornecedor.cidade);
}
}
$ javac Main.java && java Main maçã vem de Campinas pera vem de Jundiaí « o sítio mudou de cidade… maçã vem de Jundiaí « …e as duas frutas "veem" a mudança
- Fornecedor fornecedor;
- Um atributo cujo tipo é a classe
Fornecedor. Como todo atributo de tipo classe, começa valendonull. - maca.fornecedor = sitio;
- Guarda no atributo a referência para o objeto
sitio(veja Classes e Objetos). A pera recebe a mesma referência: as duas frutas apontam para o mesmo fornecedor. - maca.fornecedor.cidade
- Pontos encadeados: "a cidade do fornecedor da maçã". Leia da esquerda para a direita.
Se a Ana esquecer de ligar o fornecedor, o atributo continua null, e o segundo ponto falha:
$ javac Main.java && java Main Fornecedor: null Exception in thread "main" java.lang.NullPointerException: Cannot read field "cidade" because "<local1>.fornecedor" is null at Main.main(Main.java:17)
A mensagem aponta o culpado com precisão: "<local1>.fornecedor", o atributo fornecedor da variável local 1 (a maca), era nulo quando o programa tentou ler a cidade.
Formas diferentes de dar valor aos atributos
| Forma | Exemplo | Quando usar |
|---|---|---|
| Valor padrão (não escrever nada) | double saldo; → 0.0 | O valor padrão já é o certo (saldo começa em zero) |
| Valor inicial na declaração | int estoque = 10; | Todo objeto começa com o mesmo valor |
| Atribuir depois de criar | maca.nome = "maçã"; | O que vimos nesta aula; bom para aprender, mas trabalhoso e fácil de esquecer um campo |
| Construtor | new Fruta("maçã", 7.9) | Cada objeto nasce com os seus valores, numa linha. A forma preferida: Construtores |
| Métodos que validam (setters) | conta.depositar(100) | Quando nem todo valor é aceitável (saldo negativo?): Encapsulamento |
Nesta aula, o main muda saldo e preco à vontade. Nada impede alguém de escrever daAna.saldo = -1000000;. Em programas reais, os atributos ficam protegidos (private) e só mudam por métodos que conferem os valores. Esse é o primeiro pilar da POO, que você verá em Modificadores de Acesso e Encapsulamento. E se um atributo nunca deve mudar depois de definido, ele pode ser final (veja final).
Quando dá errado
$ javac Main.java
Main.java:9: error: cannot find symbol
maca.preso = 7.9;
^
symbol: variable preso
location: variable maca of type Fruta
1 error
O que aconteceu: a classe Fruta não tem atributo preso (era preco). Repare na última linha: o compilador procurou "na variável maca, do tipo Fruta", ou seja, nos atributos da classe Fruta.
Como resolver: corrija o nome, ou acrescente o atributo à classe se ele de fato está faltando.
$ javac Main.java
Main.java:9: error: incompatible types: String cannot be converted to double
maca.preco = "7,90";
^
1 error
O que aconteceu: o atributo é double e recebeu um texto. As aspas fazem de "7,90" uma String.
Como resolver: maca.preco = 7.90;, sem aspas e com ponto decimal (no código Java, a vírgula separa argumentos, nunca casas decimais).
NullPointerException num atributo
$ java Main Fornecedor: null Exception in thread "main" java.lang.NullPointerException: Cannot read field "cidade" because "<local1>.fornecedor" is null at Main.main(Main.java:17)
O que aconteceu: o atributo de tipo classe nunca recebeu um objeto e continuou com o valor padrão, null.
Como resolver: atribua um objeto antes de usar (maca.fornecedor = sitio;) ou dê um valor inicial na declaração. Se o atributo pode mesmo ficar vazio, teste if (maca.fornecedor != null).
O que aconteceu: você usou o atributo pelo nome da classe (Fruta.nome) ou direto no main sem um objeto. O atributo é de cada objeto; o Java precisa saber de qual.
Como resolver: crie um objeto e use objeto.nome. Veja O que é POO.
Referência
| Escrita | Significado |
|---|---|
Tipo nome; (na classe) | Declara um atributo; começa com o valor padrão do tipo |
Tipo nome = valor; (na classe) | Atributo com valor inicial |
obj.nome | Lê o atributo do objeto obj |
obj.nome = valor; | Muda o atributo de obj (os outros objetos não mudam) |
obj.n += 5;, obj.n++; | Operadores compostos funcionam com atributos |
obj.outro.campo | Atributo de um atributo (cuidado com null no meio) |
static Tipo nome; | Atributo da classe: um só, compartilhado (static) |
| Padrões | números: 0/0.0; boolean: false; char: '\u0000'; classes e arrays: null |
Missão final: dois contadores
Missão: a escola da Ana tem duas catracas na entrada. Crie a classe Catraca com o atributo int pessoas (sem valor inicial: o padrão já é 0). No main, crie duas catracas, some 3 pessoas na primeira (com ++) e 1 na segunda, e imprima catraca A: 3 e catraca B: 1.
// declare a classe Catraca aqui
public class Main {
public static void main(String[] args) {
}
}
class Catraca {
int pessoas;
}
public class Main {
public static void main(String[] args) {
Catraca a = new Catraca();
Catraca b = new Catraca();
a.pessoas++;
a.pessoas++;
a.pessoas++;
b.pessoas++;
System.out.println("catraca A: " + a.pessoas);
System.out.println("catraca B: " + b.pessoas);
}
}
Exercícios
1. Valor padrão de número
Qual é o valor de um atributo int idade; logo depois do new?
2. Valor padrão de texto
E de um atributo String email;?
3. Mudar o atributo
Complete para aumentar em 5 o atributo estoque do objeto uva:
4. Conta x = new Conta(); Conta y = new Conta(); x.saldo = 50;. Quanto vale y.saldo (tipo double)?
5. Onde se declara um atributo?
6. Uma Fruta recém-criada tem o atributo Fornecedor fornecedor;. O que acontece em System.out.println(f.fornecedor.nome);?
Resumo
- Atributos (campos, variáveis de instância) são declarados na classe, fora dos métodos, e guardam o estado de cada objeto.
- Sem valor inicial, recebem o padrão:
0,0.0,false,'\u0000'ounull. Variáveis locais não têm padrão. - De fora, use
objeto.campopara ler e mudar; operadores como+=e++funcionam. - Cada objeto tem os seus atributos: mudar
daAna.saldonão mudadoBruno.saldo. Só osstaticsão compartilhados. - Atributos de tipo classe guardam referências; vários objetos podem apontar para o mesmo. Atenção ao
nullno meio dea.b.c. - Próximos passos: métodos que usam os atributos (Métodos de Objeto) e construtores que os preenchem (Construtores).