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()")

martes, 22 de mayo de 2012

Conversión de una fecha a una cadena

Cuando es importante el que el tiempo de respuesta sea mínimo, la conversión a cadenas de tipos complejos, pero comunes, puede volverse importante. Un ejemplo de esto, es la conversión de fechas a cadenas. La mayoría de los APIs de generación de bitácoras, tocan el tema como un factor importante en el desempeño de una aplicación, puesto que la generación de las bitácoras, pueden influir drásticamente en tiempo de respuesta, dada la cantidad de las mismas que pueden generarse.

Para convertir una fecha a una cadena, en un formato determinado, particularmente sin utilizar los nombres de los días o de los meses, se puede realizar utilizando un arreglo de caracteres y utilizando funciones matemáticas simples.

01: package org.test.util;
02: 
03: import java.util.Calendar;
04: 
05: public final class DateTimeUtil {
06: 
07:         private static final Calendar calendar;
08:         private static final char[] buffer;
09: 
10:         static {
11:                 calendar = Calendar.getInstance();
12:                 buffer = new char[23];
13:         }
14: 
15:         private DateTimeUtil() {
16:         }
17: 
18:         /**
19:          * formato equivalente a "yyyy/MM/dd HH:mm:ss,SSS"
20:          */
21:         public static String formatDate(long time) {
22:                 int year;
23:                 int month;
24:                 int day;
25:                 int hour;
26:                 int minute;
27:                 int second;
28:                 int millisecond;
29:                 synchronized (calendar) {
30:                         calendar.setTimeInMillis(time);
31:                         year = calendar.get(Calendar.YEAR);
32:                         month = calendar.get(Calendar.MONTH);
33:                         day = calendar.get(Calendar.DAY_OF_MONTH);
34:                         hour = calendar.get(Calendar.HOUR_OF_DAY);
35:                         minute = calendar.get(Calendar.MINUTE);
36:                         second = calendar.get(Calendar.SECOND);
37:                         millisecond = calendar.get(Calendar.MILLISECOND);
38:                 }
39:                 synchronized (buffer) {
40:                         buffer[0] = (char) ('0' + year / 1000);
41:                         year %= 1000;
42:                         buffer[1] = (char) ('0' + year / 100);
43:                         year %= 100;
44:                         buffer[2] = (char) ('0' + year / 10);
45:                         year %= 10;
46:                         buffer[3] = (char) ('0' + year);
47:                         buffer[4] = '/';
48:                         buffer[5] = (char) ('0' + month / 10);
49:                         month %= 10;
50:                         buffer[6] = (char) ('0' + month);
51:                         buffer[7] = '/';
52:                         buffer[8] = (char) ('0' + day / 10);
53:                         day %= 10;
54:                         buffer[9] = (char) ('0' + day);
55:                         buffer[10] = ' ';
56:                         buffer[11] = (char) ('0' + hour / 10);
57:                         hour %= 10;
58:                         buffer[12] = (char) ('0' + hour);
59:                         buffer[13] = ':';
60:                         buffer[14] = (char) ('0' + minute / 10);
61:                         minute %= 10;
62:                         buffer[15] = (char) ('0' + minute);
63:                         buffer[16] = ':';
64:                         buffer[17] = (char) ('0' + second / 10);
65:                         second %= 10;
66:                         buffer[18] = (char) ('0' + second);
67:                         buffer[19] = ',';
68:                         buffer[20] = (char) ('0' + millisecond / 100);
69:                         millisecond %= 100;
70:                         buffer[21] = (char) ('0' + millisecond / 10);
71:                         millisecond %= 10;
72:                         buffer[22] = (char) ('0' + millisecond);
73:                         return new String(buffer);
74:                 }
75:         }
76: 
77: }

Cómo se puede observar, se utiliza una sola instancia de java.util.Calendar, así como una sola instancia de un arreglo de caracteres, con el fin de minimizar el uso de memoria. Debido a esto, es necesario sincronizar el código que extrae el año, mes, día, hora, minuto, segundo y milisegundo. Posteriormente se libera el objeto calendar y se sincroniza el código que realiza el formateo los datos extraídos previamente, esto utilizando las operaciones división, suma y modulo. Finalmente, se construye una cadena con el arreglo de caracteres.

El formato es fijo, por lo que no hay necesidad de tratar de detectar posibles opciones. No se utilizan formatos que dependan del lenguaje, por lo que la longitud, también es fija. Esto en conjunto ayuda a minimizar el tiempo de respuesta, dando una opción al uso de la clase java.util.SimpleDateFormat, la cual al ser una clase genérica, puede tardar un poco más, debido a la necesidad de resolver las situaciones con el idioma.

jueves, 17 de mayo de 2012

Constantes dinámicas

En ocasiones es neceasario cargar valores constantes de un archivo de configuración. Estos archivos de configuración se cargan en memoria parseando el archivo y posteriormente obteniendo la propiedad respectiva de objeto que resulta del parseo del archivo. Incluso puede exiatir la neceaidad de realizar la conversión de la esta a número, fecha, booleano, etc..

El siguiente código es común encontrarlo en cualquier desarrollo (se presume que la variable properties es del tipo java.util.Properties):


 while (condition) {

  System.out.println(Float.parseFloat(properties
    .getProperty("percent")));
  System.out.println(currentData.getCurrentAmount()
    * Float.parseFloat(properties.getProperty("percent")));
  float amount = currentData.getCurrentAmount()
    * Float.parseFloat(properties.getProperty("percent"));
  save(amount);

 }

Donde, como se puede ver, se parsea hasta 3 veces la misma propiedad. Para solventar este problema, es posible declarar en la clase (o en una clase auxiliar) esta propiedad, incluso realizando la conversión previamente.


  private static final float PERCENT = Float.parseFloat(properties
   .getProperty("percent"));

Y en el código anterior: 

  while (condition) {
    System.out.println(PERCENT);
    System.out.println(currentData.getCurrentAmount() * PERCENT);
    float amount = currentData.getCurrentAmount() * PERCENT;
    save(amount);
  }

Con lo cual se evita el procesamiento adiciónal de recuperar la propiedad y posteriormente realizar la conversión.

miércoles, 19 de octubre de 2011

Extraer un caracter de una cadena en java





En ocasiones, es necesario extraer un caracter de una cadena. Existen varias formas de realizar esta accion, una de ellas, es la siguiente:

String s = "Hola mundo";
  s.substring(0,1);

Sin embargo, la forma optima de realizar esta tarea, es utilizado la funcion charAt.

String s = "Hola mundo";
  s.charAt(0);

La principal ventaja en utilizar esta ultima funcion, radica en que se obtiene un valor directamente, mientras que en el primer codigo, se obtiene un objeto, por lo que al comparar, en el primer caso, es necesario utilizar la funcion equals, mientras que en la segunda se utiliza el operador de comparacion ==, derivado de lo mismo (al menos hasta java 1.6), el valor retornado por la funcion charAt, se puede utilizar dentro de una instruccion switch.

domingo, 21 de agosto de 2011

Concatenacion de dos cadenas (concat)

La concatenacion de cadenas, es una operacion ampliamente utilizada en los lenguajes de programacion. Pero en el caso de algunos lenguajes compilados, existen metodos poco utilizados, pero muy utiles. Un ejemplo es el metodo java.lant.String.concat o System.String.Concat que tienen algunos lenguajes de programación como java o C#. Veamos por que.


Cuando es necesario concatenar exactamente 2 cadenas y utilizar inmediatamente el resultado de esto (por ejemplo, agregar una extension a un nombre de archivo, para crearlo) es recomendable utilizar este metodo y no algun otro alternativo, como el uso de operadores o de clases auxiliares, como lo es java.lang.StringBuilder o System.StringBuilder.

¿Que ocurre, cuando se emplea StringBuilder?
  1. Creacion del objeto StringBuilder, inicializado, con la primer cadena (Creacion del primer objeto)
  2. Agregar los elementos caracteres de la segunda cadena (posiblemente expandiendo el buffer del builder, posiblemente creando el segundo objeto implicito, un arreglo de caracteres).
  3. Crear un nuevo objeto cadena (Creacion del segundo o tercer objeto)
¿Que ocurre cuando se emplea el metodo concat?
  1. Cracion de un nuevo objeto cadena, con la longitud exacta de la suma de ambos elementos (Creacion del primer y unico nuevo objeto)
  2. Copiar los elementos de la primer cadena
  3. Copiar los elementos de la segunda cadena
Cuando se trata de concatenar exactamente dos cadenas, el metodo concat es optimo, en terminos de uso de memoria, por que solo utiliza la cantidad exacta de memoria, mientras que la clase StringBuilder, al ser generica (para concatenar cualquier cantidad de cadenas) aloja bloques de memoria de tamaños distintos, tratando de evitar alojar memoria posteriormente (normalmente, son bloques cada vez mas grandes) y al finalmente, crea un objeto String, con el resultado de la concatenacion.

Nota: Dependiendo del lenguaje de programacion, asi como la version del mismo, el metodo de concatenacion, puede o no tener alguna de las siguientes optimizaciones.

  •  Si el metodo recibe dos parametros exactamente y es miembro estatico
    • Si ambos parametros son nulos, se regresa la cadena vacia.
    • Cuando alguno de los dos parametros es nulo, se regresa el otro parametro.
    • Cuando alguno de los dos parametros es la cadena vacia, se regresa el otro parametro. 
  • Cuando el metodo no es estatico y se invoca utilizando un objeto de la clase String
    • Si el parametro que se recibe es nulo, se regresa la cadena referenciada por this.
    • Si el parametro que se recibe es la cadena vacia, se regresa la cadena referenciada por this.

viernes, 15 de julio de 2011

Utilizar la memoria RAM como cache en Linux (ramdisk)

La mayoria de las distribuciones de linux cuentan con la capacidad de utilizar la memoria de acceso aleatorio como un sistema de archivos normal, donde se puede crear, leer, escribir y borrar un archivo, asi como crear directorios y borrarlos.

Lo primero que hay que ubicar, es que existan los archivos que inician con el prefijo ram, en el directorio /dev. Al ejecutar el listado de estos archivos, se obtiene una salida similar a esta:

   $ ls -l /dev/ram*
   brw-rw----. 1 root disk 1, 0 Jul 14 22:36 /dev/ram0
   brw-rw----. 1 root disk 1, 1 Jul 14 22:49 /dev/ram1
   brw-rw----. 1 root disk 1, 10 Jul 14 22:36 /dev/ram10
   brw-rw----. 1 root disk 1, 11 Jul 14 22:36 /dev/ram11
   brw-rw----. 1 root disk 1, 12 Jul 14 22:36 /dev/ram12
   brw-rw----. 1 root disk 1, 13 Jul 14 22:36 /dev/ram13
   brw-rw----. 1 root disk 1, 14 Jul 14 22:36 /dev/ram14
   brw-rw----. 1 root disk 1, 15 Jul 14 22:36 /dev/ram15
   brw-rw----. 1 root disk 1, 2 Jul 14 22:36 /dev/ram2
   brw-rw----. 1 root disk 1, 3 Jul 14 22:36 /dev/ram3
   brw-rw----. 1 root disk 1, 4 Jul 14 22:36 /dev/ram4
   brw-rw----. 1 root disk 1, 5 Jul 14 22:36 /dev/ram5
   brw-rw----. 1 root disk 1, 6 Jul 14 22:36 /dev/ram6
   brw-rw----. 1 root disk 1, 7 Jul 14 22:36 /dev/ram7
   brw-rw----. 1 root disk 1, 8 Jul 14 22:36 /dev/ram8
   brw-rw----. 1 root disk 1, 9 Jul 14 22:36 /dev/ram9

Para formatear y utilizar estos archivos, es necesario hacerlo como el usuario root. Esto, debido a que el propietario de los archivos es root.

   $ su -
   Password:
   # mkfs.ext2 -q /dev/ram1
   # mkdir tmp
   # mount /dev/ram1 tmp
   # df -k tmp
   Filesystem 1K-blocks Used Available Use% Mounted on
   /dev/ram1 15863 140 14904 1% /root/tmp

Ahora en el directorio tmp, se tiene un sistema de archivos, que utiliza la memoria de acceso aleatorio, como un directorio normal dentro del sistema. Cabe destacar que este directorio, sera destruido cuando se reinicie el sistema, por lo que cualquier informacion importante, debe ser respaldada previamente antes de reiniciar.


---

jueves, 14 de julio de 2011

Utilizar la memoria RAM como cache automatico en Linux

En los sistemas linux, el uso de memoria RAM como cache, es gestionado por el kernel de manera automatica, sin necesidad de configurar o ejecutar los programas de manera distinta a la usual.

Veamos el siguiente ejemplo, en el cual, se tiene un archivo de 25.85M aproximadamente.

   $ ls -l archivo.bin
   -rwxr-xr-x. 1 cmartine cmartine 27109840 Jul  1 10:52 archivo.bin

Utilizando el comando time, para determinar el tiempo de ejecucion y el comando cat para realizar la lectura del archivo, obtenemos lo siguiente en la primer ejecucion. Nota: se envia la salida al dispositivo /dev/null, para descartarla.

   $ time cat archivo.bin > /dev/null
   real   0m0.466s
   user   0m0.002s
   sys   0m0.123s

El tiempo que se tardo en ejecutar fue de 0.466 segundo. Posteriormente, ejecutando el mismo comando inmediatamente 3 veces seguidas, se obtiene el siguiente resultado:

   $ time cat archivo.bin > /dev/null
   real    0m0.040s
   user    0m0.002s
   sys     0m0.037s

   $ time cat archivo.bin > /dev/null
   real    0m0.026s
   user    0m0.001s
   sys     0m0.025s

   $ time cat archivo.bin > /dev/null
   real    0m0.029s
   user    0m0.000s
   sys     0m0.025s

Como se puede observar, el tiempo real, disminuyo drasticamente a menos de la decima parte del tiempo original. Esto es debido a que el kernel de linux maneja de manera automatica la memoria RAM que no se emplea en crear un cache de los archivos utilizados, sin necesidad de realizar configuracion alguna. La cantidad de memoria que esta siendoutilizada en este cache, se puede consultar utilizando el comando top.

¿Por que linux hace esto? Sencillamente por que si no se emplea la memoria, estaria desocupada (desperdiciandose cuando el sistema no la esta utilizando para realizar algun trabajo). Es importante aclara, que cuando se ejecuta un programa, el cual requiere utilizar mucha memoria, se libera parte de la memoria utilizada para el cache y se asigna al programa, pudiendo llegar a la situacion en que el cache ya no tenga memoria asignada, por que un programa requiere utilizar toda la memoria del sistema

jueves, 23 de junio de 2011

Comparacion entre 2 fechas

Comparar 2 fechas, para determinar cual es mayor, puede ser sencillo, sobre todo, si con lo que trabajas soporta el concepto de fecha y/o tiempo, por ejemplo: java.util.Date en java o System.DateTime en C#, que son lenguajes de programacion o incluso en hojas de datos, tipo Microsoft Excel o LibreOffice Calc.

El problema, se presenta, cuando es necesario trabajar con lenguajes que no conocen el concepto, como los shells de unix o cuando se almacenan archivos que contienen la fecha en el nombre del mismo.

En este punto la abstraccion de la fecha y la forma en como se representa, suele tomar un papel importante, dado que puede resolver completamente el problema, si se emplea el formato correcto.

Formas comunes de representar una fecha son: dia/mes/año en mexico o mes/dia/año en EEUU. Para nombres de archivos se suelen substituir las diagonales, por guiones, asi como el uso del nombre del mes y no el numero de mes. Por ejemplo:
  • Jun/27/2011
  • Oct/21/2011
  • Jul/1/2011
  • Oct/24/2005
  • Jul/4/2011
Si se ordena alfabeticamente, se obtiene lo siguiente:
  • Jul/1/2011
  • Jul/4/2011
  • Jun/27/2011
  • Oct/21/2011
  • Oct/24/2005
Lo cual no es lo que se requiere, sin embargo, si se reacomoda la fecha utilizando el orden año, mes, dia:
  • 2011/Jun/27
  • 2011/Oct/21
  • 2011/Jul/1
  • 2005/Oct/24
  • 2011/Jul/4
Al ordenar se obtiene lo siguiente:
  • 2005/Oct/24
  • 2011/Jul/1
  • 2011/Jul/4
  • 2011/Jun/27
  • 2011/Oct/21
El problema con el que nos encontramos es que la l esta antes que la n, en orden alfabetico, para sortear este inconveniente, en vez del nombre del mes, utilizamos el numero del mes:

  • 2011/6/27
  • 2011/10/21
  • 2011/7/1
  • 2005/10/24
  • 2011/7/4 
Al ordenar, obtenemos lo siguiente:

  • 2005/10/24
  • 2011/6/27
  • 2011/10/1
  • 2011/7/1
  • 2011/7/4
 Este resultado tampoco es correcto, esto es debido a que la longitud de los meses, es variable, por lo que, agregamos un 0 a la izquierda, cuando el mes esta formado por un solo digito (es recomendable tambien hacerlo para los dias)

  • 2011/06/27
  • 2011/10/21
  • 2011/07/01
  • 2005/10/24
  • 2011/07/04 
Al ordenar, obtenemos lo siguiente:

  • 2005/10/24
  • 2011/06/27
  • 2011/07/01
  • 2011/07/04
  • 2011/10/01
Que es finalmente lo que se requiere.

Esto, se puede aplicar a la creacion de nombres de archivos, por ejemplo, para poder ordenarlos por una fecha en particular, sobre todo cuando la fecha de creacion, es incorrecta (cuando son enviados por correo electronico y se pierde la fecha de creacion del archivo), por ejemplo los siguientes nombres de archivos seran ordenados correctamente por el sistema operativo al momento de listarlos (esto tanto en windows, linux o mac os)
  • reporte_2011-06-27.txt
  • reporte_2011-10-21.txt
  • reporte_2011-07-01.txt
  • reporte_2005-10-24.txt
  • reporte_2011-07-04.txt
 Al ser listados, se obtiene lo siguiente:
  • reporte_2005-10-24.txt
  • reporte_2011-06-27.txt
  • reporte_2011-07-01.txt
  • reporte_2011-07-04.txt
  • reporte_2011-10-21.txt
En unix, incluso, es posible emplear las fechas en este formato, con el comando expr, el cual puede comparar dos cadenas y determinar cual es mayor o menor.

jueves, 5 de mayo de 2011

Implementación la funcion compareTo de IComparable en C#






La interfaces IComparable e IComparable<t> de C#, se utilizan para comparar objetos, normalmente del mismo tipo y determinar, si alguno es mayor, menor o la equivalencia. A continuacion, se presentan 4 ejemplos de como implementar esta funcionalidad.

Nota1: Para el primer ejemplo, se utiliza el concepto de National Identification Number (CURP en México, RUN en Chile, DNI en España y Argentina o Tarjeta de Identidad en colombia, por ejemplo)
Nota2: Por razones de compatibilidad, se implementan 2 interfaces distintas, con funcionalidades similares, en la implementacion de estas clases.

class Person1 : System.IComparable, System.IComparable<Person1>
    {
        private string nin; // National Identification Number
        private string name;
        private string surname;

        //...

        public int CompareTo(object obj)
        {
            if (obj is Person1)
            {
                return obj == null ? 1 : nin.CompareTo(((Person1)obj).nin);
            }
            throw new System.ArgumentException();
        }

        public int CompareTo(Person1 p)
        {
            return p == null ? 1 : nin.CompareTo(p.nin);
        }

    }

    class Person2 : System.IComparable, System.IComparable<Person2>
    {
        private int id;
        private string name;
        private string surname;

        //...

        public int CompareTo(object obj)
        {
            if (obj is Person2)
            {
                return obj == null ? 1 : id.CompareTo(((Person2)obj).id);
            }
            throw new System.ArgumentException();
        }

        public int CompareTo(Person2 p)
        {
            return p == null ? 1 : id.CompareTo(p.id);
        }


    }

    class Person3 : System.IComparable, System.IComparable<Person3>
    {
        private int id;
        private string name;
        private string surname;

        //...

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            else
            {
                if (obj is Person3)
                {
                    Person3 o = (Person3)obj;
                    return id - o.id;
                }
                throw new System.ArgumentException();
            }
        }

        public int CompareTo(Person3 p)
        {
            return p == null ? 1 : id - p.id;
        }

    }

    class Person4 : System.IComparable, System.IComparable<Person4>
    {
        private int idPart1;
        private string idPart2;
        private string name;
        private string surname;

        //...

        private int CompareTo0(Person4 p)
        {
            int v = idPart1 - p.idPart1;
            if (v == 0)
            {
                v = idPart2.CompareTo(p.idPart2);
            }
            return v;
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            else
            {
                if (obj is Person4)
                {
                    return CompareTo0((Person4)obj);
                }
                throw new System.ArgumentException();
            }
        }

        public int CompareTo(Person4 p)
        {
            if (p == null)
            {
                return 1;
            }
            else
            {
                return CompareTo0(p);
            }
        }

    }


En el primer y segundo ejemplo, se implementa la funcionalidad, utilizando la funcion CompareTo implementada por la clase string y por la estructura int32. Regresando el resultado de ejecutar dicha funcion. En el tercer ejemplo, se muestra, como se puede implementar utilizando la diferencia entre 2 enteros (es posible utilizar cualquier tipo entero, como un byte, short, int). Finalmente, en el cuarto ejemplo, se implementa la funcionalidad utilizando 2 propiedades, donde se compara la primer propiedad y en caso de igualdad, se regresa el resultado de comparar la segunda propiedad. Tambiem, en este ultimo ejemplo, define una funcion intermedia (CompareTo0), la cual hace la comparacion real y es llamada por las 2 implementaciones del CompareTo.

Como se observa. por razones de compatibilidad, es necesario implementar la funcionalidad de 2 interfaces, una utlizando Generics y otra sin pre-generics. En la version Generics, es necesario validar que el tipo de corresponde con la clase que implementa, en caso de no coincidir, hay que generar una excepcion del tipo ArgumentException. En el caso del null, la documentacion tambien especifica regresar 1 explicitamente. Final mente, en C#, como en C, se especifican valores validos cualquier entero positivo, negativo o cero (dentro de los rangos de int32)

Es recomendable, por cuestiones de simplicidad en la implementacion, incluir en la comparacion solo los valores que corresponden a la llave primaria, comparandolos, en orden de importancia, regresando el valor, del primero que resulte distinto (como se muestra en el ejemplo 4).