terça-feira, março 09, 2010

Switch com Strings no Java 7

Uma coisa que existe em quase todas as linguagens, exceto Java e C++, é a facilidade de você usar um "switch" com o tipo String. Isso será adicionado ao Java 7.
Peço permissão ao Entanglement para postar um programa que ele mostrou no GUJ:
class TesteSwitch {
    public static void main (String[] args) {
        String s = "desprezível";
        switch (s) {
        case "desprezível":
            System.out.println ("despicable"); break;
        case "auréola":
            System.out.println ("halo"); break;
        case "fungo":
            System.out.println ("fungus"); break;
        case "fujão":
            System.out.println ("fugitive"); break;
        case "abstruso":
            System.out.println ("abstruse"); break;
        }
    }
}
Sendo que esse programa foi transformado pelo compilador para o equivalente a:
class TesteSwitch {
    public static void main (String[] args) {
        String s = "desprezível";
        int hashCode = s.hashCode();
        int i = -1;
        switch (hashCode) {
        case -644512679: if (s.equals ("auréola")) i = 1; 
                         else if (s.equals("desprezível") i = 0; 
                         break;
        case 97793703: if (s.equals ("fujão") i = 3; 
                       else if (s.equals("fungo") i = 2; 
                       break; 
        case 1732918561: if (s.equals ("abstruso") i = 4; 
                         break;
        default: break;
        }
        switch (i) {
        case 0: System.out.println ("despicable"); break;
        case 1: System.out.println ("halo"); break;
        case 2: System.out.println ("fungus"); break;
        case 3: System.out.println ("fugitive"); break;
        case 4: System.out.println ("abstruse"); break;
        }
    }
}

O compilador transforma 1 switch em 2, porque é mais fácil tratar de certos casos (como "fallthrough" e strings que têm exatamente o mesmo hash code). Por exemplo, no caso acima, "auréola" e "desprezível" têm o mesmo hash code, assim como "fujão" e "fungo". E é por isso que o switch checa se o hash code E a string são os mesmos do caso. Se o switch tiver muitos casos, essa implementação é mais rápida que simplesmente ir comparando com "ifs" encadeados.

Obviamente, assim como no caso do "switch" com inteiros, é necessário ver se o seu problema não poderia ser resolvido com hierarquias de classes ou então com enums.

Usando literais binários e separadores de milhar em Java 7

Em Java 7, poderemos usar números binários (embora sejam um pouco extensos), o que facilita a quem precisa codificar máscaras de bits.

Além disso, uma coisa bem mais útil é que podemos agora pôr separadores de dígitos dentro de constantes numéricas.

É bem mais fácil conferir pi = 3.141_592_653_589_793_238_462_643_383_279_5 em vez de 3.1415926535897932384626433832795.

Entretanto, esse suporte a separadores de milhar vale só para o compilador; as rotinas (como Integer.parseInt ou DecimalFormat.parse) não entendem o "_". Isso, provavelmente, é para garantir compatibilidade com programas já existentes.

Uma coisa que sempre me incomodou é que números octais são iniciados por um simples zero, tal como em C/C++. Isso é muito confuso; seria preferível que houvesse um indicador de base (como, digamos, a letra Q. Por exemplo, em vez de 0377, teríamos a constante 0Q377.). Como isso já é uma "pegadinha" antiga da linguagem, eu sugiro que se use sempre um separador, para você tomar cuidado com esse zero inicial. Em vez de usar o número 0377, deveríamos usar 0_377. Mas não sei se uma convenção dessas ajudaria muito ou não.

import java.text.*;

// Requer Java 7 para ser compilada
class NovosLiterais {
    public static void main (String[] args) {
        int cafeBabe = -889275714;
        assert (cafeBabe == 0xCAFEBABE); // hexadecimal
        assert (cafeBabe == 031277535276); // octal
        // 0b introduz um número em notação binária
        assert (cafeBabe == 0b11001010111111101011101010111110); // binário
        // "_" separa os dígitos.
        assert (cafeBabe == 0b1100_1010_1111_1110_1011_1010_1011_1110); // binário
        // Pode-se usar separadores de dígitos em qualquer base
        assert (cafeBabe == 0xCAFE_BABE);
        assert (cafeBabe == -889_275_714);
        assert (cafeBabe == 0_31_277_535_276);
        // Não é preciso pôr os separadores de 3 em 3 ou de 4 em 4. 
        long cnpj = 65_497_745_0001_53L;
        // É possível usar separadores de dígitos mesmo com números de ponto-flutuante
        double pi = 3.141_592_653_589_793_238_462_643_383_279_5;
        double avogadro_constant = 6.022_141_79E+23;
        // Você não pode usar o "_" no início de uma constante, porque aí teríamos
        // um identificador. Ele só pode ser usado dentro de uma constante.
        double _42; // isto é um identificador, não uma constante
        // .parseInt, .parseLong não suportam "_"
 try { 
            long x = Long.parseLong ("CAFEBABE", 16); 
            x = Long.parseLong ("CAFE_BABE", 16);
        } catch (NumberFormatException ex) { ex.printStackTrace(); }
 try { 
            int x = Integer.parseInt ("11001010", 2); 
            x = Integer.parseInt ("1100_1010", 2);
        } catch (NumberFormatException ex) { ex.printStackTrace(); }
        // DecimalFormat.parse também não suporta "_"
        NumberFormat df = DecimalFormat.getInstance();
        try { Number n = df.parse ("123_456"); 
            System.out.println (n); // imprime "123", o que indica que ele não consegue ver além do "_"
        }  catch (ParseException ex) { ex.printStackTrace(); }
    }
}

sexta-feira, março 05, 2010

Usando <> em Java 7

Em Java 7 é possível agora abreviarmos a chamada a um construtor se o tipo tiver parâmetros.
Em vez de termos de repetir todos os tipos parametrizados, é suficiente usarmos <>. Na documentação isso é chamado de "diamond" (losango), em alusão à forma do novo operador.

// Requer Java 7
import java.util.*;

class TesteNotacaoAbreviada {
    public static void listMap (Map<String, String> map) {
        for (Map.Entry<String, String> entry : map.entrySet()) {
        }
    }
    public static void main (String[] args) {
        // Note que não é necessário escrever "new HashMap<String, String>()"
        Map<String, String> traducao = new HashMap<>();        // Nem aqui é preciso escrever "new TreeMap<String, String>()"
        traducao = new TreeMap<>();
        // A notação abreviada funciona também para passagem de parâmetros, mas
        // é bem mais difícil fazê-la funcionar adequadamente. No caso abaixo, 
        // o compilador acha que o tipo que está sendo passado é new TreeMap<Object,Object>
        // e emite um erro de compilação. 
        listMap (new TreeMap<>());
    }
}

Da série "Para eu não me esquecer mais..."

Estou tendo de usar o Compuware DevPartner Studio 9.0.1 (versão de demonstração) para resolver uns problemas de desempenho em um programa em C++, e sempre me esqueço que é necessário arranjar um arquivo chamado wcore-4.9.6658.0.zip, abri-lo, copiar o arquivo wcore.dll para o diretório C:\Program Files\Common Files\Compuware\NMShared\4.9 e reiniciar os serviços do DevPartner.

quinta-feira, março 04, 2010

Identificadores com espaços no Java 7

Se você baixou uma versão recente do JDK 7 (veja http://download.java.net/jdk7 ), pode tentar compilar o seguinte programa.

class #"Teste Identificadores Com Espacos" {
    public static void main (#"String"[] #"argumentos de linha de comando") {
        System.#"out".#"println" ("Os argumentos de linha de comando passados para este programa são:");
        for (#"String" #"argumento" : #"argumentos de linha de comando") {
            System.out.printf ("%s %n", argumento); 
        }
    }
}

Isso foi introduzido pelo John Rose, um pouco "por debaixo dos panos" (já que ele é um dos committers do Sun JDK), e comunicado "after the fact" em: http://wikis.sun.com/display/mlvm/ProjectCoinProposal.
Quem se lembra daqueles identificadores de colunas no SQL (que são cercados por aspas, se contiverem caracteres especiais ou espaços) vai imediatamente reconhecer a utilidade de tais identificadores malucos. O funcionamento é mais ou menos o mesmo: se houver um caracter que não for letra, número, cifrão ou "underscore" em um identificador, você pode "forçar a barra" usando o #"".
Você pode até forçar um pouco mais a barra, e criar uma classe com um nome que é uma palavra reservada do Java.

class #"int" {
}

Só que os seguintes caracteres ainda não são aceitos*, porque são importantes para a JVM distinguir pacotes, nomes de classes, arrays de objetos, e métodos construtores:
. / ; [ ] < >
* Segundo a especificação, bastaria usar um "\\" antes deles, mas o javac não deixa usar esses caracteres mesmo com "\\". Pois é...