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.

Você vai aprender
  • Como declarar atributos numa classe, com ou sem valor inicial.
  • Os valores padrão: por que um atributo sem valor vale 0, false ou null (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. Com static, 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 atributoValor padrão
byte, short, int, long0
float, double0.0
booleanfalse
charo caractere de código 0 ('\u0000'), invisível
Qualquer classe (String, Fruta…) e arraysnull
Só atributos ganham valor padrão

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
LinhaO que fazestoquepreco
new Fruta()Cria a fruta: estoque começa em 10 (valor inicial), preco em 0.0 (padrão)100.0
maca.preco = 7.9;Atribui um valor107.9
maca.estoque = maca.estoque - 3;Lê o valor atual, subtrai 3 e guarda de volta (vendeu 3)77.9
maca.preco *= 0.9;10% de desconto, com o operador composto (veja Operadores de Atribuição)77.11
maca.estoque++;Mais uma unidade chegou87.11
Dentro da classe, não precisa do ponto

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 daAnaObjeto doBruno
titular"Ana""Bruno"
saldo380.080.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 valendo null.
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

FormaExemploQuando usar
Valor padrão (não escrever nada)double saldo; → 0.0O valor padrão já é o certo (saldo começa em zero)
Valor inicial na declaraçãoint estoque = 10;Todo objeto começa com o mesmo valor
Atribuir depois de criarmaca.nome = "maçã";O que vimos nesta aula; bom para aprender, mas trabalhoso e fácil de esquecer um campo
Construtornew 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
Mexer direto no atributo é perigoso

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

"cannot find symbol … location: variable maca of type Fruta"
$ 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.

"incompatible types: String cannot be converted to double"
$ 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).

"non-static variable … cannot be referenced from a static context"

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

EscritaSignificado
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.nomeLê 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.campoAtributo de um atributo (cuidado com null no meio)
static Tipo nome;Atributo da classe: um só, compartilhado (static)
Padrõesnú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' ou null. Variáveis locais não têm padrão.
  • De fora, use objeto.campo para ler e mudar; operadores como += e ++ funcionam.
  • Cada objeto tem os seus atributos: mudar daAna.saldo não muda doBruno.saldo. Só os static são compartilhados.
  • Atributos de tipo classe guardam referências; vários objetos podem apontar para o mesmo. Atenção ao null no meio de a.b.c.
  • Próximos passos: métodos que usam os atributos (Métodos de Objeto) e construtores que os preenchem (Construtores).