sábado, 18 de junho de 2011

Algumas ferramentas da JDK

Algumas pessoas veem me perguntar às vezes quais ferramentas usar para diagnosticar uma aplicação que não está se comportando como deveria. Na maioria das vezes é possível diagnosticar uma aplicação em execução apenas com as ferramentas que vem junto com a JDK mas que muitas pessoas desconhecem mas que (na minha opinião) todos deveríamos conhecer. A maioria das ferramentas abaixo está presente tanto na JDK5 quanto na JDK6 e a maioria delas precisa ser executada com o mesmo usuário que executa o processo. Por último, os comandos abaixo são da versão solaris da JDK e podem ter alguma diferença se executadas em outros sistemas operacionais.

JPS
Parecida com o ps dos UNIX'es, mas lista apenas os processos java em execução, mostrando o pid (o process id) e o nome da classe main que iniciou o processo. Com a opção '-v', mostra toda a linha de comando usada para iniciar o processo e com a opção '-l' mostra o nome completo da classe main.

JSTACK
Uma vez identificado o pid da aplicação, precisamo ver o que acontece com ela. O comando jstack mostra na tela um stacktrace de todas as threads da aplicação e verifica se há algum deadlock entre as threads. Além disso, é possível identificar se alguma thread está parada em algum ponto ou esperando por I/O.

JINFO
jinfo mostra todas as sytem properties e todas as flags da jvm.

JMAP
jmap mostra todas as bibliotecas ligadas à sua aplicação, bem como o offset de memória em que estão mapeadas. Esta informação não é tão útil a menos que estejamos tentando identificar a causa de um crash da jvm. Normalmente usaremos jmap -heap para ver o estado da memória separado por região incluindo o permgen. Caso um problema de memória seja identificado, é possível gerar um histograma com os contadores de todos as instâncias dos objetos vivos com a flag -histo ou ainda gerar o heap dump com a flag -dump:.

JCONSOLE
Se tudo mais falhar, é possível tentar usar uma ferramenta gráfica chamada jconsole que se conecta à um processo java e consegue ver todas as informações anteriores e ainda inspecionar objetos expostos via JMX.

Como pudemos ver, é possível obter várias informações sobre uma aplicação em execução (e possivelmente diagnosticar problemas) apenas usando as ferramentas que vem junto com a JDK.

segunda-feira, 4 de outubro de 2010

OutOfMemoryError explicado

A resposta para o problema anterior passa por duas explicações:

A pilha de execução
Todos os operadores na máquina virtual operam sobre elementos da pilha de execução. Isso significa que, por exemplo, a expressão abaixo:


int x = 1 + 2;


se torna algo como:

empilha 1; // a pilha agora contem 1
empilha 2; // a pilha agora contem 1, 2
soma; // os 2 elementos da pilha sao desempilhados e somados. A pilha contem 3
guarda em x; //o 3 eh desempilhado e guardado em x


Parecido com assembly ASM.


Variáveis locais

Variáveis locais (e argumentos de métodos) são guardados em uma "tabela" por índice, na ordem em que são declaradas em um método. Ou seja:


public static void foo() {
int i = 0;
int j = 1;
}


A variável 'i' ocupa o índice 0 da tabela de variáveis locais e a variável 'j' ocupa o índice 1. Se o método é não estático, a referência ao objeto (o 'this') ocupa o índice 0. Se o método possui argumentos, ele ocupa os índices subsequentes. Assim sendo:



public void foo(String arg0) {
int i = 0;
int j = 1;
}


O 'this' ocupa o índice 0, o 'arg0' ocupa o índice 1 e assim por diante. Quando uma variável local sai de escopo, o compilador reutiliza o índice daquela variável para a próxima declaração de variável local. Em outras palavras, no código abaixo:


public static void foo() {
{
int i = 0;
}
int j = 0;
}


a variável 'j' ocupa o índice '0' da tabela pois a variável 'i' saiu de escopo.

O problema
Sabendo disso, voltando ao problema original:


public static void exec(int size) {
{
byte[] someData0 = new byte[size];
process(someData);
}
//final do bloco
byte[] someData1 = new byte[size]; // #
process(someData);
System.out.println("ok");
}


Quando declaramos a segunda variável local 'someData1', o código a ser executado fica mais ou menos assim (em pseudo bytecode):


empilha size; // a pilha contem size e o slot 1 contem o array antigo ainda
cria_array_bytes; // desempilha um inteiro e cria um array de bytes com o tamanho passado
guarda_em_1; // sobrescreve a variável local anterior


Quando a instrução de criar um novo array de bytes é executado, a JVM lança uma exceção dizendo que não há mais espaço. Isso significa que o código, em bytecode, fica parecido com:


public static void exec(int size) {

byte[] someData0 = new byte[size]; // #1
process(someData);

//final do bloco
someData0 = new byte[size]; // #2
process(someData);
System.out.println("ok");
}


e o array criado em #1 só pode ser coletado depois que a instrução em #2 for executada - ou seja, tarde demais.
Isso explica também porque os outros exemplos funcionam corretamente. Na verdade, podemos perceber que basta declarar uma variável local qualquer entre as criações dos arrays para que o primeiro array seja coletado. De fato, o código abaixo funciona corretamente:


public static void exec(int size) {
{
byte[] someData0 = new byte[size];
process(someData);
}
int j = 0;
//final do bloco
byte[] someData1 = new byte[size]; // #
process(someData);
System.out.println("ok");
}


Conclusão
Não há nada de errado, tecnicamente, com o GC e nem com o código postado. Apenas (no bytecode) não estamos avisando que o primeiro array pode ser coletado, talvez isso devesse ser trabalho do compilador. Porém, na maioria dos casos, inserir uma instrução para anular as variáveis locais quando elas saem de escopo é desnecessário.

PS:
Estes artigos são meramente traduções/adaptações de um artigo que pode ser visto no java specialists newsletter. O artigo original e a resposta se encontram aqui e aqui.
A versão JIT-compiled da classe funciona normalmente devido a alguma otimização da JVM. Isso pode ser visto ao executar a classe com o modo 'compiled' ao invés do modo híbrido (o default) usando a flag -Xcomp.
Qualquer 'branching' (como um if ao invés do escopo vazio) vai fazer com que a JVM execute o código como esperado (sem a exceção), mesmo no modo interpretado.
Por último, alguns GC's mais espertos (como o G1) também são capazes de identificar o primeiro array como lixo e coletar corretamente.

sexta-feira, 24 de setembro de 2010

OutOfMemoryError (??)

Embora o título desse post possa parecer estranho, ele refere-se a um OutOfMemoryError que pode ocorrer em uma situação (bem específica é verdade), mas que aparentemente não deveria. Sem mais delongas, segue o código:


public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
}
//final do bloco
byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}

public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


O código acima lança uma OutOfMemoryError: java heap space, muito embora não devesse, já que o objeto 'someData' sai de escopo após a linha 7, quando o bloco termina. A primeira impressão que pode ficar é, como as duas alocações estão próximas, não dá tempo para o GC limpar a memória. Tentaremos, então, chamar explicitamente o GC para ser executado no meio tempo:



public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
}
//final do bloco
System.gc();
byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}


public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


Mesmo desse jeito, o código continua com o OutOfMemoryError. Algumas pessoas familiarizadas com o funcionamento do GC podem afirmar que chamar System.gc() não necessariamente implica no GC ser executado de verdade. Mas o código abaixo mostra(?) que, se formos insistentes, a memória é coletada apropriadamente:



public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
}
//final do bloco
for(int i = 0; i < 10; i++) System.gc();

byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}

public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


E também podemos ver que gerenciando "manualmente" a memória (ou seja, anulando a variável someData ao final do bloco), o código funciona normalmente:



public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
someData = null;
}
//final do bloco
byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}


public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


Por que será que se insistirmos, o problema some? Mais do que isso, há um problema com o gerenciamento de memória da VM? Devemos esquecer que o GC existe e gerenciar a coleta de objetos manualmente, anulando todas as variáveis quando elas deixam de ser usadas?

terça-feira, 6 de outubro de 2009

Struts 2 com Annotations

Apesar de ser um assunto não muito atual, com bastante documentação, eu tive muito trabalho para escrever minha primeira aplicação com Struts 2. O site do projeto tem muito conteúdo, mas nada mais prático para começar um projeto do zero. Em minha busca atrás de tutoriais, algo que me ajudasse, achei uma quantidade razoável de artigos/tutoriais, porém, nenhum que funcionasse efetivamente, principalmente utilizando annotations. Fiz o download de vários exemplos, uns funcionavam, outros não, mas não consegui fazer minha aplicação funcionar lendo apenas um tutorial. Dada essa dificuldade, pretendo colocar em prática o que eu aprendi, e, quem sabe, se algum desesperado precisar aprender uma aplicação simples utilizando Struts 2, esse tutorial funcione :-)

Para começar, precisamos montar nosso ambiente, vamos precisar do Eclipse, e do TomCat plugin.

Nosso projeto ficará mais ou menos assim:



Agora, criamos um projeto Java, e inserimos as bibliotecas do Struts 2 (essas são os jars necessários para o nosso exemplo simples, precisando de mais funcionalidades, vamos precisar de mais jars - a versão que estou utilizando é a 2.1.6)
  • commons-fileupload-1.1.1.jar
  • commons-logging-1.0.4.jar
  • freemaker-2.3.13.jar
  • ognl-2.6.11.jar
  • struts2-convention-plugin-2.1.6.jar (Esse jar é necessário somente se for utilizar annotations em sua aplicação, para as configurações em XML, esse jar não é necessário)
  • struts2-core-2.1.6.jar
  • xwork-2.1.2.jar

Criamos uma pasta WEB-INF no nosso projeto, que vai conter nosso querido amigo web.xml (arquivo padrão para configuração do struts). Nele, precisamos obrigatoriamente configurar um FilterDispatcher, o default do struts é o org.apache.struts2.dispatcher.FilterDispatcher, mas nada impede o mapeamento de outros filtros.








struts2
org.apache.struts2.dispatcher.FilterDispatcher



struts2
/*



index.jsp





Por que preciso de um FilterDispatcher?! Bom, ele é o responsável por filtrar requisições vindas do browser! Junto com as Actions e os Interceptors, é a camada de Controller do MVC. MV O QUE?!

MVC - Model-View-Controller, é o padrão implementado pelo Struts.

Actions - Unidade mais básica do struts. É basicamente uma ação a ser executada pelo usuário.

Interceptors - Uma das grandes novidades do Struts 2 com relação ao Struts 1, é o controle sobre o fluxo de execução de uma requisição, por meio de Interceptors. Com eles podemos executar métodos antes ou depois de uma determinada Action ser executada.

Chega de teoria! Tendo o web.xml configurado, é hora de cuidar da View! O Model não é nosso foco agora, porque ele seria o Domínio da nossa aplicação. Mas ... não temos domínio, é só um exemplo simples! Então vamos criar os jsp's necessários para o nosso exemplo.

index.jsp (repare que esse é o jsp configurado no welcome-file do web.xml)


<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>







resposta.jsp


<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>




Ok, temos a View pronta. E agora? Agora é hora de criar as Actions! No Struts 2, não é necessário estender nenhuma classe, a Action pode ser um POJO simples, desde que devidamente anotada. Repare que o valor(value) da @Action definida no método a executar, é o mesmo que colocamos na action do formulário no nosso jsp. Quando subirmos nossa aplicação, o Struts vai recuperar as classes anotadas, verificar quais são as actions (essa verificação é feita com o Struts procurando classes anotadas dentro do pacote action), e é assim que o jsp vai saber que ação executar.

Exemplo de Action (repare que é um POJO comum, anotado):

package br.com.maps.action;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;

/**
* Action de exemplo para o tutorial de Struts 2 com annotation.
*
* @author finx
* @created Oct 19, 2009
*/
public class HelloWorldAction {

private String requisicao;

private String resposta;

/**
* Executa a Action.
*
* @return resultado da execução da Action.
*/
@Override
@Action(value = "/helloworld", results = { @Result(name = "success", location = "/resposta.jsp"), @Result(name = "input", location = "/index.jsp") })
public String execute() {
this.resposta = "Olá! Eu estou executando uma ação do usuário, com a seguinte requisição: ";
return "success";
}

/**
* Retorna a mensagem da requisição.
*
* @return a mensagem da requisição.
*/
public String getRequisicao() {
return requisicao;
}

/**
* Define a mensagem da requisição passada como parâmetro.
*
* @param requisicao a mensagem da requisição a ser definida.
*/
public void setRequisicao(String requisicao) {
this.requisicao = requisicao;
}

/**
* Retorna a resposta da requisição, chamada pelo componente s:property do jsp.
*
* @return a resposta da requisição, chamada pelo componente s:property do jsp.
*/
public String getResposta() {
return this.resposta + " [" + this.getRequisicao() + "].";
}

}


Subindo nossa aplicação, ela deve ficar com essa cara:



Hmmm bem legal ... mas quando eu aperto o botão e não escrevo nada, a minha resposta é vazia! Há, aí é que entram os interceptadores. Estendendo nossa action de ActionSupport, ganhamos um fluxo de execução com interceptadores configurado no arquivo struts-default.xml, e aí é só configurar!

Exemplo de requisição vazia:



Um exemplo de como a Action ficaria com validação:

package br.com.maps.action;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
import com.opensymphony.xwork2.validator.annotations.Validations;

/**
* Action de exemplo para o tutorial de Struts 2 com annotation.
*
* @author finx
* @created Oct 19, 2009
*/
public class HelloWorldAction extends ActionSupport {

private String requisicao;

private String resposta;

/**
* Executa a Action.
*
* @return resultado da execução da Action.
*/
@Override
@Action(value = "/helloworld", results = { @Result(name = "success", location = "/resposta.jsp"), @Result(name = "input", location = "/index.jsp") })
@Validations(requiredStrings = @RequiredStringValidator(fieldName = "requisicao", message = "Campo obrigatório"))
public String execute() {
this.resposta = "Olá! Eu estou executando uma ação do usuário, com a seguinte requisição: ";
return "success";
}

/**
* Retorna a mensagem da requisição.
*
* @return a mensagem da requisição.
*/
public String getRequisicao() {
return requisicao;
}

/**
* Define a mensagem da requisição passada como parâmetro.
*
* @param requisicao a mensagem da requisição a ser definida.
*/
public void setRequisicao(String requisicao) {
this.requisicao = requisicao;
}

/**
* Retorna a resposta da requisição, chamada pelo componente s:property do jsp.
*
* @return a resposta da requisição, chamada pelo componente s:property do jsp.
*/
public String getResposta() {
return this.resposta + " [" + this.getRequisicao() + "].";
}

}


E um exemplo de como ficaria a nossa requisição vazia, com validação:



Finalmente, com o fluxo completo:



E, disponibilizando o exemplo utilizado nesse tutorial na página de projetos do blog.

terça-feira, 1 de setembro de 2009

Just Java 2009

Daqui a duas semanas (dias 15, 16 e 17 de setembro) ocorrerá a oitava edição do Just Java. O evento contará com a apresentação do trabalho "Performance com Hibernate em Cluster" por membros de nossa equipe técnica. Estudaremos um "case" de processamento distribuído de dados e os seguintes tópicos serão abordados:
  • Distribuição de tarefas por JMS
  • Hibernate
    • Cuidados com FlushMode e gerenciamento do PersistenceContext
    • Lock otimista com uso de @Version
    • Uso de cache secundário
Esperamos vê-los no evento.

terça-feira, 21 de julho de 2009

Java Generics - Parte I: Definições

Embora tenham sido introduzidos há quase cinco anos, com o lançamento do java 1.5 (codinome Tiger), os generics em java são fonte de confusão para muitas pessoas - mesmo programadores mais experientes. Esta série de posts tem como objetivo explicar como funcionam os generics em java, explicar seus diversos casos de uso e seus principais pontos de confusão. Este primeiro artigo é um pouco teórico mas artigos subsequentes se concentrarão na prática dos conceitos aqui explicados.
O que são generics
Generics é/são um recurso de linguagem que permite às classes especificarem uma parametrização "abstrata" de um tipo. Em outras palavras, os generics permitem que se escreva códigos que dependam de classes que serão definidas pelos clientes da sua classe. O exemplo óbvio são as coleções: quem escreveu a classe List, por exemplo, não poderia escrever uma variante para cada tipo de elemento que seria guardado dentro da List. Para poder definir de maneira uniforme a interface List (e sem que códigos clientes precisem fazer casts ou extender as classes), é possível escrever uma classe que tenha um parâmetro genérico, descrito na declaração da mesma. Em java, o parâmetro genérico é declarado dentro dos angle brackets '<' e '>':
public interface List<E> {

public E get(int index);

//resto da classe

}

Permitindo escrever código cliente deste modo:
ArrayList<String> listaDeStrings = new ArrayList<String>();

listaDeStrings.add("umaString");

String umaStrings = listaDeStrings.get(0);

Este recurso está presente em várias linguagens de programação, tal como C++, C#, Haskel, scala, etc, embora em algumas dessas linguagens seu nome seja diferente. Em teoria de tipos, este recurso também é conhecido como polimorfismo paramétrico. Para as definições a seguir, onde está escrito "A instância de B" deve ser lida como "A é um substituto válido para B" em relação à tipagem.

Covariância, contravariância e invariância
Um pré-requisito para se entender as confusões mais comuns causadas por generics, é preciso entender um conceito conhecido como variância de tipos genéricos:

Covariância
Por definição, diz-se que um tipo T com um parâmetro genérico B é covariante em B se e somente se toda instância de T[A0] é uma instância de T[A1] para toda classe concreta A0 que extende de uma outra classe concreta A1. Por exemplo em java (e em C#) arrays são covariantes, o que nos permite escrever:

Object[] arrayDeObjects = new String[10];
Considerando que o tipo do array é seu "parâmetro genérico", embora não use o generics explicitamente.

Por outro lado, os generics "normais" em java não são covariantes, o que torna o código abaixo inválido:

//erro de compilação
List<Object> listDeObjects = new ArrayList<String>();
Um tipo genérico covariante não permite que se "escreva" nele - em outras palavras, um tipo genérico covariante é read-only, ou seja, o tipo genérico só pode aparecer em posições covariantes (apenas como parte de retorno de métodos). Para verificar porquê, imagine que o compilador permitisse o código abaixo:
ArrayList<String> listDeStrings = new ArrayList<String>();

//a linha abaixo é reportada como erro pelo compilador
ArrayList<Object> listDeObjects = listDeStrings;

//em um array list de objects, podemos inserir um Object
listDeObjects.add(new Object());

A seguinte linha, porém, faria com que fosse lançada uma ClassCastException mesmo que ela não tenha nenhum cast:
String a = listDeStrings.get(0);
Os arrays em java resolvem (ou melhor, contornam) este problema lançando a exceção ArrayStoreException:
// array de strings
String[] arrayDeStrings = new String[10];

//sem exceção, pois arrays são covariantes
Object[] arrayDeObjects = arrayDeStrings;

// a linha abaixo, porém, lança a exceção ArrayStoreException
arrayDeObjects[0] = new Object();



Contravariância
Por outro lado, um tipo genérico T é considerado contravariante em B se e somente se para qualquer classe concreta A0 super classe de A1, toda instância de T[A0] é uma instância de T[A1]. Embora isso pareça estranho a princípio, considere a interface Comparator:
public interface Comparator<E>  {

public int compare(E arg0, E arg1);

}

Sempre que um método precisar receber um Comparator para, por exemplo, String, poderia receber na verdade um Comparator para qualquer coisa que saiba comparar Strings - por exemplo, um Comparator de Objects.

Desse modo, podemos ver que um tipo genérico contravariante é write-only, ou seja, o tipo paramétrico só pode aparecer em posições contravariantes (apenas como parte dos argumentos dos seus métodos).

Em JAVA
Como foi visto anteriormente, arrays em java são covariantes. Os tipos genéricos, porém, são invariantes nos tipos paramétricos nele definidos - ou seja, não são nem covariantes nem contravariantes. Isso permite que tipos genéricos sejam usados tanto em retorno de métodos quanto em seus argumentos. Na declaração de variáveis, porém, é possível especificar a variância de um tipo genérico através das palavras-chave extends (para covariância) e super (para contravariância).

Assim sendo, uma List covariante em um tipo T tem o tipo:

List<? extends T> listDeT = ....;


A mesma sintaxe se extende para parâmetros de métodos, como neste método presente na interface Collection:

public void addAll(Collection<? extends E> collection)


Declarando um parâmetro dessa maneira, a collection é covariante em E, o que significa que no corpo do método, não podemos chamar métodos nessa lista que declarem E como parte do parâmetro.


Por outro lado, um Comparator contravariante em K tem o tipo:

Comparator<? super K> comparatorDeK = ....;


As construções acima funcionam também para declaração de parâmetros de métodos e construtores, bem como o tipo de retorno de um método.

Note a presença do wildcard '?'. O wildcard serve para indicar que o tipo exato da List e do Comparator é desconhecido, mas deve obedecer às restrições de variância impostas (respectivamente, covariante em T e contravariante em K). Como dito anteriormente, uma List covariante é "read-only"[1] (apenas é possível ler coisas da List) e, comparativamente, o Comparator contravariante é write-only. Este tipo de variância é chamado de use-site variance, contrastando com definition-site variance (presentes em, por exemplo, C# 4.0 e scala), onde a variância é indicada na própria declaração do tipo genérico.


Conclusão

Vimos aqui a definição de generics, bem como definições sobre variância e sobre como funciona a variância de arrays e demais tipos genéricos em Java. No próximo post, serão mostrados alguns usos de tipos variantes e os problemas mais comuns que aparecem com o uso de generics.



[1] - não são read-only no sentido de que a List não pode ser modificada, mas sim no sentido que não se pode chamar métodos que usam o tipo genérico como parte do parâmetro de algum método.

sexta-feira, 10 de julho de 2009

Scala parte I - sintaxe

Durante a escrita de um outro artigo (ainda a ser publicado), percebi que preferia escrever os exemplos em uma linguagem de programação chamada Scala ao invés de Java. Por isso, ao invés de explicar todas as linhas do código, resolvi escrever um pouco sobre esta linguagem que, na minha humilde opinião, tem potencial para ser a próxima grande linguagem de programação.

O que é Scala?
Scala é uma linguagem de programação orientada a objetos, com tipagem estática e compilada, que é executada na JVM e na máquina virtual do .Net[1]. Além disso, Scala tem muitas funcionalidades típicas de linguagens de programação funcional, como closures, otimização de chamadas recursivas, etc. Por último, por ser primariamente executada na JVM, apresenta ótima compatibilidade com Java, podendo se aproveitar das muitas bibliotecas existentes. Foi inventada por Martin Odersky e seu nome se refere à escalabilidade da linguagem, na medida em que pode ser usada para escrever pequenos scripts ou grandes sistemas completos, sem que haja uma alteração na maneira de escrever.

Como usar
Todos os exemplos abaixo podem ser testados no console de Scala, invocado por scala ou scala.bat dependendo do seu sistema operacional.

Um pouco sobre a sintaxe
Na sua estrutura geral, Scala se parece muito com Java, embora os detalhes da sintaxe sejam bastante diferentes. Para começar, a declaração de variável é precedida de val ou var e o tipo da variável é pós-escrito e separado do nome da variável por ':', desse modo:

var umaString: String = "aaa";
val umNumero: Int = 1;

uma variável declarada como var pode ser reassignada, enquanto que uma variável declarada com val é imutável, ou seja, para todos os efeitos, equivalente a declarar uma variável 'final' em Java. O compilador de Scala permite, porém, que se omita o tipo de uma variável (que deve ser sempre inicializada durante a declaração):

var outraString = "aaa"
val umDouble = 0.0

E o ';' pode ser omitido se for desnecessário (ou seja, na última expressão de uma linha).
Métodos em Scala são definidos pela palavra chave 'def':

def metodoSemParametros():String = { "a" }

O ':String' significa que o método devolve String. O retorno do método, se omitido, é o resultado da última expressão do método. Um método com parâmetros:

def metodoComUmParametroInteiro(parametro: Int) = parametro + 1

Este método declara um parâmetro do tipo Int (que é o mesmo que 'int'). Neste caso, o compilador também infere e insere o tipo de retorno do método e os delimitadores também são opcionais para métodos compostos por apenas uma expressão. Por falar em expressão, Scala é composto quase que exclusivamente por expressões, o que significa que você pode atribuir quase qualquer coisa a uma variável (embora haja expressões do tipo Unit, o equivalente a void em C/C++/Java).


val maior: String = { if (1 > 2) { "1 maior que 2"} else { "1 menor que 2"} }

Mesmo para expressões desse tipo, o compilador infere o tipo da expressão e o insere se este for omitido. As chaves também podem ser omitidas para expressões únicas:

val maior = if (1 > 2) "1 maior que 2" else "1 menor que 2"

Escrever classes em Scala é bem simples:

class MinhaClasse {
var propriedadeMutavel: String = "a"
val valorImutavel = 10
}


Var e Val dentro do corpo da classe criam propriedades mutáveis e imutáveis respectivamente (o bytecode gerado é o equivalente a um campo e getters e setters).
Isso faz com que classes cliente não saibam se determinado valor vindo de outra classe é um campo ou uma invocação de método, uma propriedade conhecida como princípio de acesso a dados uniforme.

Em Scala, há o conceito de construtor primário que deve ser chamado pelos outros construtores e sua assinatura está junto com a declaracao da classe:

class Ponto(x0: Int, y0: Int) {
val x = x0
val y = y0
}

Construtores auxiliares são declarados por métodos chamados 'this'

class Ponto(x0: Int, y0:Int) {
val x = x0
val y = y0

def this() = this(0, 0) //construtor vazio
}

Em especial, o construtor primário pode já ter a declaração de propriedades de classe:
class Ponto(val x:Int, val y:Int) {
def this() = this(0, 0)
}

Métodos em Scala podem ter praticamente qualquer nome:
class Ponto(val x:Int, val y:Int) {
def this() = this(0, 0)
def +(ponto: Ponto) = new Ponto(x + ponto.x, y + ponto.y)
}

Por último, generics em scala funcionam de maneira um pouco diferente e usam [ ] ao invés de < >, mas os detalhes serão dados em um post futuro. Além disso, posts posteriores vão mostrar construções avançadas, bem como algumas das propriedades que estão tornando scala uma linguagem tão popular

[1] - Embora o suporte ao .Net esteja bem defasado e incompleto.