domingo, 22 de diciembre de 2019

Usar y tirar (autoboxing)

A partir de java 5 es posible evitar hacer conversiones explícitas de int a java.lang.Integer, por ejemplo:

   public void main() {
      Integer suma1 = sum(1, 2);
      suma1 = sum(1, 2);
   }

   static int sum(Integer a, Integer b) {
      return a + b;
   }

Cuando cuando se de-compila el código de la functión main (Útilizando Bytecode viewer), se obtiene lo siguiente:

     public main() { //()V
         ...

         L1 {
             iconst_1
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             iconst_2
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             invokestatic AutoboxExample.sum(Ljava/lang/Integer;Ljava/lang/Integer;)I
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             astore1
         }
         L3 {
             iconst_1
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             iconst_2
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             invokestatic AutoboxExample.sum(Ljava/lang/Integer;Ljava/lang/Integer;)I
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             astore1
         }
         L4 {
             return
         }
         L2 {
         }
     }

Donde se ve que el código compilado es idéntico a cuando se utiliza valueOf explícitamente.

Esta funcionalidad del compilador, evita que se creen objetos de tipo Integer mediante el constructor Integer(int). Internamente la función valueOf(int), utiliza un cache,  para disminuir la creación de objetos de un solo uso.

Este cache está configurado por defecto para los número de -128 a 127 y se puede configurar para incrementar la cantidad de elementos (-XX:AutoBoxCacheMax=<size>).

miércoles, 27 de noviembre de 2019

Usar y tirar (foreach)

Para iterar sobre un List se puede realizar de la siguiente manera:

  List<string> list = ...;

  int size = list.size();
  for (int i = 0; i < size; i++) {
   String str = list.get(i);
   // ...
   System.out.println(str);
  }

Alternativamente se puede escribir utilizando un interator y un ciclo while:

  List<string> list = ...;

  Iterator<string> iterator = list.iterator();
  while (iterator.hasNext()) {
   String str = iterator.next();
   // ...
   System.out.println(str);
  }


A partir de java 5 se puede escribir así utilizando un foreach:

  List<string> list = ...;

  for (String str : list) {
   // ...
   System.out.println(str);
  }

Al de-compilar la versión con foreach (última) se observa que el compilador, en genera el mismo código que la versión iterator-while. Pero el iterador es un objeto de "usar y tirar", debido a su corta vida, por lo que básicamente es basura, por lo que hay que justificar muy bien su uso. El objetivo de utilizar un iterator, se da principalmente en ambiente multi-hilo, donde pueden ocurrir modificaciones, mientras se esta iterando la lista, pero si la iteración se da en un ambiente de un solo hilo, no es necesario crear el iterator y lo ideal es utilizar el for-size.

martes, 26 de noviembre de 2019

Usar y tirar (vargs)

Objetos de un solo uso

Los objetos de un solo uso son comunes y muchas veces no es trivial visualizarlos, pues son creados implícitamente, por ejemplo:

 public static void main(String[] args) {
  test1("a", "b");
  test2("a", "b");
  test3(new String[] { "a", "b" });
 }
 public static void test1(String... args) {
 }
 public static void test2(String arg1, String arg2) {
 }
 public static void test3(String[] args) {
 }

Al de-compilar la función main, utilizando Bytecode Viewer, se obtiene lo siguiente 


Los segmentos L1 y L3, son la invocación del método test1 y el segmento L5 es la invocación del método test3. El código en ambos casos es idéntico, creando un arreglo de cadenas con longitud 2 (iconst_2 y newarray). La implicación es que se crea un arreglo implícitamente, al invocar una función con argumentos variables.

Si el código se modifica, para invocar 2 veces la misma función:

  test1("a", "b");

  test1("a", "b");

El resultado de de-compilar el .class se obtiene lo siguiente:


Donde es posible ver que se crean 2 arreglos distintos, lo cual provoca que el garbage collector sea invocado demasiadas veces.

Para lidiar con el problema algunas herramientas o APIs, evitan utilizar varargs. Por ejemplo Log4j 2, declara múltiples funciones con el mismo nombre, pero que reciben parámetros Object desde el 1 hasta el 9.

Funcionalidades similares, se dan con el foreach o la creación de cadenas.

domingo, 24 de noviembre de 2019

assert en Java

Encontré un caso de uso útil para la sentencia assert de java. Cuando se puede definir una constante dependiente de otra previamente definida, por ejemplo:

    public static final String EXT = ".js";
    public static final int EXT_LENGTH = 3;

    static {
        assert EXT_LENGTH == EXT.length();
    }

    public static void main(String[] args) {
        // ...
        String filename = args[0];
        if (filename.endsWith(EXT)) {
            String name = filename.substring(0, filename.length() - EXT_LENGTH);
            System.out.println(name);
            name = filename.substring(0, filename.length() - EXT_LENGTH_ALT);
            System.out.println(name);
        }
        // ...
    }

Al observar el .class, utilizando BYTECODE VIEWER, podemos ver el código equivalente al generado por el compilador:


Como es posible observar, al utilizar la constante literal EXT_LENGTH (cuyo valor se conoce en tiempo de compilación), el valor se substituye directamente, mientras que al utilizar la constante, el código no cambia, pues el valor se conoce al momento de la ejecución. El tema de utilizar assert, es validar que cuando se realiza un cambio (por ejemplo de extensión en este caso), se verifique que el valor de la constante literal se modifique también, si se utiliza maven, es posible definir la propiedad enableAssertions con el valor de true, lo que permite que los asserts se evalúen en conjunto con las pruebas unitarias (ver Surefire maven plugin).

sábado, 23 de noviembre de 2019

foreach en Java (for para Collections y arrays nativos)

A partir de la versión 5 de java la secuencia de control for, puede utilizarse como un foreach. El uso de dicha notación hace que la cantidad de código escrito sea menor, pero tiene sus implicaciones negativas, veamos el siguiente ejemplo:

void iterateArray() {
String[] array = new String[] { "alfa", "beta" };
for (String current : array) {
System.out.println(current);
}
}

void iterateCollection() {
Collection collection = Arrays.asList("alfa", "beta");
for (String current : collection) {
System.out.println(current);
}
}

Ambos códigos se "parecen", pero al revisar el código generado podemos observar las diferencias de fondo, utilizando BYTECODE VIEWER, donde es posible decompilar la clase resultante y ver lo que genera el compilador (también muestra las instrucciones de la JVM, pero en este caso nos interesa el código generado)


Como se puede ver, de inicio si bien el código escrito por el programador es menor, el código generador por el compilador, para ser ejecutado por la JVM, es similar al que se empleaba antes de java 5 (es decir sin la sentencia de control foreach). Cabe destacar que ademas por lo generar al ejecutar la instrucción iterator de la colección, se crea un objeto para iterar (ver complementación de Iterable en ArrayList por ejemplo, el cual regresa "new Itr()")