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.

terça-feira, 23 de junho de 2009

JavaFX - conceitos básicos

Um pouco de background

O cenário para RIA's está bastante aquecido e JavaFX é a tecnologia desenvolvida pela Sun para disputar este mercado com Flash/Flex e Silverlight. Estas três tecnologias tem em comum o fato de serem disponibilizadas aos usuários através do browser porém executadas numa máquina virtual local. Desta forma é possível oferecer ao usuário interfaces muito mais elaboradas do que é possível com a combinação HTML+JavaScript (o cenário no qual trabalha JSF por exemplo).

A linguagem

Um dos principais diferenciais em relação ao Java é o estilo declarativo de codificação. Por exemplo, compare a forma Java de se instanciar um objeto do tipo SomeObject e sua dependência SomeObjectChild:
SomeObjectChild child = new SomeObjectChild();
child.setValue3(123);
SomeObject ref = new SomeObject();
ref.setValue1(123);
ref.setValue2("123");
ref.setChild(child);
ref.setSomeEventListener(new SomeEventListener() {
void someEvent() {
//do something
}
});
e a forma JavaFX de se instanciar um objeto equivalente:
def ref : SomeObject = SomeObject {
value1: 123
value2: "123"
child: SomeObjectChild {
value3: 123
}
someEvent: function(): Void {
//do something
}
}
O estilo declarativo é visivelmente mais claro e conciso.

Outra recurso interessante é chamado de binding. Através deste recurso é possível amarrar um atributo a outro. Desta forma toda vez que algum atributo é modificado todos os que estiverem amarrados são notificados. Na verdade é mais do que uma notificação, a expressão com a qual foi feita a amarração é executada novamente atualizando assim o valor do atributo amarrado. Vejamos alguns exemplos:
var stage : Stage;
var lbl: Label;

lbl = Label {
font : Font {
size : 20
}
width: 100

translateX: bind (stage.scene.width - lbl.boundsInLocal.width) / 2
translateY: bind (stage.scene.height - lbl.boundsInLocal.height) / 2
text: bind
if (stage.scene.width > 300) then
"LARGE!"
else
"thin..."

textFill: bind
if (stage.scene.width > 300) then
Color.RED
else
Color.BLACK
};

stage = Stage {
title: "Simple Binding"
width: 250
height: 80
scene: Scene {
content: [lbl]
}
}
A variável stage representa uma janela que será exibida para o usuário. Esta janela irá conter apenas um texto representado pela variável lbl. Repare que os atributos do texto estão definidos usando a palavra-chave bind. Isto irá fazer com que toda vez que as propriedades stage.scene.width e stage.scene.height forem modificadas os atributos text, textFill, translateX e translateY sejam calculados novamente. Na prática isto fará com que o texto seja movido para o centro da janela sempre que ocorrer um redimensionamento da mesma e também irá mudar o texto sendo exibido e sua cor de acordo com a largura da janela.

O recurso de binding é bastante poderoso e permite manter o estado dos componentes da janela sincronizados em torno de atributos chaves sem que para isso seja necessário definir listeners de eventos como seria feito numa interface Swing tradicional.

O exemplo acima não reflete isso mas este recurso facilita a construção da interface no padrão MVC. Fica bem simples sincronizar os componentes da View que exibem dados aos atributos do Model que armazenam estes dados. Desta forma ações (Controller) que impactam o Model irão automaticamente atualizar a View sem que isto fico explícito no código das ações. Portanto é possível alcançar um menor acoplamento entre estas camadas ficando de fato possível alterar componentes da View sem impactar as camadas de Model e Controller.

Mixins

Até a versão 1.1 do JavaFX existia suporte a herança múltipla. Este recurso foi eliminado na versão 1.2 em favor de "mixins". Este recurso permite que interfaces tenham uma implementação padrão de um ou mais de seus métodos. Desta maneira, uma classe qualquer que deve implementar uma interface referencia o mixin da interface ao invés da própria interface. Vejamos um exemplo simples composto de duas interfaces, dois mixins, uma classe abstrata e uma concreta:

Interfaces (Java):

public interface Persistable {
void save();
void delete();
}

public interface Identifiable {
String getIdentity();
}

Mixins (JavaFX):

public mixin class IdentifiableMixin extends Identifiable {
public override function getIdentity() : String {
return toString();
}
}

public mixin class PersistableMixin extends Persistable {
public override function save() : Void {
Persister.save(this);
}

public override function delete() : Void {
Persister.delete(this);
}
}

Classe abstrata (Java):

public abstract class Entity {
private Long id;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}
}

Classe concreta (JavaFX):

public class SomePersistableIdentifiableEntity extends Entity, PersistableMixin, IdentifiableMixin {
public-init var field: String;

public override function toString() {
return field;
}
}

Desta maneira a classe SomePersistableIdentifiableEntity implementa as interfaces Persistable e Identifiable sem no entanto ter sido obrigado a implementar os métodos da interface pois foram utilizados as definições providas pelos "mixins". Caso necessário é possível sobreescrever os métodos para modificar algum comportamento. Graças a este recurso não foi preciso "sujar" a árvore de herança da classe com implementações dos métodos das interfaces. De uma maneira geral "mixins" promovem reuso de código e consistência nas chamadas a métodos de interfaces. Permitem que diversas classes que querem implementar determinada interface já ganhem de brinde uma implementação padrão de um ou mais de seus métodos.

Num próximo post sobre JavaFX iremos falar sobre novos modificadores de acesso a variáveis, operadores especiais para trabalhar com listas e mais.