Java e linguagens de programação
Índice
- Introdução à Programação
- Executando um programa a partir de um código
- O caso de Java
- Recomendações de leitura
Introdução à Programação
Para desenvolver programas utilizamos linguagens de programação, classificadas por diferentes paradigmas. Para termos uma noção de como funcionam boa parte dessas linguagens, veja os seguintes exemplos:
Exemplo 1:
square :: Integer -> Integer square x = x * x
Exemplo 2:
n = input('Digite um número positivo:') while n < 0: print(f'{n} não é positivo!') n = input('Digite um número positivo:') print(f'Você escolheu o número {n}')
No Exemplo 1, temos a definição de uma função (semelhante a
matemática) chamada "square
".
- Na primeira linha, estamos instruindo que
square
recebe um número inteiro e entrega como resposta outro número inteiro; - Na segunda linha, indicamos que esse inteiro se chama
x
, e a resposta de square é x multiplicado por ele mesmo (elevando ao quadrado).
A linguagem utilizada nesse exemplo é Haskell.
Já no Exemplo 2, temos um pequeno programa que:
- Pede ao usuário para digitar um número inteiro. Esse pedido é feito pelo
input('mensagem')
. - Em seguida, enquanto o número for menor que 0 (ou seja, negativo), informamos o usuário do erro e pedimos para ele digitar outro número.
- Se o número deixar de ser negativo, então o programa passa para a próxima etapa, que é mostrar o número escolhido pelo usuário.
A linguagem utilizada nesse exemplo é Python.
Essas duas linguagens possuem formas completamente diferentes de se descrever problemas nelas, visto que a primeira é do que chamamos de paradigma funcional (ver a recomendação 1) e a segunda do paradigma imperativo. Java (a linguagem que será vista nas duas primeiras fases do curso de Ciência da Computação da UFSC) se encaixa no segundo caso.
Executando um programa a partir de um código
Há diversas formas de se executar um programa a partir de um código. As mais comuns são via Compilação e Interpretação.
Partindo do exemplo em Haskell abaixo, veremos como esses dois mecanismos podem funcionar:
Arquivo "exemplo.hs":
main = do putStrLn "Hello, World!"
Compilação
A partir de um programa escrito em uma linguagem, gerar um programa equivalente em outra linguagem.
No caso, a "outra linguagem" quase sempre será a linguagem de máquina, ou seja, um código binário que seu processador consiga executar. Cada processador implementa uma ISA (Instruction Set Architecture) que especifica a forma de executar esse código binário. Por essa razão, um mesmo binário que executa em um processador i5 (que implementa a ISA Intel x86_64) não executa em um celular com processador ARM Cortex A53 (que implementa a ISA ARMv8-A), já que as duas ISA's são incompatíveis.
Em Haskell, isso é possível de ser feito com o comando:
$ ghc exemplo.hs
Isso irá compilar o código no arquivo "exemplo.hs", gerando um arquivo de
código binário executável "exemplo" (no Linux) ou "exemplo.exe" (no Windows).
Uma versão humanamente legível desse executável pode ser visto com ferramentas
como objdump exemplo
, mas entender o que ele significa demanda um pouco mais
de conhecimento. Esse binário, sendo compilado em um processador com ISA x86 ou
x86_64 (basicamente a maioria dos notebooks e desktops), poderá ser distribuído
para qualquer outro processador que implemente a mesma ISA (ou seja, qualquer
outro x86 ou x86_64, respectivamente), o que é uma vantagem. Porém, se
quisermos fazer esse mesmo programa rodar em um processador ARM, precisamos
recompilá-lo (instruindo o compilador -- no caso de Haskell, o ghc
-- qual a
ISA alvo), o que dependendo do caso pode demorar muito tempo. Além disso,
qualquer mudança no código implica também em recompilação de pelo menos parte
dele (afinal, partes que não mudaram nem sempre precisam ser recompiladas).
Tendo o executável gerado, ele pode ser executado como qualquer outro, seja por um clique duplo ou pelo terminal:
$ ./exemplo
Hello, world!
Esse é o mecanismo padrão de compilação, em que o código é compilado antes de sua distribuição. Na recomendação 2 são descritos outros dois métodos de compilação, inclusive um deles utilizado há bastante tempo por Java e outro está para chegar para Java 10 (18.3).
Interpretação
Executar diretamente um programa escrito em uma linguagem.
Ou seja, em vez de o código ser compilado para um executável, apenas distribuímos o próprio código, e ele será executado por um Interpretador. Por exemplo, se tivermos um script em Python, salvo em um arquivo "hello.py":
print("Hello, world!")
Podemos simplesmente chamar o comando python
, passando o arquivo, e o código
dele será executado na hora:
$ python hello.py
Hello, world!
O mesmo pode ser feito com o código em Haskell, visto no exemplo de compilação.
Podemos, em vez de chamar o compilador de Haskell (ghc
) para gerar um
executável e então rodá-lo, chamar o interpretador de Haskell (runghc
)
passando o nome do arquivo com o código a ser executado:
$ runghc exemplo.hs
Hello, world!
Uma das vantagens da interpretação (em comparação com a compilação) é o fato de que geralmente o processo de compilação acaba sendo lento, e para muitas ferramentas é mais importante verificar rapidamente se estão funcionando de acordo fazendo diversas alterações em pouco tempo.
O caso de Java
"E Java? É uma linguagem compilada e interpretada?"
No fundo, ser interpretada e ser compilada não é uma característica da
linguagem. É como perguntar se um parafuso é parafusado com chave de fenda ou
com uma parafusadeira: não é uma característica do parafuso, é apenas a
ferramenta que você utiliza no parafuso. Podemos pegar o exemplo de Haskell
logo acima: perceba que o código é literalmente o mesmo, mas em um momento o
compilamos com o ghc
, e em outro momento o interpretamos com o runghc
. E aí
pergunta-se: Haskell é compilada ou interpretada? A resposta é: você pode
compilar Haskell e você pode interpretar Haskell. É uma linguagem apenas, o
que você faz com ela é outra história. Se uma linguagem possui apenas
interpretador, nada impede alguém de criar um compilador para ela (e
vice-versa).
Observação: Apesar do pedantismo, é "socialmente aceito" dizer "a linguagem X é compilada" em alguns casos como uma forma de dizer "ninguém interpreta ela, apenas em raríssimas exceções, se é que existem", e vice-versa.
Tendo isso em mente, o que podemos falar sobre Java é sobre o seu compilador
mais amplamente utilizado: o javac
(que, por sinal, é feito em Java). javac
possui uma característica interessante: ele compila Java, mas não para a ISA do
processador do computador em que o programa irá rodar, e sim para uma linguagem
intermediária, chamada "bytecode". Esse bytecode pode ser interpretado
utilizando o programa java
(obs: o programa, não a linguagem!), que nada mais
nada menos do que simula uma máquina, chamada JVM (Java Virtual Machine), que
executa esse bytecode.
Ou seja, basicamente temos um compilador de Java e um interpretador de bytecode:
------------------ --------- ------------
| Código em Java | --> | javac | --> | Bytecode |
------------------ --------- ------------
------------ -------- -------
| Bytecode | --> | java | --> | JVM | --> Execução do programa
------------ -------- -------
Recomendações de leitura
- Paradigmas de programação: Imperativo? Funcional? Lógico?
- Compilação AOT e JIT.
Ferramentas
Development Kit
Para poder desenvolver programas em Java, o Java Development Kit trás consigo ferramentas como compilador, depurador (para obter detalhes de um programa em execução, como valores na memória, resultados de comandos, etc.), e outros.
Clique aqui para ver como instalar e utilizar o JDK.
Eclipse
É comum, ao desenvolver programas, utilizar IDEs (Integrated Development Environment). IDEs nada mais são do que:
- Um editor de código;
- Um sistema de projetos;
- Um conjunto de módulos que chama o compilador, interpretador e depurador conforme o programador precisar.
Uma das IDEs mais conhecidas de Java é o Eclipse, cujo tutorial de instalação se encontra aqui.
Outras ferramentas recomendadas
Caso você queira buscar outras ferramentas que não sejam o Eclipse, seguem algumas alternativas bem interessantes.
Editores de texto
OBS: Todos os editores de texto abaixo, através de plugins, podem se comportar como IDEs.
- Visual Studio Code: Tem se mostrado um editor bastante leve e muito competente. Plugins são fáceis de instalar, o visual é simples e satisfatório, os recursos disponíveis são bastante flexíveis, configuráveis, fáceis de usar, intuitivos, etc., e é extremamente fácil de conseguir suporte à sua linguagem favorita. Fortemente recomendado.
- Sublime Text: Durante algum tempo foi minha recomendação mais forte (mas perdeu seu posto para o VSCode). É mais rápido do que o VSCode no geral, mas tem menos suporte nativo, alguns recursos não são tão robustos, mas ainda assim é um editor bastante potente, com diversos plugins fáceis de instalar/usar, configurações tão flexíveis quanto às do VSCode, dentre outras vantagens. Infelizmente, a cada 10 vezes que um arquivo é salvo, uma mensagem aparece perguntando se você não quer doar um dinheiro à equipe do Sublime (isso deixa de acontecer depois que você faz a doação).
- Atom: É outro editor bastante potente, mas não tão leve quanto o Sublime Text ou o VSCode. Ao menos possui um visual simples e atrativo, plugins, então caso não consiga se acostumar com os outros dois, ainda há o Atom.
IDEs
- IntelliJ: De longe a IDE mais poderosa de Java. O único motivo que não recomendaria é por ter o costume de ocupar muita RAM e disco, então quem não tem um computador com 8GB de RAM pode sofrer. Mas é bastante versátil, tem comandos muito bons para fazer o que você nem consegue imaginar que seria possível, além de ser extremamente organizada (como diz o slogan dela: "Ergonomic and Capable IDE"). Tem suporte a desenvolvimento para Android na versão gratuita.
Sobre Java
TODO
"Hello, Java!"
Primeiro programa em Java
Exemplo direto
(Obs: antes de seguir, certifique-se de que você possui a JDK instalada e que a pasta dela está configurada na variável de ambiente "path" - veja como fazer isso na Ferramenta 1)
Primeiramente vamos dar uma olhada em um simples código em Java:
public class HelloWorld {
public static void main(String... args) {
System.out.println("Hello, world!");
}
}
Esse primeiro exemplo, salvo num arquivo "HelloWorld.java", faz nada mais nada
menos do que mostrar um texto ("Hello, world!") na tela. Uma maneira de rodá-lo
é compilando com o comando javac
e então executando
com o comando java
:
(Obs: "~/dev/java" é a pasta em que o terminal está trabalhando - e onde o código acima foi salvo -; o "$" é meramente para separar o que são os comandos (à direita do $) e o que são outras informações (à esquerda do $))
(Obs2: ls
é o comando para mostrar o conteúdo da pasta em que o terminal
está trabalhando)
~/dev/java $ ls
HelloWorld.java
~/dev/java $ javac HelloWorld.java
~/dev/java $ java HelloWorld
Hello, world!
~/dev/java $
Caso prefira rodar de outra maneira, cheque a seção de Ferramentas como executar na sua ferramenta favorita. Mas, enquanto acompanha os tutoriais, é recomendado manter-se no compilador sozinho. Utilize outra ferramenta apenas se não conseguir seguir pelo compilador no terminal/CMD do Windows.
"Hello, world!": Explicação detalhada
Veremos de dentro para fora como funciona esse programa de exemplo:
-
System.out.println(...)
: Esse comando serve para mostrar um texto na tela e em seguida pular uma linha. Existe um comando parecido que não pula a linha no final, chamadoSystem.out.print
. Para fins de comparação, o código abaixo:System.out.print("Hello,"); System.out.print(" "); System.out.print("world"); System.out.print("!"); System.out.println("Good"); System.out.println("bye"); System.out.println(","); System.out.println(" world...");
Ao ser executado, mostraria:
Hello, world!Good bye , world...
Inclusive, com isso podemos notar uma coisa: o programa é executado passo a passo, na ordem que você definiu. Portanto, se algo saiu errado, lembre-se de que em quase todas as vezes em que o programa não executa como planejado, o erro foi seu :) (acredite isso é ligeiramente motivacional).
-
public static void main(String... args)
:Cada palavra dessas envolve uma série de conceitos os quais não convém explicar logo agora, mas ao menos para que entenda para que serve essa linha: quando você executa
java AlgumaCoisa
, o que o programa faz é procurar por esse nosso colega chamado "main". Ou seja, é a partir dele que nosso programa começa a executar.Você pode fazer, por exemplo:
public class OtherThanMain { public static void foo(String... args) { System.out.println("Executando foo..."); } public static void main(String... args) { System.out.println("Executando main..."); } public static void bar(String... args) { System.out.println("Executando bar..."); } }
E, ao executar, o que irá acontecer é:
$ java OtherThanMain Executando main...
Em contrapartida, se tivermos:
public class WithoutMain { public static void thisIsNotMain(String... args) { System.out.println("Hey, this is not main!"); } }
Ao tentar executar, tomaríamos um erro:
$ java WithoutMain Error: Main method not found in class Foo, please define the main method as: public static void main(String[] args) or a JavaFX application class must extend javafx.application.Application
Esse erro está simplesmente nos dizendo "não encontrei o
main
, por favor, se for executar essa classe, defina omain
nela", o que demonstra que efetivamente omain
é o que Java procura para executar.Perceba que os comandos executados são apenas os que estão entre chaves ("{" e "}"). Isso se dá porque as chaves delimitam o início e fim de um escopo, o que será visto mais adiante na aula 3, mas por enquanto entenda como "o início e fim do
main
".Quanto aos args, trata-se dos argumentos ao executar o programa, que podem ser acessados como um vetor/array, o que será explicado na aula 5.
-
public class HelloWorld
:Em Java, tudo precisa estar dentro de uma classe, incluindo o
main
. Nesse caso, nossa classe se chama "HelloWorld" e as chaves ("{" e "}"), assim como nomain
, indicam onde começa e termina a implementação dela.Uma classe nada mais nada menos do que simboliza um modelo e um tipo de dado, o que será melhor explicado na aula 7.
-
O nome do arquivo: "HelloWorld.java":
Java possui uma característica bastante única quanto ao nome dos arquivos: obrigatoriamente o nome do arquivo deve ser o mesmo que o nome da principal classe implementada nele. Assim, se tivéssemos:
public class AnotherName {}
Nosso arquivo precisaria se chamar "AnotherName.java".
Erros de Compilação e Execução
Índice
Compreendendo erros de compilação
Erros lançados pelo javac
Os erros que o javac
mostra costumam estar no seguinte formato:
[arquivo]:[linha]: error: [mensagem]
[código]
[nº total de erros encontrados]
Em alguns casos, no lugar de error
estará escrito warning
. Warning não são
erros efetivamente (mas devem ser tratados como se fossem!), mas sim coisas que
o compilador percebem que podem gerar problemas futuramente. Por exemplo, se
você está utilizando um comando que está marcado como "Deprecated" (ou seja,
não é aconselhável utilizá-lo pois ou é inseguro ou será removido futuramente),
o seu programa não contém erros de código em si, mas o compilador irá avisá-lo
de que está utilizando o tal comando não-aconselhável.
Erros de Sintaxe (SintaxError): Ocorrem quando o código escrito não confere com a gramática da linguagem, por exemplo:
public class {
}
O código acima, ao se tentar compilá-lo, o compilador acusará um erro de sintaxe:
Example.java:1: error: <identifier> expected
public class {
^
1 error
O erro se dá porque o formato esperado para uma declaração de classe é:
[modificador de acesso] class <Nome da classe> {
[declarações]
}
Ou seja, public class Example {}
é válido, porém public class {}
(sem o
identificador para o nome da classe) não.
Compreendendo erros de execução
Erros de execução costumam ser assustadores, afinal você, um aprendiz muito contente com seus novos conhecimentos, inocentemente executa o programa:
public class Example {
public static void main(String[] args) {
var name = "Nice Guy";
System.out.println("Hello, Mr. " + name.charAt(9) + "!");
}
}
E é bombardeado com uma mensagem de erro enorme:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 9
at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:44)
at java.base/java.lang.String.charAt(String.java:692)
at Example.main(Example.java:4)
Apesar de poder assustar um pouco, é simples entender o que essas mensagens estão dizendo:
- "Exception in thread "main"": Apenas quer dizer que um erro aconteceu durante a execução do programa. "Exception" se dá pelo mecanismo de tratamento de erros de Java (que se chamam "Exceptions"). "main" é a linha de execução (thread) do programa em que ocorreu o erro, mas o conceito de threads só se vê em programação concorrente. Para os seus programas, a única thread é a "main".
java.lang.StringIndexOutOfBoundsException": É o nome do erro.
java.lang` indica que é um erro reconhecido pela biblioteca padrão de Java, e o resto é possível tentar deduzir: tentou-se acessar uma string (texto) em um índice inválido (por exemplo, o 5º caractere do texto ".- "String index ouf of range: 9": Significa que se tentou acessar o 10º caractere (índices começam em 0, portanto 9 é o 10º índice) do texto.
O resto é a ordem de qual comando executou qual subcomando até acontecer o erro:
- Primeiro se executou
main
; main
na linha 4 executou o comandoString.charAt
;String.charAt, na linha 692 chamou o comando
StringLatin1.charAt`;StringLatin1.charAt
percebeu que o índice era inválido e, na linha 44, o erro foi acusado (o que, para o caso de Exceptions, se chama "lançar uma Exception").
Armazenando Valores
Em nossos programas, não iremos querer apenas mostrar textos. Em praticamente 100% dos casos iremos trabalhar com dados, como por exemplo: dados de usuários (nome, e-mail, ...), medições (temperatura, distâncias, ...), ou mesmo elementos mais abstratos, como posição do mouse na tela, coordenadas dos vértices de um objeto 3D, cores dos pixeis de uma imagem, e por aí vai.
Para isso, precisamos de uma maneira de referenciar esses dados para poder trabalhar com eles, além de poder saber qual tipo de dado estamos trabalhando e realizar operações em cima deles. Nesse quesito, temos o que chamamos de variáveis e constantes. Uma variável/constante é uma forma de sabermos o estado atual de um dado. Veremos a seguir como trabalhar com elas.
Variáveis
Pequeno Exemplo
O exemplo abaixo ilustra um pequeno programa que cria variáveis e mostra o valor de cada uma delas:
public class VariablesExample {
public static void main(String... args) {
int x = 0;
int y = 3;
double z = 10.0;
System.out.println(x);
System.out.println(y);
System.out.println(z);
}
}
Nesse exemplo, foram criadas três variáveis: duas representando números
inteiros (simbolizado pelo int
antes do nome delas) e outra representando um
número real (simbolizado pelo double
). Perceba que essas variáveis recebem
nomes: x
, y
e z
. Esse nome é chamado de identificador, e serve para
referenciar ou guardar um dado (veremos mais adiante quanto ao conceito de
referência). Executando o exemplo:
$ javac VariablesExample.java
$ java VariablesExample
0
3
10
Para criar uma variável, deve-se seguir o padrão:
tipo identificador = valor;
-
Tipo: O tipo de dado a ser guardado ou referenciado pela variável. Veja em Tipos de dados para ver o que se pode utilizar.
-
Identificador: O nome que utilizaremos para a variável. Podemos nos referir a uma variável através de seu identificador, como visto no pequeno exemplo. Podemos, inclusive, utilizar uma variável para dar valor a outra:
int x = 10; int y = x;
Nesse caso, tanto x quanto y guardam o valor 10. Se fizermos, porém:
int x = 10; int y = x; x = 4;
A variável
x
terá mudado de valor para 4, porémy
manterá o valor 10. Inclusive, o fato de podermos mudar o valor dex
é o que caracteriza a ideia de variáveis: o valor delas pode variar durante o programa (se algum programador disser que o valor dela será alterado, como no caso dex
).
Constantes
Como visto no tópico anterior, "variáveis" possuem esse nome pois é possível alterar qual valor elas guardam. No caso de constantes, isso não pode acontecer mesmo que o programador tente. Isso é útil para agilizar o compilador quanto a otimizações, uma vez que ele pode se aproveitar do fato de que aquele valor garantidamente nunca irá mudar.
Para fazer com que um identificador seja uma constante, basta utilizar final
logo antes de sua declaração:
final int x = 10;
int y = x; // o valor de x é copiado para y
x = 4; // Erro de compilação: não se pode alterar o valor de uma constante
Quando utilizar constantes? Sempre que o valor não for feito para ser alterado no contexto em que é utilizado.
Tipos de dados
Os tipos de dados guardados por variáveis e constantes são separados entre "primitivos" e "compostos".
Primitivos
Os tipos primitivos são os que guardam a menor unidade possível de dado. São eles:
Tipo | Significado | Tamanho | Valores possíveis |
---|---|---|---|
byte | Número inteiro | 1 byte | -128 a 127 |
short | Número inteiro | 2 bytes | -65536 a 65535 |
int | Número inteiro | 4 bytes | -2³² a 2³² - 1 |
long | Número inteiro | 8 bytes | -2⁶⁴ a 2⁶⁴ - 1 |
----------- | ----------------- | --------- | ---------------------- |
float | Número real | 4 bytes | [1] |
double | Número real | 8 bytes | [1] |
----------- | ----------------- | --------- | ---------------------- |
boolean | Valor lógico | 1 byte | Falso e Verdadeiro |
----------- | ----------------- | --------- | ---------------------- |
char | Um caractere | 2 bytes | [2] |
[1]: Definir os valores possíveis para números reais não é uma tarefa tão
simples à primeira vista. Números reais utilizam um padrão de armazenamento
chamado
Ponto Flutuante (por
isso o nome float
), em que temos uma mantissa, uma base e um expoente, e o
valor de um número nesse padrão é dado por mantissa * base ^ expoente
. Por
conta disso (aliado ao fato de que, em computadores, a base e o número de bits
da mantissa e do expoente são fixos), floats conseguem maior precisão em
valores entre -1 e 1, porém a medida em que os valores ficam mais distantes de
0, os números representados com esse padrão começam a ficar mais esparços
(mantendo-se o mesmo tamanho para a mantissa).
Quanto ao nome double
, a ideia vem de "Double Precision", pelo fato de que
ocupa o dobro de espaço em memória (e portanto diz-se ter o dobro de precisão).
Quanto a quais variáveis utilizar, recomenda-se double (salvo raras exceções,
como ambientes em que o consumo de memória por floats seja o gargalo -- por
exemplo, ambientes com objetos 3D compostos de absurdamente muitos vértices
--).
(OBS: NÃO utilize float
nem double
para armazenar dados sensíveis
(como, por exemplo, dinheiro). Veja na recomendação de leitura
#4 o porquê disso.)
[2]: Textos, dentro do mundo da computação, possuem codificações específicas para indicar o que é cada caractere. Essencialmente, um caractere não passa de um número inteiro. Por exemplo:
char a = 97;
char b = 98;
char c = 65;
char d = 33;
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
O código acima mostrará:
a
b
A
!
Pois, pelo padrão de codificação Unicode, que é o que Java também utiliza (especificamente, Java utiliza o formato UTF-16, que engloba caracteres não-ocidentais, e, como o nome diz: ocupa 16 bits por caractere), 97 é o valor que simboliza a letra "a" (minúsculo), enquanto 98 simboliza "b", 65 a letra "A" (maiúsculo) e 33 simboliza "!". Porém, texto em computação não necessariamente é apenas letras (a-z, A-Z), acentos e pontuações ("!", "`", "~", ...), mas também elementos especiais como quebra de linha. Por exemplo:
char lineBreak = 13;
System.out.print("Hello, ");
System.out.print(lineBreak);
System.out.print("World!");
Perceba que executando o código acima, mesmo não tendo um println
(e sim
apenas print
, que não pula linha), aparecerá:
Hello,
World!
Pois "13" é o código que simboliza quebra de linha (em SO's baseados em Unix; em Windows, uma quebra de linha é o caractere 13 seguido do caractere 10).
Compostos
Os tipos compostos são, em resumo, todos os que não são primitivos. Por exemplo:
String
;Object
;ArrayList
;- Arrays (demarcados com um
[]
, visto mais à frente).
Em uma aula mais a frente, aprenderemos a criar nossos próprios tipos compostos.
Declaração e instanciação
Três conceitos que serão vistos frequentemente em programação imperativa:
Declaração
Trata-se de "dizer que uma variável existe". Em código:
int x;
String name;
double thing;
Perceba que essas variáveis não foram inicializadas: o que foi feito é uma declaração sem que houvesse uma inicialização, ou seja: dissemos que elas existem, mas não demos um valor.
Porém:
int x = 0;
Estamos declarando x
e inicializando.
Instanciação
Instanciar é criar um valor novo, a grosso modo. Por exemplo:
int x = 0; // Instanciamos o valor 0, que é um número inteiro
int y = 10; // Instanciamos o valor 10, que é um número inteiro
int z = x; // Instanciamos um valor novo que é uma *cópia* do valor de `x`
String name = "Alice"; // Instanciamos uma String com o texto "Alice"
String fullname = "Alice" + " " + "Cooper";
Na última linha, instanciamos 5 String
's, respectivamente:
- Uma contendo o texto "Alice";
- Outra contendo o texto " " (apenas um espaço em branco);
- Outra contendo o texto "Cooper";
- Outra sendo a concatenação de "Alice" e " ", que gera uma instância contendo "Alice ";
- Por fim, outra sendo a concatenação de "Alice " e "Cooper', que gera uma instância contendo o texto "Alice Cooper".
Porém, foi declarada apenas uma variável: fullname
. Uma situação semelhante
pode ser vista em:
int x = 0;
x = 3;
Ainda temos apenas uma única variável declarada (x
), porém inicialmente
instanciamos o valor 0 e, em seguida, o valor 3.
Nomeando adequadamente
É sempre importante lembrar que você irá reler seu código várias vezes (e outras pessoas, principalmente, mesmo que você jure que não - e uma delas pode ser você mesmo daqui a 10 meses). Então, trate de dar nomes suficiente significativos para suas variáveis.
Veja o trecho código abaixo:
var a = 75.5;
var b = 1.84;
var c = a + b * b;
O que significam a
, b
e c
? Tente o 2º exemplo:
var w = 75.5;
var h = 1.84;
var i = w + h * h;
Você pode até decifrar pelo que o código está fazendo e pelas variáveis terem uma letra mais próxima do que significam, mas:
var weight = 75.5;
var height = 1.84;
var bmi = weight + height * height;
É bem mais direto e, apenas lendo o código (dado o conhecimento da sintaxe), é
possível entender do que se trata: é um cálculo de IMC. Perceba que apenas
weight
e height
foram suficientes para entender que se tratam de peso e
altura da pessoa, e bmi
é ao menos a sigla BMI (Body Mass Index), que é uma
sigla bem estabelecida (para qualquer brasileiro, por exemplo, se você falar
IMC em vez de "Índice de Massa Corporal", ele irá saber do que se trata).
Vale ressaltar que nomes podem acabar sendo descritivos em excesso, e portanto cuide! Veja:
var personWeightInKilograms = 75.5;
var personHeightInMeters = 1.84;
var bodyMassIndex = personWeightInKilograms + personHeightInMeters * personHeightInMeters;
Perceba que o código não ficou mais legível (pelo contrário, agora há muito ruído visual). Então não descreva muito e nem deixe de descrever: faça o suficiente.
(OBS: Para lidar com diferentes unidades de medida, há formas mais adequadas do que explicitando no identificador, aproveitando tipos compostos, mas isso é algo para se ver mais à frente.)
Padrão de nomenclatura de variáveis
Em Java, o nome/identificador das variáveis segue o padrão camelCase: a primeira letra do nome é minúscula, todas as palavras são juntas e a primeira letra de cada palavra é maiúscula (que é exatamente da mesma forma como foi escrito), por exemplo:
int value;
int anotherValue;
int aValueWithAReallyBigName;
Caso especial: Siglas. No caso de siglas, é preferível que variáveis sigam padrões no estilo:
String cpf; // Todo o nome é uma sigla
DvdPlayer dvdPlayer; // O nome começa com uma sigla
int userId; // O nome termina com uma sigla (ou ela está no meio)
Padrão de nomenclatura de variáveis
O padrão de nomenclatura para constantes é SCREAMING_SNAKE_CASE: todas as letras em maiúsculo, palavras separadas por um underscore/underline ("_"). Por exemplo:
final double PI = 3.1415926535897932384626433;
final int NUM_THREADS = 4;
Recomendações de Leitura
- "Por que meu
==
não funciona para String?" - Cópia e "move"
- Operadores em Java
- Por que não utilizar float e double para representar dinheiro?
- Aritmética - Detalhes
Estrutura condicional If
Os programas precisam tomar decisões uma hora ou outra. Por exemplo, não é
sempre que determinados comandos serão executados, como: se o usuário escolhe
pagar no cartão, o código que faz o pagamento em dinheiro não será executado.
Para esse comportamento (executar um trecho de código apenas se uma condição
for verdadeira), em Java há a estrutura if
.
Sintaxe de um If
Um if
é definido como:
if (condicao) {
// sequência de comandos
}
Em condicao
, pode ser colocado qualquer valor do tipo boolean
. Por exemplo:
if (true) {
System.out.println("Entrou no if");
}
System.out.println("Após o if");
Se a sequência acima for executada, o resultado será:
Entrou no if
Após o if
Mudando um pouco o código:
if (false) {
System.out.println("Entrou no if");
}
System.out.println("Após o if");
O resultado agora será:
Após o if
Perceba que como a condição era falsa, o trecho dentro do if
não executou.
Pode-se estender isso para comparações com variáveis:
var x = 10;
var y = 20;
if (x < y) {
System.out.println("x é menor que y");
}
If-else
É possível definir o que o programa fará quando a condição do if
não for
verdadeira utilizando if-else
, por exemplo:
if (true) {
System.out.println("Entrou no if");
} else {
System.out.println("Entrou no else");
}
System.out.println("Após o if");
Executando o trecho acima:
Entrou no if
Após o if
Alterando o trecho para:
if (false) {
System.out.println("Entrou no if");
} else {
System.out.println("Entrou no else");
}
System.out.println("Após o if");
E executando:
Entrou no else
Após o if
Perceba que, como a condição do if
era falsa, foi executado o comando
presente no else
.
Brincando com o código
Salve o código abaixo como "PlayWithIf.java" e altere-o da forma que quiser, compilando e vendo como o programa se comporta:
public class PlayWithIf {
public static void main(String[] args) {
var x = 0;
var y = 10;
if (x < y) {
System.out.println(x + " is less than " + y);
} else if (x > y) {
System.out.println(x + " is greater than " + y);
} else {
System.out.println(x + " and " + y + " are equal");
}
var student1 = "Arthur";
var student2 = "arthur";
if (student1.equals(student2)) {
System.out.println("Both students have the same name");
} else {
System.out.println("Something is different with these students.");
if (student1.equalsIgnoreCase(student2)) {
System.out.println("Oh yeah, they have different cases.");
} else {
System.out.println("And it's mostly everything.");
}
}
}
}
Tente, por exemplo, ver o que acontece quando a ordem dos if
s é alterada, ou
alterar as condições, ou mesmo quebrar os if-else
em vários if
s separados.
Estruturas de repetição for
e while
Em dados momentos de um programa, é necessário repetir uma mesma sequência de
comandos em seu programa. Algumas dessas repetições serão porque há uma
condição a ser quebrada, por exemplo: atualizar o jogo enquanto ele estiver
rodando, e parar de atualizar quando ele for finalizado. Em outras, a
preocupação é executar N passos, guardando a informação de qual passo está
sendo executado no momento. Para o primeiro caso, existe o laço while
. Para o
segundo, existe o laço for
.
Termos
- Loop: significa "laço", se referindo ao fato de que o fim de um laço é o início dele próprio (dando a ideia de repetição). Na prática, loops são trechos de código que, por algum motivo, se repetem várias vezes.
- Iteração: significa "ciclo". Uma iteração é executar um ciclos de um
laço. Por exemplo, se o seu programa faz a mesma coisa para todo
i
de 0 a 10, há uma iteração parai = 0
, outra parai = 1
, etc., totalizando 11 iterações; - Iterar: é fazer uma iteração. Pode-se dizer "iterar sobre X" (em que X é uma coleção de dados) para significar "fazer a mesma ação para cada dado em X".
Estrutura for
Suponha o clássico exemplo de listar números pares de 0 a 10. Podemos concordar que o código abaixo faz o que se espera:
System.out.println(0);
System.out.println(2);
System.out.println(4);
System.out.println(6);
System.out.println(8);
System.out.println(10);
Porém, se for necessário listar até 100, o estilo de código acima fica inviável. E ficará ainda mais inviável se considerarmos de o usuário dizer qual o número máximo: como fazer um programa que liste números pares até um número qualquer que o usuário quem vai dizer qual é?
Sintaxe de um for
Nesse caso, em uma linguagem imperativa, vale mais a pena utilizar uma
estrutura de repetição como for
. A estrutura do for
é dada como:
for (inicio; condição; passo) {
// sequência de comandos
}
E seu funcionamento é:
- Primeiramente, é executado o que está em
inicio
; - Então, repete-se:
- Verifica-se se o resultado de
condição
; - Se for
false
, interrompe a repetição e segue o código após ofor
; - Se for
true
, executa a sequência de comandos no escopo dofor
; - Depois de executar a sequência de comandos, executa o que estiver no passo.
- Verifica-se se o resultado de
Funcionamento detalhado (passo a passo)
É bastante comum que esse for
seja feito da forma:
for (var i = 0; i < x; i++) {
// sequência de comandos
}
A ideia, nesse caso, é executar a mesma sequência de comandos x
vezes (ou
seja, para todo i
de 0
a x - 1
). Por exemplo:
for (var i = 0; i < 3; i++) {
System.out.printf("Mostrando %d\n", i);
}
System.out.println("Fim do programa.");
Nesse caso, o que o programa fará é:
- O valor de
i
começa como 0; i
é menor que 3? Ou seja: 0 é menor que 3? Sim, então executa o que estiver no escopo;- Mostra o texto "Mostrando 0" (pulando linha);
- Executa
i++
(ou seja,i
agora é0 + 1
, e portantoi = 1
); i
é menor que 3? Ou seja: 1 é menor que 3? Sim;- Mostra o texto "Mostrando 1";
- Executa
i++
(agorai
é 2); i
é menor que 3? Ou seja: 2 é menor que 3? Sim;- Mostra o texto "Mostrando 2";
- Executa
i++
(agorai
é 3); i
é menor que 3? Ou seja: 3 é menor que 3? Não, e portanto said dofor
;- Mostra o texto "Fim do programa.".
Visualizando como uma tabela:
Instrução | i | Resultado |
---|---|---|
var i = 0 | 0 | - |
i < 3 | 0 | true |
System.out.printf("Mostrando %d\n", i); | 0 | Console: Mostrando 0 |
i++ | 1 | - |
i < 3 | 1 | true |
System.out.printf("Mostrando %d\n", i); | 1 | Console: Mostrando 1 |
i++ | 2 | - |
i < 3 | 2 | true |
System.out.printf("Mostrando %d\n", i); | 2 | Console: Mostrando 2 |
i++ | 3 | - |
i < 3 | 3 | false (sai do for ) |
System.out.println("Fim do programa."); | * | Console: Fim do programa |
*: Vale lembrar que, após a execução do for
, como i
foi declarado no
for
, então i
já não existe mais quando o for
terminar de executar.
Adaptando para a listagem de pares de 0 a 10
Listar números de 0 a 10 pode ser, então, feito da seguinte forma:
for (var i = 0; i <= 10; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
Nesse caso, se aproveita o fato de que %
simboliza o resto de uma divisão, e
inteiros pares são os em que a divisão por 2 tem resto 0.
Ou ainda pulando i
de 2 em 2:
for (var i = 0; i <= 10; i += 2) {
System.out.println(i);
}
No tutorial seguinte, será visto o quanto for
pode ser facilmente combinado para aumentar o potencial de array
s.
Estrutura while
Sintaxe de um while
A estrutura while
é dada da forma:
while (condição) {
// Sequência de comando
}
Bastante semelhante a um if
, porém o if
executa apenas uma vez, enquanto o
while
executa enquanto condição
for true
. É possível, por exemplo, fazer
um while
que executa eternamente entregando true
como condição:
while (true) {
System.out.println("Looooop!");
}
Geralmente, o objetivo é executar a sequência de comandos até que algo se torne falso. Por exemplo, o programa abaixo pede números ao usuário até que o número esteja dentro dos limites que o programa exige:
var console = System.console();
System.out.print("Insira um número de 0 a 10:");
var input = Integer.parseInt(console.readLine());
while (input > 10 || input < 0) {
System.out.println("Número inválido! Ele deve ser de 0 a 10!");
System.out.print("Insira um número de 0 a 10:");
input = Integer.parseInt(console.readLine());
}
System.out.println("Número válido.");
Perceba que, diferente do for
, não se tem a ideia de "N passos", mas sim de
"até que X seja falso".
Exemplo de while
: calcular raíz quadrada
Um exemplo envolvendo cálculo numérico é o de cálculo de raiz quadrada de x
:
var x = 10.0;
var old = 0.0;
var guess = 100.0; // can be any double != x
while (guess != old) {
old = guess;
guess = (guess + x / guess) / 2;
}
System.out.printf("Raiz de %f: %f\n", x, guess);
A técnica utilizada é a do
Método de Newton para Cálculo de Raízes de Função,
que consiste em ir de pouco em pouco convergindo para a raiz aproveitando a
derivada da função (conceito visto em Cálculo) a cada passo. Não é importante
aqui saber como o método funciona, mas sim prestar atenção em um detalhe: o
objetivo não é executar N passos, mas sim continuar executando até que o valor
de guess
pare de mudar, e isso varia de x
para x
(alguns precisarão de
mais passos, outros de menos).
Interrompendo estruturas de repetição
É possível parar a execução de uma estrutura de repetição utilizando o comando
break
:
while (true) {
System.out.printf("Write \"end\" to quit: ");
var input = System.console().readLine();
if (input.equals("end")) {
break;
}
System.out.println("Not quitting.");
}
System.out.println("The end.");
Nesse caso, a partir do momento que o usuário digitar "end", o break
será
executado e o while
irá parar de executar (indo para o print de "The end.").
Pulando uma iteração
Em outros casos, é possível pular a iteração atual para a próxima utilizando
continue
. Por exemplo, no caso da listagem de pares:
for (var i = 0; i <= 10; i++) {
if (i % 2 != 0) {
continue;
}
System.out.println(i);
}
Nesse caso, será pulada toda iteração em que i
não for par.
Arrays
É comum precisar de listas de valores em programas. Por exemplo, é necessário lidar com listas de alunos ou professores em uma escola, jogadores e inimigos em um jogo, e por aí vai. Para isso, existem "Arrays" (ou "Vetores"), que são sequências de valores.
Criando um array
Há algumas formas de se criar um array.
Quando se tem um tamanho específico
// Em Java 9 ou anterior:
T[] array = new T[10];
// Em Java 10+:
var array = new T[10];
Em que T
é o tipo de dado dos elementos do array. Por exemplo, se for um
array de int
:
// Em Java 9 ou anterior:
int[] array = new int[10];
// Em Java 10+:
var array = new int[10];
Com isso, array
será um array de 10 inteiros, todos eles com o valor "0"
(valor padrão para inteiros, floats, doubles, etc.).
Ou se for String
:
// Em Java 9 ou anterior:
String[] array = new String[10];
// Em Java 10+:
var array = new String[10];
Porém, vale lembrar que, como String
não é um tipo primitivo, então os
elementos do array
não serão Strings vazia, mas sim null
(a grosso modo,
significa ausência de informação, o que é diferente de um texto com 0
caracteres). Então tome cuidado quando criar vetores de tipos não-primitivos,
principalmente quando tentar acessar propriedades deles:
var array = new String[3];
var isAda = array[0].equals("Ada"); // error: NullPointerException
array[0] = "Bob";
var isAda = array[0].equals("Ada"); // idAda = false
var isBob = array[0].equals("Bob"); // idAda = true
Quando se quer uma lista de elementos pré-definidos
É possível criar arrays já colocando os elementos que estarão nele:
T[] array = {value1, value2, value3, ...,};
// Ou...
var array = new T[] {value1, value2, value3, ...,};
Por exemplo:
// Em Java 9 ou anterior:
int[] array = {1, 2, 3, 4, 5};
// Em Java 10+:
var array = new int[] {1, 2, 3, 4, 5};
Nesse caso, array
terá 5 elementos, sendo eles, na ordem: 1, 2, 3, 4 e 5.
Acessando elementos de um array
É possível acessar elementos de um array utilizando array[indice]
, em que
indice
vai de 0..N-1 (sendo N o tamanho do array):
var array = new int[] {25, 19, -12};
var first = array[0]; // first = 25
var second = array[1]; // first = 18
var third = array[2]; // first = -12
Perceba que não se pode acessar array[3]
para um array de 3 elementos, já que
o primeiro elemento está no índice 0, e não 1. array[3]
seria, então, o "4º
elemento", que não existe em um array de 3 elementos.
Vale apontar também que, caso se tente acessar um índice inválido, o programa
irá fechar com o erro ArrayIndexOutOfBounds
:
var array = new int[] {25, 19, -12};
var nonexistent = array[-1]; // error: ArrayIndexOutOfBounds
var nonexistent = array[3]; // error: ArrayIndexOutOfBounds
var nonexistent = array[20]; // error: ArrayIndexOutOfBounds
É possível alterar os elementos de um array da mesma forma que se altera uma variável:
var array = new int[] {25, 19, -12};
array[0] = 0;
System.out.println(array[0]); // => 0
System.out.println(array[1]); // => 19
System.out.println(array[2]); // => -12
Propriedades de Arrays
Como acessar propriedades
Toda propriedade de um valor, em Java, pode ser acessada com .
, no formato:
valor.propriedade
. Por exemplo, a propriedade length
de um array
qualquer armazenado em uma variável chamada seuarray
pode ser acessada como
seuarray.length
.
Vale apontar que valores de tipos primitivos não possuem propriedades.
length
É possível saber o tamanho/comprimento de um array utilizando a propriedade
length
:
var array = new int[] {25, 19, -12};
System.out.printf("array has %d elements\n", array.length); // => array has 3 elements
Combinando arrays e for
s
É possível aproveitar a repetição de passos do for
para, por exemplo,
percorrer arrays:
var array = new int[] {25, 19, -12};
for (var i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
Perceba como tudo se encaixa: o passo inicial é o primeiro índice do vetor, a
cada passo o índice é incrementado, e é executado o for
até que i
chegue a
array.length - 1
. Com isso, conseguimos executar a mesma ação para todos os
elementos de um vetor. Inclusive, array[i]
pode ser lido como "o i-ésimo
elemento de array
".
Estrutura for-each
Semelhante a um for
normal, existe uma estrutura específica para iterar por
uma coleção (por exemplo, um vetor). Essa estrutura possui a seguinte sintaxe:
for (T nome: coleção) {
// Sequência de comandos
}
Em que T
é o tipo de cada item da coleção (var
é permitido), nome
é o
nome da variável que guardará cada item da coleção. Para ver melhor como ele
funciona:
var array = new int[] {25, 19, -12};
for (var item: array) {
System.out.println(item);
}
O programa acima, ao ser executado, mostrará:
25
19
-12
Ou seja, a cada iteração, item
guarda um dos itens de array
. Esse for
é
util quando o índice do elemento não é importante (no exemplo acima, o que
importa é acessar os elementos em ordem, mas efetivamente qual o índice deles
não é importante). O código é equivalente (perceba: equivalente, mas não
igual!) a:
var array = new int[] {25, 19, -12};
for (var i = 0; i < array.length; i++) {
var item = array[i];
System.out.println(item);
}
No fundo, o que o for-each
faz é aproveitar a interface
Iterable
dos padrões da linguagem, mas isso será visto noutro tutorial.
Recomendações de leitura
- Como representar matrizes adequadamente
Criando seus próprios comandos
Na maioria dos casos em programação imperativa, criar seus próprios comandos significa criar sua própria função. Funções são trechos de código que podem ser executados eventualmente. Geralmente se pode executar esses trechos utilizando o identificador dessa função.
Funções são também ótimas para se ter reuso e legibilidade, palavras que você irá ouvir bastante enquanto programador.
Funções fazem com que tenhamos apenas um lugar para consertar um erro quando
ele existir. Por exemplo: imagine que você fez um código que pede a senha para
o usuário, mas percebe que esqueceu de não mostrar a senha na tela. Se o mesmo
código estiver espalhado pelo programa, ele terá que ser consertado em cada um
dos lugares. Porém, se em vez disso tivermos uma função askPassword
, teremos
que corrigir apenas um único lugar (que é na implementação dessa função), e
então todo lugar que a chamar já estará consertado.
Definindo funções
O que compõe uma função
Em Java, uma função é composta por tipo de retorno, nome, parâmetros e implementação:
- Tipo de Retorno: Indica qual é o tipo de dado que é entregue como
resposta pela função. Pode ser qualquer tipo de dado utilizado na declaração
de uma variável, salvo o tipo
void
, que é responsável por indicar que a função apenas executa o seu código mas não entrega uma resposta. - Nome: Utilizado para referenciar a função posteriormente.
- Parâmetros: Indicam quais informações devem ser entregues à função para
que ela execute. Por exemplo, a função
square
, que eleva um número ao quadrado, necessita saber qual número será elevado ao quadrado. - Implementação: Define a sequência de código que será executada ao se chamar a função. "Chamar" uma função significa pedir para que sua implementação seja executada.
Pequeno exemplo
Um pequeno exemplo de uma função poderia ser o próprio square
mencionado
anteriormente:
int square(int x) {
return x * x;
}
Descrevendo os elementos dessa função:
- Tipo de Retorno:
int
. Ou seja, essa função pode ser utilizada em qualquer ponto em que se possa utilizar umint
, inclusive para enviar como parâmetro a outra função. - Nome:
square
. - Parâmetros:
int x
. Ou seja, essa função exige que seja enviado um valor do tipoint
como primeiro parâmetro, e internamente ele será chamado dex
. - Implementação: A implementação apenas significa "retorne a execução para
quem chamou a função e entregue
x * x
como resposta".
Tendo essas definições, o código dessa função pode ser executado a partir do seu nome, por exemplo:
System.out.println(square(3)); // => 9
System.out.println(square(-1)); // => 1
var x = 10;
System.out.println(square(x)); // => 100
var y = square(2);
System.out.println(y); // => 4
String err = square(9); // Erro: "int" não pode ser convertido para "String".
Funções com retorno "void"
Em alguns momentos, serão criadas funções para apenas para facilitar a
manutenção (minimizando a quantidade de responsabilidades de uma função maior)
ou não repetir o mesmo código ao longo do projeto, sem a intenção de dar uma
resposta. Nesses casos, utilizamos o tipo void
no retorno:
void doSomething() {
for (var i = 0; i < 3; i++) {
System.out.println("I'm doing something important...");
}
}
Nessa função, estamos criando uma função (chamada doSomething
) que não recebe
parâmetros e também não entrega uma resposta, apenas executa comandos.
Inclusive, até o momento vimos uma função com retorno void
: a função main
.
Sobrecarga
É possível, em várias linguagens de programação, definir duas ou mais funções de mesmo nome, porém com os tipos dos parâmetros diferentes. Assim, o compilador olha quais valores foram passados por parâmetro para uma função para descobrir qual deve ser chamada. Por exemplo:
void foo() {
System.out.println("Calling foo()...");
}
void foo(int x) {
System.out.printf("Calling foo(%d)...\n", x);
}
void foo(String s) {
System.out.printf("Calling foo(%s)...\n", s);
}
void foo(String s, int x) {
System.out.printf("Calling foo(%s, %d)...\n", s, x);
}
void main(String[] args) {
foo(); // => Calling foo()...
foo(3); // => Calling foo(3)...
foo("Hey", 2); // => Calling foo(Hey, 2)...
foo("Dude"); // => Calling foo(Dude)...
}
Esse mecanismo (de poder ter funções com mesmo nome, porém tipos dos parâmetros diferentes) se chama "Sobrecarga de função". Perceba que é essencial que as funções mantenham uma assinatura diferente.
Assinatura de uma função
Uma função é identificada pela sua assinatura. A assinatura de uma função é composta pelo nome e tipos dos parâmetros (observe que o tipo de retorno NÃO faz parte da assinatura). Assim, o compilador é capaz de diferenciar duas funções apenas analisando a assinatura delas. Por exemplo:
void foo() { ... }
void foo(int x) { ... } // OK, outra função
void foo(int otherParamName) { ... } // Erro: foo(int) já foi definida
int foo() { ... } // Erro: foo() já foi definida
int foo(int x) { ... } // Erro: foo(int) já foi definida
Definindo seu próprio tipo
TODO
Outros
Operadores para tipos primitivos
Índice
Operadores aritméticos
Operadores aritméticos servem para fazer manipulações algébricas. Efetivamente, somar, dividir, etc.
Operador | Sintaxe |
---|---|
Soma | x + y |
Subtração | x - y |
Multiplicação | x * y |
Divisão | x / y |
Resto* | x % y |
* É comum encontrar documentações descrevendo o operador %
como "Módulo". O
problema é que %
faz resto da divisão, e não módulo. A diferença entre
eles é visível nos números negativos:
x: | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
x resto 3: | -2 | -1 | 0 | -2 | -1 | 0 | 1 | 2 | 0 | 1 | 2 |
x módulo 3: | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
Perceba que, diferente do resto da divisão, o "módulo" sempre repete os valores [0..N), em que, para a tabela de cima, N seria 3.
Operadores relacionais
Operadores relacionais servem para verificar se alguma relação entre dois
elementos é verdadeira (ou seja, sempre retornarão true
ou false
).
Operador | Sintaxe |
---|---|
Igualdade | x == y |
Diferença | x != y |
Menor que | x < y |
Maior que | x > y |
Menor ou igual | x <= y |
Maior ou igual | x >= y |
Operadores unários
Operadores unários são aqueles que operam por apenas um único dado.
Operador | Sintaxe |
---|---|
Negação (para números) | -x |
Incremento | ++x |
Decremento | --x |
Complemento lógico* | !x |
*: O complemento lógico serve para retornar o inverso de um booleano. Ou seja,
se thing
era verdadeiro, !thing
é falso.
Outros operadores
Neste tutorial estão listados apenas os operadores mais usuais. Uma lista completa de operadores pode ser vista na documentação da Oracle.
Aritmética - Detalhes
Índice
Prioridade de operadores
Suponha o seguinte trecho de código:
var x = 16 / 8 * (4 / 2);
Qual será o valor de x
após executá-lo? Será 2 ou 1? Que operação é feita
primeiro?
Em Java, a ordem pela qual as operações são resolvidas é:
- Parênteses;
- Multiplicações e Divisões, na ordem em que aparecerem;
- Somas e subtrações, na ordem em que aparecerem.
- Operações relacionais (
>
,<
,==
,!=
,<=
,>=
);
No caso do trecho acima, a sequência seria:
16 / 8
(= 2, ficando2 * (4 / 2)
);(4 / 2)
(= 2, ficando2 * 2
);2 * 2
(= 4).
Ou seja, o valor final de x é 4.
Para aninhar operações, é necessário colocar mais parênteses internamente. Por exemplo:
var y = 2 + (4 * (2 - 8));
Assim, 2 - 8
será executado antes de 4 * ...
, que será executado antes de
2 + ...
.
Quanto a operadores relacionais, para serem intuitivos, eles possuem menor prioridade para serem resolvidos. Ou seja, se for escrito:
var z = x - 2 > 1;
O valor de z
será calculado na ordem:
x - 2
;> 3
.
Assim, a comparação fica intuitiva: x - 2
é maior que 1? Considerando x = 4
como anteriormente, o valor de z
será true
.
Aritmética entre inteiros
Tratando-se de operações entre dois inteiros, boa parte dos operadores é
intuitivo. Porém, qual será o resultado quando é feito 5 / 7
, por exemplo? É
0.714...
?
Para responder a isso, devemos saber que operações entre inteiros sempre
resultam em inteiros. Ou seja, não é possível que 5
(que é um inteiro)
divido por 7
(que é outro inteiro) resulte em 0.714...
, que é um número
real! O que se faz, na prática, é contar quantas vezes é necessário multiplicar
7
para se chegar em pelo menos 5
. Como 7
já é maior que 5
, então é
necessário multiplicar um total de 0
vezes. No final, isso é equivalente a
ignorar os números após a vírgula, que é o que chamamos de truncar.
Sendo assim, 5 / 7
é 0
(ignora-se o .714...
), 3 / 2
é 1
(ignora-se o
.5
), e por aí vai.
Aritmética entre tipos diferentes
Considerando o caso visto acima em
Aritmética entre inteiros, e se for necessário
que o resultado seja um número real? Para isso, basta que pelo menos um dos
operandos seja um número real (ou seja, float
ou double
).
Sendo assim, basta fazer 5.0 / 7
ou 5 / 7.0
. Isso se dá porque a
preferência é sempre converter os valores para o tipo mais abrangente. Nesse
caso, consideramos do menos abrangente ao mais abrangente:
- Byte;
- Short;
- Int;
- Long;
- Float;
- Double.
Recomendações de Leitura
Recomendações de leitura
- Como estudar programação
- Paradigmas de programação: Imperativo? Funcional? Lógico?
- Java: orientada a objetos ou não?