Ir para o conteúdo

A Linguagem Java

A presente secção irá introduzir a sintaxe da linguagem Java e as características técnicas associadas. A exposição será longa e com muito texto pelo que se aconselha que seja mantida como referência para ser consultada rapidamente durante a aprendizagem.

Características da Linguagem

  • Linguagem orientada a objectos. Java segue o paradigma de programação orientada a objectos1.
  • Linguagem independente da plataforma e da arquitectura. Um programa criado em Java é compilado para bytecode, este código é universal e entendido por todas as JVMs. Desta forma o código não fica limitado à plataforma onde foi compilado, e pode ser executado sem alterações em qualquer plataforma onde exista uma JVM. No entanto o bytecode criado pode ser compilado para a plataforma, tornando-se assim código nativo. Isto é conseguido recorrendo ao compilador Just-in-Time, JIT, que analisa o código e o torna nativo2.
  • Distribuída. Possui primitivas de comunicação de alto nível como carregamento de recursos via URL, Remote Method Invocation, RMI, e CORBA IIOP. Possui primitivas de comunicação de baixo nível através de Sockets. Permite também carregar código de forma transparente a partir de qualquer lugar3.
  • Robusta. Não possui ponteiros. Em Java os ponteiros são substituídos por referências e toda a aritmética de ponteiros desaparece. Existe a verificação de acesso a tabelas e operações de cast em runtime.
  • Segura. Acrescentando ao facto de que o código é verificado durante a execução, de que código desconhecido é executado isoladamente, de que não é possível aceder a índices fora de tabelas e de que não existem ponteiros com os seus acessos perigosos à memória, o Java permite ainda que o código seja autenticado através de assinaturas digitais e certificados. Bem como a criação de gestores de segurança que limitam a execução de código a tarefas inócuas.
  • Suporta multi-threading. As bibliotecas são thread safe, e o suporte para threads, além de fazer parte da biblioteca padrão, através da classe Thread e interface Runnable, está embutido na linguagem através de métodos sincronizados e semáforos associados a cada classe.

Tipos de dados

Java possui uma mistura de tipos de dados primitivos e objectos4, todos os tipos de dados têm tamanho fixo ou seja, quer em sistemas de 64bit quer em sistemas de 32bit, em Java, os tipos de dados mantém sempre o mesmo tamanho, ao contrário de outras linguagem compiladas nativamente.

A precisão de um tipo de dados é garantida em qualquer plataforma, e mantém-se sempre igual.

O quadro seguinte apresenta uma descrição dos tipos de dados primitivos existentes em Java, bem como dos seus intervalos, valor máximo, mínimo e classe de encapsulamento que permite passar um tipo primitivo para um objecto.

Tipo Primitivo Tamanho Valor mínimo Valor máximo Valores possíveis Classe de adaptação5
boolean 1 bit - - true, false Boolean
char 16 bit Unicode 0 Unicode 2^16 - 1 'a' b' 'c', etc. Character
byte 8 bit -128 128 Qualquer valor dentro da gama. Formato pode ser decimal(10, 12, -1), octal(010, 017) ou hexadecimal(0x2e, 0xff) Byte
short 16 bit -32.762 32.762 Qualquer valor dentro da gama. Formato pode ser decimal(10, 12, -1), octal(010, 017) ou hexadecimal(0x2e, 0xff) Short
int 32 bit -2.147.483.648 2.147.483.648 Qualquer valor dentro da gama. Formato pode ser decimal(10, 12, -1), octal(010, 017) ou hexadecimal(0x2e, 0xff) Integer
long 64 bit -9.223.372.036.854.775.808 9.223.372.036.854.775.808 Qualquer valor dentro da gama. Suporta os formatos decimal, octal e hexadecimalacrescentando um L ou l ao final. Ex.: 0x7ffffffffffffffL Long
float 32 bit +/-3,4e-38 +/-3,4e+38 Qualquer valor dentro da gama. Formato idêntico ao do long mas com a letra F ou f. Float
double 64 bit +/-1.7e-308 +/-1.7e+308 Qualquer valor dentro da gama. Formato decimal ou científico(2.2e-2 ou 2.2E-2) Double

Todos os tipos de dados possuem sinal e como tal, não existe a necessidade nem o suporte para o modificador unsigned, que noutras linguagens tornava uma variável com sinal numa sem sinal.

Variáveis

Java é uma linguagem strongly-typed, isto significa que todas as variáveis precisam ser declaradas antes de serem usadas. Embora possam ser declaradas em qualquer parte do ficheiro de código, é comum agrupar as declarações no topo da classe ou no fim da classe, não deixando assim que existam variáveis "perdidas" no meio outros blocos de código.

Declarar uma variável não é mais que indicar a sua visibilidade, se aplicável, o seu tipo de dados, o seu nome e, opcionalmente, uma iniciação. Exemplos:

private int idade = 55; //declaração com indicação de visibilidade e valor inicial

ou

double taxa; //apenas declaração, a visibilidade será assumida
                         //através do contexto em que a variável é declarada

As declarações acima indicadas são para os tipos primitivos já apresentados, a declaração de objectos é similar, contendo apenas o uso da palavra reservada new quando se pretende indicar o valor inicial. Exemplo:

private Pessoa p = new Pessoa();

Sem valor inicial, a declaração é em tudo igual a uma declaração dos tipos primitivos:

private Pessoa p;

Existem outros modificadores que, como o modificador private apresentado, podem ser aplicados durante a declaração de variáveis, no entanto os mesmos, bem como a explicação dos já apresentados, é deixada para mais tarde. Neste momento importa apenas reter como são feitas as declarações de variáveis e a ordem pela qual os termos aparecem.

Além de indicar à plataforma que uma variável existe e está pronta a receber dados, a declaração permite também definir o tipo de operações que podem ser aplicadas sobre as variáveis. Tipos primitivos não permitirão o mesmo tipo de operações que objectos, e vice-versa.

Java possui boxing e unboxing automático de tipos primitivos desde a sua versão 5. Embora não da mesma forma que em C#, este tipo de operação permite aliviar o programador de usar explicitamente algumas das classes de encapsulamento de dados primitivos, sendo estas usadas pela JVM quando necessário.

O processo de boxing e unboxing faz com que, em locais onde seja esperado um objecto numérico, caso o programador passe um valor numérico primitivo, a JVM coloca a variável primitiva dentro de um objecto automaticamente. Veremos casos destes no código exemplo.

Operadores

Os operadores de Java são comuns a muitas outras linguagens, efectivamente são em grande parte similares aos operadores presentes em C e em C++. Mas ao contrário de C++, Java não permite o overloading de operadores, isto é, em Java não é possível alterar o significado de um operador de soma ou subtracção, que está definido apenas para valores numéricos e para tipos primitivos.

Em alguns casos é natural que não haja necessidade de se poder somar dois objectos, afinal não faz sentido somar um objecto Carro com um objecto Poste, embora na vida real existam muito condutores que o façam. Mas para outros tipos de objectos, por exemplo um objecto Ponto2D, faz sentido poder somar duas instâncias da classe Ponto2D, afinal, a soma de dois pontos num sistema de duas dimensões é um processo perfeitamente normal.

Como foi referido, Java não permite a redefinição dos operadores mas possui algumas redefinições, que embora não cubram todos os casos, cobrem os mais comuns, é assim possível somar dois objectos de um tipo de dados que encapsule um tipo primitivo, por exemplo, somar dois objectos Double.

Tabela de precedência de operadores, os operadores mais perto do topo são os que possuem maior precedência, e dessa forma são avaliados primeiro.

Operadores Precedência
postfix expr++, expr--
unitários ++expr, --expr, +expr, -expr, ~, !
multiplicação *, /, %
adição +, -
shift <<, >>, >>>
relacionais <, >, <=, >=, instanceof
igualdade ==, !=
E bit-a-bit &
E exclusivo bit-a-bit ^
OU exclusivo bit-a-bit |
E lógico &&
OU lógico ||
ternário ?, :
atribuição =, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=

Atribuição

O operador de atribuição é, possivelmente, o operador mais simples. É este operador que permite atribuir valores a variáveis, pegando no valor da direita e atribuindo-o ao operando da esquerda.

int altura = 10;
long preco = 5.5;

altura = 15 + altura;

Operadores Aritméticos

Os operadores aritméticos são equivalentes aos usados na matemática e, dessa forma, não serão estranho. O único que poderá não ser conhecido é o operador de resto, que devolve o resto de uma divisão inteira.

  • Operador de adição, também redefinido para concatenação de strings: +
  • Operador de subtracção: -
  • Operador de multiplicação: *****
  • Operador de divisão: /
  • Operador de resto: %

Talvez seja necessário introduzir o conceito de divisão inteira: uma divisão entre dois números inteiro resultará sempre num resultado inteiro. Isto é uma regra que, pelo menos em Java, não é quebrada. Assim ao efectuarmos a conta 5/2 o resultado é 2 e não 2,5. Neste ponto entra o operador de resto, ao efectuarmos a operação 5%2 o resultado será 1, que é o resto de uma divisão inteira entre 5 e 2. Podemos fazer como na primária, 5 a dividir por 2 dá 2, resto 1.

Operadores Unitários

Os operadores unitários resultam da aplicação especial dos operadores aritméticos que requer apenas um operando, estas operações permitem efectuar operações de incrementação, decrementação ou alterações de sinal. O único operador que não faz parte dos operadores aritméticos e que é um operador unitário, é o operador de negação.

  • Definição de números positivos: +, em Java todos os números são positivos por omissão
  • Definição de números negativos: -, permite colocar uma expressão como negativa
  • Operação de incremento: ++
  • Operação de decremento: --
  • Complemento lógico: !, inverte o valor lógico de um operando

Operadores Condicionais, de Igualdade e Relação

  • Igualdade: ==
  • Diferença: !=
  • Maior que: >
  • Maior ou igual: >=
  • Menor que: <
  • Menor ou igual a: <=
  • E condicional: &&
  • OU condicional: ||
  • Comparador de tipos: instanceof, o operador de comparação de tipos permite verificar se um objecto corresponde a um tipo (classe), ex:
String s1 = "s1";
if(s1 instanceof String) {
    System.out.println("Objecto é uma String");
} else {
    System.out.println("Objecto não é uma String");
}

Operadores Bitwise e Bit Shift

Para terminar os operadores são apresentados operadores que alteram bits. Estes operadores actuam sobre a representação binária, interna, dos valores das variáveis.

  • Complemento unitário bit-a-bit: ~, inverte um padrão de bits, tornando todos os zeros em uns e todos os uns em zeros, ex: 111000110 passará a 00111001
  • Shift à esquerda com sinal: <<, move para a esquerda um conjunto de bit
  • Shift à direita com sinal: >> , move um conjunto de bits para a direita

Os operadores de shift com sinal movem um conjunto de bits cujo padrão é definido pelo operando da esquerda e o número de posições é definido pelo operando da direita.

  • Shif para a direita sem sinal: >>>, este operador introduz um zero na posição mais à esquerda.
  • E bit-a-bit: &
  • OU exclusivo bit-a-bit: ^
  • OU inclusivo bit-a-bit: |

Expressões, Blocos e Condições

Expressões

Expressões são linhas de código composta por variáveis, operadores e invocação de métodos, agrupados de acordo com a sintaxe da linguagem que são avaliadas como um valor único. Exemplo:

int idade = 5;

vector[5] = 1;

System.out.println("Valor em 5: " + vector[5]);

Blocos de Código

Blocos são todos os pedaços de código delimitados por chavetas, {}. Não são mais que uma forma de agrupar determinadas expressões ou condições que devem ser executadas em conjunto.

Através da delimitação de blocos conseguimos definir o código a executar depois de uma condição ou durante um processo especial de iniciação. Permitem-nos também definir o início e o fim de uma classe ou um método.

Estruturas de Controlo de Fluxo de Programa

Designamos estruturas de controlo de fluxo de programa, as estruturas que permitem determinar que linhas de código executar de um modo condicional. São estruturas que nos permitem dizer em que condições determina linha ou conjunto de linhas pode ser executado e são estas estruturas que nos permitem criar programas mais complexos. No entanto, as estruturas em si são bastante simples.

Em Java todas as condições são avaliadas como valores verdadeiro/falso6, assim, todas as estruturas de controlo de fluxo usam os valores verdadeiro/falso como valores de controlo7.

if-then e if-then-else

Esta é a estrutura mais simples que se pode ter num programa8. Esta estrutura permite decidir que código executar mediante o resultado de uma condição. Traduzir-se-á para a linguagem corrente como: Se <condição> Então faz <qualquer coisa> ou Se <condição> Então faz <qualquer coisa> Senão faz <outra coisa qualquer>.

if(condição) {
    //código a executar se a condição for verdadeira
}
if(condição) {
    //código a executar se a condição for verdadeira
} else {
    //código a executar se a condição for falsa.
}

Na sua primeira versão, o código dentro das chavetas9 é executado quando a condição é verdadeira. Se a condição for falsa o programa não executa o código dentro das chavetas e "salta" para a chaveta de final, retomando a execução nesse ponto10.

Na segunda versão, como adicionamos um bloco para contemplar o facto de a condição poder ser falsa, o programa avalia a condição, se for verdadeira executa o código dentro do bloco do if, se for falsa executa o código dentro do bloco do else. Depois, a execução prossegue normalmente a partir da chaveta final do bloco do else.

Esta estrutura pode ser encadeada de forma a permitir código mais complexo. Podemos colocar novas secções if em cada secção else, por exemplo:

if(condicao1) {
    //código a executar se a condição 1 for verdadeira
} else if(condicao2) {
    //código a executar se a condição 2 for verdadeira
} else if(condicao3) {
    //código a executar se a condição 3 for verdadeira
} else if(condicao4) {
    //código a executar se a condição 4 for verdadeira
} else if(condicao5) {
    //código a executar se a condição 5 for verdadeira
} else {
    //código a executar se todas as condições anteriores forem falsas
}

Embora simples e versátil, a estrutura if-then-else obriga à escrita de estruturas complexas e longas.

switch

A estrutura switch actua como um selector11, escolhendo blocos de código mediante um valor que é passado. Exemplo:

switch(valor) {
    case 1:
         //código a executar se o valor é igual a 1
         break;
    case 2:
         //código a executar se o valor é igual a 2
    case 3:
         //código a executar se o valor é igual a 3
         break;
    default:
         //código a executar se o valor não corresponde a nenhuma das opções mencionadas acima.
}

Um switch aceita valores primitivos do tipo byte, short, char, int, tipos enumerados12 e classes encapsuladoras como Character, Byte, Short, e Integer13

No nosso exemplo, a estrutura irá seleccionar o bloco de código consoante o valor passado. Assim, se valor for 1 o primeiro bloco de código é executado, ao encontrar a instrução break14, o código termina e retoma a execução na chaveta final do bloco do switch. Caso o valor seja 2, o bloco de código correspondente vai ser executado, como não existe uma instrução break, o código continuará a executar para o bloco seguinte15.

O bloco default será executado se o valor passado for diferente de todos os valores existentes no switch, como é o último bloco não é necessário ter uma instrução break

while e do-while

Tal como a estrutura if-then a estrutura while executa um bloco de código quando a condição é verdadeira, e ao contrário da estrutura if-then-else não é bloco para condição falsa. Mas enquanto as duas estruturas que vimos inicialmente executam o código uma vez apenas, a estrutura while executa o bloco de código enquanto a condição se mantiver verdadeira. Em linguagem comum dir-se-ia, Enquanto <condição> Faz <qualquer coisa>

while(condição) {
    //código a executar enquanto a condição é verdadeira
}

A estrutura while é útil quando pretendemos executar um determinado bloco um número de vezes não especificado, isto é, sabemos que queremos repetir o código, mas não temos a certeza de quantas vezes o vamos repetir.

A par com a estrutura while temos a estrutura do-while, em tudo similar mas com a particularidade de testar a condição apenas no fim do bloco. O resultado é que o bloco de código é executado sempre, pelo menos, uma vez. Se a condição for falsa o bloco não volta a executar, se for verdadeira é executado novamente16.

do {
    //código a executar
} while (condição)

for e for each

O ciclo, ou estrutura de controlo for permite iterar facilmente um conjunto de valores, tipicamente presentes num vector17. O uso típico desta estrutura é quando pretendemos executar o código um número de vezes conhecido18.

for(iniciação de variáveis; condição; incremento de variáveis) {
    //código a executar
}

Nesta estrutura, possuímos 3 secções, a primeira onde podemos fazer alguma iniciação de variáveis, a segundo onde colocamos a condição de controlo, e a terceira onde executamos incrementos. Nenhum das 3 secções é obrigatória e podemos omitir um, duas, ou todas as secções19. Exemplo:

for(int i = 0; i < 6; i++) {
    System.out.println(i);//imprimir o valor da variável i
}

int j;
for(j = 5; j > 0; j--) {//iniciamos em 5 e, em vez de incrementar, decrementamos o valor de j
    System.out.println(j);//imprimir o valor da variável j
}

for(; i < 6; i++) {//omitimos o código de iniciação
    System.out.println(i);//imprimir o valor da variável i
}

for(int i = 0, j = 5; i < 5 && j > 0; ++i, --j) {//uso de mais que uma variável, e condição mais complexa
    System.out.println("I: " + i + "tJ: " + j);//imprimir o valor das duas variáveis
}

int i = 0;
for(;; i++) {//omitimos a condição de paragem, ciclo infinito.
    System.out.println(i);//imprimir o valor da variável i
}

No Java 5, foi adicionada uma nova estrutura, que em outras linguagem se denomina por foreach, mas que em Java reutiliza uma estrutura existente, o for em vez de adicionar uma nova palavra reservada.

Assim, quando o que estamos a percorrer são objectos iteráveis20 podemos usar o for para facilitar o acesso aos seus elementos.

Antes do Java 5, para percorrer uma lista seria necessário fazer21:

Iterator it = lista.iterator();// obter o iterador da lista que pretendemos percorrer

while(it.hasNext()) {
    Object o = it.next();//obter o elemento.
    System.out.println(o);//imprimir o valor de o
}

Com o Java 5 o código anterior pode ser alterado para:

for(Object o : lista) {//Estamos a usar uma lista com elementos do tipo Object
    System.out.println(o);//imprimir o valor de o
}

Um foreach é mais útil quando usamos também genéricos, isto porque o método next() da classe Iterator devolve um objecto do tipo Object, mesmo que na nossa lista tenhamos colocado objectos do tipo String, o que nos obriga a fazer operações de cast22 sempre que depois queiramos usar correctamente o nosso objecto. Com o foreach somos obrigados a declarar o tipo da variável, se o declararmos directamente para o tipo de objectos que estão dentro da lista, evitamos estar a fazer um cast23

break, continue e return

Estas são três instruções usadas para alterar a execução de ciclos quando a condição de paragem não é suficiente ou não se aplica, permitindo terminar a estrutura, ou saltar ciclos, evitando assim a execução de algum código mas não terminando o ciclo.

Quando aplicadas dentro de ciclos aninhados24, estas instruções alteram o funcionamento do ciclo interior se forem usadas na sua forma mais simples, ou alteram o ciclo exterior quando são usadas em conjunto com etiquetas25

O uso de labels não goza da melhor das reputações, em outras linguagens as labels funcionam de forma diferente, muitas vezes associadas à instrução goto, e permitem saltos incondicionais para qualquer local no código. Por muito útil que um salto incondicional possa parecer, a sua utilização é extremamente perigosa, dando origem a código confuso, difícil de ler e de manter e onde e detecção de erros é muito difícil de fazer. Em Java a instrução goto não existe.

Labels em Java, são assim mais limitadas mas mais seguras, permitem à instrução break ou continue alterar o ciclo externo, mas estão sempre presas ao ciclo que iniciaram e não permitem a mesma utilização que labels e a instrução goto permitem noutras linguagens.

Já a vimos a instrução break na estrutura swith mas ela pode ser usada em estruturas for, tanto na sua forma tradicional como quando usada como foreach, e em estruturas while e do-while.

Para terminar, encontramos a instrução return. Esta instrução não termina apenas ciclos, devolve o controlo de execução, ou fluxo de programa, para o código que invocou o método que está a ser executado. Assim, ao encontrar uma instrução return num ciclo, esse ciclo é terminado imediatamente, o próprio método onde o ciclo está a ser executado termina e o controlo é devolvido a quem chamou o método.

A instrução return permite devolver um valor, caso seja necessário, bastando para isso colocar o valor depois da instrução:

return 5;//devolver o valor 5
return "Terminei";//devolver a string
return new Object(); // devolver um objecto criado neste ponto.

Método Main

Nesta secção é apresentado um método especial da linguagem Java, no entanto algumas das suas características estão associadas a tópicos que apenas são tratados em secções posteriores.

public static void main(String[] args] {
    ...
}

Esta é a assinatura de um método main em Java, mas porque é que este método existe e porque é que a sua assinatura é constante?

Todo o programa precisa de um ponto de entrada, de um local onde possa ter início a execução e processamento. Várias linguagens conseguem isso de formas distintas, no caso do Java, o ponto de entrada de um programa é o método main. Este método pode existir em qualquer classe do nosso programa, podem inclusive existir vários métodos main. Caso isto aconteça, apenas um será invocado de cada vez que a aplicação é iniciada, sendo o método escolhido pelo utilizador, falaremos deste ponto mais abaixo.

O método é public porque a JVM, fora do nosso package e que não entra na nossa hierarquia de classes precisa de poder aceder ao método, se o método fosse private, apenas podia ser acedido pela classe que o contém, se fosse protected apenas pela classe e suas subclasses, se possuísse visibilidade de package, apenas podia ser acedido por outras classes dentro do mesmo package. Desta forma é imprescindível que o método seja público.

Possui como tipo de retorno void porque o método não retorna qualquer valor. Em outras linguagens, como C/C++, é comum o método principal retornar um valor inteiro que representa o estado com que o programa terminou, em Java isso não acontece, e quaisquer erros existentes deverão ser tratados pelos mecanismos apropriados, por exemplo: o sistema de Tratamento de Excepções

Recebe um vector de String que contém os parâmetros passados pela linha de comandos, ou através de qualquer sistema gráfico que permita passar parâmetros a aplicações. Neste caso, apenas os parâmetros da aplicação são passados, o nome da aplicação não é passado como primeiro argumento, novamente, ao contrário de outras linguagens onde o nome da aplicação é o contado como um argumento.

E finalmente o ponto mais complicado, ou pelo menos o que costuma causar mais dúvidas: porque é que é um método estático?. A resposta é simples, se o método é marcado como static significa que é um método de classe, logo não é necessário obter uma instância do objecto para o poder invocar, e este é o ponto fundamental. Se a JVM necessitasse de instanciar um objecto para invocar o método main contido na sua classe existiriam algumas restrições a esse objecto. O que acontecia se a classe possuísse vários construtores? Qual seria o construtor invocado? Que parâmetros seriam passados aos construtores? Que consequências teria a complexidade dos construtores no arranque da aplicação? Enfim, um conjunto de dúvidas que surgiriam e que para serem resolvidas obrigariam a que a classe que contivesse o método main fosse quase uma classe especial, e obrigando o programador a restringir em grande parte o que poderia programar nessa classe.

Ao ser um método estático todos os problemas acima são eliminados, o programador é livre de criar qualquer classe como entender e, se pretender adicionar um ponto de entrada na aplicação, apenas precisa de adicionar um método estático com a assinatura acima definida.

Não existe nada mais associado ao método main que o facto de ter de ser escrito sempre da forma indicada e servir para iniciar a nossa aplicação.

Foi anteriormente mencionado que é possível ter na mesma aplicação mais que um método main. Esse não será o caso mais comum, uma aplicação tem apenas um ponto de entrada, ao ter dois, podemos assumir que são duas aplicações distintas, mas por vezes é útil que assim seja. Por exemplo, podemos construir uma aplicação com uma interface gráfica e ao mesmo tempo oferecer uma opção de linha de comandos. Se possuirmos dois métodos main é mais simples oferecer-mos ao utilizador este tipo de escolha. Podemos também pretender criar classes e métodos de teste, que ao serem colocados numa segunda linha de execução, podem permitir a sua fácil gestão e não perturbam o código normal da aplicação.

Os motivos para estas opções são vários, o que é importante é que se perceba que é possível, e que não acarreta qualquer problema ou invalida a nossa aplicação, a existência de mais do que um método main em classes diferentes.

Determinar qual o método a executar já dependerá da forma como a aplicação é iniciada. Se distribuída num JAR deverá ser definido um método a ser invocado por omissão através do Manifest-file, se a aplicação não for compactada num ficheiro JAR, o utilizador terá de indicar a classe que contém o método main que deseja iniciar.

Palavras Reservadas

Quadro com as palavras reservadas e seu significado

abstract Define um método ou classe abstracta continue Permite continuar para a próxima iteração de um ciclo sem executar as instruções que ainda faltam for Ciclo de controlo, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa
new Permite instanciar um objecto, reservando a memória e efectuando os passos necessários para a correcta utilização do objecto. switch Estrutura de controlo, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa assert Permite o uso de testes. Deve ser usada apenas para situações de depuração de código e testes.
default Identifica o bloco de código a executar num switch quando nenhuma das opções case é escolhida, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa goto Palavra reservada mas não utilizada na linguagem package Permite definir a organização das classes, interfaces ou enumerações
synchronized Palavra reservada, usada no mecanismo de threads, e que permite sincronizar um bloco de código, impedindo o acesso a esse código por mais que uma thread de cada vez boolean Tipo de dados primitivo que permite dois valores apenas true e false do Componente do ciclo do..while, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa
if Palavra reservada usada em condições. Ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa private Modificador de visibilidade mais restritivo de todos, coloca a visibilidade do elemento afectado como privada this Referência especial para o objecto em que estamos.
break Permite alterar o fluxo de programa, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa double Tipo de dados primitivo, permite valores com precisão dupla implements Palavra reservada que identifica as interfaces implementadas pela classe afectada
protected Modificador de acesso, o recurso afectado, que pode ser uma classe, um método ou uma variável, passa a ser visível apenas pelas subclasses e classes do mesmo package throw Palavra reservada que permite levantar uma excepção. Ver 11_excepcoes byte Tipo de dados primitivo, aceita valores na ordem de um byte
else Palavra reservada parte do ciclo if-then-else, ver03_linguagem#Estruturas de Controlo de Fluxo de Programa import Palavra reservada que permite importar classes de outros packages de modo a serem usados na classe importadora throws Permite indicar as excepções que um método lança e que devem ser apanhadas por quem os invoca. Ver 11_excepcoes
case Componente da estrutura switch, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa enum Permite criar uma enumeração. instanceof Permite inferir o tipo de objecto.
return Devolve o controlo do código para o método que o invocou, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa transient Marca um atributo de um objecto como não sendo persistido por mecanismos de serialização catch Componente da estrutura de tratamento de excepções
extends Parte do mecanismo de herança, indica a classe da qual uma outra classe descende int Tipo de dados primitivo que representa inteiros short Tipo de dados primitivo, representa inteiros de 16 bit
try Parte do mecanismo de excepções char Tipo de dados primitivo que representa um caractere UTF-8 final Modificador que impede a alteração de um atributo, criando assim uma constante, a redefinição de um método ou a herança de uma classe
interface Permite especificar uma interface static Indica um método, atributo ou bloco estático void Indica que um método não possui valor de retorno
class Palavra reservada que identifica a declaração de uma classe finally Componente do bloco try..cath que permite a execução, em qualquer circunstância, de um bloco de código long Tipo de dados primitivo, valor de virgula flutuante de precisão dupla
strictfp Força o uso das regras definidas pelo IEEE754, permitindo garantir o valor de vírgula flutuante que se está a manipular volatile Permite indicar que uma variável vai ser acedida e modificada por várias threads. const Palavra reservada sem uso actual
float Tipo de dados primitivo, valores de vírgula flutuante de precisão simples native Indica que um método é implementado de forma nativa, em C ou C++ super Permite aceder aos atributos e métodos da super-classe que possuem visibilidade suficiente. Todos menos os privados.
while Ciclo de decisão, ver 03_linguagem#Estruturas de Controlo de Fluxo de Programa

Comentários

Como qualquer linguagem, também Java possui comentários que permitem explicar métodos, atributos ou simples pedaços de código. Estes comentários podem ser feitos de várias formas, podendo existir em formato de uma linha, várias linhas, comentários especiais para documentação, etc.

De seguida são apresentados exemplos de 3 tipos de comentários.

/*Isto é um 

comentário que se estende por  

várias linhas*/

//Isto é um comentário de linha única

/** Isto é um comentário para ser usado na documentação ou JavaDoc
 *  Alguns IDEs geram documentação automaticamente a partir deste tipo de comentários deste tipo. Existem várias
 *  tags que podem ser usadas para formatar o aspecto final da documentação mas isso é outra história.
 */

Documentar o código é uma das tarefas mais importantes quando se programa, embora seja complicado para o programador iniciante perceber porque motivo tem de comentar o código que ele próprio faz, e que supõe ninguém irá ver, a verdade é que não é possível prever quem realmente irá ler o nosso código e, mesmo que consideremos remota a hipótese de alguém que não nós, venha a ler o código, facilmente esquecemos o motivo que nos levou a escrever determinada linha de código se olharmos para ela daqui a 6 meses, muitas vezes menos.

Porque os comentários são tão importantes e porque documentar o código é um ponto essencial da criação de um software, existem várias ferramentas que transformam os comentários existentes nos ficheiros de código em documentos mais simples de ler e de seguir. Uma dessas ferramentas é o Javadoc, que é incluído como uma das ferramentas base do JDK e para o qual existem várias tags especiais, destinadas à formatação e apresentação da documentação gerada.

Tabela com as tags Javadoc e o seus significado. Contém apenas uma breve descrição e refere-se à versão 1.5 da ferramenta Javadoc. No fim da secção podem ser consultados dois links com mais informação.

Tag Descrição
@author Representa o nome do autor da classe/método.
@deprecated Indica um método, classe ou atributo que não deverá continuar a ser usado, mesmo que continue a funcionar. Recomenda-se que seja colocado também um tag {@link} com indique o elemento que vem substituir o removido.A partir do Java 5 é possível usar ao anotação @Deprecated para maior controlo.

^ {@code texto} equivalente a %%

%%{@literal}%%

%% | Mostra um bloco de texto usando o tipo de letra para código e sem interpretar alguma tag ou código HTML. Ex: {@code A<B>C} resulta em A<B>C |

{@docRoot} Representa o caminho relativo para a pasta raiz da documentação gerada. Útil quando se pretende incluir um ficheiro como uma imagem, logótipo, etc.
@exception nome-da-classe descrição Esta tag é sinónimo da tag @throws.
{@inheritDoc} Copia a documentação da classe mais perto, a nível hierárquico, para o local onde esta tag aparece.
{@link package.classe#membro nome} Insere um link no texto com o nome indicado e que aponta para a documentação do membro especificado.
{@linkplain package.class#membro nome} Similar a {@link}, excepto que o texto do link é apresentado em texto simples
{@literal text} Mostra texto sem interpretar eventual conteúdo HTML. Ver também {@code}
@param nome-do-parâmetro descrição Permite descrever um parâmetro de entrada de um método.
@return descrição Permite descrever o valor de retorno de um método.
@see referência Adiciona uma secção "See Also" que contém o link ou texto, consoante o tipo de referência, que representa um elemento a consultar. Ver também {@link}.
@serial descrição-do-campo | include | exclude Usado na documentação de um campo serializado por omissão.
@serialField nome-do-campo tipo-do-campo descrição-do-campo Documenta um objecto do tipo ObjectStreamField, parte do grupo de campos serialPersistentFields de uma classe serializável.
@serialData descrição Documenta os tipos de dados que são usados pela serialização da classe onde a tag é usada.
@since texto Adiciona uma secção "Since" que permite indicar desde que versão determinado método ou atributo está disponível.
@throws nome-da-classe descrição Adiciona uma secção "Throws" com o nome da classe indicado.
{@value package.class#field} Permite mostrar o valor de um campo, quando usado sem argumento junto a um atributo estático, mostra o valor desse atributo
@version Adiciona um campo com a versão especificada no texto que segue a tag

Para mais informações sobre a escrita de comentários para documentação e uso de tags Javadoc consulte os documentos:

Primeira Classe, Uso Completo da Sintaxe Apresentada

As seguintes 5 classes pretendem servir de exemplo e mostrar vários aspectos da sintaxe da linguagem, o leitor não precisa ficar preocupado se não perceber alguns dos pormenores da sintaxe ou do código apresentado uma vez que muito do que aparece no exemplo será introduzido nas secções seguintes, no entanto não seria possível terminar a apresentação técnica da linguagem sem um exemplo.

As classes representam um exemplo de como criar um sistema muito simples para gerir pessoas. É possível criar pessoas e definir os seus atributos base.

package org.pap.wiki.javatutorial;

import java.util.GregorianCalendar;
//Importe de uma enumeração dentro de uma classe:
import java.util.TimeZone;
import org.pap.wiki.javatutorial.Person.Sex;

/**
 * Classe de teste.
 * Pode ser usada para correr a aplicação e ver alguns resultados.
 */
public class RunMe {

    public static void main(String[] args) {
        Person antonio = new Person("António");
        GregorianCalendar gMaria = new GregorianCalendar(TimeZone.getTimeZone("UTC"));      //invocação de um método estático
        gMaria.set(1980, 5, 6);     //Ano: 1980, Mês: 5 (Junho dado que começam em zero), Dia: 6
        Person maria = new Person("Maria", "Luz", gMaria, Sex.FEMALE);

        System.out.println("== Exemplo de sintaxe Java ==");
        System.out.println(antonio);
        System.out.println("A pessoa mencionada " + (antonio.producesMilk() ? "" : "não ") + "amamenta");
        System.out.println("--n");
        System.out.println("Maria, idade " + maria.getAge() + "anos.nSexo: " +
                (maria.getSex() == Sex.FEMALE ? "Feminino" : "Masculino"));
    }
}
package org.pap.wiki.javatutorial;

/**
 * Interface criada apenas para mostra a possibilidade de se usar mais que uma 
 * interface na mesma classe e para mostrar a sintaxe simples que permite criar 
 * uma interaface.
 */
public interface Driver {

    /**
     * Este é um método definido numa interface. Todos os métodos de uma 
     * interface precisam ser públicos ou possuir visibilidade de package, 
     * métodos privados ou protegidos não podem ser definidos em interfaces.
     * 
     * Nem podia ser de outra forma, se uma interface obriga à implementação do
     * método não podia querer que o método fosse privado, de outra forma como é 
     * que poderia ser implementado? Se fosse protegido apenas as subclasses 
     * podiam implementar o método, mas se estamos numa interface, a subclasse 
     * de uma interface é também uma interface.
     * 
     * @param km parâmetro sem sentido que serve apenas para confirmar que um 
     * método de uma interface é similar a um método de uma classe, seja 
     * abstracta ou não, não possuindo diferenças na sua sintaxe de declaração.
     */
    public void drive(int km);
}
package org.pap.wiki.javatutorial;

/**
 * Classe que representa um mamífero.
 * Esta classe pretende apenas mostrar a sintaxe de uma classe abstracta e 
 * permitir mostrar a sintaxe usada para criar subclasses.
 */
public abstract class Mamal {

    /**
     * Métodos abstractos.
     * Estes métodos terão de ser implementado numa das subclasses, não têm 
     * necessariamente de ser implementado no primeiro nível ou em qualquer 
     * outro nível específico, mas algures na hierarquia descendente desta 
     * classe terão de ser implementados.
     * 
     * As diferenças de um método abstracto para outros métodos são o uso da 
     * palavra reservada <em>abstract</em> e o facto de não existir o corpo do 
     * método com o código correspondente.
     * 
     * Este comentário mostra também o uso de <i>tags</i> de formatação, 
     * <b>tags</b> emprestadas do HTML, e que permitem dar alguma formatação aos 
     * comentários. Esta formatação aparece nas ajudas de alguns IDEs e através 
     * de ferramentas de geração de documentação, como o <em>Javadoc</em>.
     */
    public abstract void drinkMilk();
    public abstract boolean producesMilk();
}
package org.pap.wiki.javatutorial;

/**
 * Classe que representa uma morada.
 * Contém os campos e métodos necessários para criar e manter uma morada 
 * correcta.
 */
public class Address {

    private String street;
    private String city;
    private String zipCode;
    private String country;

    public Address(String street, String city, String zipCode, String country) {
        setStreet(street);
        setCity(city);
        setZipCode(zipCode);
        setCountry(country);
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        if (city == null || city.isEmpty()) {
            throw new IllegalArgumentException("Uma morada necessita de uma cidade!");
        }
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        if (street == null || street.isEmpty()) {
            throw new IllegalArgumentException("Uma morada necessita da rua!");
        }
        this.street = street;
    }

    public String getZipCode() {
        return zipCode;
    }

    public void setZipCode(String zipCode) {
        if (zipCode == null || zipCode.isEmpty()) {
            throw new IllegalArgumentException("Uma morada necessita de um código" +
                    "postarl!");
        }
        this.zipCode = zipCode;
    }

    @Override
    public boolean equals(Object obj) {
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 41 * hash + (this.street != null ? this.street.hashCode() : 0);
        hash = 41 * hash + (this.city != null ? this.city.hashCode() : 0);
        hash = 41 * hash + (this.zipCode != null ? this.zipCode.hashCode() : 0);
        hash = 41 * hash + (this.country != null ? this.country.hashCode() : 0);
        return hash;
    }

    @Override
    public String toString() {
        return street + "n" + zipCode + "n" + city + ((country != null && !country.isEmpty())
                ? " - " + country : "");
    }
}
package org.pap.wiki.javatutorial;

import java.io.Serializable;
import java.util.GregorianCalendar;

/**
 * Classe que representa uma pessoa.
 * 
 */
public class Person extends Mamal implements Serializable, Driver {

    private static final long uid = 0l;
    private String name;
    private String surname;
    private GregorianCalendar birthDate;
    private Sex sex;
    private Address address;

    public Person(String name) {
        this(name, "", null, Sex.MALE, null);
    }

    public Person(String name, String surname, GregorianCalendar birthdate, Sex sex) {
        this(name, surname, birthdate, sex, null);
    }

    public Person(String name, String surname, GregorianCalendar birthdate, Sex sexType, Address addrs) {
        this.name = name;
        this.surname = surname;
        this.birthDate = birthdate;
        sex = sexType;
        address = addrs;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public GregorianCalendar getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(GregorianCalendar birthDate) {
        this.birthDate = birthDate;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }   

    public int getAge() {
        return calculateAge();
    }

    /**
     * Método privado que permite efectuar o cálculo da idade da pessoa.
     * @return
     */
    private int calculateAge() {
        return (int) ((System.currentTimeMillis() - birthDate.getTimeInMillis()) /
                1000 / 3600 / 24 / 355);
    }

    /**
     * Método que somos obrigados a redefinir para que esta classe possa ser 
     * concreta, se não o redefinirmos, a classe terá de ser abstracta.
     * 
     * Este método introduz também as anotações que podem ser usadas em 
     * métodos e atributos. Neste caso a anotação @Overried que 
     * indica que um método é a redefinição de outro acima na hierarquia.
     * 
     * Este método, apesar de implementado, não produz qualquer resultado útil.
     * Uma invocação do método irá provocar o lançamento de uma excepção.
     */
    @Override
    public void drinkMilk() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean producesMilk() {
        return sex == Sex.FEMALE;
    }

    /**
     * Se repararem este método, que provém de uma interface, não possui a 
     * anotação @Override, isto porque o que estamos a fazer não é redefinir um 
     * método mas sim a definir. Por outras palavras, este método não existe em 
     * lugar algum na hierarquia de classes da classe onde estamos, é sim, 
     * definido por uma interface que esta classe, por acaso, implementa.
     * 
     * Desta forma a anotação não faz sentido e se for colocada o compilador irá
     * emitir um erro.
     * @param km
     */
    public void drive(int km) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void saveToFile(String filename) {
        //TODO
    }

    /**
     * Embora não esteja implementado, este método exemplifica a sintaxe de um 
     * método estático.
     * 
     * @param filename Nome do ficheiro a ler.
     */
    public static void readFromFile(String filename) {
    }

    public String toString() {
        return name;
    }

    /**
     * Demonstração de uma enumeração.
     * 
     * Nota-se a semelhança com a declaração de classes e interfaces, onde se 
     * altera apenas a palavra <em>class</em>/<em>interface</em> para 
     * <em>enum</em>.
     * 
     * A sintaxe usada no corpo da enumeração é ligeiramente diferente, mas 
     * introduz apenas algumas coisas novas que são especificas das enumerações.
     * 
     * Tal como as enumerações, é possível ter classes internas, isto é, 
     * classes dentro de outras, e tal como as classes, as enumerações podem 
     * existir no seu próprio ficheiro.
     */
    public enum Sex {

        /**
         * Uso do construtor privado para que apenas possam ser criados dois 
         * tipos de sexo, com os valores que pretendemos.
         */
        MALE("Male"),
        FEMALE("Female");
        private final String type;

        /**
         * Um construtor, como qualquer outro método ou atributo, pode ser 
         * privado, dessa forma apenas a classe que o define lhe pode aceder.
         * @param type
         */
        private Sex(String type) {
            this.type = type;
        }

        @Override
        public String toString() {
            return type;
        }
    }
}

  1. O conceito de POO é explicado em capítulos seguintes. 

  2. Existem implementações de compiladores que transformam o código Java directamente para código nativo, no entanto esses compiladores não fazem parte da filosofia de portabilidade do Java. 

  3. É possível que a nossa aplicação esteja dividida, com umas classes num servidor e outras classes noutra, a execução da aplicação, e até a programação da mesma, é transparente para o utilizador ou para o programador, não alterando em nada o facto da aplicação estar repartida por máquinas diferentes. 

  4. O grosso dos tipos de dados de Java são primitivos, os tipos de dados que sejam objectos nativos da linguagem são casos específicos usados em principalmente para sistemas com necessidade de elevada precisão. 

  5. Todos os tipos de dados nativos possuem um objecto que é responsável por fornecer encapsulamento a esse tipo de dados. Dessa forma podemos “transformar” um tipo primitivo num objecto. 

  6. Condições verdadeiro/falso são expressões cujo resultado seja sempre um valor do tipo verdadeiro(true) ou falso(false). Por vezes podem ser identificadas como expressões boleanas, mas o termo não corresponde a uma tradução correcta. 

  7. A estrutura switch pode parecer contraditória a esta afirmação mas internamente o resultado é que as suas cláusulas são avaliadas como condições verdadeiro/falso 

  8. Embora nos refiramos a esta estrutura como if-then/if-then-else, a palavra then não é usada como palavra reservada. Serve apenas para ajudar na leitura. 

  9. O uso das chavetas não é obrigatório, mas se não usarmos chavetas apenas a linha imediatamente abaixo da condição é executada. 

  10. Este é um comportamento comum a todas as estruturas de controlo de fluxo de código, se a condição é falsa, a execução retoma na chaveta que delimita o bloco correspondente à condição verdadeira. Se existir um bloco de código para situações onde a condição é falsa, esse bloco é executado. 

  11. Embora a palavra switch se traduza para interruptor, o comportamento da estrutura lembra mais um dispositivo de selecção que um interruptor comum. 

  12. Tipo enumerados apareceram no Java 5, para todos os efeitos são classes, mas com tratamento especial em algumas situações. Veremos tipos enumerados nos capítulos seguintes. 

  13. Ao usarmos classes encapsuladores o compilador aplica o conceito de boxing e unboxing automático que já vimos anteriormente. 

  14. Explicada mais abaixo. 

  15. Esta é uma característica do switch, a execução não termina ao encontrar o fim do bloco mas sim quando chegar ao fim do switch, pelo que é comum que um bloco de código termine com uma instrução break

  16. Esta é a estrutura mais comum quando, durante aprendizagem de uma linguagem de programação, se pretendem fazer menus em modo de texto. Permite mostrar sempre o menu uma vez e se o utilizador pretender sair, ter a opção para o fazer. 

  17. O ciclo for é, possivelmente, a estrutura com mais variações, sendo usada de formas bastante variadas, muitas vezes dependentes do estilo de programação de quem a usa. Vamos apenas concentrar-nos na forma tradicional de usar a estrutura. 

  18. Ao contrário da estrutura while

  19. Neste caso ficamos com um ciclo em execução constante, um ciclo infinito. 

  20. Objectos iteráveis são objectos criados de classes que implementam alguma das interfaces de iteradores, tipicamente são listas ou vectores. 

  21. Não vamos entrar em detalhes sobre iteradores neste momento 

  22. Fazer um cast não é mais que converter um tipo noutro tipo. Uma conversão de cast pode ser impossível de fazer se os tipos forem incompatíveis, e pode também provocar perda de informação. Veremos porquê em capítulos seguintes 

  23. Na verdade existe sempre uma cast, mas neste caso não é explicito pelo programador e pode ser optimizado pelo compilador sempre que possível. 

  24. Ciclos que se encontram dentro de outros ciclos 

  25. Labels no original e daqui em diante