Role para virar a página
Computer Science A · AP Computer Science A
valores, referências e os métodos que os usam
Uma variável é uma caixa com nome na memória. Uma variável primitiva guarda seu valor; uma variável de referência guarda a localização de um objeto.
int score = 42; int copy = score; // copy ganha o seu próprio 42 copy = 7; // score continua 42 String name = "Ada"; // uma referência String other = name; // a mesma referência
A figura executa estas linhas. copy = score copia o valor 42 para uma segunda caixa, então, quando copy vira 7, score fica intacta. name não guarda nenhum caractere: sua caixa guarda uma seta (vermelha) para um objeto String criado em outro lugar da memória. other = name copia a seta, não o objeto: duas setas, um objeto.
Essa diferença atravessa todo o Java e este capítulo: o que uma variável guarda, o que um método recebe e devolve, o que new cria e por que uma String nunca muda depois de existir.
Este capítulo é a Unidade 1 do AP Computer Science A, Usando objetos e métodos, 15–25% da nota de múltipla escolha: compilação e erros (1.1), tipos, expressões e atribuição (1.2–1.6), bibliotecas, documentação e métodos (1.7–1.11), objetos e sua criação (1.12–1.14) e Strings (1.15). As 42 questões de múltipla escolha da prova vêm com a Java Quick Reference, que lista todos os métodos de biblioteca que podem ser usados; a primeira questão dissertativa chama métodos de String.
e os três tipos de erro
Um algoritmo é um processo passo a passo para realizar uma tarefa ou resolver um problema. Pode ser escrito em palavras, desenhado como um diagrama ou escrito como um programa.
A sequenciação coloca os passos em ordem, e eles são executados um de cada vez: um método Java executa suas instruções de cima para baixo, a menos que algo mude o fluxo.
O código pode ser escrito em qualquer editor de texto, mas a maioria dos programadores usa um ambiente de desenvolvimento integrado (IDE), que tem ferramentas para escrever, compilar e executar. O compilador verifica o código-fonte em busca de alguns erros e o traduz; o compilador do Java gera bytecode, que a Máquina Virtual Java (JVM) executa. Os erros que o compilador encontra precisam ser corrigidos antes que o programa possa rodar.
A figura envia três programas pela linha, cada um com um tipo de erro.
int total = 7 + 3 // falta ; System.out.println(total); Um erro de sintaxe quebra as regras da linguagem. O compilador o encontra (error: ';' expected) e nada é executado.
int count = 0; System.out.println(10 / count);
Um erro em tempo de execução acontece enquanto o programa roda e normalmente o encerra de forma anormal. Este é uma exceção: um erro que o compilador não detectou, que interrompe o fluxo normal de execução (ArithmeticException: / by zero).
double avg = (7 + 8) / 2; System.out.println(avg); // prints 7.0 Um erro de lógica compila e executa, mas se comporta de forma incorreta: a média de 7 and 8 deveria ser 7.5. Só o teste com dados específicos, comparando com o resultado esperado, o encontra. (Por que imprime 7.0: página 1.3.)
int, double, boolean e referências
Um tipo de dado é um conjunto de valores e as operações sobre eles. Todo tipo é primitivo ou de referência.
O curso usa três tipos primitivos, para números e valores booleanos:
| type | values | examples |
|---|---|---|
int | integers | 7, -40, 0 |
double | números reais | 2.5, 7.0, -0.01 |
boolean | true or false | true |
O Java tem outros cinco tipos primitivos (long, short, byte, float, char); eles estão fora do curso e da prova. Tudo que não é primitivo, como uma String, é um objeto, e seu tipo é um tipo de referência.
Uma variável é um local de armazenamento que guarda um valor, que pode mudar enquanto o programa roda. Toda variável tem um nome e um tipo, e uma variável de tipo primitivo guarda um valor desse tipo.
int n = 7; double d = 2.5; boolean ok = true; double w = 7; // stores 7.0 int m = 2.5; // não compila
Um int cabe em um double: double w = 7; armazena 7.0. Um double não cabe em um int, então int m = 2.5; é um erro de compilação: incompatible types: possible lossy conversion from double to int.
Escolha o tipo pelo que o valor pode ser: a contagem de alunos é um int, um preço ou uma média um double, se uma luz está acesa um boolean.
print, println e sequências de escape
System.out.print e System.out.println exibem informações na tela. println depois move o cursor para uma nova linha; print o deixa onde está.
Um literal é a representação em código de um valor fixo: 7, 2.5, true. Um literal de string é uma sequência de caracteres entre aspas duplas: "CSA".
Alguns caracteres não podem ser digitados diretamente entre as aspas. Uma sequência de escape começa com uma barra invertida e tem um significado especial. O curso usa três:
| sequência de escape | means |
|---|---|
\" | aspas duplas |
\\ | uma barra invertida |
\n | uma quebra de linha |
System.out.print("AP "); System.out.println("CSA"); System.out.println("say \"hi\""); System.out.print("a\\b\nc"); O console então mostra (a figura digita na tela):
AP CSA say "hi" a\b c O primeiro print deixa o cursor depois de AP , então CSA cai na mesma linha. Na última linha, \\ imprime uma barra invertida e \n inicia uma nova linha no meio da string.
divisão inteira, resto e precedência
Uma expressão aritmética combina valores numéricos, variáveis e os operadores +, -, *, / e %. Dois valores int resultam em um int; pelo menos um double resulta em um double.
System.out.println(17 / 5); // 3 System.out.println(17 % 5); // 2 System.out.println(17.0 / 5); // 3.4
Dividir dois ints mantém só a parte inteira do quociente: 17 blocos formam 3 grupos completos de 5. O operador resto % dá o que sobra: 2. Com um double na divisão, o resultado é o próprio quociente, 3.4. (O curso usa % só com a ≥ 0 e b > 0.)
Precedência: *, / e % são aplicados antes de + e -; operadores de mesma precedência vão da esquerda para a direita; parênteses mudam o agrupamento. O compilador agrupa os valores com seus operadores assim, em tempo de compilação.
| expression | value |
|---|---|
2 + 3 * 4 | 14 |
(2 + 3) * 4 | 20 |
10 - 4 - 3 | 3 |
7 + 9 % 4 | 8 |
1 / 2 * 4.0 | 0.0 |
1.0 / 2 * 4 | 2.0 |
Em 1 / 2 * 4.0, 1 / 2 é uma divisão inteira e já vale 0 antes de o double aparecer.
Dividir um int pelo int zero lança uma ArithmeticException quando a linha é executada. (Dividir por zero com um double, e os valores especiais de double que isso produz, estão fora do curso.)
o lado direito, armazenado à esquerda
O operador de atribuição = avalia a expressão à sua direita até um único valor e o armazena na variável à sua esquerda.
Uma variável precisa receber um valor antes de ser usada em uma expressão, e o valor deve ser de um tipo compatível. A primeira atribuição a inicializa. Cada atribuição seguinte substitui o valor: o antigo se perde.
int a = 3; // a 3 int b = 8; // a 3, b 8 int temp = a; // a 3, b 8, temp 3 a = b; // a 8, b 8, temp 3 b = temp; // a 8, b 3, temp 3
É por isso que trocar duas variáveis exige uma terceira. Sem temp, a = b destruiria o 3 de a antes de b poder recebê-lo. A figura move cada cópia de caixa em caixa: a origem mantém o seu valor.
Quando o programa roda, uma expressão é avaliada até um valor, e esse valor tem um tipo: a + b é um int, a + 0.5 um double. Uma instrução como count = count + 1 não é uma equação; ela lê count, soma 1 e guarda o resultado de volta.
A uma variável de referência pode ser atribuído um novo objeto ou null. O literal null significa que a referência não está associada a nenhum objeto.
Atribuições dentro de expressões, como a = b = 4;, estão fora do curso.
A entrada pode ser tátil, sonora, visual ou de texto. A classe Scanner é uma forma de ler o texto digitado no teclado; a prova não cobra nenhuma forma específica de entrada do usuário.
truncamento, ampliação e arredondamento
Os operadores de conversão (int) e (double) convertem um valor de um tipo primitivo para o outro.
Converter um double para int trunca: os dígitos depois da vírgula decimal são cortados, então o valor se aproxima de zero, qualquer que seja o sinal.
| expression | value |
|---|---|
(int) 3.99 | 3 |
(int) -3.99 | -3 |
7 / 2 | 3 |
(double) 7 / 2 | 3.5 |
(double) (7 / 2) | 3.0 |
7 / 2.0 | 3.5 |
1 + 2.5 | 3.5 |
Uma conversão se aplica ao valor logo depois dela. (double) 7 / 2 converte o 7 e depois divide como doubles; (double) (7 / 2) faz primeiro a divisão inteira e converte o resultado, 3.0.
Alguns códigos ampliam um int para double automaticamente: em 1 + 2.5, e quando um int é guardado em uma variável double (página 1.2).
Para arredondar um double ao inteiro mais próximo, desloque-o meia unidade para longe de zero e depois trunque: (int) (x + 0.5) para x não negativo, (int) (x - 0.5) para x negativo.
| x | (int) x | rounded |
|---|---|---|
2.7 | 2 | 3 |
-2.7 | -2 | -3 |
3.2 | 3 | 3 |
-1.4 | -1 | -1 |
A figura converte cada um destes: o vermelho trunca em direção a zero; o azul desloca meia unidade e depois trunca.
estouro de inteiro e erro de arredondamento
Um int é armazenado em uma quantidade fixa de memória, 4 bytes, então tem uma faixa: de Integer.MIN_VALUE a Integer.MAX_VALUE, inclusive.
| constant | value |
|---|---|
Integer.MAX_VALUE | 2147483647 |
Integer.MIN_VALUE | -2147483648 |
int n = Integer.MAX_VALUE - 2; // n é 2147483645 n = n + 1; // n é 2147483646 n = n + 1; // n é 2147483647 n = n + 1; // n é -2147483648 n = n + 1; // n é -2147483647
Se uma expressão resultasse em um int fora da faixa, ocorre um estouro de inteiro (integer overflow). O resultado é um int dentro da faixa, mas não o valor esperado: um acima de MAX_VALUE é MIN_VALUE. A figura desenha os ints como um anel (fora de escala) com a emenda no topo. Não há mensagem de erro; o programa segue em frente com o número errado.
Os doubles têm outro limite. Um double também é armazenado em uma quantidade fixa de memória, então um resultado mais preciso do que ele comporta é arredondado para o valor mais próximo que ele consegue representar: um erro de arredondamento.
System.out.println(0.1 + 0.2); // 0.30000000000000004 Nem 0.1 nem 0.2 têm forma binária exata, e os pequenos erros aparecem na soma. Para evitar o erro de arredondamento, use valores int sempre que puder: conte dinheiro em centavos, não em reais. (Tipos decimais especiais que o evitam estão fora do curso.)
+=, −=, *=, /=, %=, ++ e −−
Um operador de atribuição composta faz a operação aritmética entre o valor da variável e o valor à direita e depois atribui o resultado à variável.
score += 5 faz o mesmo que score = score + 5. O mesmo vale para -=, *=, /= e %=; /= em um int é divisão inteira.
O operador de pós-incremento ++ soma 1 a uma variável numérica e a armazena; o operador de pós-decremento -- subtrai 1.
int score = 10; // score é 10 score += 5; // score é 15 score -= 3; // score é 12 score *= 2; // score é 24 score /= 5; // score é 4 score %= 3; // score é 1 score++; // score é 2 score--; // score é 1
A figura executa as linhas e guarda cada valor em uma fita. Rastreie no papel do mesmo jeito: uma linha, um novo valor.
As formas prefixadas (++x) e os incrementos dentro de outras expressões (arr[x++]) estão fora do curso.
usando classes que você não escreveu
Uma biblioteca é uma coleção de classes. Uma especificação de interface de programação de aplicações (API) diz ao programador como usá-las.
Uma classe define um tipo de referência. As classes de biblioteca são agrupadas em pacotes: String e Math ficam em java.lang, Scanner e ArrayList em java.util. Classes existentes podem ser usadas para criar objetos sem ver o código delas; a documentação é o que as torna utilizáveis.
Os atributos de uma classe são os dados relacionados a ela, guardados em variáveis: os caracteres de uma String. Seus comportamentos são o que suas instâncias podem fazer, ou o que pode ser feito com elas, e são definidos por métodos: length(), substring.
Os comentários explicam o código ao programador original e aos outros. O compilador os ignora e eles nunca são executados. O Java tem três tipos:
// a comment to the end of the line /* a block of comment, over several lines */ /** a Javadoc comment, used to make API documentation */
A figura tira String da estante e abre a sua documentação de substring.
Uma pré-condição precisa ser verdadeira logo antes de um método rodar para que ele funcione como esperado. Não se espera que o método a verifique: chamar substring com to além do fim quebra a pré-condição, e o resultado é uma exceção, não uma resposta educada.
Uma pós-condição precisa ser sempre verdadeira depois que o método roda. Ela descreve o resultado: o que é devolvido, ou os valores dos atributos do objeto depois.
parâmetros, argumentos, valores de retorno
Um método é um bloco de código com nome que só roda quando é chamado. Um bloco é qualquer código entre chaves.
Abstração procedural: você pode usar um método sabendo o que ele faz, sem saber como foi escrito. O cabeçalho dele diz o que você precisa:
public static double pow(double base, double exponent)
Um parâmetro é uma variável declarada no cabeçalho (base, exponent), usada dentro do método. A assinatura do método é o nome do método e a lista ordenada dos tipos dos parâmetros: pow(double, double). Um método sem parâmetros tem uma lista vazia: length().
Um argumento é um valor passado quando o método é chamado. Os argumentos devem corresponder aos parâmetros em número e ordem. Eles são passados por valor: cada parâmetro começa como uma cópia do seu argumento. Na figura, 2 e 10 viram as cópias 2.0 e 10.0.
double r = Math.pow(2, 10); System.out.println(r); // 1024.0 Um método não void devolve um valor do seu tipo de retorno; para usá-lo, guarde-o em uma variável ou use-o em uma expressão. Um método void não devolve nada, então não pode fazer parte de uma expressão: int x = System.out.println(5); não compila.
Os métodos são sobrecarregados quando vários têm o mesmo nome e assinaturas diferentes: Math.abs(int) e Math.abs(double). Os argumentos decidem qual é executado.
Uma chamada interrompe a sequência de instruções: as instruções do método rodam primeiro e, quando a última (ou um return) foi executada, o controle volta para logo depois da chamada.
métodos static e Math.random()
Os métodos de classe pertencem à classe, não a um objeto específico. O cabeçalho deles tem a palavra-chave static, e eles são chamados com o nome da classe e o operador ponto: Math.sqrt(2.0). Dentro da classe que os define, o nome da classe pode ser omitido.
A classe Math está em java.lang, então está disponível sem import, e tem só métodos de classe. A Java Quick Reference lista cinco:
| method | returns |
|---|---|
static int abs(int x) | o valor absoluto de um int |
static double abs(double x) | o valor absoluto de um double |
static double pow(double base, double exponent) | base elevada ao expoente |
static double sqrt(double x) | a raiz quadrada não negativa |
static double random() | um double ≥ 0.0 e < 1.0 |
A faixa de Math.random() inclui 0.0 (um extremo inclusivo) e exclui 1.0 (um extremo exclusivo). Aritmética e conversão de tipo a levam para a faixa que você quer:
int roll = (int) (Math.random() * 6) + 1;
A figura faz isso em etapas. * 6 estica [0.0, 1.0) para [0.0, 6.0). (int) trunca cada parte para 0, 1, …, 5: 6 partes iguais, portanto 6 valores igualmente prováveis. + 1 os desloca para 1, …, 6. Em geral, (int) (Math.random() * count) + low dá um de count ints começando em low.
Seus 30 lançamentos caíram 5 on 1, 5 on 2, 6 on 3, 4 on 4, 6 on 5, 4 on 6: igualmente provável não significa igualmente frequente em uma execução curta.
o modelo e o que ele cria
Uma classe é a implementação formal, ou o modelo, dos atributos e comportamentos de um objeto. Um objeto é uma instância específica de uma classe, com seus próprios valores para esses atributos.
A figura usa uma classe hipotética, Book, com dois atributos (um título e um número de páginas) e dois comportamentos (getTitle() e getPages()). Cada Book criado a partir dela tem seu próprio título e suas próprias páginas: "Maps", 120; "Birds", 340; "Stars", 96.
Classes relacionadas podem compartilhar atributos e comportamentos comuns colocando-os em uma só classe, uma superclasse. As classes que a estendem, as subclasses, aproveitam esses atributos e comportamentos sem escrevê-los de novo: uma relação de herança. (Projetar e escrever herança está fora do curso.)
Toda classe em Java é uma subclasse da classe Object, e é por isso que todo objeto tem métodos como toString() e equals.
Uma variável de tipo de referência guarda uma referência a objeto, que pode ser vista como o endereço de memória do objeto: a seta nas figuras, não o objeto.
Book b; // b pode guardar uma referência a um Book String s; // s pode guardar uma referência a uma String
new, construtores e null
Um objeto é criado com a palavra-chave new seguida de uma chamada a um dos construtores da classe.
Um construtor tem o mesmo nome da sua classe. Sua assinatura é esse nome e a lista ordenada dos tipos dos parâmetros; seu cabeçalho lista os tipos e nomes dos parâmetros. A documentação do Book hipotético lista:
Book(String title, int pages) Book(String title) String getTitle() int getPages()
Dois construtores com assinaturas diferentes: os construtores estão sobrecarregados, e os argumentos decidem qual é chamado. new Book("Maps") chama o segundo.
Book b = new Book("Maps", 120); Book c = null; A figura executa a primeira linha em ordem: new reserva memória para um Book; o construtor roda, e seus parâmetros definem os valores iniciais dos atributos; depois a referência ao novo objeto é guardada em b. Como uma chamada de método, uma chamada de construtor interrompe a sequência de instruções, e o controle volta para logo depois da chamada quando a última instrução foi executada.
Os argumentos do construtor devem ser compatíveis em número e ordem com os tipos dos parâmetros, e são passados por valor: new Book(120, "Maps") não compila, porque nenhum construtor recebe um int e depois uma String.
Uma variável de tipo de referência guarda uma referência a objeto ou, se não há objeto, null: c não se refere a nada.
o operador ponto e null
Os métodos de instância são chamados sobre um objeto da classe, com o nome do objeto, o operador ponto e o método: b.getPages().
int p = b.getPages(); // p é 120 int q = c.getPages(); // NullPointerException
A chamada segue a referência em b até o seu objeto, executa getPages() nesse objeto, e o int que ele devolve (120) é guardado em p. Outro objeto Book responderia com as suas próprias páginas.
c é null: não há objeto para onde seguir a seta. Chamar um método em uma referência null lança uma NullPointerException quando a linha é executada. Ela compila: o compilador só verifica se um Book tem um getPages().
Compare com o método de classe da página 1.10: Math.sqrt(x) é chamado na classe; s.length() é chamado em um objeto String, e responde só por aquela string.
imutáveis; a concatenação cria novos
Um objeto String representa uma sequência de caracteres. Ele é criado a partir de um literal de string, "cat", ou com o construtor, new String("cat"). String está em java.lang, disponível por padrão.
Um objeto String é imutável: depois de criado, seus caracteres nunca mudam. Os métodos chamados em uma String não a alteram; eles devolvem um novo valor.
String s = "cat"; // s é "cat" s = s + "s"; // s é "cats" String t = s + 1 + 2; // t é "cats12" String v = 1 + 2 + s; // v é "3cats"
+ e += concatenam Strings, criando um novo objeto String. Na figura, s = s + "s" constrói "cats" e faz s apontar para ele; o objeto "cat" continua exatamente "cat".
Um primitivo concatenado com uma String é convertido em String. O + funciona da esquerda para a direita, então importa onde a String aparece primeiro: em s + 1 + 2, cada número é anexado como texto; em 1 + 2 + s, 1 + 2 é somado primeiro, como ints.
Qualquer objeto pode ser concatenado com uma String: o Java chama o método toString do objeto, que toda classe herda de Object e que devolve uma string representando o objeto. As subclasses costumam sobrescrever esse método (um método público com a mesma assinatura do da superclasse, com comportamento próprio); escrever essa sobrescrita está fora do curso.
índices, length, substring, indexOf
Os caracteres de uma String têm índices de 0 até um a menos que o seu tamanho. Usar um índice fora dessa faixa lança uma StringIndexOutOfBoundsException.
A Java Quick Reference lista estes métodos de String (e o construtor; split vem com os arquivos de texto, no tópico 4.6):
| method | returns |
|---|---|
int length() | o número de caracteres |
String substring(int from, int to) | os caracteres do índice from ao índice to - 1 |
String substring(int from) | substring(from, length()) |
int indexOf(String str) | o índice da primeira ocorrência de str; −1 se não for encontrada |
boolean equals(Object other) | próxima página |
int compareTo(String other) | próxima página |
String s = "BANANAS"; s.length(); // 7 s.substring(1, 4); // "ANA" s.substring(4); // "NAS" s.indexOf("NA"); // 2 s.indexOf("X"); // -1 s.substring(3, 4); // "A"
to é o primeiro índice deixado de fora, então substring(from, to) tem to - from caracteres. indexOf encontra só a primeira ocorrência: "NA" também começa no índice 4.
Uma string com o único caractere em index é substring(index, index + 1).
equals, compareTo e não ==
equals pergunta se duas Strings têm a mesma sequência de caracteres. compareTo pergunta qual vem primeiro.
String a = "pear"; String b = new String("pear"); System.out.println(a == b); // false System.out.println(a.equals(b)); // true System.out.println(a.compareTo("peach") > 0); // true a e b se referem a dois objetos diferentes que guardam os mesmos caracteres. a.equals(b) compara os caracteres, par a par: true. a == b compara as referências, se apontam para o mesmo objeto: false. Compare Strings com equals. (A comparação de referências com == volta no tópico 2.6.)
a.compareTo(other) devolve um valor menor que 0 se a vem antes de other, 0 se são iguais e maior que 0 se a vem depois. As Strings são ordenadas com base no alfabeto: os primeiros caracteres que diferem decidem. "pear" e "peach" coincidem até o índice 3, onde 'r' vem depois de 'c', então o resultado é positivo. Confie só no sinal: é só isso que a Quick Reference promete.
O curso usa equals só para comparar uma String com outra String.