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!

Anúncios

10 pensamentos sobre “Truques bobos em Java (1)

    • Hehe, também é ruim quando uma tarefa simples exige todo o poder da linguagem… C++ tem disso. A biblioteca padrão é ilegível porque usam todo e qualquer truque que existe para fazer algo que o compilador saberia fazer mas só fornece certas pistas ao código do usuário…

      • Pois. Na real o que complica a ‘rotatef’ e macros similares em Common Lisp é que (1) elas têm que garantir que cada argumento seja avaliado exatamente uma vez, da esquerda para a direita; (2) ‘setf’ (o operador de atribuição) é extensível, então se o usuário definir um (setf (file-contents “/tmp/bla”) “Hello, world!”) da vida, as macros têm que continuar funcionando, e a interface definida pela linguagem para acessar os “setf methods” é um negócio pra lá de doido que foi feito para resolver todos os casos que podem acontecer ou não. :P

        By the way, acabei de me dar conta de que a tua ‘returnFirst’ é a ‘prog1’ do Common Lisp (com a diferença de que a ‘prog1’ é uma macro e funciona para um número arbitrário de argumentos)…

        • Haha, entendo! Até baixei o SBCL para ver a implementação do rotatef e apesar de não conseguir acompanhar cada detalhe da implementação (nem sei se quero…), identifiquei todos esses elementos aí!

          Sim, conheço o prog1 e o progn dos Lisps (Common e Emacs)! Deve ter sido a partir das minhas incursões em programação funcional que saiu a idéia (se bem que eu não devia estar pensando em prog1, mas sim em tuplas).

        • Bah, Lisp tem até prog2, para todas aquelas vezes que você quis executar uma seqüência de instruções e retornar o valor da segunda.

          Exemplifica bem o “Para resolver todos os casos que podem acontecer ou não”. Só faltou porem o número como parâmetro :-p

  1. Notei agora que se o nome da função for _, o código fica bem “interessante”:

    a = _(b, b = a);

    Exceto pela pontuação, é como escrever a = b; b = a; só que funciona!

    Haha, gostei muito mais assim!

    (se for usado num escopo pequeno, claro. Para escopos maiores, um nome mais descritivo para a função é melhor)

  2. Eis o que o CLtL tem a nos dizer:

    prog2 is provided mostly for historical compatibility.

    (prog2 a b c … z) == (progn a (prog1 b c … z))

    Occasionally it is desirable to perform one side effect, then a value-producing operation, then another side effect. In such a peculiar case, prog2 is fairly perspicuous. For example:

    (prog2 (open-a-file) (process-the-file) (close-the-file))
    ;value is that of process-the-file

    Na certa alguém em 1970 definiu essa macro, ela foi usada em três programas diferentes, virou parte do MacLisp/Interlisp e aí foi eternizada no Common Lisp. :P (Ou talvez ela fosse mais usada em uma época em que não existia with-open-file e unwind-protect…)

    • (Não sei qual a fonte de informações Common-Líspicas tu consultou, mas o CLtL ainda é a minha referência favorita, mesmo sendo outdated e tendo aquele formato “peculiar” com as alterações do CLtL2 intercaladas no texto do CLtL1. :P)

      • Consultei o que o Google achou, haha. Mas esse exemplo com abrir-processar-fechar arquivo eu encontrei. Não sei se foi o mesmo site ou se esse exemplo é tão clássico que está em todas as referências…

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s