Java Classes e Objetos
Na aula O que é POO você viu a ideia: a classe é o molde e cada objeto é uma coisa feita com ele. Agora é hora de pôr a mão no código: escrever uma classe, criar objetos com new, entender o que a variável realmente guarda (uma referência) e o que acontece quando ela não guarda nada (null).
- Como declarar uma classe e como criar objetos com
new, linha por linha. - Como criar vários objetos independentes da mesma classe.
- O que é uma referência: por que
Fruta b = a;não cria uma fruta nova, e o que o==compara. - O que é
nulle como nasce oNullPointerException. - Como organizar duas classes: em arquivos separados (
javac *.java) ou no mesmo arquivo.
Declarar uma classe
A loja da Ana vende frutas. Toda fruta tem um nome e um preço. Essa é a descrição, o molde:
class Fruta {
String nome;
double preco;
}
- class
- Palavra que começa a declaração de uma classe ("classe", em inglês).
- Fruta
- O nome da classe. Por convenção, começa com letra maiúscula e cada palavra nova também:
Fruta,ContaBancaria,AlunoBolsista(o estilo se chama PascalCase). É assim que você distingue, de relance, uma classe (Fruta) de uma variável ou método (fruta,mostrar). E o nome vai no singular: a classe descreve uma fruta. - { … }
- O corpo da classe. Tudo o que pertence a ela fica aqui dentro.
- String nome; double preco;
- Os atributos: variáveis que cada fruta vai ter. Sem
static: cada objeto terá os seus. Eles são o assunto da aula Atributos.
Declarar a classe não cria fruta nenhuma. É como desenhar a ficha em branco: ela descreve como as fichas serão, mas ainda não há ficha preenchida. Criar é o próximo passo.
Criar objetos com new
class Fruta {
String nome;
double preco;
}
public class Main {
public static void main(String[] args) {
Fruta maca = new Fruta();
maca.nome = "maçã";
maca.preco = 7.9;
Fruta banana = new Fruta();
banana.nome = "banana";
banana.preco = 3.5;
System.out.println(maca.nome + " custa " + maca.preco);
System.out.println(banana.nome + " custa " + banana.preco);
}
}
$ javac Main.java && java Main maçã custa 7.9 banana custa 3.5
A linha mais importante da aula é esta: Fruta maca = new Fruta();. Ela tem quatro partes:
- Fruta
- O tipo da variável. Toda classe que você cria vira um tipo novo, como
intouString. Esta variável só aceita frutas. - maca
- O nome da variável (minúscula, como toda variável).
- new
- "Novo". Pede ao Java que crie um objeto na memória. Sem
new, nenhum objeto nasce. - Fruta()
- Diz de qual classe é o objeto. Os parênteses chamam o construtor, um código que prepara o objeto recém-criado. Esta classe não escreveu nenhum; o Java fornece um vazio. Você vai escrever os seus em Construtores.
Depois de criado, o objeto é acessado pelo ponto: maca.nome = "maçã" lê-se "o nome da maçã recebe maçã". Cada new cria um objeto diferente: maca e banana são duas frutas, cada uma com o seu nome e o seu preço. Mudar uma não afeta a outra.
E se eu imprimir o objeto?
$ java Main maçã custa 7.9 banana custa 3.5 Fruta@251a69d7 Fruta@7344699f
Acrescentando System.out.println(maca); e System.out.println(banana);, o Java mostra o nome da classe, uma @ e um número em hexadecimal que identifica o objeto (o número pode mudar de uma máquina para outra). Repare que os números são diferentes: são dois objetos. Para imprimir algo legível, como maçã (R$ 7.9), a classe precisa de um método especial, o toString(), que você verá em A Classe Object.
Brinque com o código: crie uma terceira fruta (uma uva de 12.0) e mostre-a. Depois imprima a variável direto, com System.out.println(uva);, e veja o Fruta@….
class Fruta {
String nome;
double preco;
}
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 + " custa " + maca.preco);
}
}
A variável guarda uma referência
Aqui está o ponto que mais confunde quem começa. Uma variável de tipo primitivo (int, double) guarda o próprio valor. Uma variável de tipo classe não guarda o objeto: guarda uma referência, o "endereço" de onde o objeto está na memória. Pense no objeto como uma casa e na variável como um papel com o endereço da casa.
Veja o que acontece quando a Ana copia uma variável para outra:
class Fruta {
String nome;
double preco;
}
public class Main {
public static void main(String[] args) {
Fruta a = new Fruta();
a.nome = "maçã";
Fruta b = a;
b.nome = "pera";
System.out.println("a = " + a.nome + ", b = " + b.nome);
System.out.println(a == b);
Fruta c = new Fruta();
c.nome = "pera";
System.out.println(b == c);
System.out.println(b.nome.equals(c.nome));
}
}
$ javac Main.java && java Main a = pera, b = pera « mudou pelo b, apareceu no a true « a e b apontam para o MESMO objeto false « b e c são objetos diferentes, mesmo com o mesmo nome true « os nomes (textos) são iguais
| Linha | O que acontece |
|---|---|
Fruta a = new Fruta(); | Uma fruta é criada (casa nº 1). a guarda o endereço "casa nº 1". |
Fruta b = a; | Nenhuma fruta nova. b recebe uma cópia do endereço: agora a e b têm o mesmo papel, "casa nº 1". |
b.nome = "pera"; | Vai até a casa nº 1 e troca o nome. Como a aponta para a mesma casa, a.nome também é "pera". |
a == b | Com objetos, == compara os endereços, não o conteúdo. Mesmo endereço: true. |
Fruta c = new Fruta(); | Um novo new: casa nº 2. Mesmo com o nome igual, b == c é false. |
É a mesma regra que você viu com arrays em Parâmetros: passar um objeto para um método copia a referência, então o método mexe no mesmo objeto. E é por isso que textos se comparam com .equals(), não com == (veja Strings): String também é uma classe.
Missão: o programa mostra a = pera, b = pera, mas a Ana queria duas frutas diferentes. Mude uma linha para que b seja uma fruta nova, e a saída seja a = maçã, b = pera.
class Fruta {
String nome;
double preco;
}
public class Main {
public static void main(String[] args) {
Fruta a = new Fruta();
a.nome = "maçã";
Fruta b = a;
b.nome = "pera";
System.out.println("a = " + a.nome + ", b = " + b.nome);
}
}
class Fruta {
String nome;
double preco;
}
public class Main {
public static void main(String[] args) {
Fruta a = new Fruta();
a.nome = "maçã";
Fruta b = new Fruta();
b.nome = "pera";
System.out.println("a = " + a.nome + ", b = " + b.nome);
}
}
null: a variável que não aponta para nada
Uma variável de tipo classe pode guardar um valor especial: null ("nulo"), que quer dizer "nenhum objeto". É o papel em branco, sem endereço. Imprimir null funciona; o problema é tentar usar o objeto que não existe:
class Fruta {
String nome;
double preco;
}
public class Main {
public static void main(String[] args) {
Fruta escolhida = null;
System.out.println("Escolhida: " + escolhida);
escolhida.nome = "uva";
}
}
$ javac Main.java && java Main Escolhida: null Exception in thread "main" java.lang.NullPointerException: Cannot assign field "nome" because "<local1>" is null at Main.main(Main.java:10)
- Escolhida: null
- A linha 9 rodou: imprimir uma referência nula mostra o texto
null. - java.lang.NullPointerException
- "Exceção de ponteiro nulo", o famoso NPE. Você tentou ir até a casa de um endereço em branco.
- Cannot assign field "nome"
- "Não é possível atribuir o campo
nome…" - because "<local1>" is null
- "…porque
<local1>é nulo". O<local1>é a variável local número 1 do método (aescolhida; a número 0 é oargs). O Java não mostra o nome porque, por padrão, ojavacnão guarda os nomes das variáveis locais no.class. - at Main.main(Main.java:10)
- Onde aconteceu: método
main, linha 10. É sempre por aqui que você começa a investigar.
Compilando com a opção -g (de debug, "depuração"), o javac guarda os nomes, e a mensagem fica mais clara:
$ javac -g Main.java && java Main Escolhida: null Exception in thread "main" java.lang.NullPointerException: Cannot assign field "nome" because "escolhida" is null at Main.main(Main.java:10)
Para ler um atributo, a mensagem muda de Cannot assign field para Cannot read field; para chamar um método, Cannot invoke "…". Um caso muito comum: arrays de objetos começam cheios de null, e o new Fruta[3] cria o array, não as frutas:
class Fruta {
String nome;
double preco;
}
public class Main {
public static void main(String[] args) {
Fruta[] cesta = new Fruta[3];
System.out.println(cesta[0]);
cesta[0] = new Fruta();
cesta[0].nome = "maçã";
System.out.println(cesta[0].nome);
System.out.println(cesta[1].nome);
}
}
$ javac Main.java && java Main null maçã Exception in thread "main" java.lang.NullPointerException: Cannot read field "nome" because "<local1>[1]" is null at Main.main(Main.java:13)
A posição 0 recebeu uma fruta com new; a posição 1 continuou null. O "<local1>[1]" diz exatamente qual posição do array estava vazia.
null × "não inicializada"
São coisas diferentes. Fruta escolhida = null; é uma variável com valor (o valor "nada"); o erro só aparece ao rodar. Já Fruta escolhida; sem valor nenhum, seguido de escolhida.nome = "uva";, nem compila:
$ javac Main.java
Main.java:9: error: variable escolhida might not have been initialized
escolhida.nome = "uva";
^
1 error
Onde colocar a classe: arquivos separados ou o mesmo arquivo
Forma 1: um arquivo por classe (a forma profissional)
Em projetos de verdade, cada classe fica no seu próprio arquivo, com o mesmo nome da classe. A Ana criou uma pasta com dois arquivos:
public class Fruta {
String nome;
double preco;
}
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 + " custa " + maca.preco);
}
}
$ ls Fruta.java Main.java $ javac *.java $ ls Fruta.class Fruta.java Main.class Main.java $ java Main maçã custa 7.9
- javac *.java
- Compila todos os arquivos
.javada pasta (o*é um curinga do terminal: "todos os nomes terminados em.java"). Cada classe vira o seu.class. - java Main
- Roda a classe que tem o
main. A JVM encontraFruta.classsozinha, na mesma pasta, quando precisa dela.
Na verdade, javac Main.java também funcionaria: ao ver que Main usa Fruta, o javac procura Fruta.java na pasta e compila junto.
$ rm *.class $ javac Main.java $ ls Fruta.class Fruta.java Main.class Main.java « a Fruta.class foi compilada automaticamente
E, a partir do Java 22, o modo "arquivo-fonte" também encontra as outras classes da pasta, sem compilar antes: Java 22+
$ rm *.class $ java Main.java maçã custa 7.9
Forma 2: as duas classes no mesmo arquivo
Foi o que você fez até aqui nesta aula: class Fruta (sem public) e public class Main no mesmo Main.java. A regra é: um arquivo pode ter várias classes, mas só uma public, e ela dá nome ao arquivo.
Qual usar?
| Forma | Vantagens | Quando usar |
|---|---|---|
Um arquivo por classe, cada uma public | Fácil de achar cada classe; outras partes do programa (outros pacotes) podem usá-la; é o padrão de IDEs, Maven e Gradle | Qualquer programa de verdade |
Várias classes num arquivo, só uma public | Tudo à vista num lugar só; ótimo para exemplos curtos e para o editor do guia | Exemplos, exercícios, classes pequenas de apoio |
Quando dá errado
NullPointerException
$ java Main Exception in thread "main" java.lang.NullPointerException: Cannot assign field "nome" because "<local1>" is null at Main.main(Main.java:10)
O que aconteceu: a variável vale null e o programa tentou usar um atributo ou método dela.
Como resolver: vá à linha indicada e descubra qual variável está nula (compile com javac -g para ver o nome). Em geral faltou um new, ou um elemento de array ainda não recebeu objeto. Se null é um valor possível, teste antes: if (escolhida != null) { … }.
$ javac Main.java
Main.java:3: error: cannot find symbol
Fruta maca = new Fruta();
^
symbol: class Fruta
location: class Main
Main.java:3: error: cannot find symbol
Fruta maca = new Fruta();
^
symbol: class Fruta
location: class Main
2 errors
O que aconteceu: o compilador não encontrou a classe Fruta: não há Fruta.java na pasta, nem uma class Fruta no mesmo arquivo. O erro aparece duas vezes porque Fruta é usada duas vezes na linha (no tipo e no new).
Como resolver: confira se o arquivo existe, se está na mesma pasta e se o nome bate exatamente, com maiúsculas (Fruta.java, não fruta.java).
$ java Fruta Erro: o método main não é Fruta na classe javafx.application.Application; defina o método main como: public static void main(String[] args)
O que aconteceu: a Ana mandou rodar Fruta, que não tem main. A frase em português sai embaralhada (é um defeito da tradução do próprio JDK). Em inglês, com LANG=C java Fruta, a mensagem é clara: Error: Main method not found in class Fruta, please define the main method as: public static void main(String[] args).
Como resolver: rode a classe que tem o main: java Main.
O que aconteceu: uma classe public está num arquivo com outro nome, ou há duas classes public no mesmo arquivo.
Como resolver: deixe só uma public por arquivo, com o mesmo nome do arquivo; tire o public das outras ou mova-as para arquivos próprios. Veja O que é POO.
Referência
| Escrita | Significado |
|---|---|
class Nome { … } | Declara uma classe (nome em PascalCase, no singular) |
Nome var = new Nome(); | Cria um objeto e guarda a referência dele em var |
var.campo | Acessa um atributo do objeto |
Nome b = a; | Copia a referência: a e b apontam para o mesmo objeto |
a == b | Com objetos: true só se for o mesmo objeto |
Nome var = null; | Variável sem objeto; usar var.campo dá NullPointerException |
new Nome[3] | Cria um array com 3 posições null (não cria os objetos) |
javac *.java | Compila todas as classes da pasta |
javac -g … | Guarda os nomes das variáveis (mensagens de NPE mais claras) |
java Main.java com várias classes na pasta | Roda sem compilar antes Java 22+ |
Missão final: a classe Livro, em outro arquivo
Missão: esta caixa tem dois arquivos (veja as abas). Em Livro.java, complete a classe com os atributos String titulo e String autor. Em Main.java, crie dois livros (Dom Casmurro, de Machado de Assis, e A Hora da Estrela, de Clarice Lispector) e imprima cada um no formato Dom Casmurro, de Machado de Assis.
public class Main {
public static void main(String[] args) {
// crie os dois livros aqui
}
}
public class Livro {
// atributos aqui
}
public class Main {
public static void main(String[] args) {
Livro l1 = new Livro();
l1.titulo = "Dom Casmurro";
l1.autor = "Machado de Assis";
Livro l2 = new Livro();
l2.titulo = "A Hora da Estrela";
l2.autor = "Clarice Lispector";
System.out.println(l1.titulo + ", de " + l1.autor);
System.out.println(l2.titulo + ", de " + l2.autor);
}
}
public class Livro {
String titulo;
String autor;
}
Exercícios
1. A palavra que cria
Complete para criar um objeto da classe Aluno:
2. Nome do arquivo
A classe public class ContaBancaria precisa ficar em qual arquivo?
3. Nenhum objeto
Qual valor indica que uma variável de tipo classe não aponta para objeto nenhum?
4. Depois de Fruta x = new Fruta(); Fruta y = x; y.preco = 5;, quanto vale x.preco?
5. Quantos objetos Fruta existem depois de Fruta[] f = new Fruta[10];?
6. new Fruta() == new Fruta() vale:
Resumo
- Uma classe se declara com
class Nome { … }; o nome é PascalCase e singular. Declarar não cria objetos. new Nome()cria um objeto; cadanewé um objeto diferente, com os seus próprios atributos.- A variável guarda uma referência (o endereço).
b = afaz as duas apontarem para o mesmo objeto;==compara referências. null= nenhum objeto. Usar um atributo ou método denullcausaNullPointerException; a mensagem diz qual variável era nula.- Cada classe
publicfica no arquivo de mesmo nome. Compile tudo comjavac *.javae rode a classe domain. - Num mesmo arquivo, várias classes, mas só uma
public.