Truques bobos em Java (2)

GOTO

Oficialmente, Java não tem goto. A palavra é até reservada, mas só para poder dar erro mesmo…

A alternativa dada são break e continue rotulados. Eu achava que só era possível rotular loops (afinal, break e continue são usados em loops), mas lendo a especificação (o truque da postagem anterior eu também descobri lendo a especificação) percebi que qualquer statement pode ser rotulado, transformando break praticamente em “goto pra frente”! Edit: O continue bem que poderia ser um “goto pra trás” mas na verdade dá um erro (ver comentários deste post). É possível transformar o continue num goto para trás criando um loop “só para satisfazer o compilador” assim: label: for(;;){ <...código...> if(condição) continue label; <...código...> break label; } mas aí seria muita gambiarra. Fiquemos então com o break apenas, já que a idéia era mostrar um recurso normal da linguagem mas pouco usado.

Por exemplo, se quisermos procurar por caracteres inválidos numa String e fazer um escape nela, sem precisar alocar uma nova String caso a original já seja válida, podemos fazer um loop para testar toda a String e outro para fazer o escape apenas se o primeiro sair com break.

checkString: {
     for(...) {
         if(...) break checkString;
     }
     return originalString; // checked the whole String, no problem found
}
// invalid character found, escape it and return a new String
StringBuilder builder = new StringBuilder(originalString.length() + EXTRA_BUFFER);
for(...) {
    ...
}
return builder.toString();

Outras maneiras de fazer seriam: repetir o teste fora do for para descobrir se saiu normalmente ou com break, quebrar a lógica em funções menores, colocar todo o código de escape dentro do if que faz o break (se for só um), ou simplesmente cortar fora metade do código e montar uma nova String sempre, mesmo que não haja nenhum caracter inválido. Todas são soluções aceitáveis, mas numa biblioteca eu esperaria a implementação mais eficiente, sem cópias desnecessárias.

Anúncios

Truques bobos em Java (1)

SWAP

Desde muito tempo atrás sempre me admirou a incapacidade da linguagem Java de permitir criar uma abstração para trocar o valor de duas variáveis. Os criadores da linguagem se dedicaram mais à orientação a objetos do que com as pequenas praticidades (eu ia dizer “pequenas coisas práticas do dia-a-dia” e aí lembrei que eu não preciso de uma função swap no dia-a-dia… Mas eu quero uma mesmo assim!). Sem passagem por referência, sem retorno de múltiplos valores, sem macros, como fazer?

Em fóruns da internet, só se ouve respostas: “não dá”, “só se você criar um objeto mutável ou um array de 1 elemento, e trocar seus conteúdos na função”, “faça à mão mesmo com uma variável temporária para não perder desempenho criando objetos” (bastante sensato!) e de vez em quando alguns truques com aritmética ou XOR. Mas eu nunca vi essa sugestão:

int returnFirst(int x, int y) {
    return x;
}
int a = 8, b = 3;
a = returnFirst(b, b = a);
System.out.println("a: " + a + ", b: " + b); // prints a: 3, b: 8

Usando a função auxiliar returnFirst (que poderia ter um nome mais sucinto, mas escolhi esse nome para fins didáticos), temos uma solução que ainda cumpre quase todos os meus requisitos:

  1. A troca de valores é feita em apenas um statement;
  2. Não é necessário declarar uma variável temporária (não polui o código do chamador);
  3. Não aloca objetos temporários;
  4. Com alguns overloads, sendo um deles com um generic <T>, funciona para qualquer tipo;
  5. A implementação da função auxiliar é trivial;
  6. Não usa truques sujos que só funcionam para números inteiros.

O único trabalho é passar os argumentos na ordem certa e não se confundir com as duas atribuições! (Edit: ignore o nome da função e simplesmente leia como se fosse a=b;b=a; tá certo que se fosse escrito assim não funcionaria e tá certo que b=a executa primeiro, mas o importante é a idéia!)

A especificação da linguagem Java (Java Language Specification, Java SE 7 Edition, seção 15.12.4.2) garante que o valor de b é avaliado e passado antes de ser sobrescrito pela atribuição b = a no segundo argumento, ao contrário de outras linguagens (pelo que me lembro, C e C++ não são nada amigáveis e não garantem ordem nenhuma de avaliação dos argumentos, só garantem que eles todos serão avaliados antes da função começar a executar, obviamente. Vou ficar devendo uma referência à especificação).

Então com nossa funçãozinha auxiliar returnFirst, o valor de b é passado para o parâmetro x, logo a seguir b é sobrescrito com o valor de a, e quando returnFirst executa, não faz nada além de retornar o valor antigo de b, que pode ser atribuído a a. Tudo isso para evitar ter mais um ponto-e-vírgula na linha e uma declaração de variável :-)

Se eu usaria isso de verdade? Talvez seja útil algum dia num caso muito específico: num algoritmo com várias trocas o código ficaria mais limpo se feito dessa forma. É legal descobrir um truque desses que não encontrei no StackOverflow!

Modos

Este é mais um daqueles casos em que alguém posta algo que eu vinha querendo escrever faz tempo. Ter diferentes modos de operação num programa dificulta a vida do usuário. Se os modos têm tempo de duração, pior ainda. Perdi a conta das vezes em que eu estava explicando: “Aperta este botão. Agora navega nas opções com as flechinhas. Estas flechinhas aqui. Opa, o menu sumiu, começa de novo.” Sério, coloquem um grande botão “Voltar ao normal” e esqueçam esse negócio de modos com tempo de duração.

Num programa ou site pode ser muito legal quando basta passar o mouse por cima de um local mágico e tudo faz “uóun, bóing, blimp” e aparecem vários menus e opções [como se fossem] secretas. Enquanto isso, usuários só querem alguns pontos de referência que estejam sempre disponíveis…

http://ilyabirman.net/meanwhile/all/timed-modes/