Java e linguagens de programação

Índice

  1. Introdução à Programação
  2. Executando um programa a partir de um código
    1. Compilação
    2. Interpretação
  3. O caso de Java
  4. 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

  1. Paradigmas de programação: Imperativo? Funcional? Lógico?
  2. 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, chamado System.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 o main nela", o que demonstra que efetivamente o main é 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 no main, 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

  1. Compreendendo erros de compilação
  2. Compreendendo erros de execução

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:

  1. Primeiro se executou main;
  2. main na linha 4 executou o comando String.charAt;
  3. String.charAt, na linha 692 chamou o comando StringLatin1.charAt`;
  4. 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ém y manterá o valor 10. Inclusive, o fato de podermos mudar o valor de x é 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 de x).

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:

TipoSignificadoTamanhoValores possíveis
byteNúmero inteiro1 byte-128 a 127
shortNúmero inteiro2 bytes-65536 a 65535
intNúmero inteiro4 bytes-2³² a 2³² - 1
longNúmero inteiro8 bytes-2⁶⁴ a 2⁶⁴ - 1
-----------------------------------------------------------
floatNúmero real4 bytes[1]
doubleNúmero real8 bytes[1]
-----------------------------------------------------------
booleanValor lógico1 byteFalso e Verdadeiro
-----------------------------------------------------------
charUm caractere2 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

  1. "Por que meu == não funciona para String?"
  2. Cópia e "move"
  3. Operadores em Java
  4. Por que não utilizar float e double para representar dinheiro?
  5. 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 ifs é alterada, ou alterar as condições, ou mesmo quebrar os if-else em vários ifs 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 para i = 0, outra para i = 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 é:

  1. Primeiramente, é executado o que está em inicio;
  2. Então, repete-se:
    1. Verifica-se se o resultado de condição;
    2. Se for false, interrompe a repetição e segue o código após o for;
    3. Se for true, executa a sequência de comandos no escopo do for;
    4. Depois de executar a sequência de comandos, executa o que estiver no passo.

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á é:

  1. O valor de i começa como 0;
  2. i é menor que 3? Ou seja: 0 é menor que 3? Sim, então executa o que estiver no escopo;
  3. Mostra o texto "Mostrando 0" (pulando linha);
  4. Executa i++ (ou seja, i agora é 0 + 1, e portanto i = 1);
  5. i é menor que 3? Ou seja: 1 é menor que 3? Sim;
  6. Mostra o texto "Mostrando 1";
  7. Executa i++ (agora i é 2);
  8. i é menor que 3? Ou seja: 2 é menor que 3? Sim;
  9. Mostra o texto "Mostrando 2";
  10. Executa i++ (agora i é 3);
  11. i é menor que 3? Ou seja: 3 é menor que 3? Não, e portanto said do for;
  12. Mostra o texto "Fim do programa.".

Visualizando como uma tabela:

InstruçãoiResultado
var i = 00-
i < 30true
System.out.printf("Mostrando %d\n", i);0Console: Mostrando 0
i++1-
i < 31true
System.out.printf("Mostrando %d\n", i);1Console: Mostrando 1
i++2-
i < 32true
System.out.printf("Mostrando %d\n", i);2Console: Mostrando 2
i++3-
i < 33false (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 arrays.

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, arrayterá 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 fors

É 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

  1. 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 um int, 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 tipo int como primeiro parâmetro, e internamente ele será chamado de x.
  • 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

  1. Operadores aritméticos
  2. Operadores relacionais
  3. Operadores unários
  4. Outros operadores

Operadores aritméticos

Operadores aritméticos servem para fazer manipulações algébricas. Efetivamente, somar, dividir, etc.

OperadorSintaxe
Somax + y
Subtraçãox - y
Multiplicaçãox * y
Divisãox / 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).

OperadorSintaxe
Igualdadex == y
Diferençax != y
Menor quex < y
Maior quex > y
Menor ou igualx <= y
Maior ou igualx >= y

Operadores unários

Operadores unários são aqueles que operam por apenas um único dado.

OperadorSintaxe
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

  1. Prioridade de operadores
  2. Aritmética entre inteiros
  3. Aritmética entre tipos diferentes

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 é:

  1. Parênteses;
  2. Multiplicações e Divisões, na ordem em que aparecerem;
  3. Somas e subtrações, na ordem em que aparecerem.
  4. Operações relacionais (>, <, ==, !=, <=, >=);

No caso do trecho acima, a sequência seria:

  1. 16 / 8 (= 2, ficando 2 * (4 / 2));
  2. (4 / 2) (= 2, ficando 2 * 2);
  3. 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:

  1. x - 2;
  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:

  1. Byte;
  2. Short;
  3. Int;
  4. Long;
  5. Float;
  6. Double.

Recomendações de Leitura

  1. Operadores em Java
  2. Por que não utilizar float e double para representar dinheiro?

Recomendações de leitura

  1. Como estudar programação
  2. Paradigmas de programação: Imperativo? Funcional? Lógico?
  3. Java: orientada a objetos ou não?